Package com.extentech.formats.XLS

Source Code of com.extentech.formats.XLS.MSODrawingGroup

/*
* --------- BEGIN COPYRIGHT NOTICE ---------
* Copyright 2002-2012 Extentech Inc.
* Copyright 2013 Infoteria America Corp.
*
* This file is part of OpenXLS.
*
* OpenXLS is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* OpenXLS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with OpenXLS.  If not, see
* <http://www.gnu.org/licenses/>.
* ---------- END COPYRIGHT NOTICE ----------
*/
package com.extentech.formats.XLS;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;

import com.extentech.ExtenXLS.ImageHandle;
import com.extentech.formats.escher.*;
import com.extentech.toolkit.*;


/** <b>MSODrawingGroup: MS Office Drawing Group (EBh)</b><br>
   
    These records contain only data.<p><pre>
   
    offset  name        size    contents
    ---   
    4       rgMSODrGr    var    MSO Drawing Group Data   
   
    </p></pre>
   
   
    There is only one drawing group per client document (=MSOFBTDGGCONTAINER, 0xF000 ?).
    OfficeArtDggContainer:
      rh (8 bytes): An OfficeArtRecordHeader structure, that specifies the header for this record. The following table specifies the subfields.
     *        rh.recVer      A value that MUST be 0xF.
           rh.recInstance    A value that MUST be 0x000.
           rh.recType      A value that MUST be 0xF000.
           rh.recLen       An unsigned integer specifying the number of bytes following the header that contain document-wide file records.
      drawingGroup (variable): An OfficeArtFDGGBlock record, that specifies document-wide information about all the drawings that are saved in the file.
      blipStore (variable): An OfficeArtBStoreContainer record, that specifies the container for all the BLIPs that are used in all the drawings in the parent document.
      drawingPrimaryOptions (variable): An OfficeArtFOPT record, that specifies the default properties for all drawing objects that are contained in all the drawings in the parent document.
      drawingTertiaryOptions (variable): An OfficeArtTertiaryFOPT record, that specifies the default properties for all the drawing objects that are contained in all the drawings in the parent document.
      colorMRU (variable): An OfficeArtColorMRUContainer record, that specifies the most recently used custom colors.
      splitColors (variable): An OfficeArtSplitMenuColorContainer record, that specifies a container for the colors that were most recently used to format shapes.
   
     
    Drawing groups contain drawings.    (= numDrawings)
    Drawings in turn contain shapes that are the objects that actually mark a page. (= numShapes) 
      --Each drawing has a collection of rules that govern the shapes in the drawing
    Shape store their properties in a property table (MSOFBTOPT record of Msodrawing)
    The actual pictures and images are kept in a separate collection so can load and save separately
   
    Records that are required in the MSODrawingGroup:
    MSOFBTDGG-    Drawing Group Record- holds total # shapes saved + last or max SPID (shapeID) + number of IDclusters(FIDCLs) + total # drawings saved
    MSOFBTOPT-    Property Table Record- Default properties of newly created shapes (can be 0'd)
    MSOFBTBSTORECONTAINER- 
     MSOFBTBSE-    BLIP Store Entry- holds image type, id, size, index, len of blip name ...
   
    * @see MSODrawing
*/
public final class MSODrawingGroup extends com.extentech.formats.XLS.XLSRecord
{
  // 20070914 KSC: Save drawing recs here
    private AbstractList msoRecs   = new ArrayList();   
    // moved from Boundsheet + renamed
    public void addMsodrawingrec(MSODrawing rec){ msoRecs.add(rec);    }   
    public boolean dirtyflag= false;

    /**
     * loop through all the Msodrawing recs and return the next valid SPID
     * @return
     */
    public int getNextMsoSPID() {
      int spid= 0;
      for (int i= 0; i < msoRecs.size(); i++)
        spid= Math.max(((MSODrawing) msoRecs.get(i)).getSPID(), spid);
      return spid+1;
    }
    /**
     *   remove linked MsoDrawing rec from this drawing group + update image references if necessary
     *  NOTE THIS IS STILL EXPERIMENTAL; MUST BE TESTED WITH A VARIETY OF SCENARIOS
     */
    public void removeMsodrawingrec(MSODrawing rec, Boundsheet sheet, boolean removeObjRec) {
      int imgIdx= rec.getImageIndex()-1;
    int refCnt= this.getCRef(imgIdx);
    boolean wasHeader= rec.bIsHeader;   
    if (refCnt > 0)
      this.decCRef(imgIdx);
      msoRecs.remove(rec);
    this.updateRecord()// update msodg rec
    int idx= rec.getRecordIndex()// chart mso's have been taken out of streamer so idx will be -1
    if (idx>-1) {
      sheet.removeRecFromVec(rec)// remove Mso rec
      if (removeObjRec) // also remove associated obj rec 20080804 KSC
        sheet.removeRecFromVec(idx)// also remove linked Obj record
    }
   
      if (this.getMsodrawingrecs().size()==0) {  // no more drawing recs, delete this msodg
        sheet.removeRecFromVec(this);
        this.getWorkBook().msodg= null;
          // TODO: Unsure if there are other circumstances where MsodrawingSelection should be removed ... watch out for it
        // KSC: TODO: Necessary????  Appears so for delete chart ...(WHY??????)       
        BiffRec b= sheet.getSheetRec(MSODRAWINGSELECTION);
        if (b!=null) {
          sheet.removeRecFromVec(b);
        }
      } else {       
        if (wasHeader) { // we just removed the header; set 1st one to it
          MSODrawing mso= null;
          for (int z=0; z < this.getMsodrawingrecs().size(); z++) {
              mso= (MSODrawing) this.getMsodrawingrecs().get(z);
              if (mso.getSheet().equals(sheet) && mso.isShape) {              
                mso.setIsHeader()// make this one the header rec
                break;
              }               
          }
        }
        this.wkbook.updateMsodrawingHeaderRec(sheet);
      }   
    }
   
    /** return the Msodrawing header record for the given sheet
     *
     * @param bs
     * @return
     */
    public MSODrawing getMsoHeaderRec(Boundsheet bs) {
    for (int i= 0; i < msoRecs.size(); i++) {
      MSODrawing msd= (MSODrawing) msoRecs.get(i)// get index of first Msodrawing rec for this sheet
      // always: the 1st msodrawing rec for the sheet contains the # information ...
      if (msd.getSheet().equals(bs)) { // got it!
        if (msd.isHeader()) { 
          return msd;
        } //else
          //Logger.logErr("WorkBook.updateMsodrawingHeaderRec:  Header Record should be first rec in group.");
//        break;
      }       
    }
    return null;
    }
    public AbstractList getMsodrawingrecs() { return msoRecs; }

    int spidMax=1024, numIdClusters=2, numShapes=1, numDrawings=1;
 
  private ArrayList imageData = new ArrayList();
  private ArrayList imageType = new ArrayList()// parallel array with imageData
  private ArrayList cRef= new ArrayList()// 20071120 KSC: keep track of reference count for image data

 
   
  public void init(){
    super.init();
    data = super.getData();
  }
 
    /* 20070813 KSC: These prototype bytes works for both Images and Charts */
    public byte[] PROTOTYPE_BYTES = {15, 0, 0, -16, 82, 0, 0, 0, 0, 0, 6, -16, 24, 0, 0, 0, 2, 4, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 51, 0, 11, -16, 18, 0, 0, 0, -65, 0, 8, 0, 8, 0, -127, 1, 9, 0, 0, 8, -64, 1, 64, 0, 0, 8, 64, 0, 30, -15, 16, 0, 0, 0, 13, 0, 0, 8, 12, 0, 0, 8, 23, 0, 0, 8, -9, 0, 0, 16};
   
    public static XLSRecord getPrototype() {
        MSODrawingGroup grp = new MSODrawingGroup();
        grp.setOpcode(MSODRAWINGGROUP);
        grp.setData(grp.PROTOTYPE_BYTES);
        grp.init();
        return grp;
    }
   
    // Add associated recs necessary for Msodrawing ...
    public void initNewMSODrawingGroup() {
    // add new msodg rec to stream (just before sst)
    int index = streamer.getRecordIndex(XLSConstants.SST);
    // add unknown record that appears just before MSODrawingGroup
    XLSRecord rec = new XLSRecord();
    rec.setOpcode((short)0x1C1);
    rec.setData(this.PROTOTYPE_1C1);
    streamer.addRecordAt(rec, index++);      
    // add MSODrawingGroup
    streamer.addRecordAt(this, index);
    // also need msymystery record + msoselection ...
      Boundsheet[] b = this.getWorkBook().getWorkSheets();
      for(int i=0;i<b.length;i++) {
        int z= b[i].getIndexOf(PHONETIC);
        if (z==-1) {
             Phonetic p = new Phonetic();
             p.setData(p.PROTOTYPE_BYTES);
             p.setOpcode(XLSRecord.PHONETIC);
             p.setDebugLevel(this.DEBUGLEVEL);
             p.setStreamer(this.getStreamer());
             b[i].insertSheetRecordAt(p, b[i].getIndexOf(SELECTION)+1);               
        }
/* truly necessary???        if (i==0) { // msodrawingselection only for 1st sheet???????
             Msodrawingselection msoSelection = new Msodrawingselection();
             msoSelection.setData(msoSelection.PROTOTYPE_BYTES);
             msoSelection.setOpcode(XLSRecord.MSODRAWINGSELECTION);
             msoSelection.setDebugLevel(this.DEBUGLEVEL);
             msoSelection.setStreamer(this.getStreamer());
             b[i].insertSheetRecordAt(msoSelection, b[i].getIndexOf(Window2.class));
        }
*/       
      }
    }
 
    /**
   *
   */
  private static final long serialVersionUID = 2378100973014157878L;

  /** The XF record can either be a style XF or a BiffRec XF.
   
  */
  /*These are prototype bytes for record 0x1c1 and 0x863 that seem to accompany when there is MSODrawingGroup data*/
  public byte[] PROTOTYPE_1C1 = {-63, 1, 0, 0, -128, 56, 1, 0};
  public byte[] PROTOTYPE_863 = {99, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, -46};
 

 
  /** Parse the MSODrawingGroup bytes and generate state vars:
   * imageData, imageType, cRef
   * spidMax, numIdClusters, numDrawings, numShapes
   * 
   *
   *
   */
  public void parse(){
    imageData.clear();
    imageType.clear();
    cRef.clear();
    //data = getBytes();
    if(data == null)
        return; // no data!   
   
    /*
     * This represents the MSOFBTDGGCONTAINER record (0xFOOO) which is the Drawing Group Container
     *
     *
     *
     * The MSOFBTDGGCONTAINER Contains the following records (some are optional):
     *        rh.recVer      A value that MUST be 0xF.
           rh.recInstance    A value that MUST be 0x000.
           rh.recType      A value that MUST be 0xF000.
           rh.recLen      variable
     *     MSOFBTDGG (0xF006)    Drawing Group Record, contains number of shapes, drawings and id clusters
     *     MSOFBTCLSID (0xF016)   Clipboard format (optional)
     *     MSOFBTOPT  (0xF00B)  Property Table Record - default props of newly created shapes; only the properties that differ from
     *                 the per-property defaults are saved.  Format is same as Msodrawing.MSOFBTOPT format
     *    MSOFBTCOLORMRU (0xF11A)  MRU Color swatch ... 
     *    MSOFBTSPLITMENUCOLORS (0xF11E)  MRU colors of the top-level ..split menus
     *    MSOFBTBSTORECONTAINER (0xF001)  An array of BLIP Store Entry (BSE) Records; Each shape indexes into this array for the BLIP they use  
     *    MSOFBTBSE (0xF007)    File BLIP Store Entry Recod FBSE record; Encodes type of BLIP + size + ID + ref. count + file offset ...
     *    MSOFBTBLIP (0xF018)    
     */
    /* BLIP TYPE ENUM
     * msoblipERROR= 0
     * msoblipUNKNOWN,
     * msoblipEMF,  // enhanced meta file
     * msoblipWMF,  // windows meta file
     * msoblipPICT  // MAC pic
     * msoblipJPEG,
     * msoblipPNG,
     * msoblipDIB,
     * msoblipFirstClient=32,  // first client-defined BLIP type
     * msoblipLastClient= 255  // last ""
     */
    try{
      byte[] buf;
      ByteArrayInputStream bis = new ByteArrayInputStream(data);
      while(bis.available()>0){
        buf = new byte[8];
        int read = bis.read(buf,0, 8);
        int version = (0xF&buf[0]);
          int inst = ((0xFF&buf[1])<<4)|(0xF0&buf[0])>>4;
          int fbt = ((0xFF&buf[3])<<8)|(0xFF&buf[2]);
          int len = ByteTools.readInt(buf[4], buf[5], buf[6], buf[7]);
       
          //System.out.println("fbt:"+Integer.toHexString(fbt)+";len:"+len);
          if(fbt <0xF004)
            continue;   // under 0xF005 are container recs; we just parse the atoms for needed info ...

          // parse record denoted by fbt
          buf = new byte[len];
        read = bis.read(buf,0,len);
           
        switch(fbt){                 
          case MSODrawingConstants.MSOFBTDGG:  //0xf006:    // MSOFBTDGG - Drawing Group Record
            //  rh.recVer      A value that MUST be 0x0.
            //   rh.recInstance    A value that MUST be 0x000.
            //  rh.recType      A value that MUST be 0xF006.
            //  rh.recLen      A value that MUST be 0x00000010 + ((head.cidcl - 1) * 0x00000008)
            //   head (16 bytes): An OfficeArtFDGG record, that specifies document-wide information.
            //   
            //  Rgidcl (variable): An array of OfficeArtIDCL elements, specifying file identifier clusters that are used in the drawing. The number of elements in the array is specified by (head.cidcl – 1).
            spidMax = ByteTools.readInt(buf[0],buf[1],buf[2],buf[3]);    // maximum shape ID
            numIdClusters = ByteTools.readInt(buf[4],buf[5],buf[6],buf[7])// number of ID clusters
              numShapes = ByteTools.readInt(buf[8],buf[9],buf[10],buf[11])// total number of shapes saved
            numDrawings = ByteTools.readInt(buf[12],buf[13],buf[14],buf[15])// total number of drawings saved
            // the fixed part is followed by and array of ID clusters used internally for the translation of SPIDs to MSHHSPs (Shape Handles)
            break;
           
          case MSODrawingConstants.MSOFBTBSE:    //0xf007:    // File BLIP Store Entry Record (FBSE)
            byte[] imgBuf = getImageBytesWithBuffer(buf);
            // strip buffer from data image bytes           
//            numShapes--;  // 20070914 KSC: WHY?? -- (not a real shape? -jm)
            break;
          }
      }
    }catch (Exception e){
        Logger.logErr("Msodrawingroup parse error.",e);
    }
  }
   
  private byte[] getImageBytesWithBuffer(byte[] buf) {
//     Each BLIP in the BStore is serialized to a FBSE record;
    // btWin32, btMacOS, rgbUid[16] = identifier of blip, tag, size=BLIP size in stream
    int btWin32 = buf[0];
    imageType.add(Integer.valueOf(btWin32));
    /* parse header for testing purposes
    // inst= encoded type   
    int pos= 1;   
    int btMac= buf[pos++];   
    byte[] UID= new byte[16];
    byte[] UID2= new byte[16];
    System.arraycopy(buf, pos, UID, 0, 16);
    pos+=16;
    short tag= ByteTools.readShort(buf[pos],buf[pos+1]); pos+=2;
    int size= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4;
    int cref= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4;
    int delayOffset= ByteTools.readInt(buf[pos],buf[pos+1],buf[pos+2],buf[pos+3]); pos+=4;
    byte usage= buf[pos++];
    byte nameLen= buf[pos++];
    byte unused1= buf[pos++];
    byte unused2= buf[pos++];
    if (nameLen==0 && delayOffset==0 && buf.length > 36) {
      // BLIP record follows then
      byte[] BLIPbuf = new byte[24];
      System.arraycopy(buf, pos, BLIPbuf, 0, 24);
      int version = (0xF&BLIPbuf[0]);
        int inst = ((0xFF&BLIPbuf[1])<<4)|(0xF0&BLIPbuf[0])>>4;
        int fbt = ((0xFF&BLIPbuf[3])<<8)|(0xFF&BLIPbuf[2]);
        int len = ByteTools.readInt(BLIPbuf[4], BLIPbuf[5], BLIPbuf[6], BLIPbuf[7]);
        UID2= new byte[16];
        System.arraycopy(buf, 8, UID2, 0, 16);   
    } else if (buf.length > 36){
      Logger.logWarn("Delay? " + ((delayOffset==0)?"No":"Yes") + " Namelen=" + nameLen);
    }
    */
    int ref= ByteTools.readInt(buf[24],buf[25],buf[26],buf[27])
    cRef.add(Integer.valueOf(ref));

    int HEADERLEN = 61;
    int STARTPOS = HEADERLEN;
    int BYTELEN  = buf.length;
    if(HEADERLEN > BYTELEN) {
      BYTELEN= 0;
      STARTPOS = 0;
    }
    BYTELEN-=STARTPOS;
   
    byte[] imgBuf = new byte[BYTELEN];
    System.arraycopy(buf,STARTPOS,imgBuf,0,BYTELEN);

    imageData.add(imgBuf);
    return imgBuf;
  }
 
    /** create a new MSODrawingGroup record based upon image datas defined in imageData/imageType/cRef arrays +
     *  spidMax, numDrawings, numShapes, numIdClusters
     *
     * The squenence of records here are:
     * F000, F006, F001, F007(xNumImages), F00B ,F11E
     *    MSOFBTDGG MSOFBTBSTORECONTAINER MSOFBTBSE (x numimages)
     *
     */
    public void updateRecord(){
      MsofbtBSE[] BSE = new MsofbtBSE[imageData.size()]//(0xF007,1,0);
      byte[] imageBytes = null;
     
      ByteArrayOutputStream bos = null;
      try{
        bos = new ByteArrayOutputStream();
        for(int i=0;i<imageData.size();i++){         
          BSE[i]= new MsofbtBSE(MSODrawingConstants.MSOFBTBSE,Integer.parseInt(imageType.get(i).toString()),2);
          BSE[i].setImageData((byte[])imageData.get(i));
          BSE[i].setImageType(Integer.parseInt(imageType.get(i).toString()));
          BSE[i].setRefCount(((Integer) cRef.get(i)).intValue());    // 20071120 KSC: set the reference count for this image data
          bos.write(BSE[i].toByteArray());
         }
        imageBytes = bos.toByteArray();
      }catch(Exception e){
          Logger.logErr("Msodrawingroup createData error.",e);
      }
     
      MsofbtDgg dgg = new MsofbtDgg(MSODrawingConstants.MSOFBTDGG,0,0);
      dgg.setSpidMax(spidMax);    // 20071113 KSC
      numDrawings= getNumDrawings(); // 20100324 KSC: changed from: msoRecs.size();  // 20080908 KSC
      dgg.setNumDrawings(numDrawings);
      numShapes= getNumShapes()// 20080904 KSC: sum up dg's shapes     
      dgg.setNumShapes(numShapes);
      // 2008003 KSC: numIdClusters is solely a function of spidMax dgg.setNumIdClusters(numIdClusters);           
      byte[] dggBytes = dgg.toByteArray();
     
      MsofbtOPT OPT = new MsofbtOPT(MSODrawingConstants.MSOFBTOPT,0,3)
      // add the apparent basic shape options
      OPT.setProperty(MSODrawingConstants.msooptfFitTextToShape, false, false, 0x80008, null);
      OPT.setProperty(MSODrawingConstants.msooptfillColor, false, false, 0x8000041, null);
      OPT.setProperty(MSODrawingConstants.msooptlineColor, false, false, 0x8000040, null);
      byte[] OPTBytes = OPT.toByteArray();
     
      /* 20070915 KSC not necessary for all msodgs*/
      MsofbtSplitMenuColors SplitMenuColors = new MsofbtSplitMenuColors(MSODrawingConstants.MSOFBTSPLITMENUCOLORS,4,0);
      byte[] SplitMenuColorsBytes = SplitMenuColors.toByteArray();
     
      int totalLength = imageBytes.length;

      // 20080910 KSC: if no images, don't input n MSOFBTBSTORE
      byte[] BstoreContainerBytes= new byte[0];
      if (totalLength > 0) {
        MsofbtBstoreContainer BstoreContainer = new MsofbtBstoreContainer(MSODrawingConstants.MSOFBTBSTORECONTAINER,imageData.size(),15);
        BstoreContainer.setLength(totalLength);
        BstoreContainerBytes = BstoreContainer.toByteArray();
       
        // add up the stuff
        totalLength +=
          OPTBytes.length+
          SplitMenuColorsBytes.length+
          BstoreContainerBytes.length+
          dggBytes.length;
      } else {
        // add up the stuff
        totalLength +=
          OPTBytes.length+
          SplitMenuColorsBytes.length+
          dggBytes.length;
      }
      MsofbtDggContainer dggContainer = new MsofbtDggContainer(MSODrawingConstants.MSOFBTDGGCONTAINER,0,15)
      dggContainer.setLength(totalLength);
     
      byte[] dggContainerBytes = dggContainer.toByteArray();    
     
      int pos=0;
      byte[] retData = new byte[totalLength+dggContainerBytes.length];
     
      System.arraycopy(dggContainerBytes,0,retData,pos,dggContainerBytes.length);
      pos+=dggContainerBytes.length;
      System.arraycopy(dggBytes,0,retData,pos,dggBytes.length);
      pos+=dggBytes.length;
      System.arraycopy(BstoreContainerBytes,0,retData,pos,BstoreContainerBytes.length);
      pos+=BstoreContainerBytes.length;
      // this is the BSE array
      System.arraycopy(imageBytes,0,retData,pos,imageBytes.length);
      pos+=imageBytes.length;
      // default OPT
      System.arraycopy(OPTBytes,0,retData,pos,OPTBytes.length);
      pos+=OPTBytes.length; 
      // 20070915 KSC not truly necessary
      System.arraycopy(SplitMenuColorsBytes,0,retData,pos,SplitMenuColorsBytes.length);
      pos+=SplitMenuColorsBytes.length;     
     
      setData(retData);
    }

   
    /** sets the underlying image bytes

     *
     * @param bts  new image bytes
     * @param bs  Boundsheet
     * @param rec  original Msodrawing rec linked to image
     * @param name  original image name (used for lookups)
     * @return
     */
    public boolean setImageBytes(byte[] bts, Boundsheet bs, MSODrawing rec, String name){
    // Find original image handle - often is different than getImageIndex due to reuse, etc. of image bytes
      int trueIdx= rec.getImageIndex()-1// true index into imageData and cRef arrays
      if(trueIdx < 0)
          return false;
       
        if((imageData.size())<=trueIdx)
          return false;

      try {
        if (getCRef(trueIdx)>1) {  //20080802 KSC: if referenced more than 1x, add new so don't overwrite original
          // create new image handle with new bytes
          ImageHandle im= new ImageHandle(bts, bs);
          // Find original image handle + fill new with original info
            int index= -1;
              ImageHandle[] imgz= bs.getImages();               
              for (int i= 0; i < imgz.length; i++) {
                  if(imgz[i].getName().equals(name)) {
                    index= i;
                    break;
                  }
              }
          ImageHandle origIm= imgz[index]// get original image handle         
              im.setName(origIm.getName());    // set new with original's data
              im.setShapeName(origIm.getShapeName());
              im.setImageType(origIm.getImageType());
              // insert new image into sheet
          bs.insertImage(im, true);
          im.position(origIm)// position to original
          // now remove original mso rec
          removeMsodrawingrec(rec, bs, true)
          index= imageData.size()-1// new index
        } else  // just set the image bytes
          imageData.set(trueIdx, bts);
      }catch(Exception ex) {
          Logger.logErr("Msodrawingroup setImageBytes failed.",ex);
          return false;
      }

      updateRecord();
      parse();
      wkbook.initImages();
      return true;     
    }
   
   
    /** returns the underlying image bytes

     *
     * @param index
     * @return
     */
    public byte[] getImageBytes(int index){
      if(index < 0)
          return null;
       
        if(index >= imageData.size())
          return null;
     
      byte[] ret = null;
      try {
          ret =(byte[])imageData.get(index);
      }catch(Exception ex) {
          Logger.logErr("Msodrawingroup getImageBytes error.",ex);
      }
      return ret;
     
    }
    public int getImageType(int index){
   
      return Integer.parseInt(imageType.get(index).toString());
    }
   
    /**
     * returns the number of *unique* images in this workbook
     * @return
     */
    public int getNumImages(){
      return imageData.size();
    }
   
    /**
     * related to number of drawing objects (= images + charts) but unclear how the count goes; may include deleted, etc .
     * @return
     */
  public int getNumDrawings() {
    // 20100324 KSC: this is experimental as numDrawings do not follow any obvious logic ...
    numDrawings= 0;
    // 20100511 KSC: this is not correct ...
    for (int i= 0; i < msoRecs.size(); i++) {
      // 20100518 KSC: try this:
      if (((MSODrawing)msoRecs.get(i)).isHeader())
        numDrawings++;
      //numDrawings= Math.max(numDrawings, ((Msodrawing)msoRecs.get(i)).getDrawingId());
    }
//    if (numDrawings==0 && msoRecs.size() > 0) numDrawings++;
    return numDrawings;
  }

  /**
   * count the number of shapes in the document; shape mso's contain a msofbtSpContainer sub-record  (TODO: is this true in every case?)
   * @return
   */
  public int getNumShapes() {
    /**
     * NOTE: I've never found a clear algorithm for numShapes which results in
     * matching Excel results; however,
     * it appears that numShapes can be >= to Excel's value and open correctly;
     * problems occur when numShapes are less than what Excel expects.
     * So the below is basically the maximum numShapes available and appears to
     * result in templates
     */
    /*
    numShapes= 1; 
    for (int i= 0; i < msoRecs.size(); i++) {
      MSODrawing mso= ((MSODrawing) msoRecs.get(i));
      if (mso.isShape)
        numShapes++;
    }
    */
    numShapes= msoRecs.size();
    return numShapes;
  }
 
  /**
   * return the # of Id Clusters (charts related?)
   * @return
   */
  public int getNumIdClusters() {
    return numIdClusters;
  }
 
  /**
   * return SpidMax
   * @return
   */
  public int getSpidMax() {
    return spidMax;
  }
 
  /**
   * set SpidMax
   * @param spid
   */
  public void setSpidMax(int spid) {
    spidMax= spid;
  }
 
  /**
   * test to see if imageData is in imageArray
   * @param imgData  byte[] defining image
   * @return
   */
  protected int containsImage(byte[] imgData) {
    int z= -1;
    for (int i= 0; i < imageData.size() && z < 0; i++) {
      if (java.util.Arrays.equals(imgData, ((byte[]) imageData.get(0))))
        z= i;
    }
    return z;
  }

  /**
   * return the index into the imageData array for the specified image (via byte lookup)
   * @param imgData  image bytes
   * @return  index into imageData array
   */
  public int findImage(byte[] imgData) {
    return containsImage(imgData);
  }
  /**
   * if imageData doesn't exist, add to array
   * otherwise just inc ref count
   *
   * @param imgData  byte[] defining image
   * @param imgType  type of image
   * @param bAddUnconditionally  add new even if already referenced (used in setting image bytes)
   * @return index to image
   */
    public int addImage(byte[] imgData,int imgType, boolean bAddUnconditionally){
      int n= -1;
    // 20080908 KSC: done automatically numShapes++;  // 20080208 KSC: if add unconditionally, add even if imageData already exists
      if (bAddUnconditionally || (n=containsImage(imgData))==-1) { // 20071120 KSC: it's a unique image     
        imageData.add(imgData);
        imageType.add(Integer.valueOf(imgType));
        cRef.add(Integer.valueOf(1));
        n= imageData.size();
      } else // 20071120 KSC: If not a unqiue image, just update ref count
        incCRef(n);
        n++;
      }
      return n;
   }
   
  public void clear(){
      numShapes= 0;
    imageData.clear();
    imageType.clear();
  }
 
  /**
   * If large, MSODrawingGroup will span multiple records; merge data
   * @param rec  next MSODrawingGroup record in stream
   */
  public void mergeRecords(MSODrawingGroup rec) {
    // Merge and remove continues
        if (rec.hasContinues()) {
        rec.mergeAndRemoveContinues();    // now that data is merged, get rid of continues ...
        }
     byte[] prevData = this.getBytes();
    byte[] newData = rec.getBytes();
    byte[] totalData = new byte[newData.length];
    if(prevData!=null){ // a simple append of the data together
      totalData = new byte[prevData.length+newData.length];
      System.arraycopy(prevData,0,totalData,0,prevData.length);
      System.arraycopy(newData,0,totalData,prevData.length,newData.length);
    } else
      totalData= newData;
    this.setData(totalData);
  }

  /**
   * show pertinent information for record
   */
  public String toString() { 
    StringBuffer sb= new StringBuffer();
    sb.append("MSODrawingGroup: numShapes=" +numShapes + " numDrawings=" + numDrawings + " numIdCluster=" + numIdClusters + " spidMax=" + spidMax);
    sb.append("\nNumber of drawing records=" + msoRecs.size());
    if(data!=null)
      sb.append(" Length of data=" + data.length);
    return sb.toString();
  }
       
    // continue handling
  public void mergeAndRemoveContinues() {
    if (!this.isContinueMerged && this.hasContinues())  super.mergeContinues();   
        if (this.hasContinues()) {
            // now that data is merged, get rid of continues ...   
          Iterator it = this.continues.iterator();
          while(it.hasNext()) {
              Continue ci = (Continue)it.next();
              this.getStreamer().removeRecord(ci); // remove existing continues from stream
          }
          super.removeContinues();
          this.continues = null;
        }
    }
 
  /**
   * increment reference count for specific image data
   * @param idx
   */
  protected void incCRef(int idx) {
    if (idx >= 0 && idx < cRef.size()) {
      int cr= ((Integer)cRef.get(idx)).intValue() + 1;
        cRef.remove(idx);
        cRef.add(idx, Integer.valueOf(cr));
    } //else  20071126 KSC: it's OK, can have - indexes ...
      //Logger.logErr("Index error encountered when updating Reference Count");
  }

  /**
   * return the reference count for the specific image
   * @param idx
   */
  protected int getCRef(int idx) {
    if (idx >= 0 && idx < cRef.size()) {
        return ((Integer) cRef.get(idx)).intValue();
   
    //Logger.logErr("MSODrawingGroup: error encountered when returning Reference Count");
    return -1;
  }
 
  /**
   * decrement the reference count for the specific image
   * @param idx
   */
  protected void decCRef(int idx) {
    if (idx >= 0 && idx < cRef.size()) {
      int cr= ((Integer)cRef.get(idx)).intValue() - 1;
        cRef.remove(idx);
        cRef.add(idx, Integer.valueOf(cr));
    }
    else
      Logger.logErr("MSODrawingGroup: error encountered when decrementing Reference Count");
  }
 
  /**
   *
   * add a new Drawing Record based on existing drawing record
   * i.e. from CopyWorkSheet ...
   * @param spidMax
   * @param rec
   */
  public void addDrawingRecord(int spidMax, MSODrawing rec) {
    this.numDrawings++;   
    incCRef(rec.imageIndex-1)// increment cRef
    this.spidMax= spidMax;
    this.updateRecord();    // given all information, generate appropriate bytes 
  }
 
  /** Must ensure that oridinal drawing Id for each drawing record is correct
   *  Plus ensure SPID's are correct
     * 
     *  Not default prestreaming as we need these values when we assemble sheet recs
   */ 
  public void prestream(){
    int j= 0;
    if (dirtyflag) {       
      for (int i= 0; i < msoRecs.size(); i++) {
        MSODrawing mso= (MSODrawing) msoRecs.get(i);
        if (mso.isHeader()) {
          mso.setDrawingId(++j);
        }
      }
    } 
  }
 
  /**
   *  return the number of drawing recs
   */
  public int getNumDrawingRecs() { return msoRecs.size(); }
 
    /**
     * clear out object references in prep for closing workbook
     */
    public void close() {
      super.close();
      for (int i= 0; i < msoRecs.size(); i++) {
        MSODrawing m= (MSODrawing) msoRecs.get(i);
        m.close();
      }
      msoRecs.clear();
    }
}
TOP

Related Classes of com.extentech.formats.XLS.MSODrawingGroup

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.