OdfDrawImage.java

/**
 * **********************************************************************
 *
 * <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * <p>Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * <p>Use is subject to license terms.
 *
 * <p>Licensed 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. You can also obtain a copy of the License at
 * http://odftoolkit.org/docs/license.txt
 *
 * <p>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.
 *
 * <p>See the License for the specific language governing permissions and limitations under the
 * License.
 *
 * <p>**********************************************************************
 */
package org.odftoolkit.odfdom.incubator.doc.draw;

import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawImageElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfPackage;
import org.odftoolkit.odfdom.pkg.manifest.OdfFileEntry;
import org.odftoolkit.odfdom.type.AnyURI;
import org.odftoolkit.odfdom.type.Length;
import org.odftoolkit.odfdom.type.Length.Unit;
import org.w3c.dom.NodeList;

/** Convenient functionality for the parent ODF OpenDocument element */
public class OdfDrawImage extends DrawImageElement {

  private static final long serialVersionUID = 8409319888919451149L;
  private URI mImageURI;
  // OdfPackage necessary to adapt the manifest referencing the image
  private OdfPackage mOdfPackage;
  private OdfSchemaDocument mOdfSchemaDocument;
  private static final String SLASH = "/";

  /**
   * Creates a new instance of this class
   *
   * @param ownerDoc The XML DOM containing the draw:image element
   */
  public OdfDrawImage(OdfFileDom ownerDoc) {
    super(ownerDoc);
    mOdfSchemaDocument = (OdfSchemaDocument) ownerDoc.getDocument();
    mOdfPackage = mOdfSchemaDocument.getPackage();
  }

  /**
   * Return the URI for this image
   *
   * @return the URI of image
   */
  public URI getImageUri() {
    try {
      if (mImageURI == null) {
        mImageURI = new URI(AnyURI.encodePath(this.getXlinkHrefAttribute().toString()));
      }
    } catch (URISyntaxException ex) {
      Logger.getLogger(OdfDrawImage.class.getName()).log(Level.SEVERE, null, ex);
    }
    return mImageURI;
  }

  /**
   * The image path will be stored as URI of the href attribute
   *
   * @param packagePath The relative path from the package root to the image
   */
  public void setImagePath(String packagePath) {
    try {
      packagePath = packagePath.replaceFirst(mOdfSchemaDocument.getDocumentPath(), "");
      URI uri = new URI(AnyURI.encodePath(packagePath).toString());
      this.setXlinkHrefAttribute(AnyURI.decodePath(uri.toString()));
      mImageURI = uri;
    } catch (URISyntaxException ex) {
      Logger.getLogger(OdfDrawImage.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

  /* Helper method */
  private String getPackagePath(String imageRef) {
    if (imageRef.contains(SLASH)) {
      imageRef = imageRef.substring(imageRef.lastIndexOf(SLASH) + 1, imageRef.length());
    }
    String packagePath = OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH + imageRef;
    return packagePath = mOdfSchemaDocument.getDocumentPath() + packagePath;
  }

  /* Helper method */
  private void configureInsertedImage(String packagePath) throws Exception {
    // Set path to image attribute
    setImagePath(packagePath);
    // Set mandatory attribute xlink:type
    setXlinkTypeAttribute("simple");
    // A draw:image is always embedded in a draw:frame
    InputStream is = mOdfPackage.getInputStream(packagePath);
    DrawFrameElement odfFrame = (DrawFrameElement) this.getParentNode();
    if (odfFrame != null) {
      if (!odfFrame.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "height")
          && !odfFrame.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "width")) {
        BufferedImage image = ImageIO.read(is);
        // some image formats like SVG might not be understood by ImageIO
        if (image != null) {
          int height = image.getHeight(null);
          int width = image.getWidth(null);
          odfFrame.setSvgHeightAttribute(
              Length.mapToUnit(String.valueOf(height) + "px", Unit.CENTIMETER));
          odfFrame.setSvgWidthAttribute(
              Length.mapToUnit(String.valueOf(width) + "px", Unit.CENTIMETER));
        }
      }
    }
  }

  /**
   * Inserts the image file from the URI to the ODF package named similar as in the URI. The
   * manifest is adapted using the media type according to the suffix. Existing images are replaced.
   * Note: Default image seize will only be set, if the draw:image had been added to its draw:frame
   * prior.
   *
   * @param imageUri The URI of the image that will be added as stream to the package in the
   *     'Pictures/' graphic directory with the same image file name as in the URI. If the imageURI
   *     is relative first the user.dir is taken to make it absolute.
   * @return Returns the package path of the image, which was created based on the given URI.
   * @throws Exception If the image provided by the URI, could not be added as stream to the ODF
   *     package.
   */
  public String newImage(URI imageUri) throws Exception {
    String imageRef = imageUri.toString();
    String mediaType = OdfFileEntry.getMediaTypeString(imageRef);
    String packagePath = getPackagePath(imageRef);
    mOdfPackage.insert(imageUri, packagePath, mediaType);
    configureInsertedImage(packagePath);
    return packagePath;
  }

  /**
   * Inserts the image file from the stream to the ODF package named similar as in the provided
   * path.. The manifest is adapted using given media type. Existing images are replaced.
   *
   * @param is InputStream to be added to the ODF package
   * @param packagePath Internal path of the image in the package
   * @param mediaType The mediaType of the image. Can be obtained by the OdfFileEntry class
   *     findMediaType(String fileRef).
   * @throws Exception If the given stream could not be added to the ODF package at the packagePatch
   */
  public void newImage(InputStream is, String packagePath, String mediaType) throws Exception {
    mOdfPackage.insert(is, packagePath, mediaType);
    configureInsertedImage(packagePath);
  }

  /**
   * Inserts the image file from the stream to the ODF package named similar as in the provided
   * path.. The manifest is adapted using given media type. Existing images are replaced.
   *
   * @param fileBytes - data of the file stream to be stored in package. If NULL a directory with
   *     the given media type will be created.
   * @param packagePath Internal path of the image in the package
   * @param mediaType The mediaType of the image. Can be obtained by the OdfFileEntry class
   *     findMediaType(String fileRef).
   * @throws Exception If the given stream could not be added to the ODF package at the packagePatch
   */
  public void newImage(byte[] fileBytes, String packagePath, String mediaType) throws Exception {
    mOdfPackage.insert(fileBytes, packagePath, mediaType);
    configureInsertedImage(packagePath);
  }

  /**
   * The method returns the specific one or more images by image path since the image may be
   * inserted to the document several times.
   *
   * @param doc the document the image belongs to
   * @param imagePath the internal package path of the image.
   * @return an Image list that match the given image path if no images is found under the given
   *     path, return an empty list.
   */
  public static List<OdfDrawImage> getImageByPath(OdfSchemaDocument doc, String imagePath) {
    ArrayList<OdfDrawImage> imageList = new ArrayList<OdfDrawImage>();

    try {
      NodeList imageNodes =
          doc.getContentDom().getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "image");

      for (int i = 0; i < imageNodes.getLength(); i++) {
        OdfDrawImage image = (OdfDrawImage) imageNodes.item(i);
        if (image.getXlinkHrefAttribute().equals(imagePath)) {
          imageList.add(image);
        }
      }
      if (imageList.size() > 0) {
        return imageList;
      }
    } catch (Exception ex) {
      Logger.getLogger(OdfDrawImage.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
    }
    return imageList;
  }

  /**
   * The method deletes one or more images from image container by image path.
   *
   * @param doc the document the image should be deleted from
   * @param imagePath the internal package path of the image.
   */
  public static void deleteImageByPath(OdfSchemaDocument doc, String imagePath) {
    List<OdfDrawImage> imageList = getImageByPath(doc, imagePath);
    if (imageList != null) {
      Iterator<OdfDrawImage> it = imageList.iterator();
      while (it.hasNext()) {
        OdfDrawImage image = it.next();
        // remove the inserted picture
        String ref = image.getXlinkHrefAttribute().toString();
        doc.getPackage().remove(ref);

        // remove the draw:frame element in main document
        if (image.getParentNode() instanceof OdfDrawFrame) {
          OdfDrawFrame frame = (OdfDrawFrame) image.getParentNode();
          if (frame.getChildNodes().getLength() == 1) {
            frame.getParentNode().removeChild(frame);
          } else {
            image.getParentNode().removeChild(image);
          }
        } else {
          image.getParentNode().removeChild(image);
        }

        // remove Pictures/ directory if container does not have images
        if (getImageCount(doc) == 0) {
          doc.getPackage().remove(OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH);
        }
      }
    }
  }

  /**
   * The method deletes the specified image from image container.
   *
   * @param doc the document the image should be deleted from
   * @param image the image which need to be deleted
   */
  public static void deleteImage(OdfSchemaDocument doc, OdfDrawImage image) {

    // remove the inserted picture if it does not have reference any more
    String ref = image.getXlinkHrefAttribute().toString();
    List<OdfDrawImage> imageList = getImageByPath(doc, ref);
    if (imageList.size() == 1) {
      doc.getPackage().remove(ref);
    }

    // remove the draw:frame element in main document
    if (image.getParentNode() instanceof OdfDrawFrame) {
      OdfDrawFrame frame = (OdfDrawFrame) image.getParentNode();
      if (frame.getChildNodes().getLength() == 1) {
        frame.getParentNode().removeChild(frame);
      } else {
        image.getParentNode().removeChild(image);
      }
    } else {
      image.getParentNode().removeChild(image);
    }

    // remove Pictures/ directory if container does not have images
    if (getImageCount(doc) == 0) {
      doc.getPackage().remove(OdfPackage.OdfFile.IMAGE_DIRECTORY.getPath() + SLASH);
    }
  }

  /**
   * Get the count of image objects in the image container.
   *
   * @param doc the document the image should be counted from
   * @return the number of image in this document if no image is found, return zero
   */
  public static int getImageCount(OdfSchemaDocument doc) {
    try {
      NodeList imageNodes =
          doc.getContentDom().getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "image");
      return imageNodes.getLength();
    } catch (Exception ex) {
      Logger.getLogger(OdfDrawImage.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
    }
    return 0;
  }

  /**
   * The method return the image list in the image container.
   *
   * @param doc the document the list of images should be returned from
   * @return an image list in this document if no images is found, return an empty list.
   */
  public static List<OdfDrawImage> getImages(OdfSchemaDocument doc) {
    ArrayList<OdfDrawImage> imageList = new ArrayList<OdfDrawImage>();
    try {
      NodeList imageNodes =
          doc.getContentDom().getElementsByTagNameNS(OdfDocumentNamespace.DRAW.getUri(), "image");
      for (int i = 0; i < imageNodes.getLength(); i++) {
        OdfDrawImage image = (OdfDrawImage) imageNodes.item(i);
        imageList.add(image);
      }
    } catch (Exception ex) {
      Logger.getLogger(OdfDrawImage.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
    }
    return imageList;
  }

  /**
   * The method return the set of all the image paths.
   *
   * @param doc the document the image path set should be obtained from
   * @return an image path set in this document
   */
  public static Set<String> getImagePathSet(OdfSchemaDocument doc) {
    Set<String> paths = new HashSet<String>();
    List<OdfDrawImage> imageList = getImages(doc);
    Iterator<OdfDrawImage> it = imageList.iterator();
    while (it.hasNext()) {
      OdfDrawImage image = it.next();
      paths.add(image.getXlinkHrefAttribute().toString());
    }
    return paths;
  }
}