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 <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <set>
#include <sstream>

#include <librevenge/librevenge.h>

#include "MWAWListener.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWGraphicEncoder.hxx"
#include "MWAWGraphicListener.hxx"
#include "MWAWGraphicShape.hxx"
#include "MWAWGraphicStyle.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPictBitmap.hxx"
#include "MWAWPictMac.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSubDocument.hxx"

#include "MsWksTable.hxx"
#include "MsWksDocument.hxx"

#include "MsWksGraph.hxx"

/** Internal: the structures of a MsWksGraph */
namespace MsWksGraphInternal
{
////////////////////////////////////////
//! Internal: a list of zones ( for v4)
struct RBZone {
  RBZone()
    : m_isMain(true)
    , m_id(-2)
    , m_idList()
    , m_frame("")
  {
  }
  //! returns a unique id
  int getId() const
  {
    return m_isMain ? -1 : m_id;
  }
  //! the zone type: rbdr(true) or rbil
  bool m_isMain;
  //! the zone id
  int m_id;
  //! the list of rb
  std::vector<int> m_idList;
  //! the frame name ( if it exist )
  std::string m_frame;
};

////////////////////////////////////////
//! Internal: the generic pict
struct Zone {
  enum Type { Unknown, Shape, ChartZone, Group, Pict, Text, Textv4, Bitmap, TableZone, OLE};
  //! constructor
  Zone()
    : m_subType(-1)
    , m_zoneId(-1)
    , m_pos()
    , m_dataPos(-1)
    , m_fileId(-1)
    , m_page(-1)
    , m_decal()
    , m_finalDecal()
    , m_box()
    , m_line(-1)
    , m_style()
    , m_order(0)
    , m_extra("")
    , m_doNotSend(false)
    , m_isSent(false)
  {
    for (auto &id : m_ids) id = 0;
  }
  //! destructor
  virtual ~Zone() {}

  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Zone const &pict)
  {
    pict.print(o);
    return o;
  }
  //! return the type
  virtual Type type() const
  {
    return Unknown;
  }

  //! return a binary data (if known)
  virtual bool getBinaryData(MWAWInputStreamPtr, MWAWEmbeddedObject &picture) const
  {
    picture=MWAWEmbeddedObject();
    return false;
  }

  //! return the extra border space ( if needed)
  virtual float needExtraBorderWidth() const
  {
    return 0.0;
  }

  //! add frame parameters to propList (if needed )
  virtual void fillFrame(MWAWGraphicStyle &) const { }

  //! return the box
  MWAWBox2f getLocalBox(bool extendWithBord=true) const
  {
    float x = m_box.size().x(), y=m_box.size().y();
    MWAWVec2f min = m_box.min();
    if (x < 0) {
      min+=MWAWVec2f(x,0);
      x *= -1.0f;
    }
    if (y < 0) {
      min+=MWAWVec2f(0,y);
      y *= -1.0f;
    }
    MWAWBox2f res(min, min+MWAWVec2f(x,y));
    if (!extendWithBord) return res;
    float bExtra = needExtraBorderWidth();
    if (bExtra > 0) res.extend(2.0f*bExtra);
    return res;
  }

  MWAWPosition getPosition(MWAWPosition::AnchorTo rel) const
  {
    MWAWPosition res;
    MWAWBox2f box = getLocalBox();
    if (rel==MWAWPosition::Paragraph || rel==MWAWPosition::Frame) {
      res = MWAWPosition(box.min()+m_finalDecal, box.size(), librevenge::RVNG_POINT);
      res.setRelativePosition(rel);
      if (rel==MWAWPosition::Paragraph)
        res.m_wrapping = MWAWPosition::WBackground;
    }
    else if (rel!=MWAWPosition::Page) {
      res = MWAWPosition(MWAWVec2f(0,0), box.size(), librevenge::RVNG_POINT);
      res.setRelativePosition(MWAWPosition::Char,
                              MWAWPosition::XLeft, MWAWPosition::YTop);
    }
    else {
      res = MWAWPosition(box.min()+m_finalDecal, box.size(), librevenge::RVNG_POINT);
      res.setRelativePosition(MWAWPosition::Page);
      if (m_page >=0)
        res.setPage(m_page+1);
      res.m_wrapping =  MWAWPosition::WBackground;
    }
    if (m_order > 0) res.setOrder(m_order);
    return res;
  }

  //! the virtual print function
  virtual void print(std::ostream &o) const;

  //! the type
  int m_subType;
  //! the zone id
  int m_zoneId;
  //! the file position
  MWAWEntry m_pos;
  //! the data begin position
  long m_dataPos;
  //! the file id
  int m_fileId;
  //! the zones id (main, previous, next)
  long m_ids[3];
  //! the page
  int m_page;
  //! the local position
  MWAWBox2f m_decal;
  //! the final local position
  MWAWVec2f m_finalDecal;
  //! local bdbox
  MWAWBox2f m_box;
  //! the line position(v1)
  int m_line;
  //! the style
  MsWksGraph::Style m_style;
  //! the picture order
  int m_order;
  //! extra data
  std::string m_extra;
  //! a flag used to know if we need to send the data ( or if this is the part of a sub group)
  bool m_doNotSend;
  //! true if the zone is send
  bool m_isSent;
};

void Zone::print(std::ostream &o) const
{
  if (m_fileId >= 0) {
    o << "P" << m_fileId;
    if (m_zoneId >= 0) o << "[" << m_zoneId << "],";
    else o << ",";
  }
  for (int i = 0; i < 3; i++) {
    if (m_ids[i] <= 0) continue;
    switch (i) {
    case 0:
      o << "id=";
      break;
    case 1:
      o << "pId=";
      break;
    default:
      o << "nId=";
      break;
    }
    o << std::hex << m_ids[i] << std::dec << ",";
  }
  switch (m_subType) {
  case 0:
    o << "line,";
    break;
  case 1:
    o << "rect,";
    break;
  case 2:
    o << "rectOval,";
    break;
  case 3:
    o << "circle,";
    break;
  case 4:
    o << "arc,";
    break;
  case 5:
    o << "poly,";
    break;
  case 7:
    o << "pict,";
    break;
  case 8:
    o << "group,";
    break;
  case 9:
    o << "textbox,";
    break;
  case 0xa:
    o << "chart,";
    break;
  case 0xc:
    o << "equation/graph,";
    break;
  case 0xd:
    o << "bitmap,";
    break;
  case 0xe:
    o << "ssheet,";
    break;
  case 0xf:
    o << "textbox2,";
    break;
  case 0x10:
    o << "table,";
    break;
  case 0x100:
    o << "pict,";
    break; // V1 pict
  default:
    o << "#type=" << m_subType << ",";
  }
  if (m_page>=0) o << "page=" << m_page << ",";
  if (m_decal!=MWAWBox2f())
    o << "pos=" << m_decal << ",";
  o << "bdbox=" << m_box << ",";
  o << "style=[" << m_style << "],";
  if (m_line >= 0) o << "line=" << m_line << ",";
  if (m_extra.length()) o << m_extra;
}
////////////////////////////////////////
//! Internal: the group of a MsWksGraph
struct GroupZone final : public Zone {
  //! constructor
  explicit GroupZone(Zone const &z)
    : Zone(z)
    , m_childs()
  {
  }
  //! destructor
  ~GroupZone() final;
  //! return the type
  Type type() const final
  {
    return Group;
  }
  //! operator<<
  void print(std::ostream &o) const final
  {
    Zone::print(o);
    o << "childs=[";
    for (auto id : m_childs)
      o << "P" << id << ",";
    o << "],";
  }
  //! list of child id
  std::vector<int> m_childs;
};

GroupZone::~GroupZone()
{
}

////////////////////////////////////////
//! Internal: the simple form of a MsWksGraph ( line, rect, ...)
struct BasicShape final : public Zone {
  //! constructor
  explicit BasicShape(Zone const &z)
    : Zone(z)
    , m_shape()
  {
  }
  //! destructor
  ~BasicShape() final;
  //! return the type
  Type type() const final
  {
    return Shape;
  }
  //! operator<<
  void print(std::ostream &o) const final
  {
    Zone::print(o);
    o << m_shape << ",";
  }
  //! return the extra border size
  float needExtraBorderWidth() const final
  {
    float res=m_style.m_lineWidth;
    if (m_shape.m_type==MWAWGraphicShape::Line) {
      for (const auto &arrow : m_style.m_arrows) {
        if (!arrow.isEmpty()) res+=4;
      }
    }
    return 0.5f*res;
  }
  //! return the shape type
  MWAWGraphicStyle getStyle() const
  {
    MWAWGraphicStyle style(m_style);
    if (m_subType!=0)
      style.m_arrows[0] = style.m_arrows[1]=MWAWGraphicStyle::Arrow();
    return style;
  }

  //! the basic shape
  MWAWGraphicShape m_shape;
private:
  BasicShape(BasicShape const &) = delete;
  BasicShape &operator=(BasicShape const &) = delete;
};

BasicShape::~BasicShape()
{
}

////////////////////////////////////////
//! Internal: the table of a MsWksGraph
struct Chart final : public Zone {
  //! constructor
  explicit Chart(Zone const &z)
    : Zone(z)
    , m_chartId(0)
  {
  }
  //! empty constructor
  Chart()
    : Zone()
    , m_chartId(0)
  {
  }
  //! destructor
  ~Chart() final;

  //! return the type
  Type type() const final
  {
    return ChartZone;
  }
  //! the chart id
  int m_chartId;
};

Chart::~Chart()
{
}

////////////////////////////////////////
//! Internal: the picture of a MsWksGraph
struct DataPict final : public Zone {
  //! constructor
  explicit DataPict(Zone const &z)
    : Zone(z)
    , m_dataEndPos(-1)
    , m_naturalBox()
  {
  }
  //! empty constructor
  DataPict()
    : Zone(),
      m_dataEndPos(-1)
    , m_naturalBox()
  {
  }

  //! return the type
  Type type() const final
  {
    return Pict;
  }
  //! return a binary data (if known)
  bool getBinaryData(MWAWInputStreamPtr ip, MWAWEmbeddedObject &picture) const final;

  //! operator<<
  void print(std::ostream &o) const final
  {
    Zone::print(o);
  }
  //! the end of data (only defined when different to m_pos.end())
  long m_dataEndPos;
  //! the pict box (if known )
  mutable MWAWBox2f m_naturalBox;
};

bool DataPict::getBinaryData(MWAWInputStreamPtr ip, MWAWEmbeddedObject &picture) const
{
  picture=MWAWEmbeddedObject();
  long endPos = m_dataEndPos<=0 ? m_pos.end() : m_dataEndPos;
  long pictSize = endPos-m_dataPos;
  if (pictSize < 0) {
    MWAW_DEBUG_MSG(("MsWksGraphInternal::DataPict::getBinaryData: picture size is bad\n"));
    return false;
  }

#ifdef DEBUG_WITH_FILES
  if (1) {
    librevenge::RVNGBinaryData file;
    ip->seek(m_dataPos, librevenge::RVNG_SEEK_SET);
    ip->readDataBlock(pictSize, file);
    static int volatile pictName = 0;
    libmwaw::DebugStream f;
    f << "Pict-" << ++pictName << ".pct";
    libmwaw::Debug::dumpFile(file, f.str().c_str());
  }
#endif

  ip->seek(m_dataPos, librevenge::RVNG_SEEK_SET);
  auto res = MWAWPictData::check(ip, static_cast<int>(pictSize), m_naturalBox);
  if (res == MWAWPict::MWAW_R_BAD) {
    MWAW_DEBUG_MSG(("MsWksGraphInternal::DataPict::getBinaryData: can not find the picture\n"));
    return false;
  }

  ip->seek(m_dataPos, librevenge::RVNG_SEEK_SET);
  std::shared_ptr<MWAWPict> pict(MWAWPictData::get(ip, static_cast<int>(pictSize)));

  return pict && pict->getBinary(picture);
}

////////////////////////////////////////
//! Internal: the bitmap of a MsWksGraph
struct DataBitmap final : public Zone {
  //! constructor
  explicit DataBitmap(Zone const &z)
    : Zone(z)
    , m_numRows(0)
    , m_numCols(0)
    , m_dataSize(0)
    , m_naturalBox()
  {
  }
  //! empty constructor
  DataBitmap()
    : Zone()
    , m_numRows(0)
    , m_numCols(0)
    , m_dataSize(0)
    , m_naturalBox()
  {
  }
  //! destructor
  ~DataBitmap() final;

  //! return the type
  Type type() const final
  {
    return Bitmap;
  }
  //! return a binary data (if known)
  bool getPictureData(MWAWInputStreamPtr ip, MWAWEmbeddedObject &picture, std::vector<MWAWColor> const &palette) const;

  //! operator<<
  void print(std::ostream &o) const final
  {
    o << "nRows=" << m_numRows << ",";
    o << "nCols=" << m_numCols << ",";
    if (m_dataSize > 0)
      o << "dSize=" << std::hex << m_dataSize << std::dec << ",";
    Zone::print(o);
  }

  int m_numRows /** the number of rows*/, m_numCols/** the number of columns*/;
  long m_dataSize /** the bitmap data size */;
  //! the pict box (if known )
  mutable MWAWBox2f m_naturalBox;
};

bool DataBitmap::getPictureData(MWAWInputStreamPtr ip, MWAWEmbeddedObject &picture, std::vector<MWAWColor> const &palette) const
{
  picture=MWAWEmbeddedObject();
  if (m_dataSize <= 0 || m_dataSize < m_numRows*m_numCols) {
    MWAW_DEBUG_MSG(("MsWksGraphInternal::DataBitmap::getPictureData: dataSize size is bad\n"));
    return false;
  }
  auto szCol = int(m_dataSize/m_numRows);
  long pos = m_dataPos;

  MWAWPictBitmapIndexed *btmap = new MWAWPictBitmapIndexed(MWAWVec2i(m_numCols, m_numRows));
  if (!btmap) return false;
  btmap->setColors(palette);
  std::shared_ptr<MWAWPict> pict(btmap);
  for (int i = 0; i < m_numRows; i++) {
    ip->seek(pos, librevenge::RVNG_SEEK_SET);

    unsigned long numRead;
    uint8_t const *value = ip->read(size_t(m_numCols), numRead);
    if (!value || int(numRead) != m_numCols) return false;
    btmap->setRow(i, value);

    pos += szCol;
  }

  return pict->getBinary(picture);
}

DataBitmap::~DataBitmap()
{
}

////////////////////////////////////////
//! Internal: the table of a MsWksGraph
struct Table final : public Zone {
  //! constructor
  explicit Table(Zone const &z)
    : Zone(z)
    , m_tableId(0)
  {
  }
  //! empty constructor
  Table()
    : Zone()
    , m_tableId(0)
  {
  }
  //! destructor
  ~Table() final;

  //! return the type
  Type type() const final
  {
    return TableZone;
  }
  //! the table id
  int m_tableId;
};

Table::~Table()
{
}

////////////////////////////////////////
//! Internal: the textbox of a MsWksGraph ( v2-v3)
struct TextBox final : public Zone {
  //! constructor
  explicit TextBox(Zone const &z)
    : Zone(z)
    , m_numPositions(-1)
    , m_fontsList()
    , m_positions()
    , m_formats()
    , m_text("")
    , m_justify(MWAWParagraph::JustificationLeft)
  { }
  //! destructor
  ~TextBox() final;
  //! return the type
  Type type() const final
  {
    return Text;
  }
  //! operator<<
  void print(std::ostream &o) const final
  {
    Zone::print(o);
    switch (m_justify) {
    case MWAWParagraph::JustificationLeft:
      break;
    case MWAWParagraph::JustificationCenter:
      o << ",centered";
      break;
    case MWAWParagraph::JustificationRight:
      o << ",right";
      break;
    case MWAWParagraph::JustificationFull:
      o << ",full";
      break;
    case MWAWParagraph::JustificationFullAllLines:
      o << ",fullAllLines";
      break;
#if !defined(__clang__)
    default:
      o << ",#just=" << m_justify;
      break;
#endif
    }
  }

  //! add frame parameters to propList (if needed )
  void fillFrame(MWAWGraphicStyle &style) const final
  {
    if (!m_style.m_baseSurfaceColor.isWhite())
      style.setBackgroundColor(m_style.m_baseSurfaceColor);
  }

  //! the number of positions
  int m_numPositions;
  //! the list of fonts
  std::vector<MWAWFont> m_fontsList;
  //! the list of positions
  std::vector<int> m_positions;
  //! the list of format
  std::vector<int> m_formats;
  //! the text
  std::string m_text;
  //! the paragraph alignment
  MWAWParagraph::Justification m_justify;
private:
  TextBox(TextBox const &) = delete;
  TextBox &operator=(TextBox const &) = delete;
};

TextBox::~TextBox()
{
}

////////////////////////////////////////
//! Internal: the ole zone of a MsWksGraph ( v4)
struct OLEZone final : public Zone {
  //! constructor
  explicit OLEZone(Zone const &z)
    : Zone(z)
    , m_oleId(-1)
    , m_dim()
  { }
  //! destructor
  ~OLEZone() final;
  //! return the type
  Type type() const final
  {
    return OLE;
  }
  //! operator<<
  void print(std::ostream &o) const final
  {
    if (m_oleId >= 0) o << "ole" << m_oleId << ",";
    if (m_dim[0] > 0 && m_dim[1] > 0) o << "dim=" << m_dim << ",";
    Zone::print(o);
  }

  //! the ole id
  int m_oleId;
  //! the dimension
  MWAWVec2i m_dim;
};

OLEZone::~OLEZone()
{
}

////////////////////////////////////////
//! Internal: the textbox of a MsWksGraph ( v4)
struct TextBoxv4 final : public Zone {
  //! constructor
  explicit TextBoxv4(Zone const &z)
    : Zone(z)
    , m_text()
    , m_frame("")
  { }
  //! destructor
  ~TextBoxv4() final;
  //! return the type
  Type type() const final
  {
    return Textv4;
  }
  //! operator<<
  void print(std::ostream &o) const final
  {
    Zone::print(o);
    if (m_text.valid()) o << ", textPos=[" << m_text.begin() << "-" << m_text.end() << "]";
  }

  //! add frame parameters to propList (if needed )
  void fillFrame(MWAWGraphicStyle &style) const final
  {
    if (!m_style.m_baseSurfaceColor.isWhite())
      style.setBackgroundColor(m_style.m_baseSurfaceColor);
  }

  //! the text of positions (0-0: means no text)
  MWAWEntry m_text;
  //! the frame name
  std::string m_frame;
};


TextBoxv4::~TextBoxv4()
{
}

//! Internal the pattern ressource of a MsWksGraph
struct Pattern {
  //! constructor ( 4 int by patterns )
  Pattern(int num, uint16_t const *data)
    : m_num(num)
    , m_valuesList()
    , m_percentList()
  {
    if (m_num<=0) return;
    m_valuesList.resize(size_t(m_num)*8);
    for (size_t i=0; i < size_t(m_num)*4; ++i) {
      uint16_t val=data[i];
      m_valuesList[2*i]=static_cast<unsigned char>(val>>8);
      m_valuesList[2*i+1]=static_cast<unsigned char>(val&0xFF);
    }
    size_t pat=0;
    for (size_t i=0; i < size_t(num); ++i) {
      int numOnes=0;
      for (int j=0; j < 8; ++j) {
        uint8_t val=m_valuesList[pat++];
        for (int b=0; b < 8; b++) {
          if (val&1) ++numOnes;
          val = uint8_t(val>>1);
        }
      }
      m_percentList.push_back(float(numOnes)/64.f);
    }
  }
  //! return the pattern corresponding to an id
  bool get(int id, MWAWGraphicStyle::Pattern &pat) const
  {
    if (id < 0 || id >= m_num) {
      MWAW_DEBUG_MSG(("MsWksGraphInternal::Pattern::get: can not find pattern %d\n", id));
      return false;
    }
    pat.m_dim=MWAWVec2i(8,8);
    unsigned char const *ptr=&m_valuesList[8*size_t(id)];
    pat.m_data.resize(8);
    for (auto &data : pat.m_data)
      data=*(ptr++);
    return true;
  }
  //! return the percentage corresponding to a pattern
  float getPercent(int id) const
  {
    if (id < 0 || id >= m_num) {
      MWAW_DEBUG_MSG(("MsWksGraphInternal::Pattern::getPatternPercent: can not find pattern %d\n", id));
      return 1.0;
    }
    return m_percentList[size_t(id)];
  }

  //! the number of patterns
  int m_num;
  //! the pattern values (8 data by pattern)
  std::vector<unsigned char> m_valuesList;
  //! the pattern percent values
  std::vector<float> m_percentList;
};

////////////////////////////////////////
//! Internal: the state of a MsWksGraph
struct State {
  //! constructor
  State()
    : m_version(-1)
    , m_leftTopPos(0,0)
    , m_zonesList()
    , m_RBsMap()
    , m_font(20,12)
    , m_chartId(0)
    , m_tableId(0)
    , m_numPages(0)
    , m_rsrcPatternMap()
  {
  }
  //! return the pattern corresponding to an id
  bool getPattern(MWAWGraphicStyle::Pattern &pat, int id, long rsid=-1);
  //! return the percentage corresponding to a pattern
  float getPatternPercent(int id, long rsid=-1);
  //! init the pattern value
  void initPattern(int vers);
  //! the version
  int m_version;
  //! the page left top position in points
  MWAWVec2f m_leftTopPos;
  //! the list of zone
  std::vector<std::shared_ptr<Zone> > m_zonesList;
  //! the RBIL zone id->list id
  std::map<int, RBZone> m_RBsMap;
  //! the actual font
  MWAWFont m_font;
  //! an index used to store chart
  int m_chartId;
  //! an index used to store table
  int m_tableId;
  //! the number of pages
  int m_numPages;
  //! a map ressource id -> patterns
  std::map<long, Pattern> m_rsrcPatternMap;
};

void State::initPattern(int vers)
{
  if (!m_rsrcPatternMap.empty()) return;
  if (vers <= 2) {
    static uint16_t const valuesV2[] = {
      0xffff, 0xffff, 0xffff, 0xffff,  0xddff, 0x77ff, 0xddff, 0x77ff,  0xdd77, 0xdd77, 0xdd77, 0xdd77,  0xaa55, 0xaa55, 0xaa55, 0xaa55,
      0x55ff, 0x55ff, 0x55ff, 0x55ff,  0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa,  0xeedd, 0xbb77, 0xeedd, 0xbb77,  0x8888, 0x8888, 0x8888, 0x8888,
      0xb130, 0x031b, 0xd8c0, 0x0c8d,  0x8010, 0x0220, 0x0108, 0x4004,  0xff88, 0x8888, 0xff88, 0x8888,  0xff80, 0x8080, 0xff08, 0x0808,
      0x0000, 0x0002, 0x0000, 0x0002,  0x8040, 0x2000, 0x0204, 0x0800,  0x8244, 0x3944, 0x8201, 0x0101,  0xf874, 0x2247, 0x8f17, 0x2271,
      0x55a0, 0x4040, 0x550a, 0x0404,  0x2050, 0x8888, 0x8888, 0x0502,  0xbf00, 0xbfbf, 0xb0b0, 0xb0b0,  0x0000, 0x0000, 0x0000, 0x0000,
      0x8000, 0x0800, 0x8000, 0x0800,  0x8800, 0x2200, 0x8800, 0x2200,  0x8822, 0x8822, 0x8822, 0x8822,  0xaa00, 0xaa00, 0xaa00, 0xaa00,
      0x00ff, 0x00ff, 0x00ff, 0x00ff,  0x1122, 0x4488, 0x1122, 0x4488,  0x8040, 0x2000, 0x0204, 0x0800,  0x0102, 0x0408, 0x1020, 0x4080,
      0xaa00, 0x8000, 0x8800, 0x8000,  0xff80, 0x8080, 0x8080, 0x8080,  0x0814, 0x2241, 0x8001, 0x0204,  0x8814, 0x2241, 0x8800, 0xaa00,
      0x40a0, 0x0000, 0x040a, 0x0000,  0x0384, 0x4830, 0x0c02, 0x0101,  0x8080, 0x413e, 0x0808, 0x14e3,  0x1020, 0x54aa, 0xff02, 0x0408,
      0x7789, 0x8f8f, 0x7798, 0xf8f8,  0x0008, 0x142a, 0x552a, 0x1408,  0x0000, 0x0000, 0x0000, 0x0000,
    };
    m_rsrcPatternMap.insert(std::map<long, Pattern>::value_type(-1,Pattern(39, valuesV2)));
  }
  static uint16_t const values4002[] = {
    0xffff, 0xffff, 0xffff, 0xffff,  0x7fff, 0xffff, 0xf7ff, 0xffff,  0x7fff, 0xf7ff, 0x7fff, 0xf7ff,  0x77ff, 0xddff, 0x77ff, 0xddff,
    0x55ff, 0xddff, 0x55ff, 0xddff,  0x55ff, 0xeeff, 0x55ff, 0xbbff,  0x55ff, 0x55ff, 0x55ff, 0x55ff,  0x77dd, 0x77dd, 0x77dd, 0x77dd,
    0x55bf, 0x55ff, 0x55fb, 0x55ff,  0x55bb, 0x55ff, 0x55bb, 0x55ff,  0x55bf, 0x55ee, 0x55fb, 0x55ee,  0x55bb, 0x55ee, 0x55bb, 0x55ee,
    0x55bb, 0x55ea, 0x55bb, 0x55ae,  0x55ba, 0x55ab, 0x55ba, 0x55ab,  0x55ea, 0x55aa, 0x55ae, 0x55aa,  0xaa55, 0xaa55, 0xaa55, 0xaa55,
    0xaa15, 0xaa55, 0xaa51, 0xaa55,  0xaa45, 0xaa54, 0xaa45, 0xaa54,  0xaa44, 0xaa15, 0xaa44, 0xaa51,  0xaa44, 0xaa11, 0xaa44, 0xaa11,
    0xaa40, 0xaa11, 0xaa04, 0xaa11,  0xaa44, 0xaa00, 0xaa44, 0xaa00,  0xaa40, 0xaa00, 0xaa04, 0xaa00,  0x8822, 0x8822, 0x8822, 0x8822,
    0xaa00, 0xaa00, 0xaa00, 0xaa00,  0xaa00, 0x1100, 0xaa00, 0x4400,  0xaa00, 0x2200, 0xaa00, 0x2200,  0x8800, 0x2200, 0x8800, 0x2200,
    0x8800, 0x2000, 0x8800, 0x0200,  0x8000, 0x0800, 0x8000, 0x0800,  0x8000, 0x0000, 0x0800, 0x0000,  0x0000, 0x0000, 0x0000, 0x0000
  };
  m_rsrcPatternMap.insert(std::map<long, Pattern>::value_type(4002,Pattern(32, values4002)));
  static uint16_t const values4003[] = {
    0x0000, 0x0000, 0x0000, 0x0000,  0x8000, 0x0000, 0x0800, 0x0000,  0x8000, 0x0800, 0x8000, 0x0800,  0x8800, 0x2000, 0x8800, 0x0200,
    0x8800, 0x2200, 0x8800, 0x2200,  0xaa00, 0x2200, 0xaa00, 0x2200,  0xaa00, 0x1100, 0xaa00, 0x4400,  0xaa00, 0xaa00, 0xaa00, 0xaa00,
    0x8822, 0x8822, 0x8822, 0x8822,  0xaa44, 0xaa11, 0xaa44, 0xaa11,  0xaa45, 0xaa54, 0xaa45, 0xaa54,  0xaa55, 0xaa55, 0xaa55, 0xaa55,
    0x55ea, 0x55aa, 0x55ae, 0x55aa,  0x55ba, 0x55ab, 0x55ba, 0x55ab,  0x55bb, 0x55ee, 0x55bb, 0x55ee,  0x77dd, 0x77dd, 0x77dd, 0x77dd,
    0x55ff, 0x55ff, 0x55ff, 0x55ff,  0x55ff, 0xeeff, 0x55ff, 0xbbff,  0x77ff, 0xddff, 0x77ff, 0xddff,  0x7fff, 0xf7ff, 0x7fff, 0xf7ff,
    0x7fff, 0xffff, 0xf7ff, 0xffff,  0xffff, 0xffff, 0xffff, 0xffff
  };
  m_rsrcPatternMap.insert(std::map<long, Pattern>::value_type(4003,Pattern(22, values4003)));
  static uint16_t const values4004[] = {
    0xf0f0, 0xf0f0, 0x0f0f, 0x0f0f,  0xcccc, 0x3333, 0xcccc, 0x3333,  0x3333, 0xcccc, 0x3333, 0xcccc
  };
  m_rsrcPatternMap.insert(std::map<long, Pattern>::value_type(4004,Pattern(3, values4004)));
  static uint16_t const values7000[] = {
    0x0101, 0x1010, 0x0101, 0x1010,  0xcc00, 0x0000, 0x3300, 0x0000,  0x1122, 0x4400, 0x1122, 0x4400,  0x4422, 0x0088, 0x4422, 0x0088,
    0xf0f0, 0xf0f0, 0x0f0f, 0x0f0f,  0x9966, 0x6699, 0x9966, 0x6699,  0x0008, 0x1c3e, 0x7f3e, 0x1c08,  0x0008, 0x142a, 0x552a, 0x1408,
    0xb130, 0x031b, 0xd8c0, 0x0c8d,  0x8010, 0x0220, 0x0108, 0x4004,  0x0814, 0x2241, 0x8001, 0x0204,  0x80c0, 0x2112, 0x0c04, 0x0201,
    0xff80, 0x8080, 0xff08, 0x0808,  0x007f, 0x7f7f, 0x00f7, 0xf7f7,  0x8040, 0x2000, 0x0204, 0x0800,  0x8244, 0x3944, 0x8201, 0x0101,
    0xf078, 0x2442, 0x870f, 0x1221,  0x1020, 0x54aa, 0xff02, 0x0408,  0xf874, 0x2247, 0x8f17, 0x2271,  0xbfa0, 0xbfbd, 0xbdfd, 0x05fd,
    0x2050, 0x8888, 0x8888, 0x0502,  0x55a0, 0x4040, 0x550a, 0x0404,  0x8844, 0x2211, 0x1122, 0x4488,  0x8142, 0x2418, 0x8142, 0x2418,
    0xaa00, 0x8000, 0x8800, 0x8000,  0x0384, 0x4830, 0x0c02, 0x0101,  0x8080, 0x413e, 0x0808, 0x14e3,  0xaf5f, 0xaf5f, 0x0d0b, 0x0d0b,
    0x7789, 0x8f8f, 0x7798, 0xf8f8,  0x8814, 0x2241, 0x8800, 0xaa00,  0x40a0, 0x0000, 0x040a, 0x0000,  0xbf00, 0xbfbf, 0xb0b0, 0xb0b0
  };
  m_rsrcPatternMap.insert(std::map<long, Pattern>::value_type(7000,Pattern(32, values7000)));
  static uint16_t const values14001[] = {
    0x8844, 0x2211, 0x8844, 0x2211,  0x77bb, 0xddee, 0x77bb, 0xddee,  0x1122, 0x4488, 0x1122, 0x4488,  0xeedd, 0xbb77, 0xeedd, 0xbb77,
    0x8040, 0x2010, 0x0804, 0x0201,  0x7fbf, 0xdfef, 0xf7fb, 0xfdfe,  0x0102, 0x0408, 0x1020, 0x4080,  0xfefd, 0xfbf7, 0xefdf, 0xbf7f,
    0xe070, 0x381c, 0x0e07, 0x83c1,  0x99cc, 0x6633, 0x99cc, 0x6633,  0x8307, 0x0e1c, 0x3870, 0xe0c1,  0x3366, 0xcc99, 0x3366, 0xcc99,
    0x8142, 0x2418, 0x1824, 0x4281,  0x7ebd, 0xdbe7, 0xe7db, 0xbd7e,  0x8244, 0x2810, 0x2844, 0x8201,  0x7dbb, 0xd7ef, 0xd7bb, 0x7dfe,
    0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa,  0x00ff, 0x00ff, 0x00ff, 0x00ff,  0x8888, 0x8888, 0x8888, 0x8888,  0x7777, 0x7777, 0x7777, 0x7777,
    0xff00, 0x0000, 0xff00, 0x0000,  0x00ff, 0xffff, 0x00ff, 0xffff,  0x8080, 0x8080, 0x8080, 0x8080,  0x7f7f, 0x7f7f, 0x7f7f, 0x7f7f,
    0xff00, 0x0000, 0x0000, 0x0000,  0x00ff, 0xffff, 0xffff, 0xffff,  0xcccc, 0xcccc, 0xcccc, 0xcccc,  0xffff, 0x0000, 0xffff, 0x0000,
    0xff88, 0x8888, 0xff88, 0x8888,  0x0077, 0x7777, 0x0077, 0x7777,  0xff80, 0x8080, 0x8080, 0x8080,  0x007f, 0x7f7f, 0x7f7f, 0x7f7f
  };
  m_rsrcPatternMap.insert(std::map<long, Pattern>::value_type(14001,Pattern(32, values14001)));
}

float State::getPatternPercent(int id, long rsid)
{
  if (m_rsrcPatternMap.empty())
    initPattern(m_version);
  if (m_rsrcPatternMap.find(rsid)==m_rsrcPatternMap.end()) {
    MWAW_DEBUG_MSG(("MsWksGraphInternal::State::getPatternPercent unknown map for rsdid=%ld\n",rsid));
    return 1.0;
  }
  return m_rsrcPatternMap.find(rsid)->second.getPercent(id);
}

bool State::getPattern(MWAWGraphicStyle::Pattern &pat, int id, long rsid)
{
  if (m_rsrcPatternMap.empty())
    initPattern(m_version);
  if (m_rsrcPatternMap.find(rsid)==m_rsrcPatternMap.end()) {
    MWAW_DEBUG_MSG(("MsWksGraphInternal::State::getPattern unknown map for rsdid=%ld\n",rsid));
    return false;
  }
  return m_rsrcPatternMap.find(rsid)->second.get(id, pat);
}

////////////////////////////////////////
//! Internal: the subdocument of a MsWksGraph
class SubDocument final : public MWAWSubDocument
{
public:
  enum Type { RBILZone, Chart, Empty, Group, Table, TextBox, TextBoxv4 };
  SubDocument(MsWksGraph &pars, MWAWInputStreamPtr const &input, Type type, int zoneId)
    : MWAWSubDocument(pars.m_mainParser, input, MWAWEntry())
    , m_graphParser(&pars)
    , m_type(type)
    , m_id(zoneId)
    , m_frame("")
  {
  }
  SubDocument(MsWksGraph &pars, MWAWInputStreamPtr const &input, Type type,
              MWAWEntry const &entry, std::string const &frame=std::string(""))
    : MWAWSubDocument(pars.m_mainParser, input, entry)
    , m_graphParser(&pars)
    , m_type(type)
    , m_id(-1)
    , m_frame(frame)
  {
  }

  //! destructor
  ~SubDocument() final {}

  //! operator!=
  bool operator!=(MWAWSubDocument const &doc) const final;

  //! the parser function
  void parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType type) final;
private:
  SubDocument(SubDocument const &orig) = delete;
  SubDocument &operator=(SubDocument const &orig) = delete;

protected:
  /** the graph parser */
  MsWksGraph *m_graphParser;
  /** the type */
  Type m_type;
  /** the subdocument id*/
  int m_id;
  /** the frame name: for textv4 */
  std::string m_frame;
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType /*type*/)
{
  if (!listener.get()) {
    MWAW_DEBUG_MSG(("MsWksGraphInternal::SubDocument::parse: no listener\n"));
    return;
  }
  if (!m_graphParser) {
    MWAW_DEBUG_MSG(("MsWksGraphInternal::SubDocument::parse: no parser\n"));
    return;
  }

  long pos = m_input->tell();
  switch (m_type) {
  case Empty:
    break;
  case Chart:
    m_graphParser->sendChart(m_id);
    break;
  case Group: {
    MWAWPosition gPos;
    gPos.setRelativePosition(MWAWPosition::Frame,
                             MWAWPosition::XLeft, MWAWPosition::YTop);
    m_graphParser->sendGroupChild(m_id, gPos);
    break;
  }
  case Table:
    m_graphParser->sendTable(m_id);
    break;
  case TextBox:
    m_graphParser->sendTextBox(m_id, listener);
    break;
  case TextBoxv4:
    m_graphParser->sendFrameText(m_zone, m_frame);
    break;
  case RBILZone: {
    MsWksGraph::SendData sendData;
    sendData.m_type = MsWksGraph::SendData::RBIL;
    sendData.m_id = m_id;
    sendData.m_anchor =  MWAWPosition::Frame;
    m_graphParser->sendObjects(sendData);
    break;
  }
#if !defined(__clang__)
  default:
    MWAW_DEBUG_MSG(("MsWksGraph::SubDocument::parse: unexpected zone type\n"));
    break;
#endif
  }
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}

bool SubDocument::operator!=(MWAWSubDocument const &doc) const
{
  if (MWAWSubDocument::operator!=(doc)) return true;
  auto const *sDoc = dynamic_cast<SubDocument const *>(&doc);
  if (!sDoc) return true;
  if (m_graphParser != sDoc->m_graphParser) return true;
  if (m_id != sDoc->m_id) return true;
  if (m_type != sDoc->m_type) return true;
  if (m_frame != sDoc->m_frame) return true;
  return false;
}

}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
MsWksGraph::MsWksGraph(MsWksDocument &document)
  : m_parserState()
  , m_state(new MsWksGraphInternal::State)
  , m_mainParser(&document.getMainParser())
  , m_document(document)
  , m_tableParser()
{
  m_parserState=m_mainParser->getParserState();
  m_tableParser.reset(new MsWksTable(*m_mainParser, m_document, *this));
}

MsWksGraph::~MsWksGraph()
{
}

void MsWksGraph::setPageLeftTop(MWAWVec2f const &leftTop)
{
  m_state->m_leftTopPos=leftTop;
}

int MsWksGraph::version() const
{
  if (m_state->m_version < 0)
    m_state->m_version = m_parserState->m_version;
  return m_state->m_version;
}

int MsWksGraph::numPages(int zoneId) const
{
  if (m_state->m_numPages > 0)
    return m_state->m_numPages;

  int maxPage = 0;
  for (auto zone : m_state->m_zonesList) {
    if (zoneId >= 0 && zone->m_zoneId!=zoneId) continue;
    if (zone->m_page > maxPage)
      maxPage = zone->m_page;
  }
  m_state->m_numPages = maxPage+1;
  return m_state->m_numPages;
}

void MsWksGraph::sendFrameText(MWAWEntry const &entry, std::string const &frame)
{
  m_document.sendTextbox(entry, frame);
}

void MsWksGraph::sendChart(int zoneId)
{
  m_tableParser->sendChart(zoneId);
}

void MsWksGraph::sendTable(int zoneId)
{
  m_tableParser->sendTable(zoneId);
}

////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool MsWksGraph::getZoneGraphicStyle(int id, MWAWGraphicStyle &style) const
{
  if (id<0 || id>=int(m_state->m_zonesList.size()) || !m_state->m_zonesList[size_t(id)]) {
    MWAW_DEBUG_MSG(("MsWksGraph::getZoneGraphicStyle: unknown zone %d\n", id));
    return false;
  }
  style = m_state->m_zonesList[size_t(id)]->m_style;
  return true;
}

bool MsWksGraph::getZonePosition(int id, MWAWPosition::AnchorTo anchor, MWAWPosition &pos) const
{
  if (id<0 || id>=int(m_state->m_zonesList.size()) || !m_state->m_zonesList[size_t(id)]) {
    MWAW_DEBUG_MSG(("MsWksGraph::getZoneGraphicStyle: unknown zone %d\n", id));
    return false;
  }
  pos = m_state->m_zonesList[size_t(id)]->getPosition(anchor);
  return true;
}

bool MsWksGraph::readPictHeader(MsWksGraphInternal::Zone &pict)
{
  MWAWInputStreamPtr input=m_document.getInput();
  long pos=input->tell();
  int vers = version();
  if (!input->checkPosition(pos+(vers>=3 ? 54 : 38)) || input->readULong(1) != 0) return false;
  pict = MsWksGraphInternal::Zone();
  pict.m_subType = static_cast<int>(input->readULong(1));
  if (pict.m_subType > 0x10 || pict.m_subType == 6 || pict.m_subType == 0xb)
    return false;
  if (pict.m_subType>9) {
    if (vers<=2)
      return false;
    // we can find chart in v3 spreadsheet file
    else if (vers==3 && pict.m_subType != 0xa && m_parserState->m_type!=MWAWParserState::Spreadsheet)
      return false;
  }
  libmwaw::DebugStream f;
  int val;
  if (vers >= 3) {
    val = static_cast<int>(input->readLong(2));
    if (vers == 4 || m_parserState->m_type==MWAWParserState::Graphic)
      pict.m_page = val==0 ? -2 : val-1;
    else if (val)
      f << "f0=" << val << ",";
  }
  // color
  Style &style=pict.m_style;
  for (int i = 0; i < 2; i++) {
    auto rId = static_cast<int>(input->readLong(2));
    int cId = (vers <= 2) ? rId+1 : rId;
    MWAWColor col;
    if (m_document.getColor(cId,col,vers <= 3 ? vers : 3)) {
      if (i) style.m_baseSurfaceColor = col;
      else style.m_baseLineColor = col;
    }
    else
      f << "#col" << i << "=" << rId << ",";
  }
  bool hasSurfPatFunction=false;
  if (vers <= 2) {
    for (int i = 0; i < 2; i++) {
      auto pId = static_cast<int>(input->readLong(2));
      if (pId==38) { // empty
        if (i==0)
          style.m_lineWidth=0;
        continue;
      }
      float percent = m_state->getPatternPercent(pId);
      MWAWGraphicStyle::Pattern pattern;
      if (i==0)
        style.m_lineColor=MWAWColor::barycenter(percent, style.m_baseLineColor, 1.f-percent, style.m_baseSurfaceColor);
      else if (m_state->getPattern(pattern, pId)) {
        pattern.m_colors[0] = style.m_baseSurfaceColor;
        pattern.m_colors[1] = style.m_baseLineColor;
        style.setPattern(pattern);
      }
      else
        style.setSurfaceColor(MWAWColor::barycenter(percent, style.m_baseLineColor, 1.f-percent, style.m_baseSurfaceColor));
    }
    auto lineType=static_cast<int>(input->readLong(2));
    if (style.m_lineWidth>0) {
      switch (lineType) {
      case 0:
        style.m_lineWidth=0.;
        break;
      case 1:
        style.m_lineWidth=0.5;
        break;
      case 2: // lineW=1
        break;
      case 3:
        style.m_lineWidth=2;
        break;
      case 4:
        style.m_lineWidth=4;
        break;
      default:
        f << "#lineType=" << lineType << ",";
        break;
      }
    }
  }
  else {
    style.m_lineColor=style.m_baseLineColor;
    style.m_surfaceColor=style.m_baseSurfaceColor;
    for (int i = 0; i < 2; i++) {
      if (i) f << "surface";
      else f << "line";
      f << "Pattern=[";
      long rsid= input->readLong(2);
      if (rsid==0) f << "noColor,";
      else if (rsid==-1) f << "grad,";
      else f << "rsid=" << rsid << ",";
      auto patId = static_cast<int>(input->readULong(2));
      if (patId) f << "pat=" << patId << ",";
      else f << "_";
      if (vers==4 && rsid==-1 && patId==0xFFFF)
        hasSurfPatFunction=true;
      val = static_cast<int>(input->readLong(1));
      if (val) f << "unkn=" << val << ",";
      auto per = static_cast<int>(input->readULong(1));
      f << per << "%,";
      if (rsid<=0) {
        if (i==0 && rsid==0)
          style.m_lineWidth=0.;
      }
      else {
        float percent=1.0;
        bool done=false;
        MWAWGraphicStyle::Pattern pattern;
        if (per >= 0 && per < 100)
          percent = float(per)/100.f;
        else if (m_state->getPattern(pattern, patId, rsid)) {
          percent = m_state->getPatternPercent(patId, rsid);
          if (i) {
            pattern.m_colors[0] = style.m_baseSurfaceColor;
            pattern.m_colors[1] = style.m_baseLineColor;
            style.setPattern(pattern);
            done = true;
          }
        }
        else {
          MWAW_DEBUG_MSG(("MsWksGraph::readPictHeader:find odd pattern\n"));
          f << "##";
        }
        if (done) {
        }
        else if (i==0)
          style.m_lineColor=MWAWColor::barycenter(percent, style.m_baseLineColor, 1.f-percent, style.m_baseSurfaceColor);
        else
          style.setSurfaceColor(MWAWColor::barycenter(percent, style.m_baseLineColor, 1.f-percent, style.m_baseSurfaceColor));
      }
      f << "],";
    }
    int penSize[2];
    for (auto &pSize : penSize) pSize = static_cast<int>(input->readLong(2));
    if (style.m_lineWidth<=0)
      f << "pen=" << penSize[0] << "x" << penSize[1] << ",";
    else if (penSize[0]==penSize[1])
      style.m_lineWidth=float(penSize[0]);
    else {
      f << "pen=" << penSize[0] << "x" << penSize[1] << ",";
      style.m_lineWidth=0.5f*float(penSize[0]+penSize[1]);
    }
    if (style.m_lineWidth < 0 || style.m_lineWidth > 10) {
      f << "##penSize=" << style.m_lineWidth << ",";
      style.m_lineWidth = 1;
    }
    val = static_cast<int>(input->readLong(2));
    if (val)
      f << "f1=" << val << ",";
  }

  float offset[4];
  for (auto &off : offset) off = float(input->readLong(2));
  pict.m_decal = MWAWBox2f(MWAWVec2f(offset[0],offset[1]), MWAWVec2f(offset[3],offset[2]));
  pict.m_finalDecal = MWAWVec2f(float(offset[0]+offset[3]), float(offset[1]+offset[2]));

  // the two point which allows to create the form ( in general the bdbox)
  float dim[4];
  for (auto &d : dim) d = float(input->readLong(4))/65536.f;
  pict.m_box=MWAWBox2f(MWAWVec2f(dim[0],dim[1]),MWAWVec2f(dim[2],dim[3]));

  auto flags = static_cast<int>(input->readLong(1));
  // 2: rotations, 1:lock ?, 0: nothing, other ?
  if (vers >= 4 && (flags&1)) {
    f << "locked,";
    flags &= 0xFE;
  }
  if (vers >= 4 && (flags&2)) {
    f << "Rot=[";
    for (int i = 0; i < 32; i++)
      f << input->readLong(2) << ",";
    f << "],";
    flags &= 0xFC;
  }
  if (flags) f << "fl0=" << flags << ",";
  auto lineFlags = static_cast<int>(input->readULong(1));
  switch (lineFlags&3) {
  case 2:
    style.m_arrows[0]=MWAWGraphicStyle::Arrow::plain();
    MWAW_FALLTHROUGH;
  case 1:
    style.m_arrows[1]=MWAWGraphicStyle::Arrow::plain();
    break;
  default:
    f << "#arrow=3,";
  case 0:
    break;
  }
  if (lineFlags&0xFC) f << "#lineFlags=" << std::hex << (lineFlags&0xFC) << std::dec << ",";
  if (vers >= 3) pict.m_ids[0] = long(input->readULong(4));
  if (vers >= 4 && hasSurfPatFunction) {
    pos = input->tell();
    if (!readGradient(style)) {
      f << "##gradient,";
      input->seek(pos, librevenge::RVNG_SEEK_SET);
    }
  }
  pict.m_extra = f.str();
  pict.m_dataPos = input->tell();
  return true;
}

bool MsWksGraph::readGradient(MsWksGraph::Style &style)
{
  MWAWInputStreamPtr input=m_document.getInput();
  long pos = input->tell();

  if (!input->checkPosition(pos+22))
    return false;

  libmwaw::DebugStream f;
  f << "gradient[unknown]=[";
  auto type=static_cast<int>(input->readLong(2));
  auto val=static_cast<int>(input->readLong(2)); // always 0?
  if (val) f << "f0=" << val << ",";
  val=static_cast<int>(input->readLong(1)); // always 8?
  if (val!=8) f << "f1=" << val << ",";
  val=static_cast<int>(input->readLong(2)); // find 1 in square
  if (val) f << "f2=" << val << ",";
  val=static_cast<int>(input->readULong(2)); // always 0 ?
  if (val) f << "f3=" << std::hex << val << std::dec << ",";
  auto angle =static_cast<int>(input->readLong(2));
  val=static_cast<int>(input->readLong(2)); // 89[square]|156[square:linearbi]|255
  if (val!=0xff) f << "f4=" << val << ",";
  val=static_cast<int>(input->readLong(2)); // 54[square]|0
  if (val) f << "f5=" << val << ",";
  val=static_cast<int>(input->readLong(2)); // 18
  if (val!=0x18) f << "f6=" << val << ",";
  val=static_cast<int>(input->readULong(2));
  int subType = (val&0xf);
  val = (val>>4);
  if (val!=0xFF)
    f << "sType[high]=" << std::hex << val << std::dec << ",";
  val=static_cast<int>(input->readLong(2)); // 0
  if (val) f << "f7=" << val << ",";
  val=static_cast<int>(input->readLong(1)); // 0
  if (val) f << "f8=" << val << ",";
  f << "],";
  switch (type) {
  case 1:
    style.m_gradientStopList.resize(2);
    style.m_gradientStopList[0]=MWAWGraphicStyle::GradientStop(0.0, style.m_baseSurfaceColor);
    style.m_gradientStopList[1]=MWAWGraphicStyle::GradientStop(1.0, style.m_baseLineColor);
    style.m_gradientAngle = float(90+angle);
    style.m_gradientType = MWAWGraphicStyle::G_Linear;
    angle=type=0;
    break;
  case 2:
    style.m_gradientStopList.resize(2);
    style.m_gradientStopList[0]=MWAWGraphicStyle::GradientStop(0.0, style.m_baseSurfaceColor);
    style.m_gradientStopList[1]=MWAWGraphicStyle::GradientStop(1.0, style.m_baseLineColor);
    style.m_gradientAngle = float(90+angle);
    style.m_gradientType = MWAWGraphicStyle::G_Axial;
    angle=type=0;
    break;
  case 3:
    style.m_gradientStopList.resize(2);
    style.m_gradientStopList[0]=MWAWGraphicStyle::GradientStop(0.0, style.m_baseSurfaceColor);
    style.m_gradientStopList[1]=MWAWGraphicStyle::GradientStop(1.0, style.m_baseLineColor);
    switch (subType) {
    case 9:
      style.m_gradientPercentCenter=MWAWVec2f(0.25f,0.25f);
      break;
    case 10:
      style.m_gradientPercentCenter=MWAWVec2f(0.25f,0.75f);
      break;
    case 11:
      style.m_gradientPercentCenter=MWAWVec2f(0.75f,0.75f);
      break;
    case 12:
      style.m_gradientPercentCenter=MWAWVec2f(1.f,1.f);
      break;
    case 13:
      style.m_gradientPercentCenter=MWAWVec2f(0.f,0.f);
      break;
    default:
      f << "#subType=" << subType << ",";
    case 8: // centered
      break;
    }
    style.m_gradientType = MWAWGraphicStyle::G_Rectangular;
    angle=type=0;
    break;
  case 7:
    style.m_gradientStopList.resize(2);
    style.m_gradientStopList[0]=MWAWGraphicStyle::GradientStop(0.0, style.m_baseSurfaceColor);
    style.m_gradientStopList[1]=MWAWGraphicStyle::GradientStop(1.0, style.m_baseLineColor);
    style.m_gradientType = MWAWGraphicStyle::G_Radial;
    type = 0;
    break;
  default:
    break;
  }
  if (type) f << "#type=" << type << ",";
  if (angle) f << "#angle=" << angle << ",";
  f << "subType=" << subType << ",";
  f << "],";
  style.m_extra = f.str();
  return true;
}

int MsWksGraph::getEntryPicture(int zoneId, MWAWEntry &zone, bool autoSend, int order)
{
  MsWksGraphInternal::Zone pict;
  MWAWInputStreamPtr input=m_document.getInput();
  long pos = input->tell();

  if (!readPictHeader(pict))
    return -1;
  pict.m_zoneId = zoneId;
  pict.m_pos.setBegin(pos);
  libmwaw::DebugFile &ascFile = m_document.ascii();
  libmwaw::DebugStream f;
  int vers = version();
  long debData = input->tell();
  long dataSize = 0;
  int versSize = 0;
  switch (pict.m_subType) {
  case 0:
  case 1:
  case 2:
  case 3:
    dataSize = 1;
    break;
  case 4: // arc
    dataSize = 0xd;
    break;
  case 5: { // poly
    input->seek(3, librevenge::RVNG_SEEK_CUR);
    auto N = static_cast<int>(input->readULong(2));
    dataSize = 9+N*8;
    break;
  }
  case 7: { // picture
    if (vers >= 3) versSize = 0x14;
    dataSize = 5;
    input->seek(debData+5+versSize-2, librevenge::RVNG_SEEK_SET);
    dataSize += static_cast<int>(input->readULong(2));
    break;
  }
  case 8: // group
    if (vers >= 3) versSize = 4;
    dataSize = 0x1b;
    break;
  case 9: // textbox v<=3
    dataSize = 0x21;
    if (vers >= 3) dataSize += 0x10;
    break;
  case 0xa: // chart v4
    dataSize = 50;
    break;
  case 0xc: // equation v4
    dataSize = 0x11;
    break;
  case 0xd: { // bitmap v4
    input->seek(debData+0x29, librevenge::RVNG_SEEK_SET);
    auto sz = long(input->readULong(4));
    dataSize = 0x29+4+sz;
    break;
  }
  case 0xe: { // spreadsheet v4
    input->seek(debData+0xa7, librevenge::RVNG_SEEK_SET);
    auto pSize = static_cast<int>(input->readULong(2));
    if (pSize == 0) return -1;
    dataSize = 0xa9+pSize;
    if (!input->checkPosition(debData+dataSize))
      return -1;

    input->seek(debData+dataSize, librevenge::RVNG_SEEK_SET);
    for (int i = 0; i < 2; i++) {
      auto sz = long(input->readULong(4));
      if (sz<0 || (sz>>28)) return -1;
      dataSize += 4 + sz;
      input->seek(sz, librevenge::RVNG_SEEK_CUR);
    }
    break;
  }
  case 0xf: { // textbox v4
    input->seek(debData+0x39, librevenge::RVNG_SEEK_SET);
    dataSize = 0x3b+ long(input->readULong(2));
    break;
  }
  case 0x10: { // table v4
    input->seek(debData+0x57, librevenge::RVNG_SEEK_SET);
    dataSize = 0x59+ long(input->readULong(2));
    input->seek(debData+dataSize, librevenge::RVNG_SEEK_SET);

    for (int i = 0; i < 3; i++) {
      auto sz = long(input->readULong(4));
      if (sz<0 || ((sz>>28))) return -1;
      dataSize += 4 + sz;
      input->seek(sz, librevenge::RVNG_SEEK_CUR);
    }

    break;
  }
  default:
    MWAW_DEBUG_MSG(("MsWksGraph::getEntryPicture: type %d is not umplemented\n", pict.m_subType));
    return -1;
  }

  pict.m_pos.setEnd(debData+dataSize+versSize);
  if (!input->checkPosition(pict.m_pos.end()))
    return -1;

  input->seek(debData, librevenge::RVNG_SEEK_SET);
  if (versSize) {
    switch (pict.m_subType) {
    case 7: {
      auto ptr = long(input->readULong(4));
      f << std::hex << "ptr2=" << ptr << std::dec << ",";
      f << "depth?=" << input->readLong(1) << ",";
      float dim[4];
      for (auto &d : dim) d = float(input->readLong(4))/65536.f;
      MWAWBox2f box(MWAWVec2f(dim[1], dim[0]), MWAWVec2f(dim[3], dim[2]));
      f << "bdbox2=" << box << ",";
      break;
    }
    default:
      break;
    }
  }
  auto val = static_cast<int>(input->readLong(1)); // 0 and sometimes -1
  if (val) f << "g0=" << val << ",";
  pict.m_dataPos++;

  if (pict.m_subType > 0xd) {
    f << ", " << std::hex << input->readULong(4) << std::dec << ", BdMWAWBox2=(";
    for (int i = 0; i < 4; i++)
      f << float(input->readLong(4))/65536.f << ", ";
    f << ")";
  }

  std::shared_ptr<MsWksGraphInternal::Zone> res;
  switch (pict.m_subType) {
  case 0: { // line
    auto form = std::make_shared<MsWksGraphInternal::BasicShape>(pict);
    res = form;
    form->m_shape = MWAWGraphicShape::line(pict.m_box.min(), pict.m_box.max());
    break;
  }
  case 1: // rect
  case 2: // rectoval
  case 3: { // circle
    MWAWBox2f bdbox = pict.m_box;
    auto form = std::make_shared<MsWksGraphInternal::BasicShape>(pict);
    res = form;
    form->m_shape.m_bdBox = form->m_shape.m_formBox = bdbox;
    form->m_shape.m_type = (pict.m_subType==3) ? MWAWGraphicShape::Circle :
                           MWAWGraphicShape::Rectangle;
    if (pict.m_subType==2) {
      float sz=10;
      if (bdbox.size().x() > 0 && bdbox.size().x() < 2*sz)
        sz = bdbox.size().x()/2.f;
      if (bdbox.size().y() > 0 && bdbox.size().y() < 2*sz)
        sz = bdbox.size().y()/2.f;
      form->m_shape.m_cornerWidth=MWAWVec2f(sz,sz);
    }
    break;
  }
  case 4: {
    auto form = std::make_shared<MsWksGraphInternal::BasicShape>(pict);
    res = form;
    auto angle = float(input->readLong(2));
    auto deltaAngle = float(input->readLong(2));
    float angl2 = angle+((deltaAngle>0) ? deltaAngle : -deltaAngle);
    float dim[4]; // real Bdbox
    for (auto &d : dim) d = float(input->readLong(2));
    MWAWBox2f realBox(MWAWVec2f(dim[1],dim[0]), MWAWVec2f(dim[3],dim[2]));
    form->m_shape=MWAWGraphicShape::arc(realBox,pict.m_box,MWAWVec2f(450.f-angl2,450.f-angle));
    form->m_box = realBox;
    break;
  }
  case 5: {
    auto form = std::make_shared<MsWksGraphInternal::BasicShape>(pict);
    res = form;
    val = static_cast<int>(input->readULong(2));
    bool smooth=false;
    if (val==1)
      smooth=true;
    else if (val) f << "#smooth=" << val << ",";
    auto numPt = static_cast<int>(input->readLong(2));
    auto ptr = long(input->readULong(4));
    if (!input->checkPosition(input->tell()+8*numPt))
      return -1;
    f << std::hex << "ptr2=" << ptr << std::dec << ",";
    std::vector<MWAWVec2f> vertices;
    for (int i = 0; i < numPt; i++) {
      float x = float(input->readLong(4))/65336.f;
      float y = float(input->readLong(4))/65336.f;
      vertices.push_back(MWAWVec2f(x,y));
    }
    if (!smooth || numPt <= 2) {
      form->m_shape=MWAWGraphicShape::polygon(pict.m_box);
      form->m_shape.m_vertices = vertices;
      break;
    }
    form->m_shape=MWAWGraphicShape::path(pict.m_box);
    form->m_shape.m_path.push_back(MWAWGraphicShape::PathData('M', vertices[0]));

    MWAWVec2f middle=0.5f*(vertices[1]+vertices[0]);
    form->m_shape.m_path.push_back(MWAWGraphicShape::PathData('L', middle));
    for (size_t pt=1; pt+1 < size_t(numPt); ++pt) {
      middle=0.5f*(vertices[pt+1]+vertices[pt]);
      form->m_shape.m_path.push_back(MWAWGraphicShape::PathData('Q', middle, vertices[pt]));
    }
    form->m_shape.m_path.push_back(MWAWGraphicShape::PathData('L',vertices[size_t(numPt-1)]));
    if (vertices[0]==vertices[size_t(numPt)-1])
      form->m_shape.m_path.push_back(MWAWGraphicShape::PathData('Z'));
    break;
  }
  case 7: {
    val = static_cast<int>(input->readULong(vers >= 3 ? 1 : 2));
    if (val) f << "g1=" << val << ",";
    // skip size (already read)
    pict.m_dataPos = input->tell()+2;
    auto pct = std::make_shared<MsWksGraphInternal::DataPict>(pict);
    res = pct;
    ascFile.skipZone(pct->m_dataPos, pct->m_pos.end()-1);
    break;
  }
  case 8:
    res = readGroup(pict);
    if (!res)
      return -1;
    break;
  case 9: { // textbox normal
    auto justify = MWAWParagraph::JustificationLeft;
    val = static_cast<int>(input->readLong(2));
    switch (val) {
    case 0:
      break;
    case 1:
      justify = MWAWParagraph::JustificationCenter;
      break;
    case 2:
      justify = MWAWParagraph::JustificationFull;
      break;
    case -1:
      justify = MWAWParagraph::JustificationRight;
      break;
    default:
      f << "##align=" << val << ",";
      break;
    }
    if (vers >= 3) {
      f << "h=" << input->readLong(4) << ",";
      for (int i = 0; i < 6; i++) {
        val = static_cast<int>(input->readLong(2));
        if (val) f << "g" << i+2 << "=" << val << ",";
      }
      pict.m_dataPos += 0x10;
    }
    f << "Fl=[";
    for (int i = 0; i < 4; i++) {
      val = static_cast<int>(input->readLong(2));
      if (val) f << std::hex << val << std::dec << ",";
      else f << ",";
    }
    f << "],";
    auto numPos = static_cast<int>(input->readLong(2));
    if (numPos < 0) return -1;
    f << "numFonts=" << input->readLong(2);

    long off[4];
    for (auto &d : off) d = long(input->readULong(4));
    f << ", Ptrs=[" <<  std::hex << std::setw(8) << off[2] << ", " << std::setw(8) << off[0]
      << ", " << std::dec << long(off[1]-off[0])
      << ", "	<< std::dec << long(off[3]-off[0]) << "]";

    auto text = std::make_shared<MsWksGraphInternal::TextBox>(pict);
    text->m_justify = justify;
    text->m_numPositions = numPos;
    res = text;
    if (!readText(*text)) return -1;
    res->m_pos.setEnd(input->tell());
    break;
  }
  case 0xa: { // chart
    auto chart  = std::make_shared<MsWksGraphInternal::Chart>(pict);
    int chartId = m_state->m_chartId++;
    if (!m_tableParser->readChart(chartId, chart->m_style)) {
      return -1;
    }
    m_tableParser->setChartZoneId(chartId, int(m_state->m_zonesList.size()));
    chart->m_chartId = chartId;
    res = chart;
    res->m_pos.setEnd(input->tell());
    break;
  }
  case 0xc: { // equation
    auto ole = std::make_shared<MsWksGraphInternal::OLEZone>(pict);
    res = ole;
    int dim[2];
    for (auto &d : dim) d = static_cast<int>(input->readLong(4));
    ole->m_dim = MWAWVec2i(dim[0], dim[1]);
    val = static_cast<int>(input->readULong(2)); // always 0x4f4d ?
    f << "g0=" << std::hex << val << std::dec << ",";
    ole->m_oleId=static_cast<int>(input->readULong(4));
    val = static_cast<int>(input->readLong(2)); // always 0?
    if (val) f << "g1=" << val << ",";
    break;
  }
  case 0xd: { // bitmap
    libmwaw::DebugStream f2;
    f2 << "Graphd(II): fl(";

    long actPos = input->tell();
    for (int i = 0; i < 2; i++)
      f2 << input->readLong(2) << ", ";
    f2 << "), ";
    auto nCol = static_cast<int>(input->readLong(2));
    auto nRow = static_cast<int>(input->readLong(2));
    if (nRow <= 0 || nCol <= 0) return -1;

    f2 << "nRow=" << nRow << ", " << "nCol=" << nCol << ", ";

    f2 << std::hex << input->readULong(4) << std::dec << ", ";

    for (int i = 0; i < 3; i++) {
      f2 << "bdbox" << i << "=(";
      for (int d= 0; d < 4; d++) f2 << input->readLong(2) << ", ";
      f2 << "), ";
      if (i == 1) f2 << "unk=" << input->readLong(2) << ", ";
    }
    auto sizeLine = static_cast<int>(input->readLong(2));
    f2 << "lineSize(?)=" << sizeLine << ", ";
    long bitmapSize = input->readLong(4);
    f2 << "bitmapSize=" << std::hex << bitmapSize << ", ";

    if (bitmapSize <= 0 || (bitmapSize%nRow) != 0) {
      // sometimes, another row is added: only for big picture?
      if (bitmapSize>0 && (bitmapSize%(nRow+1)) == 0) nRow++;
      else if (bitmapSize < nCol*nRow || bitmapSize > 2*nCol*nRow)
        return -1;
      else { // maybe a not implemented case
        MWAW_DEBUG_MSG(("MsWksGraph::getEntryPicture: bitmap size is a little odd\n"));
        f2 << "###";
        ascFile.addPos(actPos);
        ascFile.addNote(f2.str().c_str());
        ascFile.addDelimiter(input->tell(),'|');
        break;
      }
    }

    auto szCol = int(bitmapSize/nRow);
    if (szCol < nCol) return -1;

    ascFile.addPos(actPos);
    ascFile.addNote(f2.str().c_str());

    pict.m_dataPos = input->tell();
    auto pct = std::make_shared<MsWksGraphInternal::DataBitmap>(pict);
    pct->m_numRows = nRow;
    pct->m_numCols = nCol;
    pct->m_dataSize = bitmapSize;
    res = pct;
    break;
  }
  case 0xe: {
    long actPos = input->tell();
    ascFile.addPos(actPos);
    ascFile.addNote("Graphe(I)");

    // first: the picture ( fixme: kept while we do not parse the spreadsheet )
    input->seek(144, librevenge::RVNG_SEEK_CUR);
    actPos = input->tell();
    ascFile.addPos(actPos);
    ascFile.addNote("Graphe(pict)");
    auto dSize = long(input->readLong(4));
    if (dSize < 0) return -1;
    pict.m_dataPos = actPos+4;

    auto pct = std::make_shared<MsWksGraphInternal::DataPict>(pict);
    pct->m_dataEndPos = actPos+4+dSize;
    res = pct;
    ascFile.skipZone(pct->m_dataPos, pct->m_dataEndPos-1);
    input->seek(actPos+4+dSize, librevenge::RVNG_SEEK_SET);

    // now the spreadsheet ( a classic WKS file )
    actPos = input->tell();
    dSize = long(input->readULong(4));
    if (dSize < 0) return -1;
    ascFile.addPos(actPos);
    ascFile.addNote("Graphe(sheet)");
    ascFile.skipZone(actPos+4, actPos+3+dSize);
#ifdef DEBUG_WITH_FILES
    if (dSize > 0) {
      librevenge::RVNGBinaryData file;
      input->seek(actPos+4, librevenge::RVNG_SEEK_SET);
      input->readDataBlock(dSize, file);
      static int volatile sheetName = 0;
      libmwaw::DebugStream f2;
      f2 << "Sheet-" << ++sheetName << ".wks";
      libmwaw::Debug::dumpFile(file, f2.str().c_str());
    }
#endif
    input->seek(actPos+4+dSize, librevenge::RVNG_SEEK_SET);

    actPos = input->tell();
    ascFile.addPos(actPos);
    ascFile.addNote("Graphe(colWidth?)"); // blocksize, unknown+list of 100 w
    break;
  }
  case 0xf: { // new text box v4 (a picture is stored)
    if (vers < 4) return -1;
    auto textbox = std::make_shared<MsWksGraphInternal::TextBoxv4>(pict);
    res = textbox;
    textbox->m_ids[1] = long(input->readULong(4));
    textbox->m_ids[2] = long(input->readULong(4));
    f << "," << std::hex << input->readULong(4)<< std::dec << ",";
    // always 0 ?
    for (int i = 0; i < 6; i++) {
      val = static_cast<int>(input->readLong(2));
      if (val) f << "f" << i << "=" << val << ",";
    }
    textbox->m_text.setBegin(input->readLong(4));
    textbox->m_text.setEnd(input->readLong(4));

    // always 0 ?
    val = static_cast<int>(input->readLong(2));
    if (val) f << "f10=" << val << ",";
    auto sz = long(input->readULong(4));
    if (sz+0x3b != dataSize)
      f << "###sz=" << sz << ",";

    pict.m_dataPos = input->tell();
    if (pict.m_dataPos != pict.m_pos.end()) {
#ifdef DEBUG_WITH_FILES
      librevenge::RVNGBinaryData file;
      input->readDataBlock(pict.m_pos.end()-pict.m_dataPos, file);
      static int volatile textboxName = 0;
      libmwaw::DebugStream f2;
      f2 << "TextBox-" << ++textboxName << ".pct";
      libmwaw::Debug::dumpFile(file, f2.str().c_str());
#endif
      ascFile.skipZone(pict.m_dataPos, pict.m_pos.end()-1);
    }
    break;
  }
  case 0x10: { // basic table
    libmwaw::DebugStream f2;
    f2 << "Graph10(II): fl=(";
    long actPos = input->tell();
    for (int i = 0; i < 3; i++)
      f2 << input->readLong(2) << ", ";
    f2 << "), ";
    auto nRow = static_cast<int>(input->readLong(2));
    auto nCol = static_cast<int>(input->readLong(2));
    f2 << "nRow=" << nRow << ", " << "nCol=" << nCol << ", ";

    // basic name font
    auto nbChar = static_cast<int>(input->readULong(1));
    if (nbChar > 31) return -1;
    std::string fName;
    for (int c = 0; c < nbChar; c++)
      fName+=char(input->readLong(1));
    f2 << fName << ",";
    input->seek(actPos+10+32, librevenge::RVNG_SEEK_SET);
    auto fSz = static_cast<int>(input->readLong(2));
    if (fSz) f << "fSz=" << fSz << ",";

    ascFile.addDelimiter(input->tell(),'|');
    ascFile.addPos(actPos);
    ascFile.addNote(f2.str().c_str());
    input->seek(actPos+0x40, librevenge::RVNG_SEEK_SET);

    // a pict
    actPos = input->tell();
    ascFile.addPos(actPos);
    ascFile.addNote("Graph10(pict)");
    auto dSize = long(input->readLong(4));
    if (dSize < 0) return -1;
    pict.m_dataPos = actPos+4;

    auto pct = std::make_shared<MsWksGraphInternal::DataPict>(pict);
    pct->m_dataEndPos = actPos+4+dSize;
    res = pct;
    ascFile.skipZone(pct->m_dataPos, pct->m_dataEndPos-1);
    input->seek(actPos+4+dSize, librevenge::RVNG_SEEK_SET);

    // the table
    f << "numRows=" << nRow << ",nCols=" << nCol << ",";
    std::shared_ptr<MsWksGraphInternal::Table> table(new MsWksGraphInternal::Table(pict));
    int tableId = m_state->m_tableId++;
    if (m_tableParser->readTable(nCol, nRow, tableId, table->m_style)) {
      table->m_tableId = tableId;
      res=table;
    }
    break;
  }
  default:
    ascFile.addDelimiter(debData, '|');
    break;
  }

  if (!res)
    res.reset(new MsWksGraphInternal::Zone(pict));
  res->m_extra += f.str();

  if (order > -1000)
    res->m_order = order;
  if (!autoSend)
    res->m_doNotSend = true;
  res->m_fileId = int(m_state->m_zonesList.size());
  m_state->m_zonesList.push_back(res);

  f.str("");
  f << "Entries(Graph" << std::hex << res->m_subType << std::dec << "):" << *res;
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  zone = res->m_pos;
  zone.setType("Graphic");
  input->seek(res->m_pos.end(), librevenge::RVNG_SEEK_SET);

  return res->m_fileId;
}

void MsWksGraph::computePositions(int zoneId, std::vector<int> &linesH, std::vector<int> &pagesH)
{
  auto numLines = int(linesH.size());
  auto nPages = int(pagesH.size());
  bool isSpreadsheet=m_parserState->m_type==MWAWParserState::Spreadsheet;
  for (auto zone : m_state->m_zonesList) {
    if (zone->m_zoneId != -1 && zoneId != zone->m_zoneId) continue;
    if (zone->m_line >= 0) {
      int h = 0;
      if (zone->m_line >= numLines) {
        MWAW_DEBUG_MSG(("MsWksGraph::computePositions: linepos is too big\n"));
        if (numLines)
          h = linesH[size_t(numLines)-1];
      }
      else
        h = linesH[size_t(zone->m_line)];
      zone->m_finalDecal = MWAWVec2f(0, float(h));
    }
    if (zone->m_page < 0 && (isSpreadsheet || zone->m_page != -2)) {
      float h = zone->m_finalDecal.y();
      float middleH=zone->m_box.center().y();
      h+=middleH;
      int p = 0;
      while (p < nPages) {
        if (h < float(pagesH[size_t(p)])) break;
        h -= float(pagesH[size_t(p++)]);
      }
      zone->m_page = p;
      zone->m_finalDecal.setY(h-middleH);
    }
  }
}

int MsWksGraph::getEntryPictureV1(int zoneId, MWAWEntry &zone, bool autoSend)
{
  MWAWInputStreamPtr input=m_document.getInput();
  if (input->isEnd()) return -1;

  long pos = input->tell();
  if (input->readULong(1) != 1) return -1;

  libmwaw::DebugFile &ascFile = m_document.ascii();
  libmwaw::DebugStream f;
  auto ptr = long(input->readULong(2));
  auto flag = static_cast<int>(input->readULong(1));
  long size = long(input->readULong(2))+6;
  if (size < 22) return -1;

  // check if we can go to the next zone
  if (!input->checkPosition(pos+size)) return -1;
  std::shared_ptr<MsWksGraphInternal::DataPict> pict(new MsWksGraphInternal::DataPict());
  pict->m_zoneId = zoneId;
  pict->m_subType = 0x100;
  pict->m_pos.setBegin(pos);
  pict->m_pos.setLength(size);

  if (ptr) f << std::hex << "ptr0=" << ptr << ",";
  if (flag) f << std::hex << "fl=" << flag << ",";

  ptr = input->readLong(4);
  if (ptr)
    f << "ptr1=" << std::hex << ptr << std::dec << ";";
  pict->m_line = static_cast<int>(input->readLong(2));
  auto val = static_cast<int>(input->readLong(2)); // almost always equal to m_linePOs
  if (val !=  pict->m_line)
    f <<  "linePos2=" << std::hex << val << std::dec << ",";
  int dim[4]; // pictbox
  for (auto &d : dim) d = static_cast<int>(input->readLong(2));
  pict->m_box = MWAWBox2f(MWAWVec2f(float(dim[1]), float(dim[0])), MWAWVec2f(float(dim[3]),float(dim[2])));

  MWAWVec2i pictMin(pict->m_box.min()), pictSize(pict->m_box.size());
  if (pictSize.x() < 0 || pictSize.y() < 0) return -1;

  if (pictSize.x() > 3000 || pictSize.y() > 3000 ||
      pictMin.x() < -200 || pictMin.y() < -200) return -1;
  pict->m_dataPos = input->tell();

  zone = pict->m_pos;
  zone.setType("GraphEntry");

  pict->m_extra = f.str();
  if (!autoSend)
    pict->m_doNotSend=true;
  pict->m_fileId = int(m_state->m_zonesList.size());
  m_state->m_zonesList.push_back(pict);
  f.str("");
  f << "Entries(GraphEntry):" << *pict;

  ascFile.skipZone(pict->m_dataPos, pict->m_pos.end()-1);
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  input->seek(pict->m_pos.end(), librevenge::RVNG_SEEK_SET);
  return pict->m_fileId;
}

// a list of picture
bool MsWksGraph::readRB(MWAWInputStreamPtr input, MWAWEntry const &entry, int kind)
{
  libmwaw::DebugFile &ascFile = m_document.ascii();
  libmwaw::DebugStream f;

  long beginRB, endRB;
  long pos=input->tell();
  f << "Entries(" << entry.name() << "):";
  const int vers=version();
  switch (kind) {
  case 0:
    pos=beginRB=entry.begin();
    endRB=entry.end();
    break;
  case 2:
    if (input->readLong(1)!=3) return false;
    f << "id=" << input->readLong(1) << ",";
    MWAW_FALLTHROUGH;
  case 1: {
    unsigned long dSz=input->readULong(4);
    beginRB=input->tell();
    if (dSz&0x80000000) {
      f << "flags[high],";
      dSz &= 0x7FFFFFFF;
    }
    endRB=beginRB+long(dSz);
    break;
  }
  default:
    MWAW_DEBUG_MSG(("MsWksGraph::readRB: unknown kind\n"));
    return false;
  }

  long const headerSize=vers<3 ? 0x150 : 0x164;
  if (endRB-beginRB+2 < headerSize || !input->checkPosition(endRB)) return false;

  MsWksGraphInternal::RBZone zone;
  if (vers==4)
    zone.m_isMain = entry.name()=="RBDR";
  zone.m_id = entry.id();

  input->seek(beginRB, librevenge::RVNG_SEEK_SET);
  f << input->readLong(4) << ", ";
  for (int i = 0; i < 4; i++) {
    long val = input->readLong(4);
    if (val) f << "#t" << i << "=" << val << ", ";
  }
  f << "type?=" << std::hex << input->readLong(2) << std::dec << ", ";
  f << "numPage=" << input->readLong(2) << ", ";
  for (int i = 0; i < 11; i++) {
    long val = input->readLong(2);
    if (!val) continue;
    if (i >= 8 && (val < -100 || val > 100)) f << "###";
    f << "f" << i << "=" << val << ", ";
  }
  f << ", unk=(";
  for (int i = 0; i < 2; i++)
    f << input->readLong(4) << ",";
  f << "), ";
  for (int i = 0; i < 9; i++) {
    long val = input->readLong(2);
    if (val) f << "#u" << i << "=" << val << ", ";
  }
  f << std::hex << "sz?=" << input->readLong(4) << std::dec << ", ";
  for (int i = 0; i < 2; i++) {
    long val = input->readLong(2);
    if (val) f << "#v" << i << "=" << val << ", ";
  }

  f << "unk1=(";
  for (int i = 0; i < 9; i++) {
    long val = input->readLong(2);
    if (val) f << val << ",";
    else f << "_,";
  }
  f << "), ";
  if (vers>=3) {
    long aPos=input->tell();
    std::string oleName;
    while (input->tell() < beginRB+headerSize-2) {
      auto val  = char(input->readLong(1));
      if (val == 0) break;
      oleName+= val;
      if (oleName.length() >= 10) break;
    }
    if (!oleName.empty()) {
      zone.m_frame = oleName;
      f << "ole='" << oleName << "', ";
    }
    ascFile.addDelimiter(input->tell(),'|');
    input->seek(aPos+20, librevenge::RVNG_SEEK_SET);
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  pos=input->tell();
  f.str("");
  f << entry.name() << "-II:";
  for (int i=0; i<118; ++i) {
    auto val = static_cast<int>(input->readLong(2));
    if (val) f << "g" << i << "=" << std::hex << val << std::dec << ",";
  }
  // can happens at least with vers=2 and this means that the size is not set
  if (endRB-beginRB+2 == headerSize)
    endRB=-1;
  auto N = static_cast<int>(input->readLong(2));
  f << "N=" << N << ",";
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  if (N == 0) return true;

  if (endRB>0)
    input->pushLimit(endRB);
  size_t actId = m_state->m_zonesList.size();
  for (int i = 0; i < N; i++) {
    pos = input->tell();
    MWAWEntry pictZone;
    if (getEntryPicture(vers==4 ? 0 : entry.id(), pictZone)>=0)
      continue;
    MWAW_DEBUG_MSG(("MsWksDocument::readGroup: can not find the end of group\n"));
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    break;
  }
  if (endRB>0)
    input->popLimit();
  for (size_t z = actId; z < m_state->m_zonesList.size(); z++) {
    auto pictZone = m_state->m_zonesList[z];
    if (!pictZone) continue;
    zone.m_idList.push_back(int(z));
    if (!zone.m_isMain)
      pictZone->m_page = -2;
  }
  if (endRB>0 && input->tell() < endRB) {
    f.str("");
    f << entry.name() << "-end:###";
    MWAW_DEBUG_MSG(("MsWksGraph::readRB: find some extra data\n"));
    ascFile.addPos(input->tell());
    ascFile.addNote(f.str().c_str());
  }
  if (endRB>0)
    input->seek(endRB, librevenge::RVNG_SEEK_SET);
  checkTextBoxLinks(zone);
  int zId = zone.getId();
  if (m_state->m_RBsMap.find(zId) != m_state->m_RBsMap.end()) {
    MWAW_DEBUG_MSG(("MsWksGraph::readRB: zone %d is already filled\n", zId));
    // ok, let's merge the two zone
    auto &orig=m_state->m_RBsMap.find(zId)->second;
    orig.m_idList.insert(orig.m_idList.end(), zone.m_idList.begin(), zone.m_idList.end());
    if (orig.m_frame.empty()) orig.m_frame=zone.m_frame;
  }
  else
    m_state->m_RBsMap[zId]=zone;
  return true;
}

void MsWksGraph::checkTextBoxLinks(MsWksGraphInternal::RBZone &rbZone)
{
  auto const &listIds = rbZone.m_idList;
  std::string const &fName = rbZone.m_frame;
  auto numZones = int(m_state->m_zonesList.size());
  std::set<long> textIds;
  std::map<long,long> prevLinks, nextLinks;
  bool ok = true;
  for (auto id : listIds) {
    if (id < 0 || id >= numZones) continue;
    auto zone = m_state->m_zonesList[size_t(id)];
    if (zone->type() != MsWksGraphInternal::Zone::Textv4)
      continue;
    static_cast<MsWksGraphInternal::TextBoxv4 &>(*zone).m_frame = fName;
    if (textIds.find(zone->m_ids[0]) != textIds.end()) {
      MWAW_DEBUG_MSG(("MsWksGraph::checkTextBoxLinks: id %lX already exists\n", static_cast<long unsigned int>(zone->m_ids[0])));
      ok = false;
      break;
    }
    textIds.insert(zone->m_ids[0]);
    if (zone->m_ids[1]>0)
      prevLinks.insert(std::map<long,long>::value_type(zone->m_ids[0],zone->m_ids[1]));
    if (zone->m_ids[2]>0)
      nextLinks.insert(std::map<long,long>::value_type(zone->m_ids[0],zone->m_ids[2]));
  }
  size_t numLinks = nextLinks.size();
  for (auto link : nextLinks) {
    if (prevLinks.find(link.second)==prevLinks.end() ||
        prevLinks.find(link.second)->second!=link.first) {
      MWAW_DEBUG_MSG(("MsWksGraph::checkTextBoxLinks: can not find prevLinks: %lX<->%lX already exists\n", static_cast<long unsigned int>(link.first), static_cast<long unsigned int>(link.second)));
      ok = false;
      break;
    }
    // check loops
    size_t w = 0;
    long actText = link.second;
    while (1) {
      if (nextLinks.find(actText)==nextLinks.end())
        break;
      actText = nextLinks.find(actText)->second;
      if (w++ > numLinks) {
        MWAW_DEBUG_MSG(("MsWksGraph::checkTextBoxLinks:find a loop for id %lX\n", static_cast<long unsigned int>(link.first)));
        ok = false;
        break;
      }
    }
  }
  if (!ok) {
    MWAW_DEBUG_MSG(("MsWksGraph::checkTextBoxLinks: problem find with text links\n"));
    for (auto zone : m_state->m_zonesList) {
      if (zone->type() != MsWksGraphInternal::Zone::Textv4)
        continue;
      zone->m_ids[1] = zone->m_ids[2] = 0;
    }
  }
}

bool MsWksGraph::readPictureV4(MWAWInputStreamPtr /*input*/, MWAWEntry const &entry)
{
  if (!entry.hasType("PICT")) {
    MWAW_DEBUG_MSG(("MsWksGraph::readPictureV4: unknown type='%s'\n", entry.type().c_str()));
    return false;
  }
  entry.setParsed(true);

  MsWksGraphInternal::Zone pict;
  pict.m_pos = entry;
  pict.m_dataPos = entry.begin();
  pict.m_page = -2;
  pict.m_zoneId = -1;

  auto pct = std::make_shared<MsWksGraphInternal::DataPict>(pict);
  std::shared_ptr<MsWksGraphInternal::Zone>res(pct);
  m_document.ascii().skipZone(entry.begin(), entry.end()-1);

  auto zId = int(m_state->m_zonesList.size());
  res->m_fileId = zId;
  m_state->m_zonesList.push_back(res);

  return true;
}

////////////////////////////////////////////////////////////
//
// Low level
//
////////////////////////////////////////////////////////////

// read/send a group
void MsWksGraph::sendGroup(int id, MWAWPosition const &pos)
{
  if (id<0 || id>=int(m_state->m_zonesList.size()) || !m_state->m_zonesList[size_t(id)] ||
      m_state->m_zonesList[size_t(id)]->type()!=MsWksGraphInternal::Zone::Group) {
    MWAW_DEBUG_MSG(("MsWksGraph::sendGroup: can not find group %d\n", id));
    return;
  }
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) return;
  auto &group=static_cast<MsWksGraphInternal::GroupZone &>(*m_state->m_zonesList[size_t(id)]);
  group.m_isSent = true;
  if (listener->getType()==MWAWListener::Graphic) {
    sendGroup(group, m_parserState->m_graphicListener);
    return;
  }
  if (!canCreateGraphic(group)) {
    if (pos.m_anchorTo == MWAWPosition::Char || pos.m_anchorTo == MWAWPosition::CharBaseLine) {
      std::shared_ptr<MsWksGraphInternal::SubDocument> subdoc
      (new MsWksGraphInternal::SubDocument(*this, m_document.getInput(), MsWksGraphInternal::SubDocument::Group, id));
      listener->insertTextBox(pos, subdoc);
      return;
    }
    MWAWPosition childPos(pos);
    childPos.setSize(MWAWVec2f(0,0));
    sendGroupChild(id, childPos);
    return;
  }
  MWAWGraphicEncoder graphicEncoder;
  MWAWGraphicListenerPtr graphicListener(new MWAWGraphicListener(*m_parserState, group.m_box, &graphicEncoder));
  graphicListener->startDocument();
  sendGroup(group, graphicListener);
  graphicListener->endDocument();
  MWAWEmbeddedObject picture;
  if (graphicEncoder.getBinaryResult(picture))
    listener->insertPicture(pos, picture);
}

void MsWksGraph::sendGroupChild(int id, MWAWPosition const &pos)
{
  if (id<0 || id>=int(m_state->m_zonesList.size()) || !m_state->m_zonesList[size_t(id)] ||
      m_state->m_zonesList[size_t(id)]->type()!=MsWksGraphInternal::Zone::Group) {
    MWAW_DEBUG_MSG(("MsWksGraph::sendGroupChild: can not find group %d\n", id));
    return;
  }
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) return;
  auto &group=static_cast<MsWksGraphInternal::GroupZone &>(*m_state->m_zonesList[size_t(id)]);
  group.m_isSent = true;

  MWAWInputStreamPtr input=m_document.getInput();
  size_t numZones=m_state->m_zonesList.size();
  size_t numChild=group.m_childs.size(), childNotSent=0;
  int numDataToMerge=0;
  MWAWBox2f partialBdBox;
  MWAWPosition partialPos(pos);
  bool isDraw=listener->getType()==MWAWListener::Graphic;
  for (size_t c=0; c < numChild; ++c) {
    int cId = group.m_childs[c];
    if (cId < 0 || cId >= int(numZones) || !m_state->m_zonesList[size_t(cId)])
      continue;
    auto const &child=*(m_state->m_zonesList[size_t(cId)]);
    bool isLast=false;
    bool canMerge=false;
    if (isDraw)
      canMerge=false;
    else if (child.type()==MsWksGraphInternal::Zone::Shape || child.type()==MsWksGraphInternal::Zone::Text) {
      MWAWBox2f origBdBox=child.getLocalBox();
      MWAWVec2f decal = child.m_decal[0] + child.m_decal[1];
      MWAWBox2f localBdBox(origBdBox[0]+decal, origBdBox[1]+decal);
      if (numDataToMerge == 0)
        partialBdBox=localBdBox;
      else
        partialBdBox=partialBdBox.getUnion(localBdBox);
      canMerge=true;
    }
    else if (child.type()==MsWksGraphInternal::Zone::Group &&
             canCreateGraphic(static_cast<MsWksGraphInternal::GroupZone const &>(child))) {
      if (numDataToMerge == 0)
        partialBdBox=child.getLocalBox();
      else
        partialBdBox=partialBdBox.getUnion(child.getLocalBox());
      canMerge=true;
    }
    if (canMerge) {
      ++numDataToMerge;
      if (c+1 < numChild)
        continue;
      isLast=true;
    }

    if (numDataToMerge>1) {
      MWAWGraphicEncoder graphicEncoder;
      MWAWGraphicListenerPtr graphicListener(new MWAWGraphicListener(*m_parserState, partialBdBox, &graphicEncoder));
      graphicListener->startDocument();
      size_t lastChild = isLast ? c : c-1;
      for (size_t ch=childNotSent; ch <= lastChild; ++ch) {
        int localCId = group.m_childs[ch];
        if (localCId < 0 || localCId >= int(numZones) || !m_state->m_zonesList[size_t(localCId)])
          continue;
        auto const &localChild=*(m_state->m_zonesList[size_t(localCId)]);
        MWAWBox2f origBdBox=localChild.getLocalBox(false);
        MWAWVec2f decal=localChild.m_decal[0]+localChild.m_decal[1];
        MWAWPosition pictPos(origBdBox[0]+decal, origBdBox.size(), librevenge::RVNG_POINT);
        pictPos.m_anchorTo=MWAWPosition::Page;
        pictPos.m_wrapping =  MWAWPosition::WBackground;
        if (localChild.type()==MsWksGraphInternal::Zone::Group)
          sendGroup(static_cast<MsWksGraphInternal::GroupZone const &>(localChild), graphicListener);
        else if (localChild.type()==MsWksGraphInternal::Zone::Shape) {
          auto const &shape=static_cast<MsWksGraphInternal::BasicShape const &>(localChild);
          graphicListener->insertShape(pictPos, shape.m_shape, shape.getStyle());
        }
        else if (localChild.type()==MsWksGraphInternal::Zone::Text) {
          std::shared_ptr<MsWksGraphInternal::SubDocument> subdoc
          (new MsWksGraphInternal::SubDocument(const_cast<MsWksGraph &>(*this), input, MsWksGraphInternal::SubDocument::TextBox, localCId));
          // a textbox can not have border
          MWAWGraphicStyle style(localChild.m_style);
          style.m_lineWidth=0;
          graphicListener->insertTextBox(pictPos, subdoc, style);
        }
      }
      graphicListener->endDocument();
      MWAWEmbeddedObject picture;
      if (graphicEncoder.getBinaryResult(picture)) {
        partialPos.setOrigin(pos.origin()+partialBdBox[0]-group.m_box[0]);
        partialPos.setSize(partialBdBox.size());
        listener->insertPicture(partialPos, picture);
        if (isLast)
          break;
        childNotSent=c;
      }
    }
    // time to send back the data
    for (; childNotSent <= c; ++childNotSent)
      send(group.m_childs[childNotSent],pos);
    numDataToMerge=0;
  }
}

bool MsWksGraph::canCreateGraphic(MsWksGraphInternal::GroupZone const &group) const
{
  if (m_parserState->getMainListener()->getType()==MWAWListener::Graphic) return false;
  auto numZones = int(m_state->m_zonesList.size());
  for (auto cId : group.m_childs) {
    if (cId < 0 || cId >= numZones || !m_state->m_zonesList[size_t(cId)])
      continue;
    auto const &child=*(m_state->m_zonesList[size_t(cId)]);
    if (child.m_page!=group.m_page)
      return false;
    switch (child.type()) {
    case MsWksGraphInternal::Zone::Shape:
    case MsWksGraphInternal::Zone::Text:
      break;
    case MsWksGraphInternal::Zone::Group:
      if (!canCreateGraphic(static_cast<MsWksGraphInternal::GroupZone const &>(child)))
        return false;
      break;
    case MsWksGraphInternal::Zone::Bitmap:
    case MsWksGraphInternal::Zone::ChartZone:
    case MsWksGraphInternal::Zone::OLE:
    case MsWksGraphInternal::Zone::Pict:
    case MsWksGraphInternal::Zone::TableZone:
    case MsWksGraphInternal::Zone::Textv4:
    case MsWksGraphInternal::Zone::Unknown:
#if !defined(__clang__)
    default:
#endif
      return false;
    }
  }
  return true;
}

void MsWksGraph::sendGroup(MsWksGraphInternal::GroupZone const &group, MWAWGraphicListenerPtr &listener) const
{
  if (!listener || !listener->isDocumentStarted()) {
    MWAW_DEBUG_MSG(("MsWksGraph::sendGroup: the listener is bad\n"));
    return;
  }
  auto numZones = int(m_state->m_zonesList.size());
  MWAWInputStreamPtr input=m_document.getInput();
  for (auto cId : group.m_childs) {
    if (cId < 0 || cId >= numZones || !m_state->m_zonesList[size_t(cId)])
      continue;
    auto const &child=*(m_state->m_zonesList[size_t(cId)]);
    MWAWVec2f decal=child.m_decal[0]+child.m_decal[1];
    MWAWPosition pictPos(child.m_box[0]+decal, child.m_box.size(), librevenge::RVNG_POINT);
    pictPos.m_anchorTo=MWAWPosition::Page;
    pictPos.m_wrapping =  MWAWPosition::WBackground;

    if (child.type()==MsWksGraphInternal::Zone::Group)
      sendGroup(static_cast<MsWksGraphInternal::GroupZone const &>(child), listener);
    else if (child.type()==MsWksGraphInternal::Zone::Shape) {
      auto const &shape=static_cast<MsWksGraphInternal::BasicShape const &>(child);
      listener->insertShape(pictPos, shape.m_shape, shape.getStyle());
    }
    else if (child.type()==MsWksGraphInternal::Zone::Text) {
      std::shared_ptr<MsWksGraphInternal::SubDocument> subdoc
      (new MsWksGraphInternal::SubDocument(const_cast<MsWksGraph &>(*this), input, MsWksGraphInternal::SubDocument::TextBox, cId));
      // a textbox can not have border
      MWAWGraphicStyle style(child.m_style);
      style.m_lineWidth=0;
      listener->insertTextBox(pictPos, subdoc, style);
    }
    else {
      MWAW_DEBUG_MSG(("MsWksGraph::sendGroup: find some unexpected child\n"));
    }
  }
}

std::shared_ptr<MsWksGraphInternal::GroupZone> MsWksGraph::readGroup(MsWksGraphInternal::Zone &header)
{
  std::shared_ptr<MsWksGraphInternal::GroupZone> group(new MsWksGraphInternal::GroupZone(header));
  libmwaw::DebugStream f;
  MWAWInputStreamPtr input=m_document.getInput();
  input->seek(header.m_dataPos, librevenge::RVNG_SEEK_SET);
  float dim[4];
  for (auto &d : dim) d = float(input->readLong(4));
  group->m_box=MWAWBox2f(MWAWVec2f(dim[0],dim[1]), MWAWVec2f(dim[2],dim[3]));
  group->m_finalDecal=MWAWVec2f(0,0);
  long ptr[2];
  for (auto &p : ptr) p = long(input->readULong(4));
  f << "ptr0=" << std::hex << ptr[0] << std::dec << ",";
  if (ptr[0] != ptr[1])
    f << "ptr1=" << std::hex << ptr[1] << std::dec << ",";
  if (version() >= 3) {
    auto val = static_cast<int>(input->readULong(4));
    if (val) f << "g1=" << val << ",";
  }

  input->seek(header.m_pos.end()-2, librevenge::RVNG_SEEK_SET);
  auto N = static_cast<int>(input->readULong(2));
  for (int i = 0; i < N; i++) {
    long pos = input->tell();
    MWAWEntry childZone;
    int childId = getEntryPicture(header.m_zoneId, childZone, false);
    if (childId < 0) {
      MWAW_DEBUG_MSG(("MsWksGraph::readGroup: can not find child\n"));
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      f << "#child,";
      break;
    }
    group->m_childs.push_back(childId);
  }
  group->m_extra += f.str();
  group->m_pos.setEnd(input->tell());
  return group;
}

// read/send a textbox zone
bool MsWksGraph::readText(MsWksGraphInternal::TextBox &textBox)
{
  if (textBox.m_numPositions < 0) return false; // can an empty text exist

  libmwaw::DebugFile &ascFile = m_document.ascii();
  libmwaw::DebugStream f;
  f << "Entries(SmallText):";
  MWAWInputStreamPtr input=m_document.getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+4*(textBox.m_numPositions+1))) return false;

  // first read the set of (positions, font)
  f << "pos=[";
  int nbFonts = 0;
  for (int i = 0; i <= textBox.m_numPositions; i++) {
    auto fPos = static_cast<int>(input->readLong(2));
    auto form = static_cast<int>(input->readLong(2));
    f << fPos << ":" << form << ", ";

    if (fPos < 0 || form < -1) return false;
    if ((form == -1 && i != textBox.m_numPositions) ||
        (!textBox.m_positions.empty() && fPos < textBox.m_positions.back())) {
      MWAW_DEBUG_MSG(("MsWksGraph::readText: find odd positions\n"));
      f << "#";
      continue;
    }

    textBox.m_positions.push_back(fPos);
    textBox.m_formats.push_back(form);
    if (form >= nbFonts) nbFonts = form+1;
  }
  f << "] ";
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  pos = input->tell();
  f.str("");
  f << "SmallText:Fonts ";

  // actualPos, -1, only exists if actualPos!= 0 ? We ignored it.
  input->readLong(2);
  if (input->readLong(2) != -1)
    input->seek(pos,librevenge::RVNG_SEEK_SET);
  else {
    ascFile.addPos(pos);
    ascFile.addNote("SmallText:char Pos");
    pos = input->tell();
  }
  f.str("");

  long endFontPos = input->tell();
  auto sizeOfData = long(input->readULong(4));
  int numFonts = (sizeOfData%0x12 == 0) ? int(sizeOfData/0x12) : 0;

  if (numFonts >= nbFonts) {
    endFontPos = input->tell()+4+sizeOfData;

    ascFile.addPos(pos);
    ascFile.addNote("SmallText: Fonts");

    for (int i = 0; i < numFonts; i++) {
      pos = input->tell();
      MWAWFont font;
      if (!readFont(font)) {
        input->seek(endFontPos, librevenge::RVNG_SEEK_SET);
        break;
      }
      textBox.m_fontsList.push_back(font);

      f.str("");
      f << "SmallText:Font"<< i
        << "(" << font.getDebugString(m_parserState->m_fontConverter) << "),";

      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      pos = input->tell();
    }
  }
  int nChar = textBox.m_positions.back()-1;
  if (nbFonts > int(textBox.m_fontsList.size())) {
    MWAW_DEBUG_MSG(("MsWksGraph::readText: can not read the fonts\n"));
    ascFile.addPos(pos);
    ascFile.addNote("SmallText:###");
    input->seek(endFontPos,librevenge::RVNG_SEEK_SET);
    textBox.m_fontsList.resize(0);
    textBox.m_positions.resize(0);
    textBox.m_numPositions = 0;
  }

  // now, syntax is : long(size) + size char
  //      - 0x16 - 0 - 0 - Fonts (default fonts)
  //      - 0x08 followed by two long, maybe interesting to look
  //      - 0x0c (or 0x18) seems followed by small int
  //      - nbChar : the strings (final)

  f.str("");
  f << "SmallText:";
  while (1) {
    if (input->isEnd()) return false;

    pos = input->tell();
    sizeOfData = long(input->readULong(4));
    if (sizeOfData == nChar) {
      bool ok = true;
      // ok we try to read the string
      std::string chaine("");
      for (int i = 0; i < sizeOfData; i++) {
        auto c = static_cast<unsigned char>(input->readULong(1));
        if (c == 0) {
          ok = false;
          break;
        }
        chaine += char(c);
      }

      if (!ok)
        input->seek(pos+4,librevenge::RVNG_SEEK_SET);
      else {
        textBox.m_text = chaine;
        f << "=" << chaine;
        ascFile.addPos(pos);
        ascFile.addNote(f.str().c_str());
        return true;
      }
    }

    if (sizeOfData <= 100+nChar && (sizeOfData%2==0)) {
      if (input->seek(sizeOfData, librevenge::RVNG_SEEK_CUR) != 0) return false;
      f << "#";
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      f.str("");
      f << "SmallText:Text";
      continue;
    }

    // fixme: we can try to find the next string
    MWAW_DEBUG_MSG(("MsWksGraph::readText: problem reading text\n"));
    f << "#";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());

    break;
  }
  return false;
}

bool MsWksGraph::readFont(MWAWFont &font)
{
  int vers = version();
  MWAWInputStreamPtr input=m_document.getInput();
  long pos = input->tell();
  libmwaw::DebugStream f;
  if (!input->checkPosition(pos+18))
    return false;
  font = MWAWFont();
  for (int i = 0; i < 3; i++) {
    auto val = static_cast<int>(input->readLong(2));
    if (val)
      f << "f" << i << "=" << val << ",";
  }
  font.setFont(static_cast<int>(input->readULong(2)));
  auto flags = static_cast<int>(input->readULong(1));
  uint32_t flag = 0;
  if (flags & 0x1) flag |= MWAWFont::boldBit;
  if (flags & 0x2) flag |= MWAWFont::italicBit;
  if (flags & 0x4) font.setUnderlineStyle(MWAWFont::Line::Simple);
  if (flags & 0x8) flag |= MWAWFont::embossBit;
  if (flags & 0x10) flag |= MWAWFont::shadowBit;
  if (flags & 0x20) {
    if (vers==1)
      font.set(MWAWFont::Script(20,librevenge::RVNG_PERCENT,80));
    else
      font.set(MWAWFont::Script::super100());
  }
  if (flags & 0x40) {
    if (vers==1)
      font.set(MWAWFont::Script(-20,librevenge::RVNG_PERCENT,80));
    else
      font.set(MWAWFont::Script::sub100());
  }
  if (flags & 0x80) f << "#smaller,";
  font.setFlags(flag);

  auto val = static_cast<int>(input->readULong(1));
  if (val) f << "#flags2=" << val << ",";
  font.setSize(float(input->readULong(2)));

  unsigned char color[3];
  for (auto &c : color) c = static_cast<unsigned char>(input->readULong(2)>>8);
  font.setColor(MWAWColor(color[0],color[1],color[2]));
  font.m_extra = f.str();
  return true;
}

void MsWksGraph::sendTextBox(int zoneId, MWAWListenerPtr listener)
{
  if (!listener || !listener->canWriteText()) {
    MWAW_DEBUG_MSG(("MsWksGraph::sendTextBox: can not find get access to the listener\n"));
    return;
  }
  if (zoneId < 0 || zoneId >= int(m_state->m_zonesList.size())) {
    MWAW_DEBUG_MSG(("MsWksGraph::sendTextBox: can not find textbox %d\n", zoneId));
    return;
  }
  auto zone = m_state->m_zonesList[size_t(zoneId)];
  if (!zone) return;
  auto &textBox = static_cast<MsWksGraphInternal::TextBox &>(*zone);
  listener->setFont(MWAWFont(20,12));
  MWAWParagraph para;
  para.m_justify=textBox.m_justify;
  listener->setParagraph(para);
  auto numFonts = int(textBox.m_fontsList.size());
  int actFormatPos = 0;
  auto numFormats = int(textBox.m_formats.size());
  if (numFormats != int(textBox.m_positions.size())) {
    MWAW_DEBUG_MSG(("MsWksGraph::sendTextBox: positions and formats have different length\n"));
    if (numFormats > int(textBox.m_positions.size()))
      numFormats = int(textBox.m_positions.size());
  }
  for (size_t i = 0; i < textBox.m_text.length(); i++) {
    if (actFormatPos < numFormats && textBox.m_positions[size_t(actFormatPos)]==int(i)) {
      int id = textBox.m_formats[size_t(actFormatPos++)];
      if (id < 0 || id >= numFonts) {
        MWAW_DEBUG_MSG(("MsWksGraph::sendTextBox: can not find a font\n"));
      }
      else
        listener->setFont(textBox.m_fontsList[size_t(id)]);
    }
    auto c = static_cast<unsigned char>(textBox.m_text[i]);
    switch (c) {
    case 0x9:
      MWAW_DEBUG_MSG(("MsWksGraph::sendTextBox: find some tab\n"));
      listener->insertChar(' ');
      break;
    case 0xd:
      if (i+1 != textBox.m_text.length())
        listener->insertEOL();
      break;
    case 0x19:
      listener->insertField(MWAWField(MWAWField::Title));
      break;
    case 0x18:
      listener->insertField(MWAWField(MWAWField::PageNumber));
      break;
    case 0x16:
      MWAW_DEBUG_MSG(("MsWksGraph::sendTextBox: find some time\n"));
      listener->insertField(MWAWField(MWAWField::Time));
      break;
    case 0x17:
      MWAW_DEBUG_MSG(("MsWksGraph::sendTextBox: find some date\n"));
      listener->insertField(MWAWField(MWAWField::Date));
      break;
    case 0x14: // fixme
      MWAW_DEBUG_MSG(("MsWksGraph::sendTextBox: footnote are not implemented\n"));
      break;
    default:
      listener->insertCharacter(c);
      break;
    }
  }
}

void MsWksGraph::send(int id, MWAWPosition const &pos)
{
  if (id < 0 || id >= int(m_state->m_zonesList.size())) {
    MWAW_DEBUG_MSG(("MsWksGraph::send: can not find zone %d\n", id));
    return;
  }
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) return;
  auto zone = m_state->m_zonesList[size_t(id)];
  zone->m_isSent = true;

  MWAWPosition pictPos(pos);
  if (pos.size()[0]<=0 || pos.size()[1]<=0)
    pictPos = zone->getPosition(pos.m_anchorTo);
  if (pictPos.m_anchorTo == MWAWPosition::Page)
    pictPos.setOrigin(pictPos.origin()+m_state->m_leftTopPos);

  MWAWInputStreamPtr input=m_document.getInput();
  bool isDraw=listener->getType()==MWAWListener::Graphic;
  switch (zone->type()) {
  case MsWksGraphInternal::Zone::Text: {
    auto &textbox = static_cast<MsWksGraphInternal::TextBox &>(*zone);
    MWAWBox2f box(MWAWVec2f(0,0),textbox.m_box.size());
    std::shared_ptr<MsWksGraphInternal::SubDocument> subdoc
    (new MsWksGraphInternal::SubDocument(*this, input, MsWksGraphInternal::SubDocument::TextBox, id));
    // a textbox can not have border
    MWAWGraphicStyle style(textbox.m_style);
    style.m_lineWidth=0;
    if (isDraw) {
      listener->insertTextBox(pictPos, subdoc, style);
      return;
    }
    MWAWPosition textPos(box[0], box.size(), librevenge::RVNG_POINT);
    MWAWGraphicEncoder graphicEncoder;
    MWAWGraphicListener graphicListener(*m_parserState, box, &graphicEncoder);
    graphicListener.startDocument();
    textPos.m_anchorTo=MWAWPosition::Page;
    textPos.m_wrapping=pos.m_wrapping;
    graphicListener.insertTextBox(textPos, subdoc, style);
    graphicListener.endDocument();
    MWAWEmbeddedObject picture;
    if (graphicEncoder.getBinaryResult(picture))
      listener->insertPicture(pictPos, picture);
    return;
  }
  case MsWksGraphInternal::Zone::TableZone: {
    auto &table = static_cast<MsWksGraphInternal::Table &>(*zone);
    if (isDraw) {
      listener->openFrame(pictPos, zone->m_style);
      m_tableParser->sendTable(table.m_tableId);
      listener->closeFrame();
      return;
    }
    std::shared_ptr<MsWksGraphInternal::SubDocument> subdoc
    (new MsWksGraphInternal::SubDocument(*this, input, MsWksGraphInternal::SubDocument::Table, table.m_tableId));
    listener->insertTextBox(pictPos, subdoc, zone->m_style);
    return;
  }
  case MsWksGraphInternal::Zone::ChartZone: {
    auto &chart = static_cast<MsWksGraphInternal::Chart &>(*zone);
    std::shared_ptr<MsWksGraphInternal::SubDocument> subdoc
    (new MsWksGraphInternal::SubDocument(*this, input, MsWksGraphInternal::SubDocument::Chart, chart.m_chartId));
    listener->insertTextBox(pictPos, subdoc, zone->m_style);
    return;
  }
  case MsWksGraphInternal::Zone::Group:
    sendGroup(id, pictPos);
    return;
  case MsWksGraphInternal::Zone::Bitmap: {
    auto &bmap = static_cast<MsWksGraphInternal::DataBitmap &>(*zone);
    MWAWEmbeddedObject picture;
    if (!bmap.getPictureData(input, picture,m_document.getPalette(4)))
      break;
    m_document.ascii().skipZone(bmap.m_dataPos, bmap.m_pos.end()-1);
    listener->insertPicture(pictPos, picture, zone->m_style);
    return;
  }
  case MsWksGraphInternal::Zone::Shape: {
    auto &shape = static_cast<MsWksGraphInternal::BasicShape &>(*zone);
    listener->insertShape(pictPos, shape.m_shape, shape.getStyle());
    return;
  }
  case MsWksGraphInternal::Zone::Pict: {
    MWAWEmbeddedObject picture;
    if (!zone->getBinaryData(input, picture))
      break;
    listener->insertPicture(pictPos, picture, zone->m_style);
    return;
  }
  case MsWksGraphInternal::Zone::Textv4: {
    auto &textbox = static_cast<MsWksGraphInternal::TextBoxv4 &>(*zone);
    std::shared_ptr<MsWksGraphInternal::SubDocument> subdoc
    (new MsWksGraphInternal::SubDocument(*this, input, MsWksGraphInternal::SubDocument::TextBoxv4, textbox.m_text, textbox.m_frame));
    MWAWGraphicStyle style;
    zone->fillFrame(style);
    // a textbox can not have a border
    style.m_lineWidth=0;
    if (zone->m_ids[1] > 0) {
      librevenge::RVNGString fName;
      fName.sprintf("Frame%ld", zone->m_ids[0]);
      style.m_frameName=fName.cstr();
    }
    if (zone->m_ids[2] > 0) {
      librevenge::RVNGString fName;
      fName.sprintf("Frame%ld", zone->m_ids[2]);
      style.m_frameNextName=fName.cstr();
    }
    listener->insertTextBox(pictPos, subdoc, style);
    return;
  }
  case MsWksGraphInternal::Zone::OLE: {
    auto &ole = static_cast<MsWksGraphInternal::OLEZone &>(*zone);
    m_document.sendOLE(ole.m_oleId, pictPos, zone->m_style);
    return;
  }
  case MsWksGraphInternal::Zone::Unknown:
#if !defined(__clang__)
  default:
#endif
    break;
  }

  MWAW_DEBUG_MSG(("MsWksGraph::send: can not send zone %d\n", id));
}

void MsWksGraph::sendAll(int zoneId, bool mainZone)
{
  MWAWPosition undefPos;
  for (size_t i = 0; i < m_state->m_zonesList.size(); i++) {
    auto zone = m_state->m_zonesList[i];
    if (zoneId >= 0 && zoneId!=zone->m_zoneId)
      continue;
    if (zone->m_doNotSend || (zone->m_isSent && mainZone))
      continue;
    undefPos.m_anchorTo = mainZone ? MWAWPosition::Page : MWAWPosition::Paragraph;
    send(int(i), undefPos);
  }
}

void MsWksGraph::sendObjects(MsWksGraph::SendData const &what)
{
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("MsWksGraph::sendObjects: listener is not set\n"));
    return;
  }

  bool first = true;
  auto numZones = int(m_state->m_zonesList.size());
  std::vector<int> listIds;
  MsWksGraphInternal::RBZone *rbZone=nullptr;
  switch (what.m_type) {
  case MsWksGraph::SendData::ALL:
    listIds.resize(size_t(numZones));
    for (int i = 0; i < numZones; i++) listIds[size_t(i)]=i;
    break;
  case MsWksGraph::SendData::RBDR:
  case MsWksGraph::SendData::RBIL: {
    int zId = what.m_type==MsWksGraph::SendData::RBDR ? -1 : what.m_id;
    if (m_state->m_RBsMap.find(zId)!=m_state->m_RBsMap.end())
      rbZone = &m_state->m_RBsMap.find(zId)->second;
    break;
  }
#if !defined(__clang__)
  default:
    break;
#endif
  }
  if (rbZone)
    listIds=rbZone->m_idList;
  bool isText=m_parserState->m_type==MWAWParserState::Text;
  if (isText && what.m_type==MsWksGraph::SendData::RBIL) {
    if (!rbZone) {
      MWAW_DEBUG_MSG(("MsWksGraph::sendObjects: can find RBIL zone %d\n", what.m_id));
      return;
    }
    if (listIds.size() != 1) {
      if (what.m_anchor == MWAWPosition::Char ||
          what.m_anchor == MWAWPosition::CharBaseLine) {
        std::shared_ptr<MsWksGraphInternal::SubDocument> subdoc
        (new MsWksGraphInternal::SubDocument(*this, m_document.getInput(), MsWksGraphInternal::SubDocument::RBILZone, what.m_id));
        MWAWPosition pictPos(MWAWVec2f(0,0), MWAWVec2f(what.m_size), librevenge::RVNG_POINT);
        pictPos.setRelativePosition(MWAWPosition::Char,
                                    MWAWPosition::XLeft, MWAWPosition::YTop);
        pictPos.m_wrapping =  MWAWPosition::WBackground;
        listener->insertTextBox(pictPos, subdoc);
        return;
      }
    }
  }
  MWAWPosition undefPos;
  undefPos.m_anchorTo = what.m_anchor;
  for (auto id : listIds) {
    if (id < 0 || id >= numZones) continue;
    auto zone = m_state->m_zonesList[size_t(id)];
    if (!zone || zone->m_doNotSend) continue;
    if (zone->m_isSent) {
      if (what.m_type == MsWksGraph::SendData::ALL ||
          (isText && what.m_anchor == MWAWPosition::Page)) continue;
    }
    if (what.m_anchor == MWAWPosition::Page) {
      if (what.m_page > 0 && zone->m_page+1 != what.m_page) continue;
      else if (what.m_page==0 && zone->m_page < 0) continue;
      else if (what.m_page==-2 && zone->m_page >=0) continue;
      undefPos=zone->getPosition(MWAWPosition::Page);
    }

    if (isText && first) {
      first = false;
      if (what.m_anchor == MWAWPosition::Page && !listener->isSectionOpened() && !listener->isParagraphOpened())
        listener->insertChar(' ');
    }
    send(int(id), undefPos);
  }
}

void MsWksGraph::flushExtra()
{
  MWAWPosition undefPos;
  undefPos.m_anchorTo=MWAWPosition::Char;
  for (size_t i = 0; i < m_state->m_zonesList.size(); i++) {
    auto zone = m_state->m_zonesList[i];
    if (!zone || zone->m_isSent || zone->m_doNotSend) continue;
    send(int(i), undefPos);
  }
}

////////////////////////////////////////////////////////////
// style
////////////////////////////////////////////////////////////
MsWksGraph::Style::~Style()
{
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: