Blob Blame History Raw
/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */

/* libmwaw
* Version: MPL 2.0 / LGPLv2+
*
* The contents of this file are subject to the Mozilla Public License Version
* 2.0 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2002 William Lachance (wrlach@gmail.com)
* Copyright (C) 2002,2004 Marc Maurer (uwog@uwog.net)
* Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch)
* Copyright (C) 2006, 2007 Andrew Ziem
* Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr)
*
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
* in which case the provisions of the LGPLv2+ are applicable
* instead of those above.
*/

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <sstream>
#include <string>

#include <librevenge/librevenge.h>

#include "MWAWFontConverter.hxx"
#include "MWAWPosition.hxx"
#include "MWAWOLEParser.hxx"
#include "MWAWPictMac.hxx"

//////////////////////////////////////////////////
// internal structure
//////////////////////////////////////////////////
/** Low level: namespace used to define/store the data used by MWAWOLEParser */
namespace MWAWOLEParserInternal
{
/** Internal: internal method to compobj definition */
class CompObj
{
public:
  //! the constructor
  CompObj()
    : m_mapCls()
  {
    initCLSMap();
  }

  /** return the CLS Name corresponding to an identifier */
  char const *getCLSName(unsigned long v)
  {
    if (m_mapCls.find(v) == m_mapCls.end()) return nullptr;
    return m_mapCls[v];
  }

protected:
  /** map CLSId <-> name */
  std::map<unsigned long, char const *> m_mapCls;

  /** initialise a map CLSId <-> name */
  void initCLSMap()
  {
    // source: binfilter/bf_so3/source/inplace/embobj.cxx
    m_mapCls[0x00000319]="Picture"; // addon Enhanced Metafile ( find in some file)

    m_mapCls[0x000212F0]="MSWordArt"; // or MSWordArt.2
    m_mapCls[0x00021302]="MSWorksWPDoc"; // addon

    // MS Apps
    m_mapCls[0x00030000]= "ExcelWorksheet";
    m_mapCls[0x00030001]= "ExcelChart";
    m_mapCls[0x00030002]= "ExcelMacrosheet";
    m_mapCls[0x00030003]= "WordDocument";
    m_mapCls[0x00030004]= "MSPowerPoint";
    m_mapCls[0x00030005]= "MSPowerPointSho";
    m_mapCls[0x00030006]= "MSGraph";
    m_mapCls[0x00030007]= "MSDraw"; // find also with ca003 ?
    m_mapCls[0x00030008]= "Note-It";
    m_mapCls[0x00030009]= "WordArt";
    m_mapCls[0x0003000a]= "PBrush";
    m_mapCls[0x0003000b]= "Equation"; // "Microsoft Equation Editor"
    m_mapCls[0x0003000c]= "Package";
    m_mapCls[0x0003000d]= "SoundRec";
    m_mapCls[0x0003000e]= "MPlayer";
    // MS Demos
    m_mapCls[0x0003000f]= "ServerDemo"; // "OLE 1.0 Server Demo"
    m_mapCls[0x00030010]= "Srtest"; // "OLE 1.0 Test Demo"
    m_mapCls[0x00030011]= "SrtInv"; //  "OLE 1.0 Inv Demo"
    m_mapCls[0x00030012]= "OleDemo"; //"OLE 1.0 Demo"

    // Coromandel / Dorai Swamy / 718-793-7963
    m_mapCls[0x00030013]= "CoromandelIntegra";
    m_mapCls[0x00030014]= "CoromandelObjServer";

    // 3-d Visions Corp / Peter Hirsch / 310-325-1339
    m_mapCls[0x00030015]= "StanfordGraphics";

    // Deltapoint / Nigel Hearne / 408-648-4000
    m_mapCls[0x00030016]= "DGraphCHART";
    m_mapCls[0x00030017]= "DGraphDATA";

    // Corel / Richard V. Woodend / 613-728-8200 x1153
    m_mapCls[0x00030018]= "PhotoPaint"; // "Corel PhotoPaint"
    m_mapCls[0x00030019]= "CShow"; // "Corel Show"
    m_mapCls[0x0003001a]= "CorelChart";
    m_mapCls[0x0003001b]= "CDraw"; // "Corel Draw"

    // Inset Systems / Mark Skiba / 203-740-2400
    m_mapCls[0x0003001c]= "HJWIN1.0"; // "Inset Systems"

    // Mark V Systems / Mark McGraw / 818-995-7671
    m_mapCls[0x0003001d]= "ObjMakerOLE"; // "MarkV Systems Object Maker"

    // IdentiTech / Mike Gilger / 407-951-9503
    m_mapCls[0x0003001e]= "FYI"; // "IdentiTech FYI"
    m_mapCls[0x0003001f]= "FYIView"; // "IdentiTech FYI Viewer"

    // Inventa Corporation / Balaji Varadarajan / 408-987-0220
    m_mapCls[0x00030020]= "Stickynote";

    // ShapeWare Corp. / Lori Pearce / 206-467-6723
    m_mapCls[0x00030021]= "ShapewareVISIO10";
    m_mapCls[0x00030022]= "ImportServer"; // "Spaheware Import Server"

    // test app SrTest
    m_mapCls[0x00030023]= "SrvrTest"; // "OLE 1.0 Server Test"

    // test app ClTest.  Doesn't really work as a server but is in reg db
    m_mapCls[0x00030025]= "Cltest"; // "OLE 1.0 Client Test"

    // Microsoft ClipArt Gallery   Sherry Larsen-Holmes
    m_mapCls[0x00030026]= "MS_ClipArt_Gallery";
    // Microsoft Project  Cory Reina
    m_mapCls[0x00030027]= "MSProject";

    // Microsoft Works Chart
    m_mapCls[0x00030028]= "MSWorksChart";

    // Microsoft Works Spreadsheet
    m_mapCls[0x00030029]= "MSWorksSpreadsheet";

    // AFX apps - Dean McCrory
    m_mapCls[0x0003002A]= "MinSvr"; // "AFX Mini Server"
    m_mapCls[0x0003002B]= "HierarchyList"; // "AFX Hierarchy List"
    m_mapCls[0x0003002C]= "BibRef"; // "AFX BibRef"
    m_mapCls[0x0003002D]= "MinSvrMI"; // "AFX Mini Server MI"
    m_mapCls[0x0003002E]= "TestServ"; // "AFX Test Server"

    // Ami Pro
    m_mapCls[0x0003002F]= "AmiProDocument";

    // WordPerfect Presentations For Windows
    m_mapCls[0x00030030]= "WPGraphics";
    m_mapCls[0x00030031]= "WPCharts";

    // MicroGrafx Charisma
    m_mapCls[0x00030032]= "Charisma";
    m_mapCls[0x00030033]= "Charisma_30"; // v 3.0
    m_mapCls[0x00030034]= "CharPres_30"; // v 3.0 Pres
    // MicroGrafx Draw
    m_mapCls[0x00030035]= "Draw"; //"MicroGrafx Draw"
    // MicroGrafx Designer
    m_mapCls[0x00030036]= "Designer_40"; // "MicroGrafx Designer 4.0"

    // STAR DIVISION
    //m_mapCls[0x000424CA]= "StarMath"; // "StarMath 1.0"
    m_mapCls[0x00043AD2]= "FontWork"; // "Star FontWork"
    //m_mapCls[0x000456EE]= "StarMath2"; // "StarMath 2.0"
  }
};

/** Internal: internal method to keep ole definition */
struct OleDef {
  OleDef()
    : m_id(-1)
    , m_subId(-1)
    , m_dir("")
    , m_base("")
    , m_name("")
  {
  }
  int m_id /**main id*/, m_subId /**subsversion id */ ;
  std::string m_dir/**the directory*/, m_base/**the base*/, m_name/**the complete name*/;
};

/** Internal: internal state of a MWAWOLEParser */
struct State {
  /** constructor */
  State(MWAWFontConverterPtr const &fontConverter, int fId)
    : m_fontConverter(fontConverter)
    , m_fontId(fId)
    , m_encoding(-1)
    , m_metaData()
    , m_unknownOLEs()
    , m_objects()
    , m_objectsPosition()
    , m_objectsId()
    , m_objectsType()
    , m_compObjIdName()
  {
  }
  //! the font converter
  MWAWFontConverterPtr m_fontConverter;
  //! the font id used to decode string
  int m_fontId;
  //! the font encoding
  int m_encoding;
  //! the meta data
  librevenge::RVNGPropertyList m_metaData;
  //! list of ole which can not be parsed
  std::vector<std::string> m_unknownOLEs;

  //! list of pictures read
  std::vector<librevenge::RVNGBinaryData> m_objects;
  //! list of picture size ( if known)
  std::vector<MWAWPosition> m_objectsPosition;
  //! list of pictures id
  std::vector<int> m_objectsId;
  //! list of picture type
  std::vector<std::string> m_objectsType;

  //! a smart ptr used to stored the list of compobj id->name
  std::shared_ptr<MWAWOLEParserInternal::CompObj> m_compObjIdName;
};
}

// constructor/destructor
MWAWOLEParser::MWAWOLEParser(std::string const &mainName, MWAWFontConverterPtr const &fontConverter, int fId)
  : m_avoidOLE(mainName)
  , m_state(new MWAWOLEParserInternal::State(fontConverter, fId))
{
}

MWAWOLEParser::~MWAWOLEParser()
{
}

int MWAWOLEParser::getFontEncoding() const
{
  return m_state->m_encoding;
}

void MWAWOLEParser::updateMetaData(librevenge::RVNGPropertyList &metaData) const
{
  librevenge::RVNGPropertyList::Iter i(m_state->m_metaData);
  for (i.rewind(); i.next();) {
    if (!metaData[i.key()])
      metaData.insert(i.key(),i()->clone());
  }
}

std::vector<std::string> const &MWAWOLEParser::getNotParse() const
{
  return m_state->m_unknownOLEs;
}

std::vector<int> const &MWAWOLEParser::getObjectsId() const
{
  return m_state->m_objectsId;
}

std::vector<MWAWPosition> const &MWAWOLEParser::getObjectsPosition() const
{
  return m_state->m_objectsPosition;
}

std::vector<librevenge::RVNGBinaryData> const &MWAWOLEParser::getObjects() const
{
  return m_state->m_objects;
}

std::vector<std::string> const &MWAWOLEParser::getObjectsType() const
{
  return m_state->m_objectsType;
}

bool MWAWOLEParser::getObject(int id, librevenge::RVNGBinaryData &obj, MWAWPosition &pos, std::string &type)  const
{
  for (size_t i = 0; i < m_state->m_objectsId.size(); i++) {
    if (m_state->m_objectsId[i] != id) continue;
    obj = m_state->m_objects[i];
    pos = m_state->m_objectsPosition[i];
    type = m_state->m_objectsType[i];
    return true;
  }
  obj.clear();
  return false;
}

void MWAWOLEParser::setObject(int id, librevenge::RVNGBinaryData const &obj, MWAWPosition const &pos,
                              std::string const &type)
{
  for (size_t i = 0; i < m_state->m_objectsId.size(); i++) {
    if (m_state->m_objectsId[i] != id) continue;
    m_state->m_objects[i] = obj;
    m_state->m_objectsPosition[i] = pos;
    m_state->m_objectsType[i] = type;
    return;
  }
  m_state->m_objects.push_back(obj);
  m_state->m_objectsPosition.push_back(pos);
  m_state->m_objectsId.push_back(id);
  m_state->m_objectsType.push_back(type);
}

// parsing
bool MWAWOLEParser::parse(MWAWInputStreamPtr file)
{
  if (!m_state->m_compObjIdName)
    m_state->m_compObjIdName.reset(new MWAWOLEParserInternal::CompObj);

  m_state->m_unknownOLEs.resize(0);
  m_state->m_objects.resize(0);
  m_state->m_objectsId.resize(0);
  m_state->m_objectsType.resize(0);

  if (!file.get()) return false;

  if (!file->isStructured()) return false;

  unsigned numSubStreams = file->subStreamCount();
  //
  // we begin by grouping the Ole by their potential main id
  //
  std::multimap<int, MWAWOLEParserInternal::OleDef> listsById;
  std::vector<int> listIds;
  for (unsigned i = 0; i < numSubStreams; ++i) {
    std::string const &name = file->subStreamName(i);
    if (name.empty() || name[name.length()-1]=='/') continue;

    // separated the directory and the name
    //    MatOST/MatadorObject1/Ole10Native
    //      -> dir="MatOST/MatadorObject1", base="Ole10Native"
    auto pos = name.find_last_of('/');

    std::string dir, base;
    if (pos == std::string::npos) base = name;
    else if (pos == 0) base = name.substr(1);
    else {
      dir = name.substr(0,pos);
      base = name.substr(pos+1);
    }
    if (dir == "" && base == m_avoidOLE) continue;

#define PRINT_OLE_NAME
#if defined(PRINT_OLE_NAME)
    MWAW_DEBUG_MSG(("OLEName=%s\n", name.c_str()));
#endif
    MWAWOLEParserInternal::OleDef data;
    data.m_name = name;
    data.m_dir = dir;
    data.m_base = base;

    // try to retrieve the identificator stored in the directory
    //  MatOST/MatadorObject1/ -> 1, -1
    //  Object 2/ -> 2, -1
    dir+='/';
    pos = dir.find('/');
    int id[2] = { -1, -1};
    while (pos != std::string::npos) {
      if (pos >= 1 && dir[pos-1] >= '0' && dir[pos-1] <= '9') {
        auto idP = pos-1;
        while (idP >=1 && dir[idP-1] >= '0' && dir[idP-1] <= '9')
          idP--;
        int val = std::atoi(dir.substr(idP, idP-pos).c_str());
        if (id[0] == -1) id[0] = val;
        else {
          id[1] = val;
          break;
        }
      }
      pos = dir.find('/', pos+1);
    }
    data.m_id = id[0];
    data.m_subId = id[1];
    if (listsById.find(data.m_id) == listsById.end())
      listIds.push_back(data.m_id);
    listsById.insert(std::multimap<int, MWAWOLEParserInternal::OleDef>::value_type(data.m_id, data));
  }

  for (auto id : listIds) {
    auto pos = listsById.lower_bound(id);

    // try to find a representation for each id
    // FIXME: maybe we must also find some for each subid
    librevenge::RVNGBinaryData pict;
    int confidence = -1000;
    MWAWPosition actualPos, potentialSize;
    bool isPict = false;

    while (pos != listsById.end()) {
      auto const &dOle = pos->second;
      if (pos++->first != id) break;

      MWAWInputStreamPtr ole = file->getSubStreamByName(dOle.m_name);
      if (!ole.get()) {
        MWAW_DEBUG_MSG(("MWAWOLEParser: error: can not find OLE part: \"%s\"\n", dOle.m_name.c_str()));
        continue;
      }
      libmwaw::DebugFile asciiFile(ole);
      asciiFile.open(dOle.m_name);

      librevenge::RVNGBinaryData data;
      bool hasData = false;
      int newConfidence = -2000;
      bool ok = true;
      MWAWPosition pictPos;

      if (strncmp("Ole", dOle.m_base.c_str(), 3) == 0 ||
          strncmp("CompObj", dOle.m_base.c_str(), 7) == 0)
        ole->setReadInverted(true);

      try {
        librevenge::RVNGPropertyList pList;
        int encoding=m_state->m_encoding;
        bool isMainOle=dOle.m_dir.empty();
        if (readMM(ole, dOle.m_base, asciiFile));
        else if (readSummaryInformation(ole, dOle.m_base, isMainOle ? m_state->m_encoding : encoding, isMainOle ? m_state->m_metaData : pList, asciiFile)) {
          if (isMainOle && m_state->m_encoding!=encoding && m_state->m_fontConverter &&
              m_state->m_encoding>=1250 && m_state->m_encoding<=1258) {
            std::stringstream s;
            s << "CP" << m_state->m_encoding;
            m_state->m_fontId=m_state->m_fontConverter->getId(s.str().c_str());
          }
        }
        else if (readObjInfo(ole, dOle.m_base, asciiFile));
        else if (readOle(ole, dOle.m_base, asciiFile));
        else if (isOlePres(ole, dOle.m_base) &&
                 readOlePres(ole, data, pictPos, asciiFile)) {
          hasData = true;
          newConfidence = 2;
        }
        else if (isOle10Native(ole, dOle.m_base) &&
                 readOle10Native(ole, data, asciiFile)) {
          hasData = true;
          // small size can be a symptom that this is a link to a
          // basic msworks data file, so we reduce confidence
          newConfidence = data.size() > 1000 ? 4 : 2;
        }
        else if (readCompObj(ole, dOle.m_base, asciiFile));
        else if (readContents(ole, dOle.m_base, data, pictPos, asciiFile)) {
          hasData = true;
          newConfidence = 3;
        }
        else if (readCONTENTS(ole, dOle.m_base, data, pictPos, asciiFile)) {
          hasData = true;
          newConfidence = 3;
        }
        else
          ok = false;
      }
      catch (...) {
        ok = false;
      }
      if (!ok) {
        m_state->m_unknownOLEs.push_back(dOle.m_name);
        asciiFile.reset();
        continue;
      }

      /** first check if this is a mac pict as other oles
          may not be understand by openOffice, ... */
      if (data.size()) {
        MWAWInputStreamPtr dataInput=MWAWInputStream::get(data, false);
        if (dataInput) {
          dataInput->seek(0, librevenge::RVNG_SEEK_SET);
          MWAWBox2f box;
          if (MWAWPictData::check(dataInput, static_cast<int>(data.size()), box) != MWAWPict::MWAW_R_BAD) {
            isPict = true;
            newConfidence = 100;
          }
        }
      }

      if (hasData && data.size()) {
        // probably only a subs data
        if (dOle.m_subId != -1) newConfidence -= 10;

        if (newConfidence > confidence ||
            (newConfidence == confidence && pict.size() < data.size())) {
          confidence = newConfidence;
          pict = data;
          actualPos = pictPos;
        }

        if (actualPos.naturalSize().x() > 0 && actualPos.naturalSize().y() > 0)
          potentialSize = actualPos;
#ifdef DEBUG_WITH_FILES
        libmwaw::Debug::dumpFile(data, dOle.m_name.c_str());
#endif
      }

      asciiFile.reset();

#ifndef DEBUG
      if (confidence >= 3) break;
#endif
    }

    if (pict.size()) {
      m_state->m_objects.push_back(pict);
      if (actualPos.naturalSize().x() <= 0 || actualPos.naturalSize().y() <= 0) {
        MWAWVec2f size = potentialSize.naturalSize();
        if (size.x() > 0 && size.y() > 0)
          actualPos.setNaturalSize(actualPos.getInvUnitScale(potentialSize.unit())*size);
      }
      m_state->m_objectsPosition.push_back(actualPos);
      m_state->m_objectsId.push_back(id);
      if (isPict)
        m_state->m_objectsType.push_back("image/pict");
      else
        m_state->m_objectsType.push_back("object/ole");
    }
  }

  return true;
}



////////////////////////////////////////
//
// small structure
//
////////////////////////////////////////
bool MWAWOLEParser::readOle(MWAWInputStreamPtr ip, std::string const &oleName,
                            libmwaw::DebugFile &ascii)
{
  if (!ip.get()) return false;

  if (oleName!="Ole") return false;

  if (ip->seek(20, librevenge::RVNG_SEEK_SET) != 0 || ip->tell() != 20) return false;

  ip->seek(0, librevenge::RVNG_SEEK_SET);

  int val[20];
  for (int &i : val) {
    i = static_cast<int>(ip->readLong(1));
    if (i < -10 || i > 10) return false;
  }

  libmwaw::DebugStream f;
  f << "@@Ole: ";
  // always 1, 0, 2, 0*
  for (int i = 0; i < 20; i++)
    if (val[i]) f << "f" << i << "=" << val[i] << ",";
  ascii.addPos(0);
  ascii.addNote(f.str().c_str());

  if (!ip->isEnd()) {
    ascii.addPos(20);
    ascii.addNote("@@Ole:###");
  }

  return true;
}

bool MWAWOLEParser::readObjInfo(MWAWInputStreamPtr input, std::string const &oleName,
                                libmwaw::DebugFile &ascii)
{
  if (oleName!="ObjInfo") return false;

  input->seek(14, librevenge::RVNG_SEEK_SET);
  if (input->tell() != 6 || !input->isEnd()) return false;

  input->seek(0, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "@@ObjInfo:";

  // always 0, 3, 4 ?
  for (int i = 0; i < 3; i++) f << input->readLong(2) << ",";

  ascii.addPos(0);
  ascii.addNote(f.str().c_str());

  return true;
}

bool MWAWOLEParser::readMM(MWAWInputStreamPtr input, std::string const &oleName,
                           libmwaw::DebugFile &ascii)
{
  if (oleName!="MM") return false;

  input->seek(14, librevenge::RVNG_SEEK_SET);
  if (input->tell() != 14 || !input->isEnd()) return false;

  input->seek(0, librevenge::RVNG_SEEK_SET);
  auto entete = static_cast<int>(input->readULong(2));
  if (entete != 0x444e) {
    if (entete == 0x4e44) {
      MWAW_DEBUG_MSG(("MWAWOLEParser::readMM: ERROR: endian mode probably bad, potentially bad PC/Mac mode detection.\n"));
    }
    return false;
  }
  libmwaw::DebugStream f;
  f << "@@MM:";

  int val[6];
  for (auto &v : val) v = static_cast<int>(input->readLong(2));

  switch (val[5]) {
  case 0:
    f << "conversion,";
    break;
  case 2:
    f << "Works3,";
    break;
  case 4:
    f << "Works4,";
    break;
  default:
    f << "version=unknown,";
    break;
  }

  // 1, 0, 0, 0, 0 : Mac file
  // 0, 1, 0, [0,1,2,4,6], 0 : Pc file
  // Note: No field seems to code the document type
  bool macFile = input->readInverted() == false;
  int normalMod = macFile ? 0:1;

  for (int i = 0; i < 5; i++) {
    if ((i%2)!=normalMod && val[i]) f << "###";
    f << val[i] << ",";
  }

  ascii.addPos(0);
  ascii.addNote(f.str().c_str());

  if (macFile) input->setReadInverted(true);
  return true;
}


bool MWAWOLEParser::readCompObj(MWAWInputStreamPtr ip, std::string const &oleName, libmwaw::DebugFile &ascii)
{
  if (strncmp(oleName.c_str(), "CompObj", 7) != 0) return false;

  // check minimal size
  const int minSize = 12 + 14+ 16 + 12; // size of header, clsid, footer, 3 string size
  if (ip->seek(minSize,librevenge::RVNG_SEEK_SET) != 0 || ip->tell() != minSize) return false;

  libmwaw::DebugStream f;
  f << "@@CompObj(Header): ";
  ip->seek(0,librevenge::RVNG_SEEK_SET);

  for (int i = 0; i < 6; i++) {
    auto val = static_cast<int>(ip->readLong(2));
    f << val << ", ";
  }

  ascii.addPos(0);
  ascii.addNote(f.str().c_str());

  ascii.addPos(12);
  // the clsid
  unsigned long clsData[4]; // ushort n1, n2, n3, b8, ... b15
  for (auto &data : clsData) data = ip->readULong(4);

  f.str("");
  f << "@@CompObj(CLSID):";
  if (clsData[1] == 0 && clsData[2] == 0xC0 && clsData[3] == 0x46000000L) {
    // normally, a referenced object
    char const *clsName = m_state->m_compObjIdName->getCLSName(clsData[0]);
    if (clsName)
      f << "'" << clsName << "'";
    else {
      MWAW_DEBUG_MSG(("MWAWOLEParser::readCompObj: unknown clsid=%ld\n", long(clsData[0])));
      f << "unknCLSID='" << std::hex << clsData[0] << "'";
    }
  }
  else {
    /* I found:
      c1dbcd28e20ace11a29a00aa004a1a72     for MSWorks.Table
      c2dbcd28e20ace11a29a00aa004a1a72     for Microsoft Works/MSWorksWPDoc
      a3bcb394c2bd1b10a18306357c795b37     for Microsoft Drawing 1.01/MSDraw.1.01
      b25aa40e0a9ed111a40700c04fb932ba     for Quill96 Story Group Class ( basic MSWorks doc?)
      796827ed8bc9d111a75f00c04fb9667b     for MSWorks4Sheet
    */
    f << "data0=(" << std::hex << clsData[0] << "," << clsData[1] << "), "
      << "data1=(" << clsData[2] << "," << clsData[3] << ")";
  }
  ascii.addNote(f.str().c_str());
  f << std::dec;
  for (int ch = 0; ch < 3; ch++) {
    long actPos = ip->tell();
    long sz = ip->readLong(4);
    bool waitNumber = sz == -1;
    if (waitNumber || sz == -2) sz = 4;
    if (sz < 0 || !ip->checkPosition(actPos+4+sz)) return false;

    std::string st;
    if (waitNumber) {
      f.str("");
      f << ip->readLong(4) << "[val*]";
      st = f.str();
    }
    else {
      for (int i = 0; i < sz; i++)
        st += char(ip->readULong(1));
    }

    f.str("");
    f << "@@CompObj:";
    switch (ch) {
    case 0:
      f << "UserType=";
      break;
    case 1:
      f << "ClipName=";
      break;
    case 2:
      f << "ProgIdName=";
      break;
    default:
      break;
    }
    f << st;
    ascii.addPos(actPos);
    ascii.addNote(f.str().c_str());
  }

  if (ip->isEnd()) return true;

  long actPos = ip->tell();
  long nbElt = 4;
  if (ip->seek(actPos+16,librevenge::RVNG_SEEK_SET) != 0 ||
      ip->tell() != actPos+16) {
    if ((ip->tell()-actPos)%4) {
      f.str("");
      f << "@@CompObj(Footer):###";
      ascii.addPos(actPos);
      ascii.addNote(f.str().c_str());
      return true;
    }
    nbElt = (ip->tell()-actPos)/4;
  }

  f.str("");
  f << "@@CompObj(Footer): " << std::hex;
  ip->seek(actPos,librevenge::RVNG_SEEK_SET);
  for (long i = 0; i < nbElt; i++)
    f << ip->readULong(4) << ",";
  ascii.addPos(actPos);
  ascii.addNote(f.str().c_str());

  ascii.addPos(ip->tell());

  return true;
}

//////////////////////////////////////////////////
// summary and doc summary
//////////////////////////////////////////////////
bool MWAWOLEParser::readSummaryInformation(MWAWInputStreamPtr input, std::string const &oleName,
    int &encoding, librevenge::RVNGPropertyList &pList, libmwaw::DebugFile &ascii) const
{
  if (oleName!="SummaryInformation" && oleName!="DocumentSummaryInformation") return false;
  input->seek(0, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "Entries(SumInfo):";
  bool isDoc=oleName=="DocumentSummaryInformation";
  if (isDoc) f << "doc,";
  auto val=int(input->readULong(2));
  if (val==0xfeff) {
    input->setReadInverted(false);
    val=0xfffe;
  }
  if (input->size()<48 || val!=0xfffe) {
    MWAW_DEBUG_MSG(("MWAWOLEParser::readSummaryInformation: header seems bad\n"));
    f << "###";
    ascii.addPos(0);
    ascii.addNote(f.str().c_str());
    return true;
  }
  for (int i=0; i<11; ++i) { // f1=1, f2=0-2
    val=int(input->readULong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  unsigned long lVal=input->readULong(4);
  if ((lVal&0xF0FFFFFF)==0) {
    lVal=(lVal>>24);
    input->setReadInverted(!input->readInverted());
  }
  if (lVal==0 || lVal>15) { // find 1 or 2 sections, unsure about the maximum numbers
    MWAW_DEBUG_MSG(("MWAWOLEParser::readSummaryInformation: summary info is bad\n"));
    f << "###sumInfo=" << std::hex << lVal << std::dec << ",";
    ascii.addPos(0);
    ascii.addNote(f.str().c_str());
    return true;
  }
  auto numSection=int(lVal);
  if (numSection!=1)
    f << "num[section]=" << numSection << ",";
  for (int i=0; i<4; ++i) {
    val=int(input->readULong(4));
    static int const expected[]= {int(0xf29f85e0),0x10684ff9,0x891ab,int(0xd9b3272b)};
    static int const docExpected[]= {int(0xd5cdd502),0x101b2e9c,0x89793,int(0xaef92c2b)};
    if ((!isDoc && val==expected[i]) || (isDoc && val==docExpected[i])) continue;
    f << "#fmid" << i << "=" << std::hex << val << std::dec << ",";
    static bool first=true;
    if (first) {
      MWAW_DEBUG_MSG(("MWAWOLEParser::readSummaryInformation: fmid is bad\n"));
      first=false;
    }
  }
  auto decal=int(input->readULong(4));
  if (decal<0x30 || input->size()<decal) {
    MWAW_DEBUG_MSG(("MWAWOLEParser::readSummaryInformation: decal is bad\n"));
    f << "decal=" << val << ",";
    ascii.addPos(0);
    ascii.addNote(f.str().c_str());
    return true;

  }
  ascii.addPos(0);
  ascii.addNote(f.str().c_str());
  if (decal!=0x30) {
    ascii.addPos(0x30);
    ascii.addNote("_");
    input->seek(decal, librevenge::RVNG_SEEK_SET);
  }

  for (int sect=0; sect<numSection; ++sect) {
    long pos=input->tell();
    f.str("");
    f << "SumInfo-A:";
    auto pSectSize=long(input->readULong(4));
    long endSect=pos+pSectSize;
    auto N=int(input->readULong(4));
    f << "N=" << N << ",";
    if (pSectSize<0 || input->size()-pos<pSectSize || (pSectSize-8)/8<N) {
      MWAW_DEBUG_MSG(("MWAWOLEParser::readSummaryInformation: psetstruct is bad\n"));
      f << "###";
      ascii.addPos(pos);
      ascii.addNote(f.str().c_str());
      return true;
    }
    f << "[";
    std::map<long,int> posToTypeMap;
    for (int i=0; i<N; ++i) {
      auto type=int(input->readULong(4));
      auto depl=int(input->readULong(4));
      if (depl<=0) continue;
      f << std::hex << depl << std::dec << ":" << type << ",";
      if ((depl-8)/8<N || depl>pSectSize-4 || posToTypeMap.find(pos+depl)!=posToTypeMap.end()) {
        f << "###";
        continue;
      }
      posToTypeMap[pos+depl]=type;
    }
    f << "],";
    ascii.addPos(pos);
    ascii.addNote(f.str().c_str());

    long endPos=input->size();
    for (auto it=posToTypeMap.begin(); it!=posToTypeMap.end(); ++it) {
      pos=it->first;
      auto nextIt=it;
      if (++nextIt!=posToTypeMap.end()) endPos=nextIt->first;
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      f.str("");
      f << "SumInfo-B" << it->second << ":";
      auto type=int(input->readULong(4));
      if (sect==0 && it->second==1 && !isDoc && type==2) {
        long value=-1;
        if (readSummaryPropertyLong(input,endPos,type,value,f) && value>=0 && value<10000) // 10000 is mac
          encoding=int(value);
      }
      else if (sect==0 && type==0x1e && !isDoc && ((it->second>=2 && it->second<=6) || it->second==8)) {
        librevenge::RVNGString text;
        if (readSummaryPropertyString(input, endPos, type, text, f) && !text.empty()) {
          static char const *attribNames[] = {
            "", "", "dc:title", "dc:subject", "meta:initial-creator",
            "meta:keywords", "dc:description"/*comment*/, "", "dc:creator"
          };
          pList.insert(attribNames[it->second], text);
        }
      }
      else if (!readSummaryProperty(input, endPos, type, ascii, f)) {
        MWAW_DEBUG_MSG(("MWAWOLEParser::readSummaryInformation: find unknown type\n"));
        f << "##type=" << std::hex << type << std::dec << ",";
      }
      if (input->tell()!=endPos && input->tell()!=pos)
        ascii.addDelimiter(input->tell(),'|');
      ascii.addPos(pos);
      ascii.addNote(f.str().c_str());
    }
    input->seek(endSect, librevenge::RVNG_SEEK_SET);
  }
  return true;
}

bool MWAWOLEParser::readSummaryPropertyString(MWAWInputStreamPtr input, long endPos, int type,
    librevenge::RVNGString &string, libmwaw::DebugStream &f) const
{
  if (!input) return false;
  long pos=input->tell();
  string.clear();
  auto sSz=long(input->readULong(4));
  if (sSz<0 || (endPos-pos-4)<sSz || pos+4+sSz>endPos) {
    MWAW_DEBUG_MSG(("MWAWOLEParser::readSummaryPropertyString: string size is bad\n"));
    f << "##stringSz=" << sSz << ",";
    return false;
  }
  std::string text("");
  for (long c=0; c < sSz; ++c) {
    auto ch=char(input->readULong(1));
    if (ch) {
      text+=ch;
      if (m_state->m_fontConverter) {
        int unicode=m_state->m_fontConverter->unicode(m_state->m_fontId, static_cast<unsigned char>(ch));
        if (unicode!=-1)
          libmwaw::appendUnicode(uint32_t(unicode), string);
      }
    }
    else if (c+1!=sSz)
      text+="##";
  }
  f << text;
  if (type==0x1f && (sSz%4))
    input->seek(sSz%4, librevenge::RVNG_SEEK_CUR);
  return true;
}

bool MWAWOLEParser::readSummaryPropertyLong(MWAWInputStreamPtr input, long endPos, int type, long &value,
    libmwaw::DebugStream &f) const
{
  if (!input) return false;
  long pos=input->tell();
  switch (type) {
  case 2: // int
  case 0x12: // uint
    if (pos+2>endPos)
      return false;
    value=type==2 ? long(input->readLong(2)) : long(input->readULong(2));
    break;
  case 3: // int
  case 9: // uint
    if (pos+4>endPos)
      return false;
    value=type==3 ? long(input->readLong(4)) : long(input->readULong(4));
    break;
  default:
    return false;
  }
  f << "val=" << value << ",";
  return true;
}

bool MWAWOLEParser::readSummaryProperty(MWAWInputStreamPtr input, long endPos, int type,
                                        libmwaw::DebugFile &ascii, libmwaw::DebugStream &f) const
{
  if (!input) return false;
  long pos=input->tell();
  // see propread.cxx
  if (type&0x1000) {
    auto N=int(input->readULong(4));
    f << "N=" << N << ",";
    f << "[";
    for (int n=0; n<N; ++n) {
      pos=input->tell();
      f << "[";
      if (!readSummaryProperty(input, endPos, type&0xFFF, ascii, f)) {
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        return false;
      }
      f << "],";
    }
    f << "],";
    return true;
  }
  switch (type) {
  case 0x10: // int1
  case 0x11: // uint1
    if (pos+1>endPos)
      return false;
    f << "val=" << char(input->readULong(1));
    break;
  case 2: // int
  case 0xb: // bool
  case 0x12: // uint
    if (pos+2>endPos)
      return false;
    if (type==2)
      f << "val=" << int(input->readLong(2)) << ",";
    else if (type==0x12)
      f << "val=" << int(input->readULong(2)) << ",";
    else if (input->readULong(2))
      f << "true,";
    break;
  case 3: // int
  case 4: // float
  case 9: // uint
    if (pos+4>endPos)
      return false;
    if (type==3)
      f << "val=" << int(input->readLong(4)) << ",";
    else if (type==9)
      f << "val=" << int(input->readULong(4)) << ",";
    else
      f << "val[fl4]=" << std::hex << input->readULong(4) << std::dec << ",";
    break;
  case 5: // double
  case 6:
  case 7:
  case 20:
  case 21:
  case 0x40:
    if (pos+8>endPos)
      return false;
    ascii.addDelimiter(input->tell(),'|');
    if (type==5)
      f << "double,";
    else if (type==6)
      f << "cy,";
    else if (type==7)
      f << "date,";
    else if (type==20)
      f << "long,";
    else if (type==21)
      f << "ulong,";
    else
      f << "fileTime,"; // readme 8 byte
    input->seek(pos+8, librevenge::RVNG_SEEK_SET);
    break;
  case 0xc: // variant
    if (pos+4>endPos)
      return false;
    type=int(input->readULong(4));
    return readSummaryProperty(input, endPos, type, ascii, f);
  // case 20: int64
  // case 21: uint64
  case 8:
  case 0x1e:
  case 0x1f: {
    librevenge::RVNGString string;
    if (!readSummaryPropertyString(input, endPos, type, string, f))
      return false;
    break;
  }
  case 0x41:
  case 0x46:
  case 0x47: {
    if (pos+4>endPos)
      return false;
    f << (type==0x41 ? "blob" : type==0x46 ? "blob[object]" : "clipboard") << ",";
    auto dSz=long(input->readULong(4));
    if (dSz<0 || pos+4+dSz>endPos)
      return false;
    if (dSz) {
      ascii.skipZone(pos+4, pos+4+dSz-1);
      input->seek(dSz, librevenge::RVNG_SEEK_CUR);
    }
    break;
  }
  /* todo type==0x47, vtcf clipboard */
  default:
    return false;
  }
  return true;
}
//////////////////////////////////////////////////
//
// OlePres001 seems to contained standart picture file and size
//    extract the picture if it is possible
//
//////////////////////////////////////////////////

bool MWAWOLEParser::isOlePres(MWAWInputStreamPtr ip, std::string const &oleName)
{
  if (!ip.get()) return false;

  if (strncmp("OlePres",oleName.c_str(),7) != 0) return false;

  if (ip->seek(40, librevenge::RVNG_SEEK_SET) != 0 || ip->tell() != 40) return false;

  ip->seek(0, librevenge::RVNG_SEEK_SET);
  for (int i= 0; i < 2; i++) {
    long val = ip->readLong(4);
    if (val < -10 || val > 10) {
      if (i!=1 && val != 0x50494354)
        return false;
    }
  }

  long actPos = ip->tell();
  long hSize = ip->readLong(4);
  if (hSize < 4) return false;
  if (ip->seek(actPos+hSize+28, librevenge::RVNG_SEEK_SET) != 0
      || ip->tell() != actPos+hSize+28)
    return false;

  ip->seek(actPos+hSize, librevenge::RVNG_SEEK_SET);
  for (int i= 3; i < 7; i++) {
    long val = ip->readLong(4);
    if (val < -10 || val > 10) {
      if (i != 5 || val > 256) return false;
    }
  }

  ip->seek(8, librevenge::RVNG_SEEK_CUR);
  long size = ip->readLong(4);

  if (size <= 0) return ip->isEnd();

  actPos = ip->tell();
  if (ip->seek(actPos+size, librevenge::RVNG_SEEK_SET) != 0
      || ip->tell() != actPos+size)
    return false;

  return true;
}

bool MWAWOLEParser::readOlePres(MWAWInputStreamPtr ip, librevenge::RVNGBinaryData &data, MWAWPosition &pos,
                                libmwaw::DebugFile &ascii)
{
  data.clear();
  if (!isOlePres(ip, "OlePres")) return false;

  pos = MWAWPosition();
  pos.setUnit(librevenge::RVNG_POINT);
  pos.setRelativePosition(MWAWPosition::Char);
  libmwaw::DebugStream f;
  f << "@@OlePress(header): ";
  ip->seek(0,librevenge::RVNG_SEEK_SET);
  for (int i = 0; i < 2; i++) {
    long val = ip->readLong(4);
    f << val << ", ";
  }

  long actPos = ip->tell();
  long hSize = ip->readLong(4);
  if (hSize < 4) return false;
  f << "hSize = " << hSize;
  ascii.addPos(0);
  ascii.addNote(f.str().c_str());

  long endHPos = actPos+hSize;
  if (!ip->checkPosition(endHPos+28)) return false;
  bool ok = true;
  f.str("");
  f << "@@OlePress(headerA): ";
  if (hSize < 14) ok = false;
  else {
    // 12,21,32|48,0
    for (int i = 0; i < 4; i++) f << ip->readLong(2) << ",";
    // 3 name of creator
    for (int ch=0; ch < 3; ch++) {
      std::string str;
      bool find = false;
      while (ip->tell() < endHPos) {
        auto c = static_cast<unsigned char>(ip->readULong(1));
        if (c == 0) {
          find = true;
          break;
        }
        str += char(c);
      }
      if (!find) {
        ok = false;
        break;
      }
      f << ", name" <<  ch << "=" << str;
    }
    if (ok) ok = ip->tell() == endHPos;
  }
  // FIXME, normally they remain only a few bits (size unknown)
  if (!ok) f << "###";
  ascii.addPos(actPos);
  ascii.addNote(f.str().c_str());

  if (ip->seek(endHPos+28, librevenge::RVNG_SEEK_SET) != 0)
    return false;

  ip->seek(endHPos, librevenge::RVNG_SEEK_SET);

  actPos = ip->tell();
  f.str("");
  f << "@@OlePress(headerB): ";
  for (int i = 3; i < 7; i++) {
    long val = ip->readLong(4);
    f << val << ", ";
  }
  // dim in TWIP ?
  auto extendX = long(ip->readULong(4));
  auto extendY = long(ip->readULong(4));
  if (extendX > 0 && extendY > 0) pos.setNaturalSize(MWAWVec2f(float(extendX)/20.f, float(extendY)/20.f));
  long fSize = ip->readLong(4);
  f << "extendX="<< extendX << ", extendY=" << extendY << ", fSize=" << fSize;

  ascii.addPos(actPos);
  ascii.addNote(f.str().c_str());

  if (fSize == 0) return ip->isEnd();

  data.clear();
  if (!ip->readDataBlock(fSize, data)) return false;

  if (!ip->isEnd()) {
    ascii.addPos(ip->tell());
    ascii.addNote("@@OlePress###");
  }

  ascii.skipZone(36+hSize,36+hSize+fSize-1);
  return true;
}

//////////////////////////////////////////////////
//
//  Ole10Native: basic Windows picture, with no size
//          - in general used to store a bitmap
//
//////////////////////////////////////////////////

bool MWAWOLEParser::isOle10Native(MWAWInputStreamPtr ip, std::string const &oleName)
{
  if (strncmp("Ole10Native",oleName.c_str(),11) != 0) return false;

  if (ip->seek(4, librevenge::RVNG_SEEK_SET) != 0 || ip->tell() != 4) return false;

  ip->seek(0, librevenge::RVNG_SEEK_SET);
  long size = ip->readLong(4);

  if (size <= 0) return false;
  if (ip->seek(4+size, librevenge::RVNG_SEEK_SET) != 0 || ip->tell() != 4+size)
    return false;

  return true;
}

bool MWAWOLEParser::readOle10Native(MWAWInputStreamPtr ip,
                                    librevenge::RVNGBinaryData &data,
                                    libmwaw::DebugFile &ascii)
{
  if (!isOle10Native(ip, "Ole10Native")) return false;

  libmwaw::DebugStream f;
  f << "@@Ole10Native(Header): ";
  ip->seek(0,librevenge::RVNG_SEEK_SET);
  long fSize = ip->readLong(4);
  f << "fSize=" << fSize;

  ascii.addPos(0);
  ascii.addNote(f.str().c_str());

  data.clear();
  if (!ip->readDataBlock(fSize, data)) return false;

  if (!ip->isEnd()) {
    ascii.addPos(ip->tell());
    ascii.addNote("@@Ole10Native###");
  }
  ascii.skipZone(4,4+fSize-1);
  return true;
}

////////////////////////////////////////////////////////////////
//
// In general a picture : a PNG, an JPEG, a basic metafile,
//    find also a MSDraw.1.01 picture (with first bytes 0x78563412="xV4") or WordArt,
//    ( with first bytes "WordArt" )  which are not sucefull read
//    (can probably contain a list of data, but do not know how to
//     detect that)
//
// To check: does this is related to MSO_BLIPTYPE ?
//        or OO/filter/sources/msfilter/msdffimp.cxx ?
//
////////////////////////////////////////////////////////////////
bool MWAWOLEParser::readContents(MWAWInputStreamPtr input,
                                 std::string const &oleName,
                                 librevenge::RVNGBinaryData &pict, MWAWPosition &pos,
                                 libmwaw::DebugFile &ascii)
{
  pict.clear();
  if (oleName!="Contents") return false;

  libmwaw::DebugStream f;
  pos = MWAWPosition();
  pos.setUnit(librevenge::RVNG_POINT);
  pos.setRelativePosition(MWAWPosition::Char);
  input->seek(0, librevenge::RVNG_SEEK_SET);
  f << "@@Contents:";

  bool ok = true;
  // bdbox 0 : size in the file ?
  long dim[2];
  for (auto &d : dim) d = input->readLong(4);
  f << "bdbox0=(" << dim[0] << "," << dim[1]<<"),";
  for (int i = 0; i < 3; i++) {
    /* 0,{10|21|75|101|116}x2 */
    auto val = long(input->readULong(4));
    if (val < 1000)
      f << val << ",";
    else
      f << std::hex << "0x" << val << std::dec << ",";
    if (val > 0x10000) ok=false;
  }
  // new bdbox : size of the picture ?
  long naturalSize[2];
  for (auto &size : naturalSize) size = input->readLong(4);
  f << std::dec << "bdbox1=(" << naturalSize[0] << "," << naturalSize[1]<<"),";
  f << "unk=" << input->readULong(4) << ","; // 24 or 32
  if (input->isEnd()) {
    MWAW_DEBUG_MSG(("MWAWOLEParser: warning: Contents header length\n"));
    return false;
  }
  if (dim[0] > 0 && dim[0] < 3000 &&
      dim[1] > 0 && dim[1] < 3000)
    pos.setSize(MWAWVec2f(float(dim[0]),float(dim[1])));
  else {
    MWAW_DEBUG_MSG(("MWAWOLEParser: warning: Contents odd size : %ld %ld\n", dim[0], dim[1]));
  }
  if (naturalSize[0] > 0 && naturalSize[0] < 5000 &&
      naturalSize[1] > 0 && naturalSize[1] < 5000)
    pos.setNaturalSize(MWAWVec2f(float(naturalSize[0]),float(naturalSize[1])));
  else {
    MWAW_DEBUG_MSG(("MWAWOLEParser: warning: Contents odd naturalsize : %ld %ld\n", naturalSize[0], naturalSize[1]));
  }

  long actPos = input->tell();
  auto size = long(input->readULong(4));
  if (size <= 0) ok = false;
  if (ok) {
    input->seek(actPos+size+4, librevenge::RVNG_SEEK_SET);
    if (input->tell() != actPos+size+4 || !input->isEnd()) {
      ok = false;
      MWAW_DEBUG_MSG(("MWAWOLEParser: warning: Contents unexpected file size=%ld\n",
                      size));
    }
  }

  if (!ok) f << "###";
  f << "dataSize=" << size;

  ascii.addPos(0);
  ascii.addNote(f.str().c_str());

  input->seek(actPos+4, librevenge::RVNG_SEEK_SET);

  if (ok) {
    if (input->readDataBlock(size, pict))
      ascii.skipZone(actPos+4, actPos+size+4-1);
    else {
      input->seek(actPos+4, librevenge::RVNG_SEEK_SET);
      ok = false;
    }
  }

  if (!input->isEnd()) {
    ascii.addPos(actPos);
    ascii.addNote("@@Contents:###");
  }

  if (!ok) {
    MWAW_DEBUG_MSG(("MWAWOLEParser: warning: read ole Contents: failed\n"));
  }
  return ok;
}

////////////////////////////////////////////////////////////////
//
// Another different type of contents (this time in majuscule)
// we seem to contain the header of a EMF and then the EMF file
//
////////////////////////////////////////////////////////////////
bool MWAWOLEParser::readCONTENTS(MWAWInputStreamPtr input,
                                 std::string const &oleName,
                                 librevenge::RVNGBinaryData &pict, MWAWPosition &pos,
                                 libmwaw::DebugFile &ascii)
{
  pict.clear();
  if (oleName!="CONTENTS") return false;

  libmwaw::DebugStream f;

  pos = MWAWPosition();
  pos.setUnit(librevenge::RVNG_POINT);
  pos.setRelativePosition(MWAWPosition::Char);
  input->seek(0, librevenge::RVNG_SEEK_SET);
  f << "@@CONTENTS:";

  auto hSize = long(input->readULong(4));
  if (input->isEnd()) return false;
  f << "hSize=" << std::hex << hSize << std::dec;

  if (hSize <= 52 || input->seek(hSize+8,librevenge::RVNG_SEEK_SET) != 0
      || input->tell() != hSize+8) {
    MWAW_DEBUG_MSG(("MWAWOLEParser: warning: CONTENTS headerSize=%ld\n",
                    hSize));
    return false;
  }

  // minimal checking of the "copied" header
  input->seek(4, librevenge::RVNG_SEEK_SET);
  auto type = long(input->readULong(4));
  if (type < 0 || type > 4) return false;
  auto newSize = long(input->readULong(4));

  f << ", type=" << type;
  if (newSize < 8) return false;

  if (newSize != hSize) // can sometimes happen, pb after a conversion ?
    f << ", ###newSize=" << std::hex << newSize << std::dec;

  // checkme: two bdbox, in document then data : units ?
  //     Maybe first in POINT, second in TWIP ?
  for (int st = 0; st < 2 ; st++) {
    long dim[4];
    for (auto &d : dim) d = input->readLong(4);

    bool ok = dim[0] >= 0 && dim[2] > dim[0] && dim[1] >= 0 && dim[3] > dim[2];
    if (ok && st==0) pos.setNaturalSize(MWAWVec2f(float(dim[2]-dim[0]), float(dim[3]-dim[1])));
    if (st==0) f << ", bdbox(Text)";
    else f << ", bdbox(Data)";
    if (!ok) f << "###";
    f << "=(" << dim[0] << "x" << dim[1] << "<->" << dim[2] << "x" << dim[3] << ")";
  }
  char dataType[5];
  for (int i = 0; i < 4; i++) dataType[i] = char(input->readULong(1));
  dataType[4] = '\0';
  f << ",typ=\""<<dataType<<"\""; // always " EMF" ?

  for (int i = 0; i < 2; i++) { // always id0=0, id1=1 ?
    auto val = static_cast<int>(input->readULong(2));
    if (val) f << ",id"<< i << "=" << val;
  }
  auto dataLength = long(input->readULong(4));
  f << ",length=" << dataLength+hSize;

  ascii.addPos(0);
  ascii.addNote(f.str().c_str());

  ascii.addPos(input->tell());
  f.str("");
  f << "@@CONTENTS(2)";
  for (int i = 0; i < 12 && 4*i+52 < hSize; i++) {
    // f0=7,f1=1,f5=500,f6=320,f7=1c4,f8=11a
    // or f0=a,f1=1,f2=2,f3=6c,f5=480,f6=360,f7=140,f8=f0
    // or f0=61,f1=1,f2=2,f3=58,f5=280,f6=1e0,f7=a9,f8=7f
    // f3=some header sub size ? f5/f6 and f7/f8 two other bdbox ?
    auto val = long(input->readULong(4));
    if (val) f << std::hex << ",f" << i << "=" << val;
  }
  for (int i = 0; 2*i+100 < hSize; i++) {
    // g0=e3e3,g1=6,g2=4e6e,g3=4
    // g0=e200,g1=4,g2=a980,g3=3,g4=4c,g5=50
    // ---
    auto val = long(input->readULong(2));
    if (val) f << std::hex << ",g" << i << "=" << val;
  }
  ascii.addNote(f.str().c_str());

  if (dataLength <= 0 || input->seek(hSize+4+dataLength,librevenge::RVNG_SEEK_SET) != 0
      || input->tell() != hSize+4+dataLength || !input->isEnd()) {
    MWAW_DEBUG_MSG(("MWAWOLEParser: warning: CONTENTS unexpected file length=%ld\n",
                    dataLength));
    return false;
  }

  input->seek(4+hSize, librevenge::RVNG_SEEK_SET);
  if (!input->readEndDataBlock(pict)) return false;

  ascii.skipZone(hSize+4, input->tell()-1);
  return true;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: