diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 0fb5cac07c..d0d73c8d8a 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -43,7 +43,13 @@ Created a common interface for handling PowerPoint files, irrespective of if they are .ppt or .pptx Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx - + + HSLF: Support for getting embedded sounds from slide show + HSLF: Initial support for rendering slides into images + HSLF: Support for getting OLE object data from slide show + HSLF: Implemented more methods in PPGraphics2D + HSLF: Added Freeform shape which can contain both lines and Bezier curves + 41071 - Improved text extraction in HSLF 30311 - Conditional Formatting - improved API, added HSSFSheetConditionalFormatting Update the formula parser code to use a HSSFWorkbook, rather than the low level model.Workbook, to make things cleaner and make supporting XSSF formulas in future much easier Fix the logger used by POIFSFileSystem, so that commons-logging isn't required when not used diff --git a/src/documentation/content/xdocs/index.xml b/src/documentation/content/xdocs/index.xml index 5d5067c8a9..e6f136652b 100644 --- a/src/documentation/content/xdocs/index.xml +++ b/src/documentation/content/xdocs/index.xml @@ -136,8 +136,7 @@
HSLF for PowerPoint Documents

HSLF is our port of the Microsoft PowerPoint 97(-2003) file format to pure - Java. It supports read and write capabilities of some, but not yet all - of the core records. Please see the HSLF project page for more information.

diff --git a/src/documentation/content/xdocs/poifs/embeded.xml b/src/documentation/content/xdocs/poifs/embeded.xml index d888e2ed53..a4620f5a94 100644 --- a/src/documentation/content/xdocs/poifs/embeded.xml +++ b/src/documentation/content/xdocs/poifs/embeded.xml @@ -61,9 +61,9 @@
Files embeded in PowerPoint

PowerPoint does not normally store embeded files in the OLE2 layer. Instead, they are held within records - of the main PowerPoint file. To get at them, you need to - find the appropriate data within the PowerPoint stream, - and work from that.

+ of the main PowerPoint file. +
See the HSLF Tutorial + for how to retrieve embedded OLE objects from a presentation

diff --git a/src/documentation/content/xdocs/slideshow/how-to-shapes.xml b/src/documentation/content/xdocs/slideshow/how-to-shapes.xml index 36e4a11387..7959eedaf6 100644 --- a/src/documentation/content/xdocs/slideshow/how-to-shapes.xml +++ b/src/documentation/content/xdocs/slideshow/how-to-shapes.xml @@ -40,6 +40,12 @@
  • How to create bulleted lists
  • Hyperlinks
  • Tables
  • +
  • How to remove shapes
  • +
  • How to retrieve embedded OLE objects
  • +
  • How to retrieve embedded sounds
  • +
  • How to create shapes of arbitrary geometry
  • +
  • Shapes and Graphics2D
  • +
  • How to convert slides into images
  • Features @@ -80,14 +86,8 @@
    How to get shapes contained in a particular slide -

    The superclass of all shapes in HSLF is the Shape class - the elemental object that composes a drawing. - The following pictute shows the class tree of HSLF shapes: -

    - Class Tree of HSLF Shapes -

    -

    - The following fragment demonstrates how to iterate over shapes for each slide. + The following code demonstrates how to iterate over shapes for each slide.

    SlideShow ppt = new SlideShow(new HSLFSlideShow("slideshow.ppt")); @@ -440,7 +440,186 @@
    - + +
    How to remove shapes from a slide + + + Shape[] shape = slide.getShapes(); + for (int i = 0; i < shape.length; i++) { + + //remove the shape + boolean ok = slide.removeShape(shape[i]); + if(ok){ + //the shape was removed. Do something. + } + } + +
    + +
    How to retrieve embedded OLE objects + + + Shape[] shape = slide.getShapes(); + for (int i = 0; i < shape.length; i++) { + if (shape[i] instanceof OLEShape) { + OLEShape ole = (OLEShape) shape[i]; + ObjectData data = ole.getObjectData(); + String name = ole.getInstanceName(); + if ("Worksheet".equals(name)) { + HSSFWorkbook wb = new HSSFWorkbook(data.getData()); + } else if ("Document".equals(name)) { + HWPFDocument doc = new HWPFDocument(data.getData()); + } + } + } + +
    + + +
    How to retrieve embedded sounds + + + FileInputStream is = new FileInputStream(args[0]); + SlideShow ppt = new SlideShow(is); + is.close(); + + SoundData[] sound = ppt.getSoundData(); + for (int i = 0; i < sound.length; i++) { + //save *WAV sounds on disk + if(sound[i].getSoundType().equals(".WAV")){ + FileOutputStream out = new FileOutputStream(sound[i].getSoundName()); + out.write(sound[i].getData()); + out.close(); + } + } + +
    + + +
    How to create shapes of arbitrary geometry + + + SlideShow ppt = new SlideShow(); + Slide slide = ppt.createSlide(); + + java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath(); + path.moveTo(100, 100); + path.lineTo(200, 100); + path.curveTo(50, 45, 134, 22, 78, 133); + path.curveTo(10, 45, 134, 56, 78, 100); + path.lineTo(100, 200); + path.closePath(); + + Freeform shape = new Freeform(); + shape.setPath(path); + slide.addShape(shape); + +
    + + +
    How to draw into a slide using Graphics2D + + Current implementation of the PowerPoint Graphics2D driver is not fully compliant with the java.awt.Graphics2D specification. + Some features like clipping, drawing of images are not yet supported. + + + SlideShow ppt = new SlideShow(); + Slide slide = ppt.createSlide(); + + //draw a simple bar graph + //bar chart data. The first value is the bar color, the second is the width + Object[] def = new Object[]{ + Color.yellow, new Integer(100), + Color.green, new Integer(150), + Color.gray, new Integer(75), + Color.red, new Integer(200), + }; + + //all objects are drawn into a shape group so we need to create one + + ShapeGroup group = new ShapeGroup(); + //define position of the drawing in the slide + Rectangle bounds = new java.awt.Rectangle(200, 100, 350, 300); + //if you want to draw in the entire slide area then define the anchor as follows: + //Dimension pgsize = ppt.getPageSize(); + //java.awt.Rectangle bounds = new java.awt.Rectangle(0, 0, pgsize.width, pgsize.height); + + group.setAnchor(bounds); + slide.addShape(group); + + //draw a simple bar chart + Graphics2D graphics = new PPGraphics2D(group); + int x = bounds.x + 50, y = bounds.y + 50; + graphics.setFont(new Font("Arial", Font.BOLD, 10)); + for (int i = 0, idx = 1; i < def.length; i+=2, idx++) { + graphics.setColor(Color.black); + int width = ((Integer)def[i+1]).intValue(); + graphics.drawString("Q" + idx, x-20, y+20); + graphics.drawString(width + "%", x + width + 10, y + 20); + graphics.setColor((Color)def[i]); + graphics.fill(new Rectangle(x, y, width, 30)); + y += 40; + } + graphics.setColor(Color.black); + graphics.setFont(new Font("Arial", Font.BOLD, 14)); + graphics.draw(bounds); + graphics.drawString("Performance", x + 70, y + 40); + + FileOutputStream out = new FileOutputStream("hslf-graphics2d.ppt"); + ppt.write(out); + out.close(); + + +
    + + +
    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 may look differently from PowerPoint due to the following reasons: +

    +
      +
    • Java2D renders fonts differently vs PowerPoint. There are always some differences in the way the font glyphs are painted
    • +
    • HSLF uses java.awt.font.LineBreakMeasurer to break text into lines. PowerPoint may do it in a different way.
    • +
    • If a font from the presentation is not avaiable, then the JDK default font will be used.
    • +
    +

    + Current Limitations: +

    +
      +
    • Some types of shapes are not yet supported (WordArt, complex auto-shapes)
    • +
    • Only Bitmap images (PNG, JPEG, DIB) can be rendered in Java
    • +
    + + 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/documentation/content/xdocs/slideshow/index.xml b/src/documentation/content/xdocs/slideshow/index.xml index 438aab3f13..a4718a6d67 100755 --- a/src/documentation/content/xdocs/slideshow/index.xml +++ b/src/documentation/content/xdocs/slideshow/index.xml @@ -26,6 +26,7 @@ + @@ -37,9 +38,12 @@ Powerpoint '97(-2007) file format. It does not support the new PowerPoint 2007 .pptx file format, which is not OLE2 based.

    -

    HSLF provides a way to read powerpoint presentations, and extract text from it. - It also provides some (currently limited) edit capabilities. +

    HSLF provides a way to read, create or modify PowerPoint presentations. In particular, it provides:

    +
      +
    • api for data extraction (text, pictures, embedded objects, sounds)
    • +
    • usermodel api for creating, reading and modifying ppt files
    • +
    This code currently lives the scratchpad area diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index cef12de4c8..8a9b8e1208 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -40,7 +40,13 @@ Created a common interface for handling PowerPoint files, irrespective of if they are .ppt or .pptx Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx
    - + + HSLF: Support for getting embedded sounds from slide show + HSLF: Initial support for rendering slides into images + HSLF: Support for getting OLE object data from slide show + HSLF: Implemented more methods in PPGraphics2D + HSLF: Added Freeform shape which can contain both lines and Bezier curves + 41071 - Improved text extraction in HSLF 30311 - Conditional Formatting - improved API, added HSSFSheetConditionalFormatting Update the formula parser code to use a HSSFWorkbook, rather than the low level model.Workbook, to make things cleaner and make supporting XSSF formulas in future much easier Fix the logger used by POIFSFileSystem, so that commons-logging isn't required when not used diff --git a/src/documentation/release-guide.txt b/src/documentation/release-guide.txt new file mode 100755 index 0000000000..8a38150ac1 --- /dev/null +++ b/src/documentation/release-guide.txt @@ -0,0 +1,135 @@ +POI Release Guide + + +(I) Prerequisites + + 1. You should read the Apache Release FAQ + 2. You must have shell access to people.apache.org + 3. Release manager must have his public key appended to the KEYS file checked in to SVN and the key published on one of the public key servers. + More info can be found here: http://www.apache.org/dev/release-signing.html + 4. You must have JDK 1.4 / 1.5 + 5. You must have the following utilities installed on your local machine and available in your path: + * ssh + * gnupg + * openssl + For Windows users, install Cygwin and make sure you have the above utilities + 6. The POI build system requires two components to perform a build + * Ant + * Forrest. + POI 3.0.2 and 3.1 were built using Ant 1.6.2 and Forrest 0.5 + +(II) Making release artefacts + 1. Update version id in build.xml. + 2. Tag current version. Include the current revision number in the comment + +{code} +$ svn cp https://svn.apache.org/repos/asf/poi/trunk \ +https://svn.apache.org/repos/asf/poi/tags/$TAG \ +-m "tag r649911 as 3.1-beta1" +{code} + +where $TAG is the release tag, for example, REL_3_1_BETA1 + + 3. Checkout the tagged version +{code} +cd tags +svn checkout https://svn.apache.org/repos/asf/poi/tags/TAG +{code} + + 4. Merge (if required) + +{code} +cd $TAG +$ svn merge https://svn.apache.org/repos/asf/poi/tags/TAG \ +https://svn.apache.org/repos/asf/poi/trunk +{code} + + 5. Start a new section in sites.xml and status.xml. + + 6. Build as if the vote had passed. The buid date must be +7 days from current. +{code} +ant build +{code} +After build you should have the following files in the build/dist: + +{code} +poi-$TAG-$DATE.jar +poi-bin-$TAG-$DATE.tar.gz +poi-bin-$TAG-$DATE.zip +poi-contrib-$TAG-$DATE.jar +poi-scratchpad-$TAG-$DATE.jar +poi-src-$TAG-$DATE.tar.gz +poi-src-$TAG-$DATE.zip +{code} + +where $TAG is the release tag specified in build.xml in the version.id property, $DATE is the release date (typically +7 days from the actual build date). + 7. Build Mavn POM files +{code} +ant maven-dist +{code} + + 8. Signing the release artifacts: +{code} +cd build/dist +for i in *.zip ; do + gpg --armor --output $i.asc --detach-sig $i; +done +for i in *.gz ; do + gpg --armor --output $i.asc --detach-sig $i; +done +{code} + +Verify the signatures: + +{code} +gpg --multifile --verify *.asc +{code} + + 9. Create MD5 checksums for all artifacts to be published: + +{code} +for i in *.zip ; do + openssl md5 < $i > $i.md5 +done +for i in *.gz ; do + openssl md5 < $i > $i.md5 +done +{code} + + 10. Upload to your area at people.apache.org. +There should be two directories: +main +maven + +Make sure that the all files have read permission. + + (III) After the vote: + +Log-in on people.apache.org + +1. Go to ~/POI-3.1-BETA1 + +cd ~/POI-3.1-BETA1/main + +BETA and ALPHA releases: +cp *-src-* /www/www.apache.org/dist/poi/dev/src +cp *-bin-* /www/www.apache.org/dist/poi/dev/bin + +FINAL release: +cp *-src-* /www/www.apache.org/dist/poi/release/src +cp *-bin-* /www/www.apache.org/dist/poi/release/bin + +cd ~/POI-3.1-BETA1/maven + +cp -r org.apache.poi /www/people.apache.org/repo/m1-ibiblio-rsync-repository/ +cp -r poi/poms /www/people.apache.org/repo/m1-ibiblio-rsync-repository/poi + + +2. Make sure that the files are owned by the unix group apcvs and that they are writable by this group. + +3. Wait for the distributions to appear on your favourite mirror + +4. Send announcements: + - to poi-user and poi-dev lists + - send announcements to announcement@apache.org, announcements@jakarta.apache.org + diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java index 48816131a4..34a5a15841 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java @@ -83,7 +83,12 @@ public class HSSFTextbox */ public void setString( RichTextString string ) { - this.string = (HSSFRichTextString) string; + HSSFRichTextString rtr = (HSSFRichTextString)string; + + // If font is not set we must set the default one + if (rtr.numFormattingRuns() == 0) rtr.applyFont((short)0); + + this.string = rtr; } /** diff --git a/src/scratchpad/examples/src/org/apache/poi/hslf/examples/DataExtraction.java b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/DataExtraction.java new file mode 100755 index 0000000000..05bf82472f --- /dev/null +++ b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/DataExtraction.java @@ -0,0 +1,148 @@ + +/* ==================================================================== + 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.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.usermodel.Range; +import org.apache.poi.hwpf.usermodel.Paragraph; + +import java.io.*; + +/** + * Demonstrates how you can extract misc embedded data from a ppt file + * + * @author Yegor Kozlov + */ +public class DataExtraction { + + public static void main(String args[]) throws Exception { + + if (args.length == 0) { + usage(); + return; + } + + FileInputStream is = new FileInputStream(args[0]); + SlideShow ppt = new SlideShow(is); + is.close(); + + //extract all sound files embedded in this presentation + SoundData[] sound = ppt.getSoundData(); + for (int i = 0; i < sound.length; i++) { + String type = sound[i].getSoundType(); //*.wav + String name = sound[i].getSoundName(); //typically file name + byte[] data = sound[i].getData(); //raw bytes + + //save the sound on disk + FileOutputStream out = new FileOutputStream(name + type); + out.write(data); + out.close(); + } + + //extract embedded OLE documents + Slide[] slide = ppt.getSlides(); + for (int i = 0; i < slide.length; i++) { + Shape[] shape = slide[i].getShapes(); + for (int j = 0; j < shape.length; j++) { + if (shape[j] instanceof OLEShape) { + OLEShape ole = (OLEShape) shape[j]; + ObjectData data = ole.getObjectData(); + String name = ole.getInstanceName(); + if ("Worksheet".equals(name)) { + + //save xls on disk + FileOutputStream out = new FileOutputStream(name + "-("+(j)+").xls"); + InputStream dis = data.getData(); + byte[] chunk = new byte[2048]; + int count; + while ((count = dis.read(chunk)) >= 0) { + out.write(chunk,0,count); + } + is.close(); + out.close(); + } else if ("Document".equals(name)) { + HWPFDocument doc = new HWPFDocument(data.getData()); + //read the word document + Range r = doc.getRange(); + for(int k = 0; k < r.numParagraphs(); k++) { + Paragraph p = r.getParagraph(k); + System.out.println(p.text()); + } + + //save on disk + FileOutputStream out = new FileOutputStream(name + "-("+(j)+").doc"); + doc.write(out); + out.close(); + } else { + System.err.println("Processing " + name); + } + } + + } + } + + //Pictures + for (int i = 0; i < slide.length; i++) { + Shape[] shape = slide[i].getShapes(); + for (int j = 0; j < shape.length; j++) { + if (shape[j] instanceof Picture) { + Picture p = (Picture) shape[j]; + PictureData data = p.getPictureData(); + String name = p.getPictureName(); + int type = data.getType(); + String ext; + switch (type) { + case Picture.JPEG: + ext = ".jpg"; + break; + case Picture.PNG: + ext = ".png"; + break; + case Picture.WMF: + ext = ".wmf"; + break; + case Picture.EMF: + ext = ".emf"; + break; + case Picture.PICT: + ext = ".pict"; + break; + case Picture.DIB: + ext = ".dib"; + break; + default: + continue; + } + FileOutputStream out = new FileOutputStream("pict-" + j + ext); + out.write(data.getData()); + out.close(); + } + + } + } + + } + + private static void usage(){ + System.out.println("Usage: DataExtraction ppt"); + } +} diff --git a/src/scratchpad/examples/src/org/apache/poi/hslf/examples/Graphics2DDemo.java b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/Graphics2DDemo.java new file mode 100755 index 0000000000..87a59c38bb --- /dev/null +++ b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/Graphics2DDemo.java @@ -0,0 +1,80 @@ + +/* ==================================================================== + 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.SlideShow; +import org.apache.poi.hslf.model.*; + +import java.awt.*; +import java.io.FileOutputStream; +import java.io.FileInputStream; + +/** + * Demonstrates how to draw into a slide using the HSLF Graphics2D driver. + * + * @author Yegor Kozlov + */ +public class Graphics2DDemo { + + /** + * A simple bar chart demo + */ + public static void main(String[] args) throws Exception { + SlideShow ppt = new SlideShow(); + + //bar chart data. The first value is the bar color, the second is the width + Object[] def = new Object[]{ + Color.yellow, new Integer(40), + Color.green, new Integer(60), + Color.gray, new Integer(30), + Color.red, new Integer(80), + }; + + Slide slide = ppt.createSlide(); + + ShapeGroup group = new ShapeGroup(); + //define position of the drawing in the slide + Rectangle bounds = new java.awt.Rectangle(200, 100, 350, 300); + group.setAnchor(bounds); + group.setCoordinates(new java.awt.Rectangle(0, 0, 100, 100)); + slide.addShape(group); + Graphics2D graphics = new PPGraphics2D(group); + + //draw a simple bar graph + int x = 10, y = 10; + graphics.setFont(new Font("Arial", Font.BOLD, 10)); + for (int i = 0, idx = 1; i < def.length; i+=2, idx++) { + graphics.setColor(Color.black); + int width = ((Integer)def[i+1]).intValue(); + graphics.drawString("Q" + idx, x-5, y+10); + graphics.drawString(width + "%", x + width+3, y + 10); + graphics.setColor((Color)def[i]); + graphics.fill(new Rectangle(x, y, width, 10)); + y += 15; + } + graphics.setColor(Color.black); + graphics.setFont(new Font("Arial", Font.BOLD, 14)); + graphics.draw(group.getCoordinates()); + graphics.drawString("Performance", x + 30, y + 10); + + FileOutputStream out = new FileOutputStream("hslf-graphics.ppt"); + ppt.write(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 0000000000..8a75297121 --- /dev/null +++ b/src/scratchpad/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java @@ -0,0 +1,108 @@ + +/* ==================================================================== + 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.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + graphics.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/HSLFSlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java index 29f01b3154..dc967fd5b4 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java @@ -27,22 +27,13 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; +import java.util.*; import org.apache.poi.POIDocument; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; import org.apache.poi.hslf.exceptions.EncryptedPowerPointFileException; import org.apache.poi.hslf.exceptions.HSLFException; -import org.apache.poi.hslf.record.CurrentUserAtom; -import org.apache.poi.hslf.record.ExOleObjStg; -import org.apache.poi.hslf.record.PersistPtrHolder; -import org.apache.poi.hslf.record.PositionDependentRecord; -import org.apache.poi.hslf.record.Record; -import org.apache.poi.hslf.record.UserEditAtom; +import org.apache.poi.hslf.record.*; import org.apache.poi.hslf.usermodel.ObjectData; import org.apache.poi.hslf.usermodel.PictureData; import org.apache.poi.hslf.model.Shape; @@ -253,6 +244,7 @@ public class HSLFSlideShow extends POIDocument private Record[] read(byte[] docstream, int usrOffset){ ArrayList lst = new ArrayList(); + HashMap offset2id = new HashMap(); while (usrOffset != 0){ UserEditAtom usr = (UserEditAtom) Record.buildRecordAtOffset(docstream, usrOffset); lst.add(new Integer(usrOffset)); @@ -266,6 +258,7 @@ public class HSLFSlideShow extends POIDocument Integer offset = (Integer)entries.get(id); lst.add(offset); + offset2id.put(offset, id); } usrOffset = usr.getLastUserEditAtomOffset(); @@ -278,6 +271,11 @@ public class HSLFSlideShow extends POIDocument for (int i = 0; i < a.length; i++) { Integer offset = (Integer)a[i]; rec[i] = (Record)Record.buildRecordAtOffset(docstream, offset.intValue()); + if(rec[i] instanceof PersistRecord) { + PersistRecord psr = (PersistRecord)rec[i]; + Integer id = (Integer)offset2id.get(offset); + psr.setPersistId(id.intValue()); + } } return rec; 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 cf65a9b13c..299fe55225 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 = getLogicalAnchor2D(); + if(outline == null){ + logger.log(POILogger.WARN, "Outline not found 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 0000000000..5d345e6de4 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java @@ -0,0 +1,373 @@ +/* ==================================================================== + 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; + } + }; + + shapes[ShapeTypes.Can] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + //m10800,qx0@1l0@2qy10800,21600,21600@2l21600@1qy10800,xem0@1qy10800@0,21600@1nfe + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 5400); + + GeneralPath path = new GeneralPath(); + + path.append(new Arc2D.Float(0, 0, 21600, adjval, 0, 180, Arc2D.OPEN), false); + path.moveTo(0, adjval/2); + + path.lineTo(0, 21600 - adjval/2); + path.closePath(); + + path.append(new Arc2D.Float(0, 21600 - adjval, 21600, adjval, 180, 180, Arc2D.OPEN), false); + path.moveTo(21600, 21600 - adjval/2); + + path.lineTo(21600, adjval/2); + path.append(new Arc2D.Float(0, 0, 21600, adjval, 180, 180, Arc2D.OPEN), false); + path.moveTo(0, adjval/2); + path.closePath(); + return path; + } + }; + + shapes[ShapeTypes.LeftBrace] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + //m21600,qx10800@0l10800@2qy0@11,10800@3l10800@1qy21600,21600e + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 1800); + int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 10800); + + GeneralPath path = new GeneralPath(); + path.moveTo(21600, 0); + + path.append(new Arc2D.Float(10800, 0, 21600, adjval*2, 90, 90, Arc2D.OPEN), false); + path.moveTo(10800, adjval); + + path.lineTo(10800, adjval2 - adjval); + + path.append(new Arc2D.Float(-10800, adjval2 - 2*adjval, 21600, adjval*2, 270, 90, Arc2D.OPEN), false); + path.moveTo(0, adjval2); + + path.append(new Arc2D.Float(-10800, adjval2, 21600, adjval*2, 0, 90, Arc2D.OPEN), false); + path.moveTo(10800, adjval2 + adjval); + + path.lineTo(10800, 21600 - adjval); + + path.append(new Arc2D.Float(10800, 21600 - 2*adjval, 21600, adjval*2, 180, 90, Arc2D.OPEN), false); + + return path; + } + }; + + shapes[ShapeTypes.RightBrace] = new ShapeOutline(){ + public java.awt.Shape getOutline(Shape shape){ + //m,qx10800@0 l10800@2qy21600@11,10800@3l10800@1qy,21600e + int adjval = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUSTVALUE, 1800); + int adjval2 = shape.getEscherProperty(EscherProperties.GEOMETRY__ADJUST2VALUE, 10800); + + GeneralPath path = new GeneralPath(); + path.moveTo(0, 0); + + path.append(new Arc2D.Float(-10800, 0, 21600, adjval*2, 0, 90, Arc2D.OPEN), false); + path.moveTo(10800, adjval); + + path.lineTo(10800, adjval2 - adjval); + + path.append(new Arc2D.Float(10800, adjval2 - 2*adjval, 21600, adjval*2, 180, 90, Arc2D.OPEN), false); + path.moveTo(21600, adjval2); + + path.append(new Arc2D.Float(10800, adjval2, 21600, adjval*2, 90, 90, Arc2D.OPEN), false); + path.moveTo(10800, adjval2 + adjval); + + path.lineTo(10800, 21600 - adjval); + + path.append(new Arc2D.Float(-10800, 21600 - 2*adjval, 21600, adjval*2, 270, 90, Arc2D.OPEN), false); + + return path; + } + }; + + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Background.java b/src/scratchpad/src/org/apache/poi/hslf/model/Background.java index 4906922a82..e2718f6aca 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Background.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Background.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -19,6 +18,14 @@ package org.apache.poi.hslf.model; import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.hslf.usermodel.PictureData; +import org.apache.poi.hslf.blip.Bitmap; +import org.apache.poi.util.POILogger; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; /** * Background shape @@ -27,12 +34,42 @@ import org.apache.poi.ddf.EscherContainerRecord; */ public class Background extends Shape { - protected Background(EscherContainerRecord escherRecord, Shape parent){ + protected Background(EscherContainerRecord escherRecord, Shape parent) { super(escherRecord, parent); } - protected EscherContainerRecord createSpContainer(boolean isChild){ + protected EscherContainerRecord createSpContainer(boolean isChild) { return null; } + public void draw(Graphics2D graphics) { + Fill f = getFill(); + Dimension pg = getSheet().getSlideShow().getPageSize(); + Rectangle anchor = new Rectangle(0, 0, pg.width, pg.height); + switch (f.getFillType()) { + case Fill.FILL_SOLID: + Color color = f.getForegroundColor(); + graphics.setPaint(color); + graphics.fill(anchor); + break; + case Fill.FILL_PICTURE: + PictureData data = f.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; + } + Image scaledImg = img.getScaledInstance(anchor.width, anchor.height, Image.SCALE_SMOOTH); + graphics.drawImage(scaledImg, anchor.x, anchor.y, null); + + } + break; + default: + logger.log(POILogger.WARN, "unsuported fill type: " + f.getFillType()); + break; + } + } } 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 c4df7b6779..49393250d1 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 e34d651b3d..d31237f8db 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java @@ -22,6 +22,7 @@ import org.apache.poi.util.POILogger; import java.awt.geom.*; import java.util.ArrayList; +import java.util.Arrays; /** * A "Freeform" shape. @@ -33,6 +34,16 @@ import java.util.ArrayList; * @author Yegor Kozlov */ public class Freeform extends AutoShape { + + public static final byte[] SEGMENTINFO_MOVETO = new byte[]{0x00, 0x40}; + public static final byte[] SEGMENTINFO_LINETO = new byte[]{0x00, (byte)0xAC}; + public static final byte[] SEGMENTINFO_ESCAPE = new byte[]{0x01, 0x00}; + public static final byte[] SEGMENTINFO_ESCAPE2 = new byte[]{0x01, 0x20}; + public static final byte[] SEGMENTINFO_CUBICTO = new byte[]{0x00, (byte)0xAD}; + public static final byte[] SEGMENTINFO_CUBICTO2 = new byte[]{0x00, (byte)0xB3}; //OpenOffice inserts 0xB3 instead of 0xAD. + public static final byte[] SEGMENTINFO_CLOSE = new byte[]{0x01, (byte)0x60}; + public static final byte[] SEGMENTINFO_END = new byte[]{0x00, (byte)0x80}; + /** * Create a Freeform object and initialize it from the supplied Record container. * @@ -82,36 +93,37 @@ public class Freeform extends AutoShape { switch (type) { case PathIterator.SEG_MOVETO: pntInfo.add(new Point2D.Double(vals[0], vals[1])); - segInfo.add(new byte[]{0x00, 0x40}); + segInfo.add(SEGMENTINFO_MOVETO); break; case PathIterator.SEG_LINETO: pntInfo.add(new Point2D.Double(vals[0], vals[1])); - segInfo.add(new byte[]{0x00, (byte)0xAC}); - segInfo.add(new byte[]{0x01, 0x00 }); + segInfo.add(SEGMENTINFO_LINETO); + segInfo.add(SEGMENTINFO_ESCAPE); break; case PathIterator.SEG_CUBICTO: pntInfo.add(new Point2D.Double(vals[0], vals[1])); pntInfo.add(new Point2D.Double(vals[2], vals[3])); pntInfo.add(new Point2D.Double(vals[4], vals[5])); - segInfo.add(new byte[]{0x00, (byte)0xAD}); - segInfo.add(new byte[]{0x01, 0x20 }); + segInfo.add(SEGMENTINFO_CUBICTO); + segInfo.add(SEGMENTINFO_ESCAPE2); break; case PathIterator.SEG_QUADTO: + //TODO: figure out how to convert SEG_QUADTO into SEG_CUBICTO logger.log(POILogger.WARN, "SEG_QUADTO is not supported"); break; case PathIterator.SEG_CLOSE: pntInfo.add(pntInfo.get(0)); - segInfo.add(new byte[]{0x00, (byte)0xAC}); - segInfo.add(new byte[]{0x01, 0x00 }); - segInfo.add(new byte[]{0x00, (byte)0xAC}); - segInfo.add(new byte[]{0x01, (byte)0x60}); + segInfo.add(SEGMENTINFO_LINETO); + segInfo.add(SEGMENTINFO_ESCAPE); + segInfo.add(SEGMENTINFO_LINETO); + segInfo.add(SEGMENTINFO_CLOSE); isClosed = true; break; } it.next(); } - if(!isClosed) segInfo.add(new byte[]{0x00, (byte)0xAC}); + if(!isClosed) segInfo.add(SEGMENTINFO_LINETO); segInfo.add(new byte[]{0x00, (byte)0x80}); EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); @@ -147,4 +159,85 @@ public class Freeform extends AutoShape { setAnchor(bounds); } + + /** + * Gets the freeform path + * + * @return the freeform path + */ + public GeneralPath getPath(){ + EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__SHAPEPATH, 0x4)); + + EscherArrayProperty verticesProp = (EscherArrayProperty)getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__VERTICES + 0x4000)); + if(verticesProp == null) verticesProp = (EscherArrayProperty)getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__VERTICES)); + + EscherArrayProperty segmentsProp = (EscherArrayProperty)getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__SEGMENTINFO + 0x4000)); + if(segmentsProp == null) segmentsProp = (EscherArrayProperty)getEscherProperty(opt, (short)(EscherProperties.GEOMETRY__SEGMENTINFO)); + + //sanity check + if(verticesProp == null) { + logger.log(POILogger.WARN, "Freeform is missing GEOMETRY__VERTICES "); + return null; + } + if(segmentsProp == null) { + logger.log(POILogger.WARN, "Freeform is missing GEOMETRY__SEGMENTINFO "); + return null; + } + + Rectangle2D bounds = getAnchor2D(); + float right = (float)bounds.getX(); + float bottom = (float)bounds.getY(); + + GeneralPath path = new GeneralPath(); + 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( + ((float)x*POINT_DPI/MASTER_DPI + right), + ((float)y*POINT_DPI/MASTER_DPI + bottom)); + } 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( + ((float)x1*POINT_DPI/MASTER_DPI + right), ((float)y1*POINT_DPI/MASTER_DPI + bottom), + ((float)x2*POINT_DPI/MASTER_DPI + right), ((float)y2*POINT_DPI/MASTER_DPI + bottom), + ((float)x3*POINT_DPI/MASTER_DPI + right), ((float)y3*POINT_DPI/MASTER_DPI + bottom)); + + } 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( + ((float)x*POINT_DPI/MASTER_DPI + right), ((float)y*POINT_DPI/MASTER_DPI + bottom)); + } + } else if (Arrays.equals(pnext, SEGMENTINFO_CLOSE)){ + path.closePath(); + } + } + } + + 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 9237183689..370da96d6b 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 = getLogicalAnchor2D(); + 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 4371e2a371..5b1b1016e4 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 new file mode 100755 index 0000000000..3f574c36fa --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/OLEShape.java @@ -0,0 +1,160 @@ +/* +* 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.*; +import org.apache.poi.hslf.usermodel.SlideShow; +import org.apache.poi.hslf.usermodel.ObjectData; +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.util.POILogger; + + +/** + * A shape representing embedded OLE obejct. + * + * @author Yegor Kozlov + */ +public class OLEShape extends Picture { + protected ExEmbed _exEmbed; + + /** + * Create a new OLEShape + * + * @param idx the index of the picture + */ + public OLEShape(int idx){ + super(idx); + } + + /** + * Create a new OLEShape + * + * @param idx the index of the picture + * @param parent the parent shape + */ + public OLEShape(int idx, Shape parent) { + super(idx, parent); + } + + /** + * Create a OLEShape object + * + * @param escherRecord the EscherSpContainer record which holds information about + * this picture in the Slide + * @param parent the parent shape of this picture + */ + protected OLEShape(EscherContainerRecord escherRecord, Shape parent){ + super(escherRecord, parent); + } + + /** + * Returns unique identifier for the OLE object. + * + * @return the unique identifier for the OLE object + */ + public int getObjectID(){ + return getEscherProperty(EscherProperties.BLIP__PICTUREID); + } + + /** + * Returns unique identifier for the OLE object. + * + * @return the unique identifier for the OLE object + */ + public ObjectData getObjectData(){ + SlideShow ppt = getSheet().getSlideShow(); + ObjectData[] ole = ppt.getEmbeddedObjects(); + + //persist reference + int ref = getExEmbed().getExOleObjAtom().getObjStgDataRef(); + for (int i = 0; i < ole.length; i++) { + if(ole[i].getExOleObjStg().getPersistId() == ref) return ole[i]; + + } + logger.log(POILogger.WARN, "OLE data not found"); + return null; + } + + /** + * Return the record container for this embedded object. + * + *

    + * It contains: + * 1. ExEmbedAtom.(4045) + * 2. ExOleObjAtom (4035) + * 3. CString (4026), Instance MenuName (1) used for menus and the Links dialog box. + * 4. CString (4026), Instance ProgID (2) that stores the OLE Programmatic Identifier. + * A ProgID is a string that uniquely identifies a given object. + * 5. CString (4026), Instance ClipboardName (3) that appears in the paste special dialog. + * 6. MetaFile( 4033), optional + *

    + * @return + */ + public ExEmbed getExEmbed(){ + if(_exEmbed == null){ + SlideShow ppt = getSheet().getSlideShow(); + + ExObjList lst = ppt.getDocumentRecord().getExObjList(); + if(lst == null){ + logger.log(POILogger.WARN, "ExObjList not found"); + return null; + } + + int id = getObjectID(); + Record[] ch = lst.getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i] instanceof ExEmbed){ + ExEmbed embd = (ExEmbed)ch[i]; + if( embd.getExOleObjAtom().getObjID() == id) _exEmbed = embd; + } + } + } + return _exEmbed; + } + + /** + * Returns the instance name of the embedded object, e.g. "Document" or "Workbook". + * + * @return the instance name of the embedded object + */ + public String getInstanceName(){ + return getExEmbed().getMenuName(); + } + + /** + * Returns the full name of the embedded object, + * e.g. "Microsoft Word Document" or "Microsoft Office Excel Worksheet". + * + * @return the full name of the embedded object + */ + public String getFullName(){ + return getExEmbed().getClipboardName(); + } + + /** + * Returns the ProgID that stores the OLE Programmatic Identifier. + * A ProgID is a string that uniquely identifies a given object, for example, + * "Word.Document.8" or "Excel.Sheet.8". + * + * @return the ProgID + */ + public String getProgID(){ + return getExEmbed().getProgId(); + } +} 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 23088f056b..37cedce0fa 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 910d5c8503..e10986966b 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java @@ -21,13 +21,16 @@ import org.apache.poi.hslf.usermodel.PictureData; import org.apache.poi.hslf.usermodel.SlideShow; import org.apache.poi.hslf.record.Document; import org.apache.poi.hslf.blip.Bitmap; +import org.apache.poi.hslf.exceptions.HSLFException; 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.io.UnsupportedEncodingException; import java.util.List; import java.util.Arrays; @@ -128,7 +131,7 @@ public class Picture extends SimpleShape { //set default properties for a picture EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); - setEscherProperty(opt, EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 8388736); + setEscherProperty(opt, EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 0x800080); //another weird feature of powerpoint: for picture id we must add 0x4000. setEscherProperty(opt, (short)(EscherProperties.BLIP__BLIPTODISPLAY + 0x4000), idx); @@ -192,14 +195,70 @@ public class Picture extends SimpleShape { return null; } + /** + * Name of this picture. + * + * @return name of this picture + */ + public String getPictureName(){ + EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); + EscherComplexProperty prop = (EscherComplexProperty)getEscherProperty(opt, EscherProperties.BLIP__BLIPFILENAME); + String name = null; + if(prop != null){ + try { + name = new String(prop.getComplexData(), "UTF-16LE"); + int idx = name.indexOf('\u0000'); + return idx == -1 ? name : name.substring(0, idx); + } catch (UnsupportedEncodingException e){ + throw new HSLFException(e); + } + } + return name; + } + + /** + * Name of this picture. + * + * @param name of this picture + */ + public void setPictureName(String name){ + EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); + try { + byte[] data = (name + '\u0000').getBytes("UTF-16LE"); + EscherComplexProperty prop = new EscherComplexProperty(EscherProperties.BLIP__BLIPFILENAME, false, data); + opt.addEscherProperty(prop); + } catch (UnsupportedEncodingException e){ + throw new HSLFException(e); + } + } + /** * By default set the orininal image size */ protected void afterInsert(Sheet sh){ + super.afterInsert(sh); java.awt.Rectangle anchor = getAnchor(); if (anchor.equals(new java.awt.Rectangle())){ setDefaultSize(); } } + 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 == null ? "NA" : data.getClass().getName())); + } + } } 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 f93392a9a9..e96e349002 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java @@ -17,12 +17,12 @@ 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.hslf.record.PPDrawing; import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogFactory; -import java.util.Iterator; +import java.util.*; import java.awt.*; import java.awt.geom.Rectangle2D; @@ -166,12 +166,24 @@ public abstract class Shape { if ((flags & EscherSpRecord.FLAG_CHILD) != 0){ EscherChildAnchorRecord rec = (EscherChildAnchorRecord)getEscherChild(_escherContainer, EscherChildAnchorRecord.RECORD_ID); anchor = new java.awt.Rectangle(); - anchor = new Rectangle2D.Float( - (float)rec.getDx1()*POINT_DPI/MASTER_DPI, - (float)rec.getDy1()*POINT_DPI/MASTER_DPI, - (float)(rec.getDx2()-rec.getDx1())*POINT_DPI/MASTER_DPI, - (float)(rec.getDy2()-rec.getDy1())*POINT_DPI/MASTER_DPI - ); + if(rec == null){ + logger.log(POILogger.WARN, "EscherSpRecord.FLAG_CHILD is set but EscherChildAnchorRecord was not found"); + EscherClientAnchorRecord clrec = (EscherClientAnchorRecord)getEscherChild(_escherContainer, EscherClientAnchorRecord.RECORD_ID); + anchor = new java.awt.Rectangle(); + anchor = new Rectangle2D.Float( + (float)clrec.getCol1()*POINT_DPI/MASTER_DPI, + (float)clrec.getFlag()*POINT_DPI/MASTER_DPI, + (float)(clrec.getDx1()-clrec.getCol1())*POINT_DPI/MASTER_DPI, + (float)(clrec.getRow1()-clrec.getFlag())*POINT_DPI/MASTER_DPI + ); + } else { + anchor = new Rectangle2D.Float( + (float)rec.getDx1()*POINT_DPI/MASTER_DPI, + (float)rec.getDy1()*POINT_DPI/MASTER_DPI, + (float)(rec.getDx2()-rec.getDx1())*POINT_DPI/MASTER_DPI, + (float)(rec.getDy2()-rec.getDy1())*POINT_DPI/MASTER_DPI + ); + } } else { EscherClientAnchorRecord rec = (EscherClientAnchorRecord)getEscherChild(_escherContainer, EscherClientAnchorRecord.RECORD_ID); @@ -186,6 +198,10 @@ public abstract class Shape { return anchor; } + public Rectangle2D getLogicalAnchor2D(){ + return getAnchor2D(); + } + /** * Sets the anchor (the bounding box rectangle) of this shape. * All coordinates should be expressed in points (72 dpi). @@ -245,7 +261,7 @@ public abstract class Shape { * @return escher property or null if not found. */ public static EscherProperty getEscherProperty(EscherOptRecord opt, int propId){ - for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); ) + if(opt != null) for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); ) { EscherProperty prop = (EscherProperty) iterator.next(); if (prop.getPropertyNumber() == propId) @@ -294,7 +310,18 @@ public abstract class Shape { public int getEscherProperty(short propId){ EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, propId); - return prop == null ? 0 : prop.getPropertyNumber(); + 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(); } /** @@ -314,7 +341,58 @@ public abstract class Shape { * @param sh - owning shape */ protected void afterInsert(Sheet sh){ + PPDrawing ppdrawing = sh.getPPDrawing(); + EscherContainerRecord dgContainer = (EscherContainerRecord) ppdrawing.getEscherRecords()[0]; + + EscherDgRecord dg = (EscherDgRecord) Shape.getEscherChild(dgContainer, EscherDgRecord.RECORD_ID); + + int id = allocateShapeId(dg); + setShapeId(id); + } + + /** + * Allocates new shape id for the new drawing group id. + * + * @param dg EscherDgRecord of the sheet that owns the shape being created + * + * @return a new shape id. + */ + protected int allocateShapeId(EscherDgRecord dg) + { + EscherDggRecord dgg = _sheet.getSlideShow().getDocumentRecord().getPPDrawingGroup().getEscherDggRecord(); + if(dgg == null){ + logger.log(POILogger.ERROR, "EscherDggRecord not found"); + return 0; + } + + dgg.setNumShapesSaved( dgg.getNumShapesSaved() + 1 ); + + // Add to existing cluster if space available + for (int i = 0; i < dgg.getFileIdClusters().length; i++) + { + EscherDggRecord.FileIdCluster c = dgg.getFileIdClusters()[i]; + if (c.getDrawingGroupId() == dg.getDrawingGroupId() && c.getNumShapeIdsUsed() != 1024) + { + int result = c.getNumShapeIdsUsed() + (1024 * (i+1)); + c.incrementShapeId(); + dg.setNumShapes( dg.getNumShapes() + 1 ); + dg.setLastMSOSPID( result ); + if (result >= dgg.getShapeIdMax()) + dgg.setShapeIdMax( result + 1 ); + return result; + } + } + + // Create new cluster + dgg.addCluster( dg.getDrawingGroupId(), 0 ); + dgg.getFileIdClusters()[dgg.getFileIdClusters().length-1].incrementShapeId(); + dg.setNumShapes( dg.getNumShapes() + 1 ); + int result = (1024 * dgg.getFileIdClusters().length); + dg.setLastMSOSPID( result ); + if (result >= dgg.getShapeIdMax()) + dgg.setShapeIdMax( result + 1 ); + return result; } /** @@ -333,14 +411,32 @@ 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); + } + + /** + * @return id for the shape. + */ + public int getShapeId(){ + EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); + return spRecord == null ? 0 : spRecord.getShapeId(); + } + + /** + * Sets shape ID + * + * @param id of the shape + */ + public void setShapeId(int id){ + EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); + if(spRecord != null) spRecord.setShapeId(id); } /** @@ -364,4 +460,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 getLogicalAnchor2D(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java index 76b43ebb8a..4abc8c1c00 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java @@ -76,13 +76,19 @@ public class ShapeFactory { case ShapeTypes.TextBox: shape = new TextBox(spContainer, parent); break; - case ShapeTypes.PictureFrame: - shape = new Picture(spContainer, parent); + case ShapeTypes.PictureFrame: { + EscherOptRecord opt = (EscherOptRecord)Shape.getEscherChild(spContainer, EscherOptRecord.RECORD_ID); + EscherProperty prop = Shape.getEscherProperty(opt, EscherProperties.BLIP__PICTUREID); + if(prop != null) + shape = new OLEShape(spContainer, parent); //presence of BLIP__PICTUREID indicates it is an embedded object + else + shape = new Picture(spContainer, parent); break; + } case ShapeTypes.Line: shape = new Line(spContainer, parent); break; - case ShapeTypes.NotPrimitive: + case ShapeTypes.NotPrimitive: { EscherOptRecord opt = (EscherOptRecord)Shape.getEscherChild(spContainer, EscherOptRecord.RECORD_ID); EscherProperty prop = Shape.getEscherProperty(opt, EscherProperties.GEOMETRY__VERTICES); if(prop != null) @@ -93,6 +99,7 @@ public class ShapeFactory { shape = new AutoShape(spContainer, parent); } break; + } default: shape = new AutoShape(spContainer, parent); break; 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 b22ed8d70b..b57a6e2336 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,8 @@ import org.apache.poi.hslf.record.EscherTextboxWrapper; import java.util.ArrayList; import java.util.List; import java.awt.geom.Rectangle2D; +import java.awt.geom.AffineTransform; +import java.awt.*; /** * Represents a group of shapes. @@ -113,6 +115,47 @@ public class ShapeGroup extends Shape{ spgr.setRectY2((anchor.y + anchor.height)*MASTER_DPI/POINT_DPI); } + /** + * Sets the coordinate space of this group. All children are constrained + * to these coordinates. + * + * @param anchor the coordinate space of this group + */ + public void setCoordinates(Rectangle2D anchor){ + EscherContainerRecord spContainer = (EscherContainerRecord)_escherContainer.getChildRecords().get(0); + EscherSpgrRecord spgr = (EscherSpgrRecord)getEscherChild(spContainer, EscherSpgrRecord.RECORD_ID); + + int x1 = (int)Math.round(anchor.getX()*MASTER_DPI/POINT_DPI); + int y1 = (int)Math.round(anchor.getY()*MASTER_DPI/POINT_DPI); + int x2 = (int)Math.round((anchor.getX() + anchor.getWidth())*MASTER_DPI/POINT_DPI); + int y2 = (int)Math.round((anchor.getY() + anchor.getHeight())*MASTER_DPI/POINT_DPI); + + spgr.setRectX1(x1); + spgr.setRectY1(y1); + spgr.setRectX2(x2); + spgr.setRectY2(y2); + + } + + /** + * Gets the coordinate space of this group. All children are constrained + * to these coordinates. + * + * @return the coordinate space of this group + */ + public Rectangle2D getCoordinates(){ + EscherContainerRecord spContainer = (EscherContainerRecord)_escherContainer.getChildRecords().get(0); + EscherSpgrRecord spgr = (EscherSpgrRecord)getEscherChild(spContainer, EscherSpgrRecord.RECORD_ID); + + Rectangle2D.Float anchor = new Rectangle2D.Float(); + anchor.x = (float)spgr.getRectX1()*POINT_DPI/MASTER_DPI; + anchor.y = (float)spgr.getRectY1()*POINT_DPI/MASTER_DPI; + anchor.width = (float)(spgr.getRectX2() - spgr.getRectX1())*POINT_DPI/MASTER_DPI; + anchor.height = (float)(spgr.getRectY2() - spgr.getRectY1())*POINT_DPI/MASTER_DPI; + + return anchor; + } + /** * Create a new ShapeGroup and create an instance of EscherSpgrContainer which represents a group of shapes */ @@ -190,14 +233,24 @@ public class ShapeGroup extends Shape{ * @return the anchor of this shape group */ public Rectangle2D getAnchor2D(){ - EscherContainerRecord groupInfoContainer = (EscherContainerRecord)_escherContainer.getChild(0); - EscherSpgrRecord spgr = (EscherSpgrRecord)getEscherChild(groupInfoContainer, EscherSpgrRecord.RECORD_ID); - Rectangle2D anchor = new Rectangle2D.Float( - (float)spgr.getRectX1()*POINT_DPI/MASTER_DPI, - (float)spgr.getRectY1()*POINT_DPI/MASTER_DPI, - (float)(spgr.getRectX2() - spgr.getRectX1())*POINT_DPI/MASTER_DPI, - (float)(spgr.getRectY2() - spgr.getRectY1())*POINT_DPI/MASTER_DPI - ); + EscherContainerRecord spContainer = (EscherContainerRecord)_escherContainer.getChildRecords().get(0); + EscherClientAnchorRecord clientAnchor = (EscherClientAnchorRecord)getEscherChild(spContainer, EscherClientAnchorRecord.RECORD_ID); + Rectangle2D.Float anchor = new Rectangle2D.Float(); + if(clientAnchor == null){ + logger.log(POILogger.INFO, "EscherClientAnchorRecord was not found for shape group. Searching for EscherChildAnchorRecord."); + EscherChildAnchorRecord rec = (EscherChildAnchorRecord)getEscherChild(spContainer, EscherChildAnchorRecord.RECORD_ID); + anchor = new Rectangle2D.Float( + (float)rec.getDx1()*POINT_DPI/MASTER_DPI, + (float)rec.getDy1()*POINT_DPI/MASTER_DPI, + (float)(rec.getDx2()-rec.getDx1())*POINT_DPI/MASTER_DPI, + (float)(rec.getDy2()-rec.getDy1())*POINT_DPI/MASTER_DPI + ); + } else { + anchor.x = (float)clientAnchor.getCol1()*POINT_DPI/MASTER_DPI; + anchor.y = (float)clientAnchor.getFlag()*POINT_DPI/MASTER_DPI; + anchor.width = (float)(clientAnchor.getDx1() - clientAnchor.getCol1())*POINT_DPI/MASTER_DPI ; + anchor.height = (float)(clientAnchor.getRow1() - clientAnchor.getFlag())*POINT_DPI/MASTER_DPI; + } return anchor; } @@ -222,5 +275,27 @@ public class ShapeGroup extends Shape{ public Hyperlink getHyperlink(){ return null; } - + + public void draw(Graphics2D graphics){ + Rectangle2D anchor = getAnchor2D(); + Rectangle2D coords = getCoordinates(); + + //transform coordinates + AffineTransform at = graphics.getTransform(); + /* + if(!anchor.equals(coords)){ + graphics.scale(anchor.getWidth()/coords.getWidth(), anchor.getHeight()/coords.getHeight()); + + graphics.translate( + anchor.getX()*coords.getWidth()/anchor.getWidth() - coords.getX(), + anchor.getY()*coords.getHeight()/anchor.getHeight() - coords.getY()); + } + */ + Shape[] sh = getShapes(); + for (int i = 0; i < sh.length; i++) { + sh[i].draw(graphics); + } + + graphics.setTransform(at); + } } 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 0000000000..069fdbc4d0 --- /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 0000000000..fa947ddfed --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapePainter.java @@ -0,0 +1,102 @@ +/* ==================================================================== + 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.util.POILogger; +import org.apache.poi.util.POILogFactory; + +import java.awt.*; +import java.awt.geom.Rectangle2D; + +/** + * Paint a shape into java.awt.Graphics2D + * + * @author Yegor Kozlov + */ +public class ShapePainter { + protected static POILogger logger = POILogFactory.getLogger(ShapePainter.class); + + public static void paint(SimpleShape shape, Graphics2D graphics){ + Rectangle2D anchor = shape.getLogicalAnchor2D(); + 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) { + //TODO: implement gradient and texture fill patterns + graphics.setPaint(fillColor); + graphics.fill(outline); + } + + //border + Color lineColor = shape.getLineColor(); + if (lineColor != null){ + graphics.setPaint(lineColor); + float width = (float)shape.getLineWidth(); + if(width == 0) width = 0.75f; + + int dashing = shape.getLineDashing(); + //TODO: implement more dashing styles + float[] dashptrn = null; + switch(dashing){ + case Line.PEN_SOLID: + dashptrn = null; + break; + case Line.PEN_PS_DASH: + dashptrn = new float[]{width, width}; + break; + case Line.PEN_DOTGEL: + dashptrn = new float[]{width*4, width*3}; + break; + default: + logger.log(POILogger.WARN, "unsupported dashing: " + dashing); + dashptrn = new float[]{width, width}; + 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 abf2fec589..d9c8903d5b 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 @@ -246,14 +247,6 @@ public abstract class Sheet { EscherContainerRecord spgr = (EscherContainerRecord) Shape.getEscherChild(dgContainer, EscherContainerRecord.SPGR_CONTAINER); spgr.addChildRecord(shape.getSpContainer()); - EscherDgRecord dg = (EscherDgRecord) Shape.getEscherChild(dgContainer, EscherDgRecord.RECORD_ID); - dg.setNumShapes(dg.getNumShapes() + 1); - - int shapeId = dg.getLastMSOSPID()+1; - dg.setLastMSOSPID(shapeId); - - EscherSpRecord sp = shape.getSpContainer().getChildById(EscherSpRecord.RECORD_ID); - if(sp != null) sp.setShapeId(shapeId); shape.setSheet(this); shape.afterInsert(this); @@ -329,4 +322,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 0831d453b5..ea0719a7f1 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,8 @@ import org.apache.poi.util.LittleEndian; import org.apache.poi.hslf.record.ColorSchemeAtom; import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; /** * An abstract simple (non-group) shape. @@ -199,4 +201,87 @@ 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 Rectangle2D getLogicalAnchor2D(){ + Rectangle2D anchor = getAnchor2D(); + + //if it is a groupped shape see if we need to transform the coordinates + if (_parent != null){ + Shape top = _parent; + while(top.getParent() != null) top = top.getParent(); + + Rectangle2D clientAnchor = top.getAnchor2D(); + Rectangle2D spgrAnchor = ((ShapeGroup)top).getCoordinates(); + + double scalex = (double)spgrAnchor.getWidth()/clientAnchor.getWidth(); + double scaley = (double)spgrAnchor.getHeight()/clientAnchor.getHeight(); + + double x = clientAnchor.getX() + (anchor.getX() - spgrAnchor.getX())/scalex; + double y = clientAnchor.getY() + (anchor.getY() - spgrAnchor.getY())/scaley; + double width = anchor.getWidth()/scalex; + double height = anchor.getHeight()/scaley; + + anchor = new Rectangle2D.Double(x, y, width, height); + + } + + int angle = getRotation(); + if(angle != 0){ + double centerX = anchor.getX() + anchor.getWidth()/2; + double centerY = anchor.getY() + anchor.getHeight()/2; + + AffineTransform trans = new AffineTransform(); + trans.translate(centerX, centerY); + trans.rotate(Math.toRadians(angle)); + trans.translate(-centerX, -centerY); + + Rectangle2D rect = trans.createTransformedShape(anchor).getBounds2D(); + if((anchor.getWidth() < anchor.getHeight() && rect.getWidth() > rect.getHeight()) || + (anchor.getWidth() > anchor.getHeight() && rect.getWidth() < rect.getHeight()) ){ + trans = new AffineTransform(); + trans.translate(centerX, centerY); + trans.rotate(Math.PI/2); + trans.translate(-centerX, -centerY); + anchor = trans.createTransformedShape(anchor).getBounds2D(); + } + } + return anchor; + } + + 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 ea7201751d..e618ae2303 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 e442ae3047..26870dbdbb 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,31 @@ 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; + } } prop = getStyleAttribute(txtype, level, name, isCharacter); } @@ -119,4 +127,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 947c41af63..43dab58af1 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 @@ -113,6 +112,8 @@ public class Table extends ShapeGroup { } protected void afterInsert(Sheet sh){ + super.afterInsert(sh); + EscherContainerRecord spCont = (EscherContainerRecord) getSpContainer().getChild(0); List lst = spCont.getChildRecords(); EscherOptRecord opt = (EscherOptRecord)lst.get(lst.size()-2); 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 36789ad91a..7fa69b1d0c 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 a1d45ffbae..dda55a2c7b 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 0000000000..c1eadc336c --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java @@ -0,0 +1,253 @@ +/* ==================================================================== + 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; + } + + /** + * Convert the underlying set of rich text runs into java.text.AttributedString + */ + 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) { + logger.log(POILogger.INFO, "Skipping RichTextRun with zero length"); + 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; + } + + 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.getLogicalAnchor2D(); + + 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); + + boolean prStart = text.charAt(startIndex) == '\n'; + if(prStart) measurer.setPosition(startIndex++); + + RichTextRun rt = run.getRichTextRunAt(startIndex == text.length() ? (startIndex-1) : startIndex); + if(rt == null) { + logger.log(POILogger.WARN, "RichTextRun not found at pos" + startIndex + "; 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((float)anchor.getWidth(), + 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(); + + float lineHeight = (float)textLayout.getBounds().getHeight(); + int linespacing = rt.getLineSpacing(); + if(linespacing == 0) linespacing = 100; + + TextElement el = new TextElement(); + if(linespacing >= 0){ + el.ascent = textLayout.getAscent()*linespacing/100; + } else { + el.ascent = -linespacing*Shape.POINT_DPI/Shape.MASTER_DPI; + } + + el._align = rt.getAlignment(); + el._text = textLayout; + el._textOffset = rt.getTextOffset(); + + if (prStart){ + int sp = rt.getSpaceBefore(); + float spaceBefore; + if(sp >= 0){ + spaceBefore = lineHeight * sp/100; + } else { + spaceBefore = -sp*Shape.POINT_DPI/Shape.MASTER_DPI; + } + el.ascent += spaceBefore; + } + + float descent; + if(linespacing >= 0){ + descent = (textLayout.getDescent() + textLayout.getLeading())*linespacing/100; + } else { + descent = -linespacing*Shape.POINT_DPI/Shape.MASTER_DPI; + } + if (prStart){ + int sp = rt.getSpaceAfter(); + float spaceAfter; + if(sp >= 0){ + spaceAfter = lineHeight * sp/100; + } else { + spaceAfter = -sp*Shape.POINT_DPI/Shape.MASTER_DPI; + } + el.ascent += spaceAfter; + } + el.descent = descent; + + textHeight += el.ascent + el.descent; + + if(rt.isBullet() && (prStart || startIndex == 0)){ + it.setIndex(startIndex); + + AttributedString bat = new AttributedString(Character.toString(rt.getBulletChar())); + Color clr = rt.getBulletColor(); + if (clr != null) bat.addAttribute(TextAttribute.FOREGROUND, clr); + else bat.addAttribute(TextAttribute.FOREGROUND, it.getAttribute(TextAttribute.FOREGROUND)); + bat.addAttribute(TextAttribute.FAMILY, it.getAttribute(TextAttribute.FAMILY)); + bat.addAttribute(TextAttribute.SIZE, it.getAttribute(TextAttribute.SIZE)); + + TextLayout bulletLayout = new TextLayout(bat.getIterator(), graphics.getFontRenderContext()); + if(text.substring(startIndex, endIndex).length() > 1){ + el._bullet = bulletLayout; + el._bulletOffset = rt.getBulletOffset(); + } + } + lines.add(el); + } + + int valign = _shape.getVerticalAlignment(); + double y0 = anchor.getY(); + switch (valign){ + case TextShape.AnchorTopBaseline: + case TextShape.AnchorTop: + y0 += _shape.getMarginTop(); + break; + case TextShape.AnchorBottom: + y0 += anchor.getHeight() - textHeight - _shape.getMarginBottom(); + break; + default: + case TextShape.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.ascent; + + Point2D.Double pen = new Point2D.Double(); + pen.y = y0; + switch (elem._align) { + default: + 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; + } + 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.descent; + } + } + + + static class TextElement { + public TextLayout _text; + public int _textOffset; + public TextLayout _bullet; + public int _bulletOffset; + public int _align; + public float ascent, descent; + } +} 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 5834e2eaec..2f77ac5ffa 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; } @@ -635,4 +639,20 @@ public class TextRun public Hyperlink[] getHyperlinks(){ return Hyperlink.find(this); } + + /** + * Fetch RichTextRun at a given position + * + * @param pos 0-based index in the text + * @return RichTextRun or null if not found + */ + public RichTextRun getRichTextRunAt(int pos){ + for (int i = 0; i < _rtRuns.length; i++) { + int start = _rtRuns[i].getStartIndex(); + int end = _rtRuns[i].getEndIndex(); + if(pos >= start && pos < end) return _rtRuns[i]; + } + return null; + } + } 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 3c45b34b0b..7249817bed 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java @@ -184,6 +184,8 @@ public abstract class TextShape extends SimpleShape { * @param sh the sheet we are adding to */ protected void afterInsert(Sheet sh){ + super.afterInsert(sh); + EscherTextboxWrapper _txtbox = getEscherTextboxWrapper(); if(_txtbox != null){ PPDrawing ppdrawing = sh.getPPDrawing(); @@ -513,4 +515,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/ExOleObjStg.java b/src/scratchpad/src/org/apache/poi/hslf/record/ExOleObjStg.java index 7c47427927..197497fd02 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/ExOleObjStg.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/ExOleObjStg.java @@ -30,7 +30,10 @@ import org.apache.poi.util.LittleEndian; * * @author Daniel Noll */ -public class ExOleObjStg extends RecordAtom { +public class ExOleObjStg extends RecordAtom implements PersistRecord { + + private int _persistId; // Found from PersistPtrHolder + /** * Record header. */ @@ -109,4 +112,19 @@ public class ExOleObjStg extends RecordAtom { out.write(_header); out.write(_data); } + + /** + * Fetch our sheet ID, as found from a PersistPtrHolder. + * Should match the RefId of our matching SlidePersistAtom + */ + public int getPersistId() { + return _persistId; + } + + /** + * Set our sheet ID, as found from a PersistPtrHolder + */ + public void setPersistId(int id) { + _persistId = id; + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawingGroup.java b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawingGroup.java index 8d25ce2140..c7de1fccd7 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawingGroup.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawingGroup.java @@ -37,6 +37,8 @@ public class PPDrawingGroup extends RecordAtom { private byte[] _header; private EscherContainerRecord dggContainer; + //cached dgg + private EscherDggRecord dgg; protected PPDrawingGroup(byte[] source, int start, int len) { // Get the header @@ -116,4 +118,17 @@ public class PPDrawingGroup extends RecordAtom { public EscherContainerRecord getDggContainer(){ return dggContainer; } + + public EscherDggRecord getEscherDggRecord(){ + if(dgg == null){ + for(Iterator it = dggContainer.getChildRecords().iterator(); it.hasNext();){ + EscherRecord r = (EscherRecord) it.next(); + if(r instanceof EscherDggRecord){ + dgg = (EscherDggRecord)r; + break; + } + } + } + return dgg; + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/PersistRecord.java b/src/scratchpad/src/org/apache/poi/hslf/record/PersistRecord.java new file mode 100755 index 0000000000..8238d10fb9 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/PersistRecord.java @@ -0,0 +1,37 @@ + +/* ==================================================================== + 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.record; + +/** + * A record that can be referenced in PersistPtr storage. + * + * @author Yegor Kozlov + */ +public interface PersistRecord { + + /** + * Fetch the persist ID + */ + public int getPersistId(); + + /** + * Set the persist ID + */ + public void setPersistId(int id); +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java index a1bc499453..45038f7a28 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java @@ -70,9 +70,10 @@ public class RecordTypes { public static final Type List = new Type(2000,null); public static final Type FontCollection = new Type(2005,FontCollection.class); public static final Type BookmarkCollection = new Type(2019,null); + public static final Type SoundCollection = new Type(2020,SoundCollection.class); public static final Type SoundCollAtom = new Type(2021,null); - public static final Type Sound = new Type(2022,null); - public static final Type SoundData = new Type(2023,null); + public static final Type Sound = new Type(2022,Sound.class); + public static final Type SoundData = new Type(2023,SoundData.class); public static final Type BookmarkSeedAtom = new Type(2025,null); public static final Type ColorSchemeAtom = new Type(2032,ColorSchemeAtom.class); public static final Type ExObjRefAtom = new Type(3009,null); diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Sound.java b/src/scratchpad/src/org/apache/poi/hslf/record/Sound.java new file mode 100755 index 0000000000..211a9b0ee4 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/Sound.java @@ -0,0 +1,136 @@ +/* ==================================================================== + 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.record; + +import org.apache.poi.util.POILogger; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * A container holding information about a sound. It contains: + *

    + *

  • 1. CString (4026), Instance 0: Name of sound (e.g. "crash") + *
  • 2. CString (4026), Instance 1: Type of sound (e.g. ".wav") + *
  • 3. CString (4026), Instance 2: Reference id of sound in sound collection + *
  • 4. CString (4026), Instance 3, optional: Built-in id of sound, for sounds we ship. This is the id that?s in the reg file. + *
  • 5. SoundData (2023), optional + *

    + * + * @author Yegor Kozlov + */ +public class Sound extends RecordContainer { + /** + * Record header data. + */ + private byte[] _header; + + // Links to our more interesting children + private CString _name; + private CString _type; + private SoundData _data; + + + /** + * Set things up, and find our more interesting children + * + * @param source the source data as a byte array. + * @param start the start offset into the byte array. + * @param len the length of the slice in the byte array. + */ + protected Sound(byte[] source, int start, int len) { + // Grab the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Find our children + _children = Record.findChildRecords(source,start+8,len-8); + findInterestingChildren(); + } + + private void findInterestingChildren() { + // First child should be the ExHyperlinkAtom + if(_children[0] instanceof CString) { + _name = (CString)_children[0]; + } else { + logger.log(POILogger.ERROR, "First child record wasn't a CString, was of type " + _children[0].getRecordType()); + } + + // Second child should be the ExOleObjAtom + if (_children[1] instanceof CString) { + _type = (CString)_children[1]; + } else { + logger.log(POILogger.ERROR, "Second child record wasn't a CString, was of type " + _children[1].getRecordType()); + } + + for (int i = 2; i < _children.length; i++) { + if(_children[i] instanceof SoundData){ + _data = (SoundData)_children[i]; + break; + } + } + + } + + /** + * Returns the type (held as a little endian in bytes 3 and 4) + * that this class handles. + * + * @return the record type. + */ + public long getRecordType() { + return RecordTypes.Sound.typeID; + } + + /** + * Have the contents printer out into an OutputStream, used when + * writing a file back out to disk. + * + * @param out the output stream. + * @throws java.io.IOException if there was an error writing to the stream. + */ + public void writeOut(OutputStream out) throws IOException { + writeOut(_header[0],_header[1],getRecordType(),_children,out); + } + + /** + * Name of the sound (e.g. "crash") + * + * @return name of the sound + */ + public String getSoundName(){ + return _name.getText(); + } + + /** + * Type of the sound (e.g. ".wav") + * + * @return type of the sound + */ + public String getSoundType(){ + return _type.getText(); + } + + /** + * The sound data + * + * @return the sound data. + */ + public byte[] getSoundData(){ + return _data == null ? null : _data.getData(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/SoundCollection.java b/src/scratchpad/src/org/apache/poi/hslf/record/SoundCollection.java new file mode 100755 index 0000000000..c244859ea0 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/SoundCollection.java @@ -0,0 +1,73 @@ +/* ==================================================================== + 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.record; + +import org.apache.poi.util.POILogger; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * Is a container for all sound related atoms and containers. It contains: + *
  • 1. SoundCollAtom (2021) + *
  • 2. Sound (2022), for each sound, if any + * + * @author Yegor Kozlov + */ +public class SoundCollection extends RecordContainer { + /** + * Record header data. + */ + private byte[] _header; + + /** + * Set things up, and find our more interesting children + * + * @param source the source data as a byte array. + * @param start the start offset into the byte array. + * @param len the length of the slice in the byte array. + */ + protected SoundCollection(byte[] source, int start, int len) { + // Grab the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Find our children + _children = Record.findChildRecords(source,start+8,len-8); + } + + /** + * Returns the type (held as a little endian in bytes 3 and 4) + * that this class handles. + * + * @return the record type. + */ + public long getRecordType() { + return RecordTypes.SoundCollection.typeID; + } + + /** + * Have the contents printer out into an OutputStream, used when + * writing a file back out to disk. + * + * @param out the output stream. + * @throws java.io.IOException if there was an error writing to the stream. + */ + public void writeOut(OutputStream out) throws IOException { + writeOut(_header[0],_header[1],getRecordType(),_children,out); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/SoundData.java b/src/scratchpad/src/org/apache/poi/hslf/record/SoundData.java new file mode 100755 index 0000000000..42be48f2ac --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/SoundData.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.record; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.InflaterInputStream; + +import org.apache.poi.util.LittleEndian; + +/** + * Storage for embedded sounds. + * + * @author Yegor Kozlov + */ +public class SoundData extends RecordAtom { + + /** + * Record header. + */ + private byte[] _header; + + /** + * Record data. + */ + private byte[] _data; + + /** + * Constructs a new empty sound container. + */ + protected SoundData() { + _header = new byte[8]; + _data = new byte[0]; + + LittleEndian.putShort(_header, 2, (short)getRecordType()); + LittleEndian.putInt(_header, 4, _data.length); + } + + /** + * Constructs the link related atom record from its + * source data. + * + * @param source the source data as a byte array. + * @param start the start offset into the byte array. + * @param len the length of the slice in the byte array. + */ + protected SoundData(byte[] source, int start, int len) { + // Get the header. + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Get the record data. + _data = new byte[len-8]; + System.arraycopy(source,start+8,_data,0,len-8); + } + + /** + * Returns the sound data. + * + * @return the sound data + */ + public byte[] getData() { + return _data; + } + + /** + * Gets the record type. + * + * @return the record type. + */ + public long getRecordType() { + return RecordTypes.SoundData.typeID; + } + + /** + * Write the contents of the record back, so it can be written + * to disk. + * + * @param out the output stream to write to. + * @throws java.io.IOException if an error occurs. + */ + public void writeOut(OutputStream out) throws IOException { + out.write(_header); + out.write(_data); + } +} 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 fdaa9eec2c..187dec12a1 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/ObjectData.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/ObjectData.java index 66b4f0373a..957e78868e 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/ObjectData.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/ObjectData.java @@ -48,4 +48,13 @@ public class ObjectData { public InputStream getData() { return storage.getData(); } + + /** + * Return the record that contains the object data. + * + * @return the record that contains the object data. + */ + public ExOleObjStg getExOleObjStg() { + return storage; + } } 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 b250285ac9..2a09f2224c 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; @@ -368,12 +495,12 @@ public class RichTextRun { */ public Color getFontColor() { int rgb = getCharTextPropVal("font.color"); - if (rgb >= 0x8000000) { - int idx = rgb % 0x8000000; - ColorSchemeAtom ca = parentRun.getSheet().getColorScheme(); - if(idx >= 0 && idx <= 7) rgb = ca.getColor(idx); - } + int cidx = rgb >> 24; + if (rgb % 0x1000000 == 0){ + ColorSchemeAtom ca = parentRun.getSheet().getColorScheme(); + if(cidx >= 0 && cidx <= 7) rgb = ca.getColor(cidx); + } Color tmp = new Color(rgb, true); return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed()); } @@ -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,133 @@ 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 = getParaTextPropVal("bullet.color"); + if(rgb == -1) return getFontColor(); + + int cidx = rgb >> 24; + if (rgb % 0x1000000 == 0){ + ColorSchemeAtom ca = parentRun.getSheet().getColorScheme(); + if(cidx >= 0 && cidx <= 7) rgb = ca.getColor(cidx); + } + 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; + } + + /** + * Sets spacing after a paragraph. + *

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

    + */ + public void setSpaceAfter(int val) { + setParaTextPropVal("spaceafter", val); + } + + /** + * Returns spacing after a paragraph + *

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

    + * + * @return the spacing before a paragraph + */ + public int getSpaceAfter() { + int val = getParaTextPropVal("spaceafter"); + return val == -1 ? 0 : val; + } // --------------- Internal HSLF methods, not intended for end-user use! ------- /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java index 21e30ac2a9..828255087b 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java @@ -487,7 +487,21 @@ public class SlideShow public PictureData[] getPictureData() { return _hslfSlideShow.getPictures(); } - + + /** + * Returns the data of all the embedded OLE object in the SlideShow + */ + public ObjectData[] getEmbeddedObjects() { + return _hslfSlideShow.getEmbeddedObjects(); + } + + /** + * Returns the data of all the embedded sounds in the SlideShow + */ + public SoundData[] getSoundData() { + return SoundData.find(_documentRecord); + } + /** * Return the current page size */ diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SoundData.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SoundData.java new file mode 100755 index 0000000000..ad7922ef61 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SoundData.java @@ -0,0 +1,93 @@ +/* ==================================================================== + 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.usermodel; + +import org.apache.poi.hslf.record.*; + +import java.util.ArrayList; + +/** + * A class that represents sound data embedded in a slide show. + * + * @author Yegor Kozlov + */ +public class SoundData { + /** + * The record that contains the object data. + */ + private Sound _container; + + /** + * Creates the object data wrapping the record that contains the sound data. + * + * @param container the record that contains the sound data. + */ + public SoundData(Sound container) { + this._container = container; + } + + /** + * Name of the sound (e.g. "crash") + * + * @return name of the sound + */ + public String getSoundName(){ + return _container.getSoundName(); + } + + /** + * Type of the sound (e.g. ".wav") + * + * @return type of the sound + */ + public String getSoundType(){ + return _container.getSoundType(); + } + + /** + * Gets an input stream which returns the binary of the sound data. + * + * @return the input stream which will contain the binary of the sound data. + */ + public byte[] getData() { + return _container.getSoundData(); + } + + /** + * Find all sound records in the supplied Document records + * + * @param document the document to find in + * @return the array with the sound data + */ + public static SoundData[] find(Document document){ + ArrayList lst = new ArrayList(); + Record[] ch = document.getChildRecords(); + for (int i = 0; i < ch.length; i++) { + if(ch[i].getRecordType() == RecordTypes.SoundCollection.typeID){ + RecordContainer col = (RecordContainer)ch[i]; + Record[] sr = col.getChildRecords(); + for (int j = 0; j < sr.length; j++) { + if(sr[j] instanceof Sound){ + lst.add(new SoundData((Sound)sr[j])); + } + } + } + + } + return (SoundData[])lst.toArray(new SoundData[lst.size()]); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/41071.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/41071.ppt new file mode 100755 index 0000000000..4a443025e2 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/41071.ppt differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/ringin.wav b/src/scratchpad/testcases/org/apache/poi/hslf/data/ringin.wav new file mode 100755 index 0000000000..f8d4292d0f Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/ringin.wav differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/data/sound.ppt b/src/scratchpad/testcases/org/apache/poi/hslf/data/sound.ppt new file mode 100755 index 0000000000..2d1316acf3 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hslf/data/sound.ppt differ diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestFreeform.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestFreeform.java new file mode 100755 index 0000000000..5e7e7cc4b7 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestFreeform.java @@ -0,0 +1,80 @@ +/* ==================================================================== + 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 junit.framework.TestCase; +import org.apache.poi.hslf.usermodel.SlideShow; +import org.apache.poi.hslf.usermodel.RichTextRun; +import org.apache.poi.hslf.HSLFSlideShow; + +import java.awt.*; +import java.awt.Rectangle; +import java.awt.geom.*; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Test Freeform object. + * The Freeform shape is constructed from java.awt.GeneralPath. + * Check that the get/set path accessors are consistent. + * (TODO: verification of Bezier curves is more difficult due to rounding error. Figure out a test approach for that) + * + * @author Yegor Kozlov + */ +public class TestFreeform extends TestCase { + + public void testClosedPath() throws Exception { + + GeneralPath path1 = new GeneralPath(); + path1.moveTo(100, 100); + path1.lineTo(200, 100); + path1.lineTo(200, 200); + path1.lineTo(100, 200); + path1.closePath(); + + Freeform p = new Freeform(); + p.setPath(path1); + + GeneralPath path2 = p.getPath(); + assertTrue(new Area(path1).equals(new Area(path2))); + } + + public void testLine() throws Exception { + + GeneralPath path1 = new GeneralPath(new Line2D.Double(100, 100, 200, 100)); + + Freeform p = new Freeform(); + p.setPath(path1); + + GeneralPath path2 = p.getPath(); + assertTrue(new Area(path1).equals(new Area(path2))); + } + + public void testRectangle() throws Exception { + + GeneralPath path1 = new GeneralPath(new Rectangle2D.Double(100, 100, 200, 50)); + + Freeform p = new Freeform(); + p.setPath(path1); + + GeneralPath path2 = p.getPath(); + assertTrue(new Area(path1).equals(new Area(path2))); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestOleEmbedding.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestOleEmbedding.java index 7be6f9dc32..cb177048ba 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestOleEmbedding.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestOleEmbedding.java @@ -21,10 +21,19 @@ package org.apache.poi.hslf.model; import java.io.*; +import java.util.List; +import java.util.ArrayList; import org.apache.poi.hslf.HSLFSlideShow; import org.apache.poi.hslf.usermodel.ObjectData; import org.apache.poi.hslf.usermodel.PictureData; +import org.apache.poi.hslf.usermodel.SlideShow; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.usermodel.Range; +import org.apache.poi.hwpf.usermodel.Paragraph; import junit.framework.TestCase; @@ -59,4 +68,41 @@ public class TestOleEmbedding extends TestCase slideShow.close(); } } + + public void testOLEShape() throws Exception { + String dirname = System.getProperty("HSLF.testdata.path"); + File file = new File(dirname, "ole2-embedding-2003.ppt"); + FileInputStream is = new FileInputStream(file); + SlideShow ppt = new SlideShow(is); + is.close(); + + Slide slide = ppt.getSlides()[0]; + Shape[] sh = slide.getShapes(); + int cnt = 0; + for (int i = 0; i < sh.length; i++) { + if(sh[i] instanceof OLEShape){ + cnt++; + OLEShape ole = (OLEShape)sh[i]; + ObjectData data = ole.getObjectData(); + if("Worksheet".equals(ole.getInstanceName())){ + //Voila! we created a workbook from the embedded OLE data + HSSFWorkbook wb = new HSSFWorkbook(data.getData()); + HSSFSheet sheet = wb.getSheetAt(0); + //verify we can access the xls data + assertEquals(1, sheet.getRow(0).getCell((short)0).getNumericCellValue(), 0); + assertEquals(1, sheet.getRow(1).getCell((short)0).getNumericCellValue(), 0); + assertEquals(2, sheet.getRow(2).getCell((short)0).getNumericCellValue(), 0); + assertEquals(3, sheet.getRow(3).getCell((short)0).getNumericCellValue(), 0); + assertEquals(8, sheet.getRow(5).getCell((short)0).getNumericCellValue(), 0); + } else if ("Document".equals(ole.getInstanceName())){ + //creating a HWPF document + HWPFDocument doc = new HWPFDocument(data.getData()); + String txt = doc.getRange().getParagraph(0).text(); + assertEquals("OLE embedding is thoroughly unremarkable.\r", txt); + } + } + + } + assertEquals("Expected 2 OLE shapes", 2, cnt); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java index c2e081ee5c..511ef7f745 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestShapes.java @@ -307,4 +307,22 @@ public class TestShapes extends TestCase { sl = ppt.getSlides()[0]; assertEquals("expected 0 shaped in " + file, 0, sl.getShapes().length); } + + public void testShapeId() throws IOException { + SlideShow ppt = new SlideShow(); + Slide slide = ppt.createSlide(); + Shape shape; + + shape = new Line(); + assertEquals(0, shape.getShapeId()); + slide.addShape(shape); + assertTrue(shape.getShapeId() > 0); + + int shapeId = shape.getShapeId(); + + shape = new Line(); + assertEquals(0, shape.getShapeId()); + slide.addShape(shape); + assertEquals(shapeId + 1, shape.getShapeId()); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestSound.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestSound.java new file mode 100755 index 0000000000..97a4b7c5b5 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestSound.java @@ -0,0 +1,86 @@ + +/* ==================================================================== + 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.record; + + +import junit.framework.TestCase; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Arrays; + +import org.apache.poi.hslf.HSLFSlideShow; +import org.apache.poi.hslf.usermodel.SlideShow; + +/** + * Tests Sound-related records: SoundCollection(2020), Sound(2022) and SoundData(2023)). + * + * @author Yegor Kozlov + */ +public class TestSound extends TestCase { + public void testRealFile() throws Exception { + String cwd = System.getProperty("HSLF.testdata.path"); + FileInputStream is = new FileInputStream(new File(cwd, "sound.ppt")); + SlideShow ppt = new SlideShow(is); + is.close(); + + // Get the document + Document doc = ppt.getDocumentRecord(); + SoundCollection soundCollection = null; + Record[] doc_ch = doc.getChildRecords(); + for (int i = 0; i < doc_ch.length; i++) { + if(doc_ch[i] instanceof SoundCollection){ + soundCollection = (SoundCollection)doc_ch[i]; + break; + } + } + assertNotNull(soundCollection); + + Sound sound = null; + Record[] sound_ch = soundCollection.getChildRecords(); + int k = 0; + for (int i = 0; i < sound_ch.length; i++) { + if(sound_ch[i] instanceof Sound){ + sound = (Sound)sound_ch[i]; + k++; + } + } + assertNotNull(sound); + assertEquals(1, k); + + assertEquals("ringin.wav", sound.getSoundName()); + assertEquals(".WAV", sound.getSoundType()); + assertNotNull(sound.getSoundData()); + + File f = new File(cwd, "ringin.wav"); + int length = (int)f.length(); + byte[] ref_data = new byte[length]; + is = new FileInputStream(f); + is.read(ref_data); + is.close(); + + assertTrue(Arrays.equals(ref_data, sound.getSoundData())); + + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java index 19e7af6417..6947ff9cfd 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java @@ -360,4 +360,24 @@ public class TestBugs extends TestCase { assertTrue("No Exceptions while reading file", true); } + + /** + * Bug 41071: Will not extract text from Powerpoint TextBoxes + */ + public void test41071() throws Exception { + FileInputStream is = new FileInputStream(new File(cwd, "41071.ppt")); + SlideShow ppt = new SlideShow(is); + is.close(); + + Slide slide = ppt.getSlides()[0]; + Shape[] sh = slide.getShapes(); + assertEquals(1, sh.length); + assertTrue(sh[0] instanceof TextShape); + TextShape tx = (TextShape)sh[0]; + assertEquals("Fundera, planera och involvera.", tx.getTextRun().getText()); + + TextRun[] run = slide.getTextRuns(); + assertEquals(1, run.length); + assertEquals("Fundera, planera och involvera.", run[0].getText()); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java index 797f97cda7..78458f0630 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java @@ -437,4 +437,33 @@ public class TestPictures extends TestCase{ assertTrue(pdata instanceof WMF); assertEquals(Picture.WMF, pdata.getType()); } + + public void testGetPictureName() throws Exception { + SlideShow ppt = new SlideShow(new HSLFSlideShow(new File(cwd, "ppt_with_png.ppt").getPath())); + Slide slide = ppt.getSlides()[0]; + + Picture p = (Picture)slide.getShapes()[0]; //the first slide contains JPEG + assertEquals("test", p.getPictureName()); + } + + public void testSetPictureName() throws Exception { + SlideShow ppt = new SlideShow(); + + Slide slide = ppt.createSlide(); + File img = new File(cwd, "tomcat.png"); + int idx = ppt.addPicture(img, Picture.PNG); + Picture pict = new Picture(idx); + pict.setPictureName("tomcat.png"); + slide.addShape(pict); + + //serialize and read again + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ppt.write(out); + out.close(); + + ppt = new SlideShow(new ByteArrayInputStream(out.toByteArray())); + + Picture p = (Picture)ppt.getSlides()[0].getShapes()[0]; + assertEquals("tomcat.png", p.getPictureName()); + } } 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 3fda0fda17..3d89d0272f 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", diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSoundData.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSoundData.java new file mode 100755 index 0000000000..95de38f8ba --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestSoundData.java @@ -0,0 +1,63 @@ +/* ==================================================================== + 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.usermodel; + +import org.apache.poi.hslf.*; +import org.apache.poi.hslf.exceptions.HSLFException; +import org.apache.poi.hslf.blip.*; +import org.apache.poi.hslf.model.*; +import junit.framework.TestCase; + +import java.io.*; +import java.util.Arrays; + +/** + * Test reading sound data from a ppt + * + * @author Yegor Kozlov + */ +public class TestSoundData extends TestCase{ + + protected File cwd; + + public void setUp() throws Exception { + cwd = new File(System.getProperty("HSLF.testdata.path")); + } + + /** + * Read a reference sound file from disk and compare it from the data extracted from the slide show + */ + public void testSounds() throws Exception { + //read the reference sound file + File f = new File(cwd, "ringin.wav"); + int length = (int)f.length(); + byte[] ref_data = new byte[length]; + FileInputStream is = new FileInputStream(f); + is.read(ref_data); + is.close(); + + is = new FileInputStream(new File(cwd, "sound.ppt")); + SlideShow ppt = new SlideShow(is); + is.close(); + + SoundData[] sound = ppt.getSoundData(); + assertEquals("Expected 1 sound", 1, sound.length); + + assertTrue(Arrays.equals(ref_data, sound[0].getData())); + } +} diff --git a/src/testcases/org/apache/poi/hssf/data/28774.xls b/src/testcases/org/apache/poi/hssf/data/28774.xls new file mode 100755 index 0000000000..476f3bafe9 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/28774.xls differ diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index 49adad8523..259e3d0c86 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -878,4 +878,15 @@ public final class TestBugs extends TestCase { } assertEquals(713, rowsSeen); } + + /** + * Bug 28774: Excel will crash when opening xls-files with images. + */ + public void test28774() { + + HSSFWorkbook wb = openSample("28774.xls"); + assertTrue("no errors reading sample xls", true); + writeOutAndReadBack(wb); + assertTrue("no errors writing sample xls", true); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFTextbox.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFTextbox.java index f7ce61faf2..f836d4284e 100755 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFTextbox.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFTextbox.java @@ -49,4 +49,23 @@ public final class TestHSSFTextbox extends TestCase{ assertEquals(HSSFTextbox.VERTICAL_ALIGNMENT_CENTER, textbox.getVerticalAlignment()); } + /** + * Excel requires at least one format run in HSSFTextbox. + * When inserting text make sure that if font is not set we must set the default one. + */ + public void testSetDeafultTextFormat() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFTextbox textbox1 = patriarch.createTextbox(new HSSFClientAnchor(0,0,0,0,(short)1,1,(short)3,3)); + HSSFRichTextString rt1 = new HSSFRichTextString("Hello, World!"); + assertEquals(0, rt1.numFormattingRuns()); + textbox1.setString(rt1); + + HSSFRichTextString rt2 = textbox1.getString(); + assertEquals(1, rt2.numFormattingRuns()); + assertEquals(HSSFRichTextString.NO_FONT, rt2.getFontOfFormattingRun(0)); + } + }