diff --git a/src/documentation/content/xdocs/hslf/how-to-shapes.xml b/src/documentation/content/xdocs/hslf/how-to-shapes.xml index eec5ffcaf..6460f2b1d 100644 --- a/src/documentation/content/xdocs/hslf/how-to-shapes.xml +++ b/src/documentation/content/xdocs/hslf/how-to-shapes.xml @@ -44,6 +44,7 @@
  • How to retrieve embedded OLE objects
  • How to create shapes of arbitrary geometry
  • Shapes and Graphics2D
  • +
  • How to convert slides into images
  • Features @@ -549,7 +550,54 @@
    - + + +
    Export PowerPoint slides into java.awt.Graphics2D +

    + HSLF provides a way to export slides into images. You can capture slides into java.awt.Graphics2D object (or any other) + and serialize it into a PNG or JPEG format. Please note, although HSLF attempts to render slides as close to PowerPoint as possible, + the output might look differently from PowerPoint due to the following reasons: +

    + +

    + Current Limitations: +

    + + + FileInputStream is = new FileInputStream("slideshow.ppt"); + SlideShow ppt = new SlideShow(is); + is.close(); + + Dimension pgsize = ppt.getPageSize(); + + Slide[] slide = ppt.getSlides(); + for (int i = 0; i < slide.length; i++) { + + BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics = img.createGraphics(); + //clear the drawing area + graphics.setPaint(Color.white); + graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height)); + + //render + slide[i].draw(graphics); + + //save the output + FileOutputStream out = new FileOutputStream("slide-" + (i+1) + ".png"); + javax.imageio.ImageIO.write(img, "png", out); + out.close(); + } + + +
    + diff --git a/src/scratchpad/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java new file mode 100755 index 000000000..aa8145051 --- /dev/null +++ b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java @@ -0,0 +1,103 @@ + +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hslf.examples; + +import org.apache.poi.hslf.usermodel.*; +import org.apache.poi.hslf.model.*; +import org.apache.poi.hslf.record.TextHeaderAtom; + +import javax.imageio.ImageIO; +import java.io.IOException; +import java.io.FileOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; + +/** + * Demonstrates how you can use HSLF to convert each slide into a PNG image + * + * @author Yegor Kozlov + */ +public class PPT2PNG { + + public static void main(String args[]) throws Exception { + + if (args.length == 0) { + usage(); + return; + } + + int slidenum = -1; + float scale = 1; + String file = null; + + for (int i = 0; i < args.length; i++) { + if (args[i].startsWith("-")) { + if ("-scale".equals(args[i])){ + scale = Float.parseFloat(args[++i]); + } else if ("-slide".equals(args[i])) { + slidenum = Integer.parseInt(args[++i]); + } + } else { + file = args[i]; + } + } + if(file == null){ + usage(); + return; + } + + FileInputStream is = new FileInputStream(file); + SlideShow ppt = new SlideShow(is); + is.close(); + + Dimension pgsize = ppt.getPageSize(); + int width = (int)(pgsize.width*scale); + int height = (int)(pgsize.height*scale); + + Slide[] slide = ppt.getSlides(); + for (int i = 0; i < slide.length; i++) { + if (slidenum != -1 && slidenum != (i+1)) continue; + + String title = slide[i].getTitle(); + System.out.println("Rendering slide "+slide[i].getSlideNumber() + (title == null ? "" : ": " + title)); + + BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics = img.createGraphics(); + graphics.setPaint(Color.white); + graphics.fill(new Rectangle2D.Float(0, 0, width, height)); + + graphics.scale((double)width/pgsize.width, (double)height/pgsize.height); + + slide[i].draw(graphics); + + String fname = file.replaceAll("\\.ppt", "-" + (i+1) + ".png"); + FileOutputStream out = new FileOutputStream(fname); + ImageIO.write(img, "png", out); + out.close(); + } + } + + private static void usage(){ + System.out.println("Usage: PPT2PNG [-scale -slide ] ppt"); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/AutoShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/AutoShape.java index cf65a9b13..dc5f798cd 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/AutoShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/AutoShape.java @@ -18,6 +18,9 @@ package org.apache.poi.hslf.model; import org.apache.poi.ddf.*; +import org.apache.poi.util.POILogger; + +import java.awt.geom.Rectangle2D; /** * Represents an AutoShape. @@ -102,4 +105,17 @@ public class AutoShape extends TextShape { setEscherProperty((short)(EscherProperties.GEOMETRY__ADJUSTVALUE + idx), val); } + + public java.awt.Shape getOutline(){ + ShapeOutline outline = AutoShapes.getShapeOutline(getShapeType()); + Rectangle2D anchor = getAnchor2D(); + if(outline == null){ + logger.log(POILogger.WARN, "getOutline() is not implemented for " + ShapeTypes.typeName(getShapeType())); + return anchor; + } else { + java.awt.Shape shape = outline.getOutline(this); + return AutoShapes.transform(shape, anchor); + } + } + } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java b/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java new file mode 100755 index 000000000..9d9f5e204 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java @@ -0,0 +1,293 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hslf.model; + +import org.apache.poi.ddf.EscherProperties; + +import java.awt.geom.*; + +/** + * Stores definition of auto-shapes. + * See the Office Drawing 97-2007 Binary Format Specification for details. + * + * TODO: follow the spec and define all the auto-shapes + * + * @author Yegor Kozlov + */ +public class AutoShapes { + protected static ShapeOutline[] shapes; + + + /** + * Return shape outline by shape type + * @param type shape type see {@link ShapeTypes} + * + * @return the shape outline + */ + public static ShapeOutline getShapeOutline(int type){ + ShapeOutline outline = shapes[type]; + return outline; + } + + /** + * Auto-shapes are defined in the [0,21600] coordinate system. + * We need to transform it into normal slide coordinates + * + */ + public static java.awt.Shape transform(java.awt.Shape outline, Rectangle2D anchor){ + AffineTransform at = new AffineTransform(); + at.translate(anchor.getX(), anchor.getY()); + at.scale( + 1.0f/21600*anchor.getWidth(), + 1.0f/21600*anchor.getHeight() + ); + return at.createTransformedShape(outline); + } + + static { + shapes = new ShapeOutline[255]; + + shapes[ShapeTypes.Rectangle] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + Rectangle2D path = new Rectangle2D.Float(0, 0, 21600, 21600); + return path; + } + }; + + shapes[ShapeTypes.RoundRectangle] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400); + RoundRectangle2D path = new RoundRectangle2D.Float(0, 0, 21600, 21600, adjval, adjval); + return path; + } + }; + + shapes[ShapeTypes.Ellipse] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + Ellipse2D path = new Ellipse2D.Float(0, 0, 21600, 21600); + return path; + } + }; + + shapes[ShapeTypes.Diamond] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + GeneralPath path = new GeneralPath(); + path.moveTo(10800, 0); + path.lineTo(21600, 10800); + path.lineTo(10800, 21600); + path.lineTo(0, 10800); + path.closePath(); + return path; + } + }; + + //m@0,l,21600r21600 + shapes[ShapeTypes.IsocelesTriangle] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 10800); + GeneralPath path = new GeneralPath(); + path.moveTo(adjval, 0); + path.lineTo(0, 21600); + path.lineTo(21600, 21600); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.RightTriangle] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + GeneralPath path = new GeneralPath(); + path.moveTo(0, 0); + path.lineTo(21600, 21600); + path.lineTo(0, 21600); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.Parallelogram] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400); + + GeneralPath path = new GeneralPath(); + path.moveTo(adjval, 0); + path.lineTo(21600, 0); + path.lineTo(21600 - adjval, 21600); + path.lineTo(0, 21600); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.Trapezoid] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400); + + GeneralPath path = new GeneralPath(); + path.moveTo(0, 0); + path.lineTo(adjval, 21600); + path.lineTo(21600 - adjval, 21600); + path.lineTo(21600, 0); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.Hexagon] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400); + + GeneralPath path = new GeneralPath(); + path.moveTo(adjval, 0); + path.lineTo(21600 - adjval, 0); + path.lineTo(21600, 10800); + path.lineTo(21600 - adjval, 21600); + path.lineTo(adjval, 21600); + path.lineTo(0, 10800); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.Octagon] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 6326); + + GeneralPath path = new GeneralPath(); + path.moveTo(adjval, 0); + path.lineTo(21600 - adjval, 0); + path.lineTo(21600, adjval); + path.lineTo(21600, 21600-adjval); + path.lineTo(21600-adjval, 21600); + path.lineTo(adjval, 21600); + path.lineTo(0, 21600-adjval); + path.lineTo(0, adjval); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.Plus] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400); + + GeneralPath path = new GeneralPath(); + path.moveTo(adjval, 0); + path.lineTo(21600 - adjval, 0); + path.lineTo(21600 - adjval, adjval); + path.lineTo(21600, adjval); + path.lineTo(21600, 21600-adjval); + path.lineTo(21600-adjval, 21600-adjval); + path.lineTo(21600-adjval, 21600); + path.lineTo(adjval, 21600); + path.lineTo(adjval, 21600-adjval); + path.lineTo(0, 21600-adjval); + path.lineTo(0, adjval); + path.lineTo(adjval, adjval); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.Pentagon] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + + GeneralPath path = new GeneralPath(); + path.moveTo(10800, 0); + path.lineTo(21600, 8259); + path.lineTo(21600 - 4200, 21600); + path.lineTo(4200, 21600); + path.lineTo(0, 8259); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.DownArrow] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + //m0@0 l@1@0 @1,0 @2,0 @2@0,21600@0,10800,21600xe + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 16200); + int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 5400); + GeneralPath path = new GeneralPath(); + path.moveTo(0, adjval); + path.lineTo(adjval2, adjval); + path.lineTo(adjval2, 0); + path.lineTo(21600-adjval2, 0); + path.lineTo(21600-adjval2, adjval); + path.lineTo(21600, adjval); + path.lineTo(10800, 21600); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.UpArrow] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + //m0@0 l@1@0 @1,21600@2,21600@2@0,21600@0,10800,xe + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400); + int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 5400); + GeneralPath path = new GeneralPath(); + path.moveTo(0, adjval); + path.lineTo(adjval2, adjval); + path.lineTo(adjval2, 21600); + path.lineTo(21600-adjval2, 21600); + path.lineTo(21600-adjval2, adjval); + path.lineTo(21600, adjval); + path.lineTo(10800, 0); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.Arrow] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + //m@0, l@0@1 ,0@1,0@2@0@2@0,21600,21600,10800xe + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 16200); + int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 5400); + GeneralPath path = new GeneralPath(); + path.moveTo(adjval, 0); + path.lineTo(adjval, adjval2); + path.lineTo(0, adjval2); + path.lineTo(0, 21600-adjval2); + path.lineTo(adjval, 21600-adjval2); + path.lineTo(adjval, 21600); + path.lineTo(21600, 10800); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.LeftArrow] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + //m@0, l@0@1,21600@1,21600@2@0@2@0,21600,,10800xe + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400); + int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 5400); + GeneralPath path = new GeneralPath(); + path.moveTo(adjval, 0); + path.lineTo(adjval, adjval2); + path.lineTo(21600, adjval2); + path.lineTo(21600, 21600-adjval2); + path.lineTo(adjval, 21600-adjval2); + path.lineTo(adjval, 21600); + path.lineTo(0, 10800); + path.closePath(); + return path; + } + }; + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Fill.java b/src/scratchpad/src/org/apache/poi/hslf/model/Fill.java index c4df7b677..49393250d 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Fill.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Fill.java @@ -22,12 +22,10 @@ import org.apache.poi.ddf.*; import org.apache.poi.hslf.record.*; import org.apache.poi.hslf.usermodel.PictureData; import org.apache.poi.hslf.usermodel.SlideShow; -import org.apache.poi.hslf.exceptions.HSLFException; import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogFactory; import java.awt.*; -import java.util.*; /** * Represents functionality provided by the 'Fill Effects' dialog in PowerPoint. @@ -137,13 +135,15 @@ public class Fill { EscherOptRecord opt = (EscherOptRecord)Shape.getEscherChild(shape.getSpContainer(), EscherOptRecord.RECORD_ID); EscherSimpleProperty p1 = (EscherSimpleProperty)Shape.getEscherProperty(opt, EscherProperties.FILL__FILLCOLOR); EscherSimpleProperty p2 = (EscherSimpleProperty)Shape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST); + EscherSimpleProperty p3 = (EscherSimpleProperty)Shape.getEscherProperty(opt, EscherProperties.FILL__FILLOPACITY); int p2val = p2 == null ? 0 : p2.getPropertyValue(); + int alpha = p3 == null ? 255 : ((p3.getPropertyValue() >> 8) & 0xFF); Color clr = null; if (p1 != null && (p2val & 0x10) != 0){ int rgb = p1.getPropertyValue(); - clr = shape.getColor(rgb); + clr = shape.getColor(rgb, alpha); } return clr; } @@ -176,7 +176,7 @@ public class Fill { Color clr = null; if (p1 != null && (p2val & 0x10) != 0){ int rgb = p1.getPropertyValue(); - clr = shape.getColor(rgb); + clr = shape.getColor(rgb, 255); } return clr; } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java b/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java index 28e41f8b0..d31237f8d 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java @@ -19,7 +19,6 @@ package org.apache.poi.hslf.model; import org.apache.poi.ddf.*; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.POILogger; -import org.apache.poi.util.HexDump; import java.awt.geom.*; import java.util.ArrayList; @@ -237,4 +236,8 @@ public class Freeform extends AutoShape { return path; } + + public java.awt.Shape getOutline(){ + return getPath(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Line.java b/src/scratchpad/src/org/apache/poi/hslf/model/Line.java index 923718368..073efa395 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Line.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Line.java @@ -19,6 +19,9 @@ package org.apache.poi.hslf.model; import org.apache.poi.ddf.*; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Line2D; + /** * Represents a line in a PowerPoint drawing * @@ -126,4 +129,8 @@ public class Line extends SimpleShape { return _escherContainer; } + public java.awt.Shape getOutline(){ + Rectangle2D anchor = getAnchor2D(); + return new Line2D.Double(anchor.getX(), anchor.getY(), anchor.getX() + anchor.getWidth(), anchor.getY() + anchor.getHeight()); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java b/src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java index 4371e2a37..5b1b1016e 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java @@ -17,6 +17,8 @@ package org.apache.poi.hslf.model; import org.apache.poi.hslf.record.SheetContainer; +import org.apache.poi.hslf.record.Record; +import org.apache.poi.hslf.record.RecordTypes; import org.apache.poi.hslf.model.textproperties.TextProp; /** @@ -37,4 +39,32 @@ public abstract class MasterSheet extends Sheet { */ public abstract TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) ; + + /** + * Checks if the shape is a placeholder. + * (placeholders aren't normal shapes, they are visible only in the Edit Master mode) + * + * + * @return true if the shape is a placeholder + */ + public static boolean isPlaceholder(Shape shape){ + if(!(shape instanceof TextShape)) return false; + + TextShape tx = (TextShape)shape; + TextRun run = tx.getTextRun(); + if(run == null) return false; + + Record[] records = run._records; + for (int i = 0; i < records.length; i++) { + int type = (int)records[i].getRecordType(); + if (type == RecordTypes.BaseTextPropAtom.typeID || + type == RecordTypes.DateTimeMCAtom.typeID || + type == RecordTypes.GenericDateMCAtom.typeID || + type == RecordTypes.FooterMCAtom.typeID || + type == RecordTypes.SlideNumberMCAtom.typeID + ) return true; + + } + return false; + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java index 908dc4d9b..3f574c36f 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java @@ -17,24 +17,13 @@ package org.apache.poi.hslf.model; import org.apache.poi.ddf.*; -import org.apache.poi.hslf.usermodel.PictureData; import org.apache.poi.hslf.usermodel.SlideShow; import org.apache.poi.hslf.usermodel.ObjectData; -import org.apache.poi.hslf.record.Document; import org.apache.poi.hslf.record.ExObjList; import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.ExEmbed; -import org.apache.poi.hslf.blip.Bitmap; import org.apache.poi.util.POILogger; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.awt.*; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.List; -import java.util.Arrays; - /** * A shape representing embedded OLE obejct. diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java index 23088f056..37cedce0f 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java @@ -27,7 +27,6 @@ import java.awt.image.renderable.RenderableImage; import java.awt.geom.*; import java.text.AttributedCharacterIterator; import java.util.Map; -import java.util.ArrayList; import org.apache.poi.hslf.usermodel.RichTextRun; import org.apache.poi.hslf.exceptions.HSLFException; import org.apache.poi.util.POILogger; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java index 910d5c850..1e8dbcc25 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java @@ -26,6 +26,7 @@ import org.apache.poi.util.POILogger; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.awt.*; +import java.awt.geom.Rectangle2D; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; @@ -202,4 +203,22 @@ public class Picture extends SimpleShape { } } + public void draw(Graphics2D graphics){ + PictureData data = getPictureData(); + if (data instanceof Bitmap){ + BufferedImage img = null; + try { + img = ImageIO.read(new ByteArrayInputStream(data.getData())); + } + catch (Exception e){ + logger.log(POILogger.WARN, "ImageIO failed to create image. image.type: " + data.getType()); + return; + } + Rectangle anchor = getAnchor(); + Image scaledImg = img.getScaledInstance(anchor.width, anchor.height, Image.SCALE_SMOOTH); + graphics.drawImage(scaledImg, anchor.x, anchor.y, null); + } else { + logger.log(POILogger.WARN, "Rendering of metafiles is not yet supported. image.type: " + data.getType()); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java index 7b67c35fb..9ac61dbff 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java @@ -17,7 +17,6 @@ package org.apache.poi.hslf.model; import org.apache.poi.ddf.*; -import org.apache.poi.hslf.model.ShapeTypes; import org.apache.poi.hslf.record.ColorSchemeAtom; import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogFactory; @@ -297,6 +296,17 @@ public abstract class Shape { return prop == null ? 0 : prop.getPropertyValue(); } + /** + * Get the value of a simple escher property for this shape. + * + * @param propId The id of the property. One of the constants defined in EscherOptRecord. + */ + public int getEscherProperty(short propId, int defaultValue){ + EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); + EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, propId); + return prop == null ? defaultValue : prop.getPropertyValue(); + } + /** * @return The shape container and it's children that can represent this * shape. @@ -333,14 +343,14 @@ public abstract class Shape { _sheet = sheet; } - protected Color getColor(int rgb){ + protected Color getColor(int rgb, int alpha){ if (rgb >= 0x8000000) { int idx = rgb - 0x8000000; ColorSchemeAtom ca = getSheet().getColorScheme(); if(idx >= 0 && idx <= 7) rgb = ca.getColor(idx); } Color tmp = new Color(rgb, true); - return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed()); + return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed(), alpha); } /** @@ -364,4 +374,16 @@ public abstract class Shape { return Hyperlink.find(this); } + public void draw(Graphics2D graphics){ + logger.log(POILogger.INFO, "Rendering " + getShapeName()); + } + + /** + * Return shape outline as a java.awt.Shape object + * + * @return the shape outline + */ + public java.awt.Shape getOutline(){ + return getAnchor2D(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java index b22ed8d70..837d76b61 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java @@ -24,6 +24,7 @@ import org.apache.poi.hslf.record.EscherTextboxWrapper; import java.util.ArrayList; import java.util.List; import java.awt.geom.Rectangle2D; +import java.awt.*; /** * Represents a group of shapes. @@ -222,5 +223,11 @@ public class ShapeGroup extends Shape{ public Hyperlink getHyperlink(){ return null; } - + + public void draw(Graphics2D graphics){ + Shape[] sh = getShapes(); + for (int i = 0; i < sh.length; i++) { + sh[i].draw(graphics); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeOutline.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeOutline.java new file mode 100755 index 000000000..069fdbc4d --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeOutline.java @@ -0,0 +1,27 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hslf.model; + +/** + * Date: Apr 17, 2008 + * + * @author Yegor Kozlov + */ +public interface ShapeOutline { + java.awt.Shape getOutline(Shape shape); + +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java new file mode 100755 index 000000000..4e3054569 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java @@ -0,0 +1,85 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hslf.model; + + +import java.awt.*; +import java.awt.geom.Rectangle2D; + +/** + * Paint a shape into java.awt.Graphics2D + * + * @author Yegor Kozlov + */ +public class ShapePainter { + + public static void paint(SimpleShape shape, Graphics2D graphics){ + Rectangle2D anchor = shape.getAnchor2D(); + java.awt.Shape outline = shape.getOutline(); + + //flip vertical + if(shape.getFlipVertical()){ + graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight()); + graphics.scale(1, -1); + graphics.translate(-anchor.getX(), -anchor.getY()); + } + //flip horizontal + if(shape.getFlipHorizontal()){ + graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY()); + graphics.scale(-1, 1); + graphics.translate(-anchor.getX() , -anchor.getY()); + } + + //rotate transform + double angle = shape.getRotation(); + + if(angle != 0){ + double centerX = anchor.getX() + anchor.getWidth()/2; + double centerY = anchor.getY() + anchor.getHeight()/2; + + graphics.translate(centerX, centerY); + graphics.rotate(Math.toRadians(angle)); + graphics.translate(-centerX, -centerY); + } + + //fill + Color fillColor = shape.getFill().getForegroundColor(); + if (fillColor != null) { + graphics.setPaint(fillColor); + graphics.fill(outline); + } + + //border + Color lineColor = shape.getLineColor(); + if (lineColor != null){ + graphics.setPaint(lineColor); + float width = (float)shape.getLineWidth(); + int dashing = shape.getLineDashing(); + //TODO: implement more dashing styles + float[] dashptrn = null; + switch(dashing){ + case Line.PEN_PS_DASH: + dashptrn = new float[]{2, 2}; + break; + } + + Stroke stroke = new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dashptrn, 0.0f); + graphics.setStroke(stroke); + graphics.draw(outline); + } + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java index abf2fec58..2a1785bd1 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Vector; +import java.awt.*; /** * This class defines the common format of "Sheets" in a powerpoint @@ -329,4 +330,7 @@ public abstract class Sheet { return _background; } + public void draw(Graphics2D graphics){ + + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java index 0831d453b..0155ca3f5 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java @@ -22,6 +22,7 @@ import org.apache.poi.util.LittleEndian; import org.apache.poi.hslf.record.ColorSchemeAtom; import java.awt.*; +import java.awt.geom.AffineTransform; /** * An abstract simple (non-group) shape. @@ -199,4 +200,41 @@ public class SimpleShape extends Shape { getFill().setForegroundColor(color); } + /** + * Whether the shape is horizontally flipped + * + * @return whether the shape is horizontally flipped + */ + public boolean getFlipHorizontal(){ + EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); + return (spRecord.getFlags()& EscherSpRecord.FLAG_FLIPHORIZ) != 0; + } + + /** + * Whether the shape is vertically flipped + * + * @return whether the shape is vertically flipped + */ + public boolean getFlipVertical(){ + EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); + return (spRecord.getFlags()& EscherSpRecord.FLAG_FLIPVERT) != 0; + } + + /** + * Rotation angle in degrees + * + * @return rotation angle in degrees + */ + public int getRotation(){ + int rot = getEscherProperty(EscherProperties.TRANSFORM__ROTATION); + int angle = (rot >> 16) % 360; + + return angle; + } + + public void draw(Graphics2D graphics){ + AffineTransform at = graphics.getTransform(); + ShapePainter.paint(this, graphics); + graphics.setTransform(at); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java index ea7201751..e618ae230 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java @@ -21,17 +21,12 @@ package org.apache.poi.hslf.model; import java.util.Vector; -import java.util.List; -import java.util.Iterator; -import java.util.ArrayList; +import java.awt.*; -import org.apache.poi.hslf.record.PPDrawing; import org.apache.poi.hslf.record.SlideAtom; import org.apache.poi.hslf.record.TextHeaderAtom; import org.apache.poi.hslf.record.ColorSchemeAtom; import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet; -import org.apache.poi.ddf.EscherContainerRecord; -import org.apache.poi.ddf.EscherRecord; /** * This class represents a slide in a PowerPoint Document. It allows @@ -263,10 +258,85 @@ public class Slide extends Sheet return sa.getFollowMasterBackground(); } - public Background getBackground() { + /** + * Sets whether this slide draws master sheet objects + * + * @param flag true if the slide draws master sheet objects, + * false otherwise + */ + public void setFollowMasterObjects(boolean flag){ + SlideAtom sa = getSlideRecord().getSlideAtom(); + sa.setFollowMasterObjects(flag); + } + + /** + * Whether this slide follows master color scheme + * + * @return true if the slide follows master color scheme, + * false otherwise + */ + public boolean getFollowMasterScheme(){ + SlideAtom sa = getSlideRecord().getSlideAtom(); + return sa.getFollowMasterScheme(); + } + + /** + * Sets whether this slide draws master color scheme + * + * @param flag true if the slide draws master color scheme, + * false otherwise + */ + public void setFollowMasterScheme(boolean flag){ + SlideAtom sa = getSlideRecord().getSlideAtom(); + sa.setFollowMasterScheme(flag); + } + + /** + * Whether this slide draws master sheet objects + * + * @return true if the slide draws master sheet objects, + * false otherwise + */ + public boolean getFollowMasterObjects(){ + SlideAtom sa = getSlideRecord().getSlideAtom(); + return sa.getFollowMasterObjects(); + } + + /** + * Background for this slide. + */ + public Background getBackground() { if(getFollowMasterBackground()) return getMasterSheet().getBackground(); else return super.getBackground(); } + + /** + * Color scheme for this slide. + */ + public ColorSchemeAtom getColorScheme() { + if(getFollowMasterScheme()){ + return getMasterSheet().getColorScheme(); + } + return super.getColorScheme(); + } + + public void draw(Graphics2D graphics){ + MasterSheet master = getMasterSheet(); + if(getFollowMasterBackground()) master.getBackground().draw(graphics); + if(getFollowMasterObjects()){ + Shape[] sh = master.getShapes(); + for (int i = 0; i < sh.length; i++) { + if(MasterSheet.isPlaceholder(sh[i])) continue; + + sh[i].draw(graphics); + } + } + Shape[] sh = getShapes(); + for (int i = 0; i < sh.length; i++) { + sh[i].draw(graphics); + } + } + } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java b/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java index e442ae304..2a8467437 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java @@ -21,12 +21,6 @@ import org.apache.poi.hslf.model.textproperties.TextProp; import org.apache.poi.hslf.model.textproperties.TextPropCollection; import org.apache.poi.hslf.record.*; import org.apache.poi.hslf.usermodel.SlideShow; -import org.apache.poi.hslf.record.StyleTextPropAtom.*; -import org.apache.poi.ddf.EscherContainerRecord; -import org.apache.poi.ddf.EscherRecord; - -import java.util.List; -import java.util.Iterator; /** * SlideMaster determines the graphics, layout, and formatting for all the slides in a given presentation. @@ -82,17 +76,33 @@ public class SlideMaster extends MasterSheet { if (prop != null) break; } if (prop == null) { - switch (txtype) { - case TextHeaderAtom.CENTRE_BODY_TYPE: - case TextHeaderAtom.HALF_BODY_TYPE: - case TextHeaderAtom.QUARTER_BODY_TYPE: - txtype = TextHeaderAtom.BODY_TYPE; - break; - case TextHeaderAtom.CENTER_TITLE_TYPE: - txtype = TextHeaderAtom.TITLE_TYPE; - break; - default: - return null; + if(isCharacter) { + switch (txtype) { + case TextHeaderAtom.CENTRE_BODY_TYPE: + case TextHeaderAtom.HALF_BODY_TYPE: + case TextHeaderAtom.QUARTER_BODY_TYPE: + txtype = TextHeaderAtom.BODY_TYPE; + break; + case TextHeaderAtom.CENTER_TITLE_TYPE: + txtype = TextHeaderAtom.TITLE_TYPE; + break; + default: + return null; + } + } else { + switch (txtype) { + case TextHeaderAtom.CENTRE_BODY_TYPE: + case TextHeaderAtom.QUARTER_BODY_TYPE: + txtype = TextHeaderAtom.BODY_TYPE; + break; + case TextHeaderAtom.CENTER_TITLE_TYPE: + txtype = TextHeaderAtom.TITLE_TYPE; + break; + default: + return null; + } + return null; + } prop = getStyleAttribute(txtype, level, name, isCharacter); } @@ -119,4 +129,34 @@ public class SlideMaster extends MasterSheet { } } } + + /** + * Checks if the shape is a placeholder. + * (placeholders aren't normal shapes, they are visible only in the Edit Master mode) + * + * + * @return true if the shape is a placeholder + */ + public static boolean isPlaceholder(Shape shape){ + if(!(shape instanceof TextShape)) return false; + + TextShape tx = (TextShape)shape; + TextRun run = tx.getTextRun(); + if(run == null) return false; + + Record[] records = run._records; + for (int i = 0; i < records.length; i++) { + int type = (int)records[i].getRecordType(); + if (type == RecordTypes.OEPlaceholderAtom.typeID || + type == RecordTypes.SlideNumberMCAtom.typeID || + type == RecordTypes.DateTimeMCAtom.typeID || + type == RecordTypes.GenericDateMCAtom.typeID || + type == RecordTypes.FooterMCAtom.typeID ){ + return true; + + } + + } + return false; + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Table.java b/src/scratchpad/src/org/apache/poi/hslf/model/Table.java index 947c41af6..dd4a6a33f 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Table.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Table.java @@ -23,7 +23,6 @@ import org.apache.poi.util.LittleEndian; import java.util.*; import java.util.List; import java.awt.*; -import java.awt.geom.Rectangle2D; /** * Represents a table in a PowerPoint presentation diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TableCell.java b/src/scratchpad/src/org/apache/poi/hslf/model/TableCell.java index 36789ad91..7fa69b1d0 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TableCell.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TableCell.java @@ -18,9 +18,7 @@ package org.apache.poi.hslf.model; import org.apache.poi.ddf.*; -import org.apache.poi.hslf.record.EscherTextboxWrapper; import org.apache.poi.hslf.record.TextHeaderAtom; -import org.apache.poi.hslf.usermodel.RichTextRun; import java.awt.*; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java index a1d45ffba..dda55a2c7 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextBox.java @@ -19,15 +19,6 @@ package org.apache.poi.hslf.model; import org.apache.poi.ddf.*; -import org.apache.poi.hslf.record.*; -import org.apache.poi.hslf.usermodel.RichTextRun; -import org.apache.poi.hslf.exceptions.HSLFException; -import org.apache.poi.util.POILogger; - -import java.awt.*; -import java.awt.font.FontRenderContext; -import java.awt.font.TextLayout; -import java.io.IOException; /** * Represents a TextFrame shape in PowerPoint. diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java new file mode 100755 index 000000000..cfd98bdca --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java @@ -0,0 +1,238 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hslf.model; + +import org.apache.poi.hslf.usermodel.RichTextRun; +import org.apache.poi.util.POILogger; +import org.apache.poi.util.POILogFactory; + +import java.text.AttributedString; +import java.text.AttributedCharacterIterator; +import java.awt.font.TextAttribute; +import java.awt.font.LineBreakMeasurer; +import java.awt.font.TextLayout; +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Point2D; +import java.util.ArrayList; + +/** + * Paint text into java.awt.Graphics2D + * + * @author Yegor Kozlov + */ +public class TextPainter { + protected POILogger logger = POILogFactory.getLogger(this.getClass()); + + protected TextShape _shape; + + public TextPainter(TextShape shape){ + _shape = shape; + } + + public AttributedString getAttributedString(TextRun txrun){ + String text = txrun.getText(); + AttributedString at = new AttributedString(text); + RichTextRun[] rt = txrun.getRichTextRuns(); + for (int i = 0; i < rt.length; i++) { + int start = rt[i].getStartIndex(); + int end = rt[i].getEndIndex(); + if(start == end) continue; + + at.addAttribute(TextAttribute.FAMILY, rt[i].getFontName(), start, end); + at.addAttribute(TextAttribute.SIZE, new Float(rt[i].getFontSize()), start, end); + at.addAttribute(TextAttribute.FOREGROUND, rt[i].getFontColor(), start, end); + if(rt[i].isBold()) at.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, start, end); + if(rt[i].isItalic()) at.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, start, end); + if(rt[i].isUnderlined()) { + at.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, start, end); + at.addAttribute(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_TWO_PIXEL, start, end); + } + if(rt[i].isStrikethrough()) at.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON, start, end); + int superScript = rt[i].getSuperscript(); + if(superScript != 0) at.addAttribute(TextAttribute.SUPERSCRIPT, superScript > 0 ? TextAttribute.SUPERSCRIPT_SUPER : TextAttribute.SUPERSCRIPT_SUB, start, end); + + } + return at; + } + + protected RichTextRun getRichTextRunAt(int pos){ + RichTextRun[] rt = _shape.getTextRun().getRichTextRuns(); + for (int i = 0; i < rt.length; i++) { + int start = rt[i].getStartIndex(); + int end = rt[i].getEndIndex(); + if(pos >= start && pos < end) return rt[i]; + } + return null; + } + + public void paint(Graphics2D graphics){ + TextRun run = _shape.getTextRun(); + if (run == null) return; + + String text = run.getText(); + if (text == null || text.equals("")) return; + + AttributedString at = getAttributedString(run); + + AttributedCharacterIterator it = at.getIterator(); + int paragraphStart = it.getBeginIndex(); + int paragraphEnd = it.getEndIndex(); + + Rectangle2D anchor = _shape.getAnchor2D(); + + float textHeight = 0; + ArrayList lines = new ArrayList(); + LineBreakMeasurer measurer = new LineBreakMeasurer(it, graphics.getFontRenderContext()); + measurer.setPosition(paragraphStart); + while (measurer.getPosition() < paragraphEnd) { + int startIndex = measurer.getPosition(); + int nextBreak = text.indexOf('\n', measurer.getPosition() + 1); + RichTextRun rt = getRichTextRunAt(startIndex + 1); + if(rt == null) { + logger.log(POILogger.WARN, "RichTextRun not found at pos" + (startIndex + 1) + "; text.length: " + text.length()); + break; + } + + float wrappingWidth = (float)anchor.getWidth() - _shape.getMarginLeft() - _shape.getMarginRight(); + wrappingWidth -= rt.getTextOffset(); + + if (_shape.getWordWrap() == TextShape.WrapNone) { + wrappingWidth = _shape.getSheet().getSlideShow().getPageSize().width; + } + + TextLayout textLayout = measurer.nextLayout(wrappingWidth + 1, + nextBreak == -1 ? paragraphEnd : nextBreak, true); + if (textLayout == null) { + textLayout = measurer.nextLayout(wrappingWidth, + nextBreak == -1 ? paragraphEnd : nextBreak, false); + } + if(textLayout == null){ + logger.log(POILogger.WARN, "Failed to break text into lines: wrappingWidth: "+wrappingWidth+ + "; text: " + rt.getText()); + measurer.setPosition(rt.getEndIndex()); + continue; + } + int endIndex = measurer.getPosition(); + + TextElement el = new TextElement(); + el._startIndex = startIndex; + el._endIndex = endIndex; + el._align = rt.getAlignment(); + el._text = textLayout; + el._textOffset = rt.getTextOffset(); + + boolean prStart = text.charAt(startIndex) == '\n' || startIndex == 0; + if (text.charAt(startIndex) == '\n'){ + int spaceBefore = rt.getSpaceBefore(); + if (spaceBefore != 0) { + float val = (textLayout.getAscent() + textLayout.getDescent()) * spaceBefore/100; + textHeight += val; + } + } + if(rt.isBullet() && prStart){ + it.setIndex(startIndex); + + AttributedString bat = new AttributedString(Character.toString(rt.getBulletChar()), it.getAttributes()); + int bulletSize = rt.getBulletSize(); + if (bulletSize != -1){ + Float sz = (Float)bat.getIterator().getAttribute(TextAttribute.SIZE); + if(sz != null) bat.addAttribute(TextAttribute.SIZE, new Float(sz.floatValue()*bulletSize/100)); + } + + TextLayout bulletLayout = new TextLayout(bat.getIterator(), graphics.getFontRenderContext()); + if(text.substring(startIndex, endIndex).length() > 1){ + el._bullet = bulletLayout; + el._bulletOffset = rt.getBulletOffset(); + } + } + + textHeight += textLayout.getAscent() + textLayout.getLeading(); + + int lineSpacing = rt.getLineSpacing(); + if(lineSpacing != 0) el._spacing = textLayout.getDescent()*lineSpacing/100; + else el._spacing = textLayout.getDescent(); + + textHeight += el._spacing; + + lines.add(el); + } + + int valign = _shape.getVerticalAlignment(); + double y0 = anchor.getY(); + switch (valign){ + case TextBox.AnchorTopBaseline: + case TextBox.AnchorTop: + y0 += _shape.getMarginTop(); + break; + case TextBox.AnchorBottom: + y0 += anchor.getHeight() - textHeight - _shape.getMarginBottom(); + break; + default: + case TextBox.AnchorMiddle: + float delta = (float)anchor.getHeight() - textHeight - _shape.getMarginTop() - _shape.getMarginBottom(); + y0 += _shape.getMarginTop() + delta/2; + break; + } + + //finally draw the text fragments + for (int i = 0; i < lines.size(); i++) { + TextElement elem = (TextElement)lines.get(i); + y0 += elem._text.getAscent(); + + Point2D.Double pen = new Point2D.Double(); + pen.y = y0; + switch (elem._align) { + case TextShape.AlignLeft: + pen.x = anchor.getX() + _shape.getMarginLeft(); + break; + case TextShape.AlignCenter: + pen.x = anchor.getX() + _shape.getMarginLeft() + + (anchor.getWidth() - elem._text.getAdvance() - _shape.getMarginLeft() - _shape.getMarginRight()) / 2; + break; + case TextShape.AlignRight: + pen.x = anchor.getX() + _shape.getMarginLeft() + + (anchor.getWidth() - elem._text.getAdvance() - _shape.getMarginLeft() - _shape.getMarginRight()); + break; + default: + pen.x = anchor.getX() + _shape.getMarginLeft(); + break; + } + if(elem._bullet != null){ + elem._bullet.draw(graphics, (float)(pen.x + elem._bulletOffset), (float)pen.y); + } + elem._text.draw(graphics, (float)(pen.x + elem._textOffset), (float)pen.y); + + y0 += elem._text.getDescent(); + y0 += elem._text.getLeading(); + + y0 += elem._spacing; + } + } + + + public static class TextElement { + public TextLayout _text; + public int _textOffset; + public TextLayout _bullet; + public int _bulletOffset; + public int _align; + public int _startIndex; + public int _endIndex; + public float _spacing; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java index 5834e2eae..e425b83f1 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java @@ -534,6 +534,10 @@ public class TextRun // The messes things up on everything but a Mac, so translate // them to \n String text = rawText.replace('\r','\n'); + + //0xB acts like cariage return in page titles + text = text.replace((char) 0x0B, '\n'); + return text; } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java index 3c45b34b0..24c312637 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java @@ -513,4 +513,12 @@ public abstract class TextShape extends SimpleShape { } } } + + public void draw(Graphics2D graphics){ + AffineTransform at = graphics.getTransform(); + ShapePainter.paint(this, graphics); + new TextPainter(this).paint(graphics); + graphics.setTransform(at); + } + } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java index fdaa9eec2..187dec12a 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java @@ -150,7 +150,7 @@ public class StyleTextPropAtom extends RecordAtom new TextProp(2, 0x800000, "symbol"), new TextProp(2, 0x20000, "font.size"), new TextProp(4, 0x40000, "font.color"), - new TextProp(2, 0x80000, "offset"), + new TextProp(2, 0x80000, "superscript"), new TextProp(2, 0x100000, "char_unknown_1"), new TextProp(2, 0x1000000, "char_unknown_3"), new TextProp(2, 0x2000000, "char_unknown_4"), diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java index b250285ac..54cf60c48 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java @@ -37,9 +37,6 @@ import org.apache.poi.hslf.record.ColorSchemeAtom; /** * Represents a run of text, all with the same style * - * TODO: finish all the getters and setters to the - * font/character/paragraph properties (currently only - * has some of them) */ public class RichTextRun { /** The TextRun we belong to */ @@ -125,7 +122,25 @@ public class RichTextRun { public int getLength() { return length; } - + + /** + * The beginning index, inclusive. + * + * @return the beginning index, inclusive. + */ + public int getStartIndex(){ + return startPos; + } + + /** + * The ending index, exclusive. + * + * @return the ending index, exclusive. + */ + public int getEndIndex(){ + return startPos + length; + } + /** * Fetch the text, in output suitable form */ @@ -313,35 +328,143 @@ public class RichTextRun { // --------------- Friendly getters / setters on rich text properties ------- - + + /** + * Is the text bold? + */ public boolean isBold() { return isCharFlagsTextPropVal(CharFlagsTextProp.BOLD_IDX); } + + /** + * Is the text bold? + */ public void setBold(boolean bold) { setCharFlagsTextPropVal(CharFlagsTextProp.BOLD_IDX, bold); } + /** + * Is the text italic? + */ public boolean isItalic() { return isCharFlagsTextPropVal(CharFlagsTextProp.ITALIC_IDX); } + + /** + * Is the text italic? + */ public void setItalic(boolean italic) { setCharFlagsTextPropVal(CharFlagsTextProp.ITALIC_IDX, italic); } + /** + * Is the text underlined? + */ public boolean isUnderlined() { return isCharFlagsTextPropVal(CharFlagsTextProp.UNDERLINE_IDX); } + + /** + * Is the text underlined? + */ public void setUnderlined(boolean underlined) { setCharFlagsTextPropVal(CharFlagsTextProp.UNDERLINE_IDX, underlined); } - + + /** + * Does the text have a shadow? + */ + public boolean isShadowed() { + return isCharFlagsTextPropVal(CharFlagsTextProp.SHADOW_IDX); + } + + /** + * Does the text have a shadow? + */ + public void setShadowed(boolean flag) { + setCharFlagsTextPropVal(CharFlagsTextProp.SHADOW_IDX, flag); + } + + /** + * Is this text embossed? + */ + public boolean isEmbossed() { + return isCharFlagsTextPropVal(CharFlagsTextProp.RELIEF_IDX); + } + + /** + * Is this text embossed? + */ + public void setEmbossed(boolean flag) { + setCharFlagsTextPropVal(CharFlagsTextProp.RELIEF_IDX, flag); + } + + /** + * Gets the strikethrough flag + */ + public boolean isStrikethrough() { + return isCharFlagsTextPropVal(CharFlagsTextProp.STRIKETHROUGH_IDX); + } + + /** + * Sets the strikethrough flag + */ + public void setStrikethrough(boolean flag) { + setCharFlagsTextPropVal(CharFlagsTextProp.STRIKETHROUGH_IDX, flag); + } + + /** + * Gets the subscript/superscript option + * + * @return the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript + */ + public int getSuperscript() { + int val = getCharTextPropVal("superscript"); + return val == -1 ? 0 : val; + } + + /** + * Sets the subscript/superscript option + * + * @param val the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript + */ + public void setSuperscript(int val) { + setCharTextPropVal("superscript", val); + } + + /** + * Gets the font size + */ public int getFontSize() { return getCharTextPropVal("font.size"); } + + + /** + * Sets the font size + */ public void setFontSize(int fontSize) { setCharTextPropVal("font.size", fontSize); } - + + /** + * Gets the font index + */ + public int getFontIndex() { + return getCharTextPropVal("font.index"); + } + + /** + * Sets the font index + */ + public void setFontIndex(int idx) { + setCharTextPropVal("font.index", idx); + } + + + /** + * Sets the font name to use + */ public void setFontName(String fontName) { if (slideShow == null) { //we can't set font since slideshow is not assigned yet @@ -352,6 +475,10 @@ public class RichTextRun { setCharTextPropVal("font.index", fontIdx); } } + + /** + * Gets the font name + */ public String getFontName() { if (slideShow == null) { return _fontname; @@ -427,7 +554,7 @@ public class RichTextRun { /** * Sets indentation level * - * @param level indentation level. Must be in the range [0, 5] + * @param level indentation level. Must be in the range [0, 4] */ public void setIndentLevel(int level) { if(paragraphStyle != null ) paragraphStyle.setReservedField((short)level); @@ -488,6 +615,107 @@ public class RichTextRun { public int getTextOffset() { return getParaTextPropVal("text.offset")*Shape.POINT_DPI/Shape.MASTER_DPI; } + + /** + * Sets the bullet size + */ + public void setBulletSize(int size) { + setParaTextPropVal("bullet.size", size); + } + + /** + * Returns the bullet size + */ + public int getBulletSize() { + return getParaTextPropVal("bullet.size"); + } + + /** + * Sets the bullet color + */ + public void setBulletColor(Color color) { + int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 254).getRGB(); + setParaTextPropVal("bullet.color", rgb); + } + + /** + * Returns the bullet color + */ + public Color getBulletColor() { + int rgb = getCharTextPropVal("bullet.color"); + if (rgb >= 0x8000000) { + int idx = rgb % 0x8000000; + ColorSchemeAtom ca = parentRun.getSheet().getColorScheme(); + if(idx >= 0 && idx <= 7) rgb = ca.getColor(idx); + } + + Color tmp = new Color(rgb, true); + return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed()); + } + + /** + * Sets the bullet font + */ + public void setBulletFont(int idx) { + setParaTextPropVal("bullet.font", idx); + } + + /** + * Returns the bullet font + */ + public int getBulletFont() { + return getParaTextPropVal("bullet.font"); + } + + /** + * Sets the line spacing. + *

    + * If linespacing >= 0, then linespacing is a percentage of normal line height. + * If linespacing < 0, the absolute value of linespacing is the spacing in master coordinates. + *

    + */ + public void setLineSpacing(int val) { + setParaTextPropVal("linespacing", val); + } + + /** + * Returns the line spacing + *

    + * If linespacing >= 0, then linespacing is a percentage of normal line height. + * If linespacing < 0, the absolute value of linespacing is the spacing in master coordinates. + *

    + * + * @return the spacing between lines + */ + public int getLineSpacing() { + int val = getParaTextPropVal("linespacing"); + return val == -1 ? 0 : val; + } + + /** + * Sets spacing before a paragraph. + *

    + * If spacebefore >= 0, then spacebefore is a percentage of normal line height. + * If spacebefore < 0, the absolute value of spacebefore is the spacing in master coordinates. + *

    + */ + public void setSpaceBefore(int val) { + setParaTextPropVal("spacebefore", val); + } + + /** + * Returns spacing before a paragraph + *

    + * If spacebefore >= 0, then spacebefore is a percentage of normal line height. + * If spacebefore < 0, the absolute value of spacebefore is the spacing in master coordinates. + *

    + * + * @return the spacing before a paragraph + */ + public int getSpaceBefore() { + int val = getParaTextPropVal("spacebefore"); + return val == -1 ? 0 : val; + } // --------------- Internal HSLF methods, not intended for end-user use! ------- /** diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSlideOrdering.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSlideOrdering.java index 3fda0fda1..3d89d0272 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSlideOrdering.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSlideOrdering.java @@ -141,7 +141,7 @@ public class TestSlideOrdering extends TestCase { "ROMANCE: AN ANALYSIS", "AGENDA", "You are an important supplier of various items that I need", - (char)0x0B + "Although The Psycho set back my relationship process, recovery is luckily enough under way", + '\n' + "Although The Psycho set back my relationship process, recovery is luckily enough under way", "Since the time that we seriously go out together, you rank highly among existing relationships", "Although our personal interests are mostly compatible, the greatest gap exists in Sex and Shopping", "Your physical characteristics are strong when compared with your competition",