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

#include <librevenge/librevenge.h>

#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWGraphicListener.hxx"
#include "MWAWGraphicShape.hxx"
#include "MWAWGraphicStyle.hxx"
#include "MWAWHeader.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPictBitmap.hxx"
#include "MWAWPictData.hxx"
#include "MWAWPrinter.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSubDocument.hxx"

#include "FreeHandParser.hxx"

/** Internal: the structures of a FreeHandParser */
namespace FreeHandParserInternal
{
//! the different zone type
enum ZoneType { Z_Unknown, Z_Color, Z_ColorGroup, Z_Dash, Z_DashGroup, Z_Data, Z_Fill, Z_FillGroup, Z_Group,
                Z_LineStyle, Z_LineStyleGroup, Z_Note, Z_Picture, Z_PictureName, Z_String, Z_Shape, Z_StyleGroup
              };

/** struct which defines the screen parameters in FreeHandParserInternal */
struct ScreenMode {
  //! constructor
  ScreenMode()
    : m_function(0)
    , m_angle(0)
    , m_lineByInch(0)
    , m_value(0)
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, ScreenMode const &screen)
  {
    switch (screen.m_function) {
    case 0: // unset
    case -1: // default
      break;
    case 1:
      o << "function=round,";
      break;
    case 2:
      o << "function=line,";
      break;
    default:
      MWAW_DEBUG_MSG(("FreeHandParserInternal::operator<<(ScreenMode): find unexpected screen function\n"));
      o << "function=###" << screen.m_function << ",";
      break;
    }
    if (screen.m_angle < 0 || screen.m_angle>0)
      o << "angle=" << screen.m_angle << ",";
    if (screen.m_lineByInch==0xFFFF)
      o << "lineByInch*,";
    else
      o << "lineByInch=" << screen.m_lineByInch << ",";
    if (screen.m_value)
      o << "unkn0=" << screen.m_value << ",";
    return o;
  }

  //! the  function
  int m_function;
  //! the  angle
  float m_angle;
  //! the  line/inch
  int m_lineByInch;
  //! unknow value
  int m_value;
};

/** small structure of FreeHandParserInternal used to stored
    a shape header
*/
struct ShapeHeader {
  //! constructor
  ShapeHeader()
    : m_size(0)
    , m_type(0)
    , m_note("")
    , m_dataId(0)
    , m_layerId(-1)
    , m_screen()
    , m_extra("")
  {
    for (auto &value : m_values) value=0;
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, ShapeHeader const &shape)
  {
    if (shape.m_layerId>=0) o << "layer=" << shape.m_layerId << ",";
    if (shape.m_dataId) o << "data=Z" << shape.m_dataId << ",";
    if (!shape.m_note.empty()) o << "note=\"" << shape.m_note << "\",";
    if (shape.m_screen.isSet()) o << "screen=[" << *shape.m_screen << "],";
    for (int i=0; i<3; ++i) {
      if (!shape.m_values[i])
        continue;
      int val=shape.m_values[i];
      if (i==1 && (val&1)) {
        o << "locked,";
        val&=0xFFFE;
      }
      if (val)
        o << "unkn" << i << "=" << val << ",";
    }
    if (shape.m_values[3]) // always a 1005 zone ?
      o << "unknZone=Z" << shape.m_values[3] << ",";
    o << shape.m_extra;
    return o;
  }
  //! a field related to the zone size
  long m_size;
  //! the zone type
  int m_type;
  //! the note
  std::string m_note;
  //! the data id (used to store a note, ...)
  int m_dataId;
  //! the layer id
  int m_layerId;
  //! the screen mode
  MWAWVariable<ScreenMode> m_screen;
  //! the unknown values
  int m_values[4];
  //! extra data
  std::string m_extra;
};

/** small structure of FreeHandParserInternal used to stored
    a fill style
*/
struct FillStyle {
  //! constructor
  FillStyle()
    : m_type(MWAWGraphicStyle::G_None)
    , m_pattern()
    , m_angle(0)
    , m_logarithm(false)
  {
    for (auto &colorId : m_colorId) colorId=0;
  }
  //! the gradient type
  MWAWGraphicStyle::GradientType m_type;
  //! the color id
  int m_colorId[2];
  //! the pattern
  MWAWGraphicStyle::Pattern m_pattern;
  //! the angle
  float m_angle;
  //! flag to know if a flag has logarithmic scale
  bool m_logarithm;
};

/** small structure of FreeHandParserInternal used to stored
    a line style
*/
struct LineStyle {
  //! constructor
  LineStyle()
    : m_width(1)
    , m_colorId(0)
    , m_dashId(0)
    , m_pattern()
    , m_miterLimit(0)
    , m_cap(MWAWGraphicStyle::C_Butt)
    , m_join(MWAWGraphicStyle::J_Miter)
  {
  }
  //! the line width
  float m_width;
  //! the color id
  int m_colorId;
  //! the dash id
  int m_dashId;
  //! the pattern
  MWAWGraphicStyle::Pattern m_pattern;
  //! the miter limit
  float m_miterLimit;
  //! the line cap
  MWAWGraphicStyle::LineCap m_cap;
  //! the line join
  MWAWGraphicStyle::LineJoin m_join;
};
/** small structure of FreeHandParserInternal used to stored
    a style header
*/
struct StyleHeader {
  //! constructor
  StyleHeader()
    : m_size(0)
    , m_type(0)
    , m_labelId(0)
    , m_screen()
    , m_unknownValue(0)
    , m_extra("")
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, StyleHeader const &style)
  {
    if (style.m_labelId) o << "label=Z" << style.m_labelId << ",";
    if (style.m_screen.isSet()) o << "screen=[" << *style.m_screen << "],";
    if (style.m_unknownValue)
      o << "unkn0=" << style.m_unknownValue << ",";
    o << style.m_extra;
    return o;
  }
  //! a field related to the zone size
  long m_size;
  //! the zone type
  int m_type;
  //! the label id
  int m_labelId;
  //! the screen mode
  MWAWVariable<ScreenMode> m_screen;
  //! the first unknown value
  int m_unknownValue;
  //! extra data
  std::string m_extra;
};

/** small structure of FreeHandParserInternal used to stored a shape
*/
struct Shape {
  //! the different type
  enum Type { Line, Rectangle, Ellipse, Path, BackgroundPicture, Picture, Group, JoinGroup, Unknown };

  //! constructor
  Shape()
    : m_id(0)
    , m_type(Unknown)
    , m_layerId(-1)
    , m_lineId(0)
    , m_fillId(0)
    , m_transformation()
    , m_box()
    , m_corner()
    , m_vertices()
    , m_closed(false)
    , m_evenOdd(false)
    , m_joinDistance(0)
    , m_childs()
    , m_picture()
    , m_dataId(0)
    , m_isSent(false)
  {
  }

  //! try to returns a shape and position
  bool updateShape(MWAWGraphicShape &shape) const
  {
    if (m_type==Line || m_type==Rectangle || m_type==Ellipse) {
      MWAWBox2f box=m_box;
      if (m_type==Line)
        shape=MWAWGraphicShape::line(box[0],box[1]);
      else if (m_type==Rectangle)
        shape=MWAWGraphicShape::rectangle(box, m_corner);
      else
        shape=MWAWGraphicShape::circle(box);
      return true;
    }
    if (m_type!=Path || m_vertices.empty()) return false;
    if (m_vertices.size()<6) {
      // probably an aborted spline, transform in a point
      MWAWVec2f pt=m_vertices[0];
      shape=MWAWGraphicShape::line(pt,pt);
      return true;
    }
    shape.m_type=MWAWGraphicShape::Polygon;
    MWAWBox2f box;
    bool needSpline=false;
    for (size_t i=0; i+2<m_vertices.size(); i+=3) {
      MWAWVec2f pt=m_vertices[i];
      if (i==0)
        box=MWAWBox2f(pt,pt);
      else
        box=box.getUnion(MWAWBox2f(pt,pt));
      if (!needSpline && (m_vertices[i]!=m_vertices[i+1] || m_vertices[i]!=m_vertices[i+2]))
        needSpline=true;
      shape.m_vertices.push_back(pt);
    }
    shape.m_bdBox=box;
    if (m_closed)
      shape.m_vertices.push_back(shape.m_vertices.front());
    if (!needSpline)
      return true;

    MWAWVec2f prevPoint, pt1;
    bool hasPrevPoint = false;
    shape.m_type=MWAWGraphicShape::Path;
    shape.m_vertices.clear();
    for (size_t i=0; i+2<m_vertices.size()+3; i+=3) {
      bool end=i+2>=m_vertices.size();
      if (end) {
        if (!m_closed)
          break;
        if (end && !hasPrevPoint && m_vertices[0]==m_vertices[1]) {
          shape.m_path.push_back(MWAWGraphicShape::PathData('Z'));
          break;
        }
        i=0;
      }
      MWAWVec2f pt=m_vertices[i];
      pt1 = m_vertices[i+1];
      char type = hasPrevPoint ? 'C' : i==0 ? 'M' : (m_vertices[i]!=m_vertices[i+1]) ? 'S' : 'L';
      shape.m_path.push_back(MWAWGraphicShape::PathData(type, pt, hasPrevPoint ? prevPoint : pt1, pt1));
      hasPrevPoint = m_vertices[i]!=m_vertices[i+2];
      if (hasPrevPoint)
        prevPoint=m_vertices[i+2];
      if (end)
        break;
    }
    return true;
  }
  //! the zone id
  int m_id;
  //! the type
  Type m_type;
  //! the layer
  int m_layerId;
  //! the line id
  int m_lineId;
  //! the fill id
  int m_fillId;

  //! the transformation
  MWAWTransformation m_transformation;
  //! the main box (for line, rectangle, ellipse)
  MWAWBox2f m_box;
  //! the corner size
  MWAWVec2f m_corner;
  //! the list of point for path: 3 Vec2f defining each point
  std::vector<MWAWVec2f> m_vertices;
  //! a flag to know if a path is closed
  bool m_closed;
  //! a flag to know how path intersection are defined
  bool m_evenOdd;
  //! the join distance
  float m_joinDistance;
  //! the list of child (for group and join group )
  std::vector<int> m_childs;
  //! the picture entry
  MWAWEntry m_picture;
  //! the id of a the picture date
  int m_dataId;
  //! flag to known if a shape is sent
  mutable bool m_isSent;
};

/** structure of FreeHandParserInternal used to stored a font */
struct Font {
  //! constructor
  Font()
    : m_font()
    , m_nameId(0)
    , m_colorId(0)
  {
  }
  //! the font
  MWAWFont m_font;
  //! the font name id
  int m_nameId;
  //! the font color id
  int m_colorId;
};

/** structure of FreeHandParserInternal used to stored a textbox */
struct Textbox {
  //! constructor
  explicit Textbox(int id)
    : m_id(id)
    , m_layerId(-1)
    , m_box()
    , m_transformation()
    , m_spacings(0,0)
    , m_scalings(1,1)
    , m_baseline(0)
    , m_justify(MWAWParagraph::JustificationLeft)
    , m_text()
    , m_posToFontMap()
    , m_isSent(false)
  {
  }
  //! the textbox id
  int m_id;
  //! the layer id
  int m_layerId;
  //! the main box
  MWAWBox2f m_box;
  //! the transformation
  MWAWTransformation m_transformation;
  //! the letter/word spacing
  MWAWVec2f m_spacings;
  //! the horizontal/vertical scalings
  MWAWVec2f m_scalings;
  //! the baseline
  float m_baseline;
  //! the paragraph justification
  MWAWParagraph::Justification m_justify;
  //! the text data
  MWAWEntry m_text;
  //! map char pos to font
  std::map<int,Font> m_posToFontMap;
  //! flag to known if a shape is sent
  mutable bool m_isSent;
};

////////////////////////////////////////
//! Internal: the state of a FreeHandParser
struct State {
  //! constructor
  State()
    : m_mainGroupId(0)
    , m_transform()
    , m_zIdToTypeMap()
    , m_zIdToColorMap()
    , m_zIdToDashMap()
    , m_zIdToDataMap()
    , m_zIdToFillStyleMap()
    , m_zIdToLineStyleMap()
    , m_zIdToStringMap()
    , m_zIdToPostscriptMap()
    , m_zIdToShapeMap()
    , m_zIdToTextboxMap()
    , m_actualLayer(-1)
    , m_sendIdSet()
    , m_sendLayerSet()
  {
  }
  //! try to return a zone type
  ZoneType getZoneType(int id) const
  {
    if (m_zIdToTypeMap.find(id)==m_zIdToTypeMap.end())
      return Z_Unknown;
    return m_zIdToTypeMap.find(id)->second;
  }
  //! try to add a id
  bool addZoneId(int id, ZoneType zoneType)
  {
    if (m_zIdToTypeMap.find(id)!=m_zIdToTypeMap.end())
      return m_zIdToTypeMap.find(id)->second==zoneType;
    m_zIdToTypeMap[id]=zoneType;
    return true;
  }
  //! try to update the fill style
  bool updateFillStyle(int zId, MWAWGraphicStyle &style) const;
  //! try to update the line style
  bool updateLineStyle(int zId, MWAWGraphicStyle &style) const;
  //! try to update the group layer id, return 0 or the new layer id
  int updateGroupLayerId(int zId, std::set<int> &seen);
  //! the main group id
  int m_mainGroupId;
  //! the main transformation
  MWAWTransformation m_transform;
  //! the list of id seen
  std::map<int, ZoneType> m_zIdToTypeMap;
  //! the list zoneId to color
  std::map<int, MWAWColor> m_zIdToColorMap;
  //! the list zoneId to dash
  std::map<int, std::vector<float> > m_zIdToDashMap;
  //! the list zoneId to data map
  std::map<int, MWAWEntry> m_zIdToDataMap;
  //! the list zoneId to fillStyle
  std::map<int, FillStyle> m_zIdToFillStyleMap;
  //! the list zoneId to lineStyle
  std::map<int, LineStyle> m_zIdToLineStyleMap;
  //! the list zoneId to string
  std::map<int, std::string> m_zIdToStringMap;
  //! the list zoneId to postscrip code
  std::map<int, std::string> m_zIdToPostscriptMap;
  //! the list zoneId to shape
  std::map<int, Shape> m_zIdToShapeMap;
  //! the list zoneId to textbox
  std::map<int, Textbox> m_zIdToTextboxMap;
  //! the actual layer
  int m_actualLayer;
  //! a set of send id used to avoid potential loop
  std::set<int> m_sendIdSet;
  //! a set of create layer to avoid dupplicating layer
  std::set<int> m_sendLayerSet;
};

bool State::updateFillStyle(int zId, MWAWGraphicStyle &style) const
{
  if (!zId) return true;
  // can be a simple color
  if (m_zIdToColorMap.find(zId)!=m_zIdToColorMap.end()) {
    style.setSurfaceColor(m_zIdToColorMap.find(zId)->second);
    return true;
  }
  if (m_zIdToFillStyleMap.find(zId)==m_zIdToFillStyleMap.end()) {
    static bool first=true;
    if (first) {
      first=false;
      MWAW_DEBUG_MSG(("FreeHandParserInternal::State::updateFillStyle: can not find style %d\n", zId));
    }
    return false;
  }
  FillStyle const &fill=m_zIdToFillStyleMap.find(zId)->second;
  int numColors=fill.m_type==MWAWGraphicStyle::G_None ? 1 : 2;
  MWAWColor colors[2];
  for (int i=0; i<numColors; ++i) {
    if (fill.m_colorId[i]==0) {
      colors[i]=MWAWColor::white();
      continue;
    }
    if (m_zIdToColorMap.find(fill.m_colorId[i])==m_zIdToColorMap.end()) {
      MWAW_DEBUG_MSG(("FreeHandParserInternal::State::updateFillStyle: can not find some color %d\n", fill.m_colorId[i]));
      return false;
    }
    colors[i]=m_zIdToColorMap.find(fill.m_colorId[i])->second;
  }
  if (!fill.m_pattern.empty()) {
    auto pat=fill.m_pattern;
    pat.m_colors[0]=MWAWColor::white();
    pat.m_colors[1]=colors[0];
    style.setPattern(pat);
    return true;
  }
  if (fill.m_type==MWAWGraphicStyle::G_None) {
    style.setSurfaceColor(colors[0]);
    return true;
  }
  style.m_gradientType = fill.m_type;
  style.m_gradientAngle = 270.f-fill.m_angle;
  style.m_gradientStopList.resize(2);
  for (size_t i=0; i<2; ++i)
    style.m_gradientStopList[i]=MWAWGraphicStyle::GradientStop(float(i), colors[i]);
  return true;
}

bool State::updateLineStyle(int zId, MWAWGraphicStyle &style) const
{
  if (!zId) {
    style.m_lineWidth=0;
    return true;
  }
  if (m_zIdToLineStyleMap.find(zId)==m_zIdToLineStyleMap.end()) {
    MWAW_DEBUG_MSG(("FreeHandParserInternal::State::updateLineStyle: can not find style %d\n", zId));
    style.m_lineWidth=1;
    return false;
  }
  LineStyle const &line=m_zIdToLineStyleMap.find(zId)->second;
  style.m_lineWidth=line.m_width;
  MWAWColor color=MWAWColor::white();
  if (line.m_colorId) {
    if (m_zIdToColorMap.find(line.m_colorId)==m_zIdToColorMap.end()) {
      static bool first=true;
      if (first) {
        first=false;
        MWAW_DEBUG_MSG(("FreeHandParserInternal::State::updateLineStyle: can not find some color %d\n", line.m_colorId));
      }
    }
    else
      color=m_zIdToColorMap.find(line.m_colorId)->second;
  }
  if (!line.m_pattern.empty()) {
    auto pat=line.m_pattern;
    // CHECKME
    pat.m_colors[0]=MWAWColor::white();
    pat.m_colors[1]=color;
    pat.getAverageColor(style.m_lineColor);
  }
  else
    style.m_lineColor=color;
  if (line.m_dashId) {
    if (m_zIdToDashMap.find(line.m_dashId)==m_zIdToDashMap.end()) {
      MWAW_DEBUG_MSG(("FreeHandParserInternal::State::updateLineStyle: can not find dash %d\n", line.m_dashId));
    }
    else if (m_zIdToDashMap.find(line.m_dashId)->second.size()>1)
      style.m_lineDashWidth=m_zIdToDashMap.find(line.m_dashId)->second;
  }
  style.m_lineCap=line.m_cap;
  style.m_lineJoin=line.m_join;
  return true;
}

int State::updateGroupLayerId(int zId, std::set<int> &seen)
{
  if (m_zIdToTextboxMap.find(zId)!=m_zIdToTextboxMap.end())
    return m_zIdToTextboxMap.find(zId)->second.m_layerId;
  if (m_zIdToShapeMap.find(zId)==m_zIdToShapeMap.end())
    return -1;
  Shape &shape=m_zIdToShapeMap.find(zId)->second;
  if (seen.find(zId)!=seen.end() || (shape.m_type!=Shape::Group && shape.m_type!=Shape::JoinGroup))
    return shape.m_layerId;
  int layerId=-1;
  seen.insert(zId);
  bool first=true;
  for (auto &child : shape.m_childs) {
    int newLayerId=updateGroupLayerId(child, seen);
    if (newLayerId==-1 || (!first && layerId!=newLayerId))
      layerId=-1;
    else
      layerId=newLayerId;
    first=false;
  }
  shape.m_layerId=layerId;
  seen.erase(zId);
  return layerId;
}

////////////////////////////////////////
//! Internal: the subdocument of a FreeHandParser
class SubDocument final : public MWAWSubDocument
{
public:
  SubDocument(FreeHandParser &pars, MWAWInputStreamPtr const &input, int zoneId)
    : MWAWSubDocument(&pars, input, MWAWEntry())
    , m_id(zoneId) {}

  //! destructor
  ~SubDocument() final {}

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

  //! the parser function
  void parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType type) final;

protected:
  //! the subdocument id
  int m_id;
private:
  SubDocument(SubDocument const &orig);
  SubDocument &operator=(SubDocument const &orig);
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType)
{
  if (!listener || !listener->canWriteText()) {
    MWAW_DEBUG_MSG(("FreeHandParserInternal::SubDocument::parse: no listener\n"));
    return;
  }
  auto *parser=dynamic_cast<FreeHandParser *>(m_parser);
  if (!parser) {
    MWAW_DEBUG_MSG(("FreeHandParserInternal::SubDocument::parse: no parser\n"));
    return;
  }
  long pos = m_input->tell();
  parser->sendText(m_id);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}

}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
FreeHandParser::FreeHandParser(MWAWInputStreamPtr const &input, MWAWRSRCParserPtr const &rsrcParser, MWAWHeader *header)
  : MWAWGraphicParser(input, rsrcParser, header)
  , m_state()
{
  init();
}

FreeHandParser::~FreeHandParser()
{
}

void FreeHandParser::init()
{
  resetGraphicListener();
  setAsciiName("main-1");

  m_state.reset(new FreeHandParserInternal::State);

  getPageSpan().setMargins(0.1);
}

////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void FreeHandParser::parse(librevenge::RVNGDrawingInterface *docInterface)
{
  if (!getInput().get() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = false;
  try {
    // create the asciiFile
    ascii().setStream(getInput());
    ascii().open(asciiName());
    checkHeader(nullptr);
    ok = createZones();
    if (ok) {
      createDocument(docInterface);
      sendZone(m_state->m_mainGroupId, m_state->m_transform);
#ifdef DEBUG
      flushExtra();
#endif
    }
    ascii().reset();
  }
  catch (...) {
    MWAW_DEBUG_MSG(("FreeHandParser::parse: exception catched when parsing\n"));
    ok = false;
  }

  resetGraphicListener();
  if (!ok) throw(libmwaw::ParseException());
}

////////////////////////////////////////////////////////////
// create the document
////////////////////////////////////////////////////////////
void FreeHandParser::createDocument(librevenge::RVNGDrawingInterface *documentInterface)
{
  if (!documentInterface) return;
  if (getGraphicListener()) {
    MWAW_DEBUG_MSG(("FreeHandParser::createDocument: listener already exist\n"));
    return;
  }

  // create the page list
  MWAWPageSpan ps(getPageSpan());
  ps.setPageSpan(1);
  std::vector<MWAWPageSpan> pageList(1,ps);
  MWAWGraphicListenerPtr listen(new MWAWGraphicListener(*getParserState(), pageList, documentInterface));
  setGraphicListener(listen);
  listen->startDocument();
}

////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool FreeHandParser::createZones()
{
  MWAWInputStreamPtr input = getInput();
  long pos;
  libmwaw::DebugStream f;
  bool readSome=false;
  int zId=1;
  int const vers=version();
  if (vers==2) {
    pos=input->tell();
    libmwaw::PrinterInfo info;
    if (!info.read(input)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      if (input->readULong(4)==0) { // null print info is ok
        ascii().addPos(pos);
        ascii().addNote("_");
        input->seek(pos+0x78, librevenge::RVNG_SEEK_SET);
      }
      else
        input->seek(pos, librevenge::RVNG_SEEK_SET);
    }
    else {
      f.str("");
      f << "Entries(PrintInfo):" << info;
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
    }
  }
  while (!input->isEnd()) {
    pos=input->tell();
    while ((vers==1 && readZoneV1(zId)) || (vers==2 && readZoneV2(zId))) {
      readSome=true;
      pos=input->tell();
      if (zId) ++zId;
    }
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    if (!input->checkPosition(pos+5))
      break;
    // ok try to continue
    bool ok=true;
    zId=0;
    while (!input->isEnd()) {
      auto val=static_cast<unsigned long>(input->readULong(4));
      if (input->isEnd()) {
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        ok=false;
        break;
      }
      if (!val || (val&0xFF00)) {
        input->seek(-1, librevenge::RVNG_SEEK_CUR);
        continue;
      }
      if (val&0xFF0000) {
        input->seek(-2, librevenge::RVNG_SEEK_CUR);
        continue;
      }
      if (val&0xFF000000) {
        input->seek(-3, librevenge::RVNG_SEEK_CUR);
        continue;
      }
      input->seek(-4, librevenge::RVNG_SEEK_CUR);
      long actPos=input->tell();
      if ((vers==1 && readZoneV1(zId)) || (vers==2 && readZoneV2(zId))) {
        if (pos!=actPos) {
          MWAW_DEBUG_MSG(("FreeHandParser::createZones: find some unexpected data\n"));
          ascii().addPos(pos);
          ascii().addNote("Entries(Unknown):###");
        }
        break;
      }
      input->seek(actPos+4, librevenge::RVNG_SEEK_SET);
      continue;
    }
    if (!ok) break;
  }
  pos=input->tell();
  f.str("");
  f << "Entries(End):";
  if (input->readLong(4)!=-1) {
    MWAW_DEBUG_MSG(("FreeHandParser::createZones: find unexpected end data\n"));
    f << "###";
  }
  if (readSome && m_state->m_mainGroupId) {
    std::set<int> seen;
    m_state->updateGroupLayerId(m_state->m_mainGroupId, seen);
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return readSome;
}

bool FreeHandParser::readZoneV1(int zId)
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  if (!input->checkPosition(pos+5))
    return false;
  auto val=static_cast<int>(input->readULong(4));
  if (val < 0 || (static_cast<unsigned long>(val)&0xFF000000)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  auto type=static_cast<int>(input->readULong(2));
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  switch (type) {
  // does type=1 exist ?
  case 2:
    return readStyleGroup(zId);
  case 3:
    return readStringZone(zId);

  // 4001-4002
  case 0xfa1:
    return readRootGroup(zId);
  case 0xfa2:
    return readGroupV1(zId);

  // 4101-4104
  case 0x1005:
    return readTransformGroup(zId);
  case 0x1006:
    return readTextboxV1(zId);
  case 0x1007:
    return readBackgroundPicture(zId);
  case 0x1008:
    return readJoinGroup(zId);

  // 4202-4204
  // does type=0x1069 exist ?
  case 0x106a:
  case 0x106b:
  case 0x106c:
    return readColor(zId);

  // 4301-4305
  case 0x10cd:
    return readFillStyle(zId);
  case 0x10ce:
    return readLineStyle(zId);
  case 0x10cf:
    return readPostscriptStyle(zId);
  case 0x10d0:
  case 0x10d1:
    return readFillStyle(zId);

  // 4401-4405
  case 0x1131: // rectangle
  case 0x1132: // ellipse
  // does type=0x1133 exist ?
  case 0x1134: // spline
  case 0x1135: // line
    return readShape(zId);

  // 4501
  case 0x1195:
    return readDash(zId);
  default:
    break;
  }
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  return false;
}

bool FreeHandParser::readZoneV2(int zId)
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  if (!input->checkPosition(pos+5))
    return false;
  auto val=static_cast<int>(input->readULong(4));
  if (val < 0 || (static_cast<unsigned long>(val)&0xFF000000)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  auto type=static_cast<int>(input->readULong(2));
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  switch (type) {
  case 5: // style group
    return readStyleGroup(zId);
  case 6:
    return readStringZone(zId);
  case 0x1389:
    return readRootGroup(zId);
  case 0x138a:
    return readGroupV2(zId);
  case 0x138b:
    return readDataZone(zId);
  case 0x13ed:
    return readTransformGroup(zId);
  case 0x13ee:
    return readTextboxV2(zId);
  case 0x13f0:
    return readJoinGroup(zId);
  case 0x13f8:
    return readPictureZone(zId);
  case 0x1452: // basic
  case 0x1453: // tint
  case 0x1454: // cmyk
  case 0x1455: // pantome ?
    return readColor(zId);
  case 0x14b5: // basic
    return readFillStyle(zId);
  case 0x14b6: // line style
    return readLineStyle(zId);
  case 0x14b7: // gradient linear
  case 0x14b8: // radial
    return readFillStyle(zId);
  case 0x14c9: // line, always follow 14d3
  case 0x14ca: // surf, always follow 14d4
    return readPostscriptStyle(zId);
  case 0x14d3: // pattern
    return readFillStyle(zId);
  case 0x14d4: // pattern line style
    return readLineStyle(zId);
  case 0x14dd: // tile style
    return readFillStyle(zId);
  case 0x1519: // rectangle
  case 0x151a: // ellipse
    return readShape(zId);
  case 0x151c:
    return readShape(zId);
  case 0x151d: // line
    return readShape(zId);
  case 0x157d:
    return readDash(zId);
  default:
    break;
  }
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  return false;
}

////////////////////////////////////////////////////////////
// read the header
////////////////////////////////////////////////////////////
bool FreeHandParser::checkHeader(MWAWHeader *header, bool strict)
{
  *m_state = FreeHandParserInternal::State();
  MWAWInputStreamPtr input = getInput();
  if (!input || !input->hasDataFork() || !input->checkPosition(128))
    return false;

  libmwaw::DebugStream f;
  f << "FileHeader:";
  input->seek(0, librevenge::RVNG_SEEK_SET);
  auto signature=long(input->readULong(4));
  int vers=1, val;
  if (signature==0x61636633) {
    val=static_cast<int>(input->readULong(2)); // the subversion?
    if (strict && val>=9) return false;
    if (val!=5)
      f << "f0=" << val << ",";
  }
  else if (signature==0x46484432) {
    if (!input->checkPosition(256))
      return false;
    vers=2;
    val=static_cast<int>(input->readULong(2)); // the subversion?
    if (strict && val>20) return false;
    if (val!=9)
      f << "f0=" << val << ",";
  }
  else
    return false;
  val=static_cast<int>(input->readULong(2));
  if (val!=100) f << "f1=" << val << ",";
  float dim[8];
  for (auto &d : dim) d=float(input->readLong(2))/10.f;
  f << "page[sz]=" << MWAWVec2f(dim[0],dim[1]) << ",";
  f << "paper[sz]=" << MWAWVec2f(dim[2],dim[3]) << ",";
  if (dim[4]>0 || dim[5]>0)
    f << "unkn[sz]=" << MWAWVec2f(dim[4],dim[5]) << ",";
  f << "margins=" << MWAWVec2f(dim[6],dim[7]) << ",";
  if (vers>1) {
    ascii().addDelimiter(input->tell(),'|');
    input->seek(30, librevenge::RVNG_SEEK_CUR);
    for (int i=0; i<3; ++i) {
      val=static_cast<int>(input->readULong(2));
      if (!val) continue;
      // checkme: odd
      if (i==0 && (val&0x20) && dim[0]>dim[1]) {
        f << "landscape,";
        getPageSpan().setFormOrientation(MWAWPageSpan::LANDSCAPE);
        for (int j=0; j<4; ++j) {
          if (j==1) continue;
          std::swap(dim[2*j],dim[2*j+1]);
        }
        val &= 0xFFDF;
      }
      if (val)
        f << i+2 << "=" << std::hex << val << std::dec << ",";
    }
  }
  else {
    for (int i=0; i<2; ++i) { // f2=1|2|a
      val=static_cast<int>(input->readULong(2));
      if (!val) continue;
      if (i==1) {
        if (val&1) {
          f << "landscape,";
          getPageSpan().setFormOrientation(MWAWPageSpan::LANDSCAPE);
          for (int j=0; j<4; ++j)
            std::swap(dim[2*j],dim[2*j+1]);
        }
        if (val&2) f << "crop[mark],";
        if (val&4) f << "center[mark],";
        if (val&8) f << "separation[name],";
        if (val&0x10) f << "file[name&date],";
        if (val&0x40) f << "include[processColor],";
        if (val&0x80) f << "display[quality]=better,";
        if (val&0x100) f << "print[quality]=better,";
        val &= 0xFE20;
      }
      if (val)
        f << "f" << i+2 << "=" << std::hex << val << std::dec << ",";
    }
  }
  if (dim[2]>0 && dim[3]>0) {
    getPageSpan().setFormLength(double(dim[2])/72.);
    getPageSpan().setFormWidth(double(dim[3])/72.);
    if (dim[0]+dim[6]<=dim[2]) {
      getPageSpan().setMarginBottom(double(dim[6])/72.0);
      getPageSpan().setMarginTop(double(dim[2]-dim[0]-dim[6])/72.0);
    }
    else {
      MWAW_DEBUG_MSG(("FreeHandParser::checkHeader: the vertical margins seems bad\n"));
      if (dim[0]<=dim[2]) {
        getPageSpan().setMarginBottom(double(dim[2]-dim[0])/2.0/72.0);
        getPageSpan().setMarginTop(double(dim[2]-dim[0])/2.0/72.0);
      }
    }
    if (dim[1]+dim[7]<=dim[3]) {
      getPageSpan().setMarginRight(double(dim[7])/72.0);
      getPageSpan().setMarginLeft(double(dim[3]-dim[1]-dim[7])/72.0);
    }
    else {
      MWAW_DEBUG_MSG(("FreeHandParser::checkHeader: the horizontal margins seems bad\n"));
      if (dim[1]<=dim[3]) {
        getPageSpan().setMarginLeft(double(dim[3]-dim[1])/2.0/72.0);
        getPageSpan().setMarginRight(double(dim[3]-dim[1])/2.0/72.0);
      }
    }
  }
  else {
    if (strict)
      return false;
    MWAW_DEBUG_MSG(("FreeHandParser::checkHeader: the paper size seems bad\n"));
  }
  // transform orig from page content LeftBot -> origin form page LeftTop
  m_state->m_transform=
    MWAWTransformation::translation(72.f*MWAWVec2f(float(getPageSpan().getMarginLeft()),float(getPageSpan().getPageLength()+getPageSpan().getMarginTop()))) *
    MWAWTransformation::scale(MWAWVec2f(1,-1));
  if (vers==1) {
    val=static_cast<int>(input->readULong(4));
    switch ((val>>29)) {
    case 0: // point
      break;
    case 1:
      f << "unit=picas,";
      break;
    case 2:
      f << "unit=inches,";
      break;
    case 3:
      f << "unit=decimal[inches],";
      break;
    case 4:
      f << "unit=millimeters,";
      break;
    default:
      MWAW_DEBUG_MSG(("FreeHandParser::checkHeader: find unknown unit\n"));
      f << "##units=" << ((val>>29)&7) << ",";
    }
    val &= 0x1FFF;
    if (val) f << "grid[size]=" << float(val)/65536.f/10.f << ",";
    for (int i=0; i<4; ++i) { // f4=0|200
      val=static_cast<int>(input->readULong(2));
      if (val)
        f << "f" << i+4 << "=" << std::hex << val << std::dec << ",";
    }
  }

  for (int i=0; i<2; ++i) {
    // checkme no sure what are limit
    long actPos=input->tell();
    auto sSz=static_cast<int>(input->readULong(1));
    if (sSz>31) {
      if (strict) return false;
      MWAW_DEBUG_MSG(("FreeHandParser::checkHeader: string size %d seems bad\n", i));
      f << "##sSz,";
      sSz=0;
    }
    std::string name;
    for (int s=0; s<sSz; ++s) name+=char(input->readULong(1));
    if (!name.empty()) {
      char const *wh[]= {"printer", "paper"};
      f << wh[i] << "=" << name << ",";
    }
    input->seek(actPos+32, librevenge::RVNG_SEEK_SET);
  }
  if (vers==1) {
    for (int i=0; i<5; ++i) { // g0=0|41, g1=0|3-7, g3=0|20, g4=0|b4
      val=static_cast<int>(input->readULong(2));
      if (val)
        f << "g" << i << "=" << std::hex << val << std::dec << ",";
    }
    // big number
    f << "unkn=" << std::hex << input->readULong(4) << std::dec << ",";
    for (int i=0; i<5; ++i) { // always 0
      val=static_cast<int>(input->readULong(2));
      if (val)
        f << "h" << i << "=" << val << ",";
    }
  }
  else {
    ascii().addDelimiter(input->tell(),'|');
    f << "unkn=" << std::hex << input->readULong(4) << std::dec << ",";
    // always 0
    for (int i=0; i<64; ++i) {
      val=static_cast<int>(input->readULong(2));
      if (val)
        f << "h" << i << "=" << val << ",";
    }
    // check for printer info or null printer info
    if (strict) {
      libmwaw::PrinterInfo info;
      if (!info.read(input)) {
        input->seek(256, librevenge::RVNG_SEEK_SET);
        if (input->readULong(4))
          return false;
      }
    }
  }
  ascii().addPos(0);
  ascii().addNote(f.str().c_str());
  setVersion(vers);
  if (header)
    header->reset(MWAWDocument::MWAW_T_FREEHAND, vers, MWAWDocument::MWAW_K_DRAW);
  input->seek(vers==1 ? 128 : 256, librevenge::RVNG_SEEK_SET);

  return true;
}

////////////////////////////////////////////////////////////
// try to read the zone
////////////////////////////////////////////////////////////
bool FreeHandParser::readRootGroup(int zId)
{
  if ((zId && zId!=1) || !m_state->m_zIdToTypeMap.empty())
    return false;
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  int const vers=version();
  if (!input->checkPosition(pos+(vers==1 ? 24 : 34)))
    return false;
  f.str("");
  f << "Entries(Root):";
  auto dSz=static_cast<int>(input->readULong(4)); // probably related to size
  auto opCode=static_cast<int>(input->readULong(2));
  if ((vers==1 && opCode !=0xfa1) || (vers>1 && opCode != 0x1389))
    return false;
  if (vers>1) dSz-=4;
  if (dSz!=0x34) {
    MWAW_DEBUG_MSG(("FreeHandParser::readRootGroup: find unexpected zone size\n"));
    f << "#sz?=" << dSz << ",";
  }
  if (vers==1) {
    for (int i=0; i<2; ++i) { // always 0 ?
      auto val=static_cast<int>(input->readLong(2));
      if (!val) continue;
      MWAW_DEBUG_MSG(("FreeHandParser::readRootGroup: find unknown zone %d\n", i));
      f << "#f" << i << "=" << val << ",";
    }
  }
  auto id=static_cast<int>(input->readLong(2));
  if (id) {
    m_state->m_mainGroupId=id;
    m_state->addZoneId(id, FreeHandParserInternal::Z_Group);
    f << "main=Z" << id << ",";
  }
  if (vers==1) {
    for (int i=0; i<6; ++i) {
      id=static_cast<int>(input->readLong(2));
      if (!id) continue;
      // the first group is a style group, but I never find any child, so...
      FreeHandParserInternal::ZoneType type[6]= {
        FreeHandParserInternal::Z_StyleGroup, FreeHandParserInternal::Z_FillGroup, FreeHandParserInternal::Z_LineStyleGroup,
        FreeHandParserInternal::Z_ColorGroup, FreeHandParserInternal::Z_DashGroup, FreeHandParserInternal::Z_ColorGroup
      };
      if (!m_state->addZoneId(id, type[i])) {
        MWAW_DEBUG_MSG(("FreeHandParser::readRootGroup: find dupplicated id\n"));
        f << "###";
      }
      char const *wh[6]= { "groupStyle0", "fillStyle", "lineStyle", "colStyle", "dashStyle", "colStyle2" };
      f << wh[i] << "=Z" << id << ",";
    }
  }
  else {
    // at least 8, maybe more
    for (int i=0; i<8; ++i) {
      id=static_cast<int>(input->readLong(2));
      if (!id) continue;
      FreeHandParserInternal::ZoneType type[8]= {
        FreeHandParserInternal::Z_ColorGroup, FreeHandParserInternal::Z_FillGroup, FreeHandParserInternal::Z_LineStyleGroup,
        FreeHandParserInternal::Z_StyleGroup, FreeHandParserInternal::Z_FillGroup, FreeHandParserInternal::Z_LineStyleGroup,
        FreeHandParserInternal::Z_DashGroup, FreeHandParserInternal::Z_ColorGroup,
      };
      if (!m_state->addZoneId(id, type[i])) {
        MWAW_DEBUG_MSG(("FreeHandParser::readRootGroup: find dupplicated id\n"));
        f << "###";
      }
      char const *wh[8]= { "colStyle", "fillStyle", "lineStyle", "groupStyle3", "fillStyle[unamed]", "lineStyle[unamed]", "dashStyle", "colStyle2"};
      f << wh[i] << "=Z" << id << ",";
    }
    for (int i=0; i<5; ++i) {
      auto val=static_cast<int>(input->readULong(2));
      if (!val) continue;
      MWAW_DEBUG_MSG(("FreeHandParser::readRootGroup: find unknown group id\n"));
      f << "###Z" << val << ",";
    }
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  return true;
}

bool FreeHandParser::readGroupV1(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  if (!input->checkPosition(pos+20))
    return false;
  f.str("");
  if (zId)
    f << "Entries(Group)[Z" << zId << "]:";
  else
    f << "Entries(Group):";
  if (zId) {
    auto type=m_state->getZoneType(zId);
    if (type!=FreeHandParserInternal::Z_Group && type!=FreeHandParserInternal::Z_Shape) {
      MWAW_DEBUG_MSG(("FreeHandParser::readGroupV1: find unexpected zone type for zone %d\n", zId));
    }
  }

  auto dSz=static_cast<int>(input->readULong(4)); // probably related to size
  f << "sz=" << dSz << ",";
  if (input->readULong(2)!=0xfa2) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  FreeHandParserInternal::Shape res;
  res.m_id=zId;
  res.m_type=FreeHandParserInternal::Shape::Group;
  ascii().addDelimiter(input->tell(),'|');
  input->seek(pos+18, librevenge::RVNG_SEEK_SET);
  ascii().addDelimiter(input->tell(),'|');
  auto N=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(pos+20+2*N)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << "childs=[";
  for (int i=0; i<N; ++i) {
    auto id=static_cast<int>(input->readULong(2));
    if (!m_state->addZoneId(id, FreeHandParserInternal::Z_Shape)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    res.m_childs.push_back(id);
    f << "Z" << id << ",";
  }
  f <<"],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  if (zId && m_state->m_zIdToShapeMap.find(zId)==m_state->m_zIdToShapeMap.end())
    m_state->m_zIdToShapeMap.insert(std::map<int,FreeHandParserInternal::Shape>::value_type(zId,res));
  return true;
}

bool FreeHandParser::readGroupV2(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  if (!input->checkPosition(pos+20))
    return false;
  f.str("");
  if (zId)
    f << "Entries(Group)[Z" << zId << "]:";
  else
    f << "Entries(Group):";
  if (zId) {
    auto type=m_state->getZoneType(zId);
    if (type!=FreeHandParserInternal::Z_Group && type!=FreeHandParserInternal::Z_Shape) {
      MWAW_DEBUG_MSG(("FreeHandParser::readGroupV2: find unexpected zone type for zone %d\n", zId));
    }
  }

  auto dSz=static_cast<int>(input->readULong(4)); // probably related to size
  f << "sz=" << dSz << ",";
  if (input->readULong(2)!=0x138a) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  FreeHandParserInternal::Shape res;
  res.m_id=zId;
  res.m_type=FreeHandParserInternal::Shape::Group;
  for (int i=0; i<2; ++i) { // always 0
    auto val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  auto sSz=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(input->tell()+sSz+8)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  ascii().addDelimiter(input->tell(),'|');
  /* find
     00000000000000000000000000000000
     000f000a000000000000000000000000a89d800180017fff7fff7fff7fff7fff7fff7fff7fff7fff7fff4ead7fff80017fff52807fff7fff7fff60144ead8fff670a
   */
  input->seek(sSz, librevenge::RVNG_SEEK_CUR);
  ascii().addDelimiter(input->tell(),'|');
  float dim[2];
  for (auto &d : dim) d=float(input->readLong(2))/10.f;
  if (MWAWVec2f(dim[0],dim[1])!=MWAWVec2f(0,0))
    f << "dim?=" << MWAWVec2f(dim[0],dim[1]) << ",";
  auto val=static_cast<int>(input->readLong(2));
  if (val) f << "f2=" << val << ",";

  auto N=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(input->tell()+2*N)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << "childs=[";
  for (int i=0; i<N; ++i) {
    auto id=static_cast<int>(input->readULong(2));
    if (!m_state->addZoneId(id, FreeHandParserInternal::Z_Shape)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    res.m_childs.push_back(id);
    f << "Z" << id << ",";
  }
  f <<"],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  if (zId && m_state->m_zIdToShapeMap.find(zId)==m_state->m_zIdToShapeMap.end())
    m_state->m_zIdToShapeMap.insert(std::map<int,FreeHandParserInternal::Shape>::value_type(zId,res));
  return true;
}

bool FreeHandParser::readJoinGroup(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  FreeHandParserInternal::ShapeHeader shape;
  int const vers=version();
  if (!readShapeHeader(shape)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if ((vers==1 && shape.m_type!=0x1008) || (vers>1 && shape.m_type!=0x13f0)
      || !input->checkPosition(input->tell()+8)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f.str("");
  if (zId)
    f << "Entries(JoinGrp)[Z" << zId << "]:" << shape;
  else
    f << "Entries(JoinGrp):" << shape;
  if (zId) {
    auto type=m_state->getZoneType(zId);
    if (type!=FreeHandParserInternal::Z_Shape) {
      MWAW_DEBUG_MSG(("FreeHandParser::readJoinGroup: find unexpected zone type for zone %d\n", zId));
    }
  }
  FreeHandParserInternal::Shape res;
  res.m_id=zId;
  res.m_layerId=shape.m_layerId;
  res.m_type=FreeHandParserInternal::Shape::JoinGroup;
  if (shape.m_size!=0x24) f << "sz?=" << shape.m_size << ",";
  res.m_joinDistance=float(input->readLong(4))/65536.f;
  if (res.m_joinDistance<0 || res.m_joinDistance>0)
    f << "dist=" << res.m_joinDistance << ",";
  f << "childs=[";
  for (int i=0; i<2; ++i) {
    auto id=static_cast<int>(input->readULong(2));
    if (!m_state->addZoneId(id, FreeHandParserInternal::Z_Shape)) {
      MWAW_DEBUG_MSG(("FreeHandParser::readJoinGroup: find unexpected child id\n"));
      f << "###";
    }
    res.m_childs.push_back(id);
    f << "Z" << id << ",";
  }
  f <<"],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  if (zId && m_state->m_zIdToShapeMap.find(zId)==m_state->m_zIdToShapeMap.end())
    m_state->m_zIdToShapeMap.insert(std::map<int,FreeHandParserInternal::Shape>::value_type(zId,res));
  static bool first=true;
  if (first) {
    MWAW_DEBUG_MSG(("FreeHandParser::readJoinGroup: Ooops, sending text on path is unimplemented\n"));
    first=false;
  }
  return true;
}

bool FreeHandParser::readTransformGroup(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  FreeHandParserInternal::ShapeHeader shape;
  if (!readShapeHeader(shape)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  int const vers=version();
  if ((vers==1 && shape.m_type!=0x1005) || (vers>1 && shape.m_type!=0x13ed) || !input->checkPosition(input->tell()+30)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f.str("");
  if (zId)
    f << "Entries(TransformGrp)[Z" << zId << "]:" << shape;
  else
    f << "Entries(TransformGrp):" << shape;
  if (shape.m_size!=0x38) f << "sz?=" << shape.m_size << ",";
  if (zId) {
    auto zType=m_state->getZoneType(zId);
    if (zType!=FreeHandParserInternal::Z_Group && zType!=FreeHandParserInternal::Z_Shape) {
      MWAW_DEBUG_MSG(("FreeHandParser::readTransformGroup: find unexpected zone type for zone %d\n", zId));
    }
  }
  FreeHandParserInternal::Shape res;
  res.m_id=zId;
  res.m_layerId=shape.m_layerId;
  res.m_type=FreeHandParserInternal::Shape::Group;
  auto id=static_cast<int>(input->readULong(2));
  if (!m_state->addZoneId(id, FreeHandParserInternal::Z_Group)) {
    MWAW_DEBUG_MSG(("FreeHandParser::readTransformGroup: find unexpected child id\n"));
    f << "###";
  }
  f << "child=Z" << id << ",";
  res.m_childs.push_back(id);
  auto val=static_cast<int>(input->readULong(2)); // always 0
  if (val) f << "f0=" << val << ",";
  f << "flags=" << std::hex << input->readULong(2) << std::dec << ",";
  f << "rot=[";
  float dim[6];
  for (int i=0; i<4; ++i) {
    dim[i]=float(input->readLong(4))/65536.f;
    f << dim[i] << ",";
  }
  f << "],";
  f << "trans=[";
  for (int i=0; i<2; ++i) {
    dim[i+4]=float(input->readLong(4))/65536.f/10.f;
    f << dim[i+4] << ",";
  }
  f << "],";
  res.m_transformation=MWAWTransformation(MWAWVec3f(dim[0],dim[2],dim[4]),MWAWVec3f(dim[1],dim[3],dim[5]));

  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  if (zId && m_state->m_zIdToShapeMap.find(zId)==m_state->m_zIdToShapeMap.end())
    m_state->m_zIdToShapeMap.insert(std::map<int,FreeHandParserInternal::Shape>::value_type(zId,res));
  return true;
}

bool FreeHandParser::readStyleGroup(int zId)
{
  int const vers=version();
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  if (!input->checkPosition(pos+(vers>1 ? 12 : 16)))
    return false;
  f.str("");
  if (zId)
    f << "Entries(StyleGrp)[Z" << zId << "]:";
  else
    f << "Entries(StyleGrp):";
  auto cType=FreeHandParserInternal::Z_Unknown;
  bool checkDSize=true;
  if (zId) {
    auto zType=m_state->getZoneType(zId);
    checkDSize=false;
    if (zType==FreeHandParserInternal::Z_ColorGroup)
      cType=FreeHandParserInternal::Z_Color;
    else if (zType==FreeHandParserInternal::Z_DashGroup)
      cType=FreeHandParserInternal::Z_Dash;
    else if (zType==FreeHandParserInternal::Z_FillGroup)
      cType=FreeHandParserInternal::Z_Fill;
    else if (zType==FreeHandParserInternal::Z_LineStyleGroup)
      cType=FreeHandParserInternal::Z_LineStyle;
    else if (zType!=FreeHandParserInternal::Z_StyleGroup) {
      checkDSize=true;
      MWAW_DEBUG_MSG(("FreeHandParser::readStyleGroup: find unexpected zone type for zone %d\n", zId));
    }
  }
  auto dSz=static_cast<int>(input->readULong(4)); // probably related to size
  f << "sz?=" << dSz << ",";
  auto opCode=static_cast<int>(input->readULong(2));
  if ((vers==1 && opCode!=2) || (vers>1 && opCode!=5))
    return false;
  if (vers==1) {
    for (int i=0; i<2; ++i) { // always f0=0,f1=16 ?
      auto val=static_cast<int>(input->readLong(2));
      if (val)
        f << "f" << i << "=" << val << ",";
    }
  }
  auto N=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(input->tell()+4+2*N) ||
      (vers==1 && checkDSize && N!=(dSz-16)/2) ||
      (vers>1 && checkDSize && N!=(dSz-12)/2))
    return false;
  for (int i=0; i<2; ++i) { // always 0?
    auto val=static_cast<int>(input->readLong(2));
    if (val)
      f << "f" << i+2 << "=" << val << ",";
  }
  f << "childs=[";
  for (int i=0; i<N; ++i) {
    auto id=static_cast<int>(input->readULong(2));
    if (!m_state->addZoneId(id, cType)) {
      if (checkDSize) return false;
      MWAW_DEBUG_MSG(("FreeHandParser::readStyleGroup: find unexpected child zone %d\n", id));
      f << "###";
    }
    f << "Z" << id << ",";
  }
  f << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  return true;
}

bool FreeHandParser::readStringZone(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  auto dSz=long(input->readULong(4));
  auto opCode=static_cast<int>(input->readLong(2));
  int const vers=version();
  // v1: opcode=3, v2: opcode=6
  if (vers==2) {
    dSz-=4;
    opCode-=3;
  }
  if (opCode!=3 || dSz<3 || !input->checkPosition(pos+dSz+2)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f.str("");
  if (zId)
    f << "Entries(String)[Z" << zId << "]:";
  else
    f << "Entries(String):";
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_String) {
    MWAW_DEBUG_MSG(("FreeHandParser::readStringZone: find unexpected zone type for zone %d\n", zId));
  }
  auto sSz=int(input->readULong(1));
  if (sSz+5>dSz || (!zId && sSz+6<dSz)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  std::string name;
  for (int i=0; i<sSz; ++i) name += char(input->readULong(1));
  f << name << ",";
  if (zId && m_state->m_zIdToStringMap.find(zId)==m_state->m_zIdToStringMap.end())
    m_state->m_zIdToStringMap[zId]=name;
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+dSz+2, librevenge::RVNG_SEEK_SET);
  return true;
}

bool FreeHandParser::readShapeHeader(FreeHandParserInternal::ShapeHeader &shape)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;
  long pos=input->tell();
  int const vers=version();
  if (!input->checkPosition(pos+(vers==1 ? 20 : 18))) return false;
  shape.m_size=long(input->readULong(4));
  shape.m_type=static_cast<int>(input->readULong(2));
  if (vers>1) {
    shape.m_dataId=static_cast<int>(input->readULong(2));
    if (shape.m_dataId && !m_state->addZoneId(shape.m_dataId, FreeHandParserInternal::Z_Note)) {
      MWAW_DEBUG_MSG(("FreeHandParser::readShapeHeader: find unexpected data id\n"));
      f << "###dataId";
    }
    shape.m_values[0]=static_cast<int>(input->readLong(2)); // always 0
    shape.m_layerId=static_cast<int>(input->readULong(2));
    shape.m_values[1]=static_cast<int>(input->readLong(2)); // always 0
    // now to multiple of 256 ???
    f << "unkn=[" << float(input->readLong(2))/256.f << "," << float(input->readLong(2))/256.f << "],";
    shape.m_extra=f.str();
    return true;
  }
  // always 0, if not we may have a problem...
  shape.m_values[0]=static_cast<int>(input->readLong(2));
  auto dataSz=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(pos+14+dataSz)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if (dataSz) {
    auto sSz=static_cast<int>(input->readULong(1));
    if (sSz==dataSz-1) {
      for (int i=0; i<sSz; ++i)
        shape.m_note+=char(input->readULong(1));
    }
    else {
      MWAW_DEBUG_MSG(("FreeHandParser::readShapeHeader: find unexpected special size\n"));
      f << "##specialSize=" << dataSz << ",";
      input->seek(dataSz-1, librevenge::RVNG_SEEK_CUR);
    }
  }
  shape.m_layerId=static_cast<int>(input->readULong(2));
  /* val1,val2: always 0, if not we may have a problem...
     val3: sometimes a 1005 zone
   */
  for (int i=0; i<3; ++i)
    shape.m_values[i+1]=static_cast<int>(input->readLong(2));
  if (shape.m_values[3]) {
    if (!m_state->addZoneId(shape.m_values[3], FreeHandParserInternal::Z_Shape)) {
      MWAW_DEBUG_MSG(("FreeHandParser::readShapeHeader: find unexpected shape id\n"));
      f << "###shapeId";
    }
  }
  // the special zone
  dataSz=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(input->tell()+dataSz)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if (dataSz==8) {
    long actPos=input->tell();
    if (!readScreenMode(*shape.m_screen)) {
      MWAW_DEBUG_MSG(("FreeHandParser::readShapeHeader: can not read screen mode\n"));
      f << "##screenMode,";
      input->seek(actPos+8, librevenge::RVNG_SEEK_SET);
    }
  }
  else if (dataSz) {
    MWAW_DEBUG_MSG(("FreeHandParser::readShapeHeader: find unexpected special size\n"));
    f << "##specialSize=" << dataSz << ",";
    input->seek(dataSz, librevenge::RVNG_SEEK_CUR);
  }
  shape.m_extra=f.str();
  return true;
}

bool FreeHandParser::readScreenMode(FreeHandParserInternal::ScreenMode &screen)
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  if (!input->checkPosition(pos+8)) return false;
  screen.m_function=static_cast<int>(input->readLong(2));
  screen.m_angle=float(input->readLong(2))/10.f;
  screen.m_lineByInch=static_cast<int>(input->readULong(2));
  screen.m_value=static_cast<int>(input->readLong(2)); // always 0?
  return true;
}

bool FreeHandParser::readStyleHeader(FreeHandParserInternal::StyleHeader &style)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;
  long pos=input->tell();
  if (!input->checkPosition(pos+12)) return false;
  style.m_size=long(input->readULong(4));
  style.m_type=static_cast<int>(input->readULong(2));
  if (version()==1) {
    // always 0, if not we may have a problem...
    style.m_unknownValue=static_cast<int>(input->readLong(2));
    auto dataSz=static_cast<int>(input->readULong(2));
    if (!input->checkPosition(pos+12+dataSz)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    if (dataSz==8) {
      long actPos=input->tell();
      if (!readScreenMode(*style.m_screen)) {
        MWAW_DEBUG_MSG(("FreeHandParser::readStyleHeader: can not read screen mode\n"));
        f << "##screenMode,";
        input->seek(actPos+8, librevenge::RVNG_SEEK_SET);
      }
    }
    else if (dataSz) {
      MWAW_DEBUG_MSG(("FreeHandParser::readStyleHeader: find unexpected special size\n"));
      f << "##specialSize=" << dataSz << ",";
      input->seek(dataSz, librevenge::RVNG_SEEK_CUR);
    }
  }
  auto id=static_cast<int>(input->readULong(2));
  if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_String)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  else
    style.m_labelId=id;
  return true;
}

bool FreeHandParser::readColor(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  f.str("");
  if (zId)
    f << "Entries(Color)[Z" << zId << "]:";
  else
    f << "Entries(Color):";
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_Color) {
    MWAW_DEBUG_MSG(("FreeHandParser::readColor: find unexpected zone type for zone %d\n", zId));
  }
  FreeHandParserInternal::StyleHeader zone;
  if (!readStyleHeader(zone)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  int expectedSize=0;
  int const vers=version();
  switch (zone.m_type) {
  case 0x106a:
  case 0x1452:
    f << "color,";
    expectedSize=12;
    if ((vers==1 && zone.m_size!=0x1c) || (vers>1 && zone.m_size!=0x1e))
      f << "#sz?=" << zone.m_size << ",";
    break;
  case 0x106b:
    f << "tint,";
    expectedSize=4;
    if (zone.m_size!=0x16)
      f << "#sz?=" << zone.m_size << ",";
    break;
  case 0x1453:
    f << "tint,";
    expectedSize=10;
    if (zone.m_size!=0x1e)
      f << "#sz?=" << zone.m_size << ",";
    break;
  case 0x106c:
    f << "cmyk,";
    expectedSize=8;
    if (zone.m_size!=0x18)
      f << "#sz?=" << zone.m_size << ",";
    break;
  case 0x1454:
    f << "cmyk,";
    expectedSize=14;
    if (zone.m_size!=0x20)
      f << "#sz?=" << zone.m_size << ",";
    break;
  case 0x1455:
    f << "pantome?,";
    expectedSize=22;
    if (zone.m_size!=0x28)
      f << "#sz?=" << zone.m_size << ",";
    break;
  default:
    break;
  }
  long endPos=input->tell()+expectedSize;
  if (expectedSize==0 || !input->checkPosition(endPos)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << zone;
  MWAWColor color;
  if (zone.m_type==0x106a || zone.m_type==0x1452) {
    unsigned char col[3];
    for (auto &c : col) c=static_cast<unsigned char>(input->readULong(2)>>8);
    color=MWAWColor(col[0],col[1],col[2]);
    f << color << ",";
    auto val=static_cast<int>(input->readLong(2));
    if (val) f << "id=" << val << ",";
    val=static_cast<int>(input->readLong(2)); // flag or big number
    if (val) f << "f0=" << val << ",";
    val=static_cast<int>(input->readLong(2)); // always 1
    if (val!=1) f << "f1=" << val << ",";
  }
  else if (zone.m_type==0x106b || zone.m_type==0x1453) {
    if (zone.m_type==0x1453) {
      unsigned char col[3];
      for (auto &c : col) c=static_cast<unsigned char>(input->readULong(2)>>8);
      color=MWAWColor(col[0],col[1],col[2]);
      f << color << ",";
    }
    auto cId=static_cast<int>(input->readULong(2));
    if (cId && !m_state->addZoneId(cId, FreeHandParserInternal::Z_Color)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    f << "main[color]=Z" << cId << ",";
    MWAWColor mainColor(MWAWColor::white());
    if (cId && m_state->m_zIdToColorMap.find(cId)!=m_state->m_zIdToColorMap.end())
      mainColor=m_state->m_zIdToColorMap.find(cId)->second;
    else if (cId) {
      static bool first=true;
      if (first) {
        MWAW_DEBUG_MSG(("FreeHandParser::readColor: can not find some main color\n"));
        first=false;
      }
    }
    float tint=float(input->readULong(2))/65535.f;
    if (zone.m_type==0x106b)
      color=MWAWColor::barycenter(tint, mainColor, 1-tint, MWAWColor::white());
    f << "percent=" << tint << ",";
  }
  else if (zone.m_type==0x1455) {
    unsigned char col[3];
    for (auto &c : col) c=static_cast<unsigned char>(input->readULong(2)>>8);
    color=MWAWColor(col[0],col[1],col[2]);
    f << color << ",";
    // what is that ?
    for (int i=0; i<8; ++i) { // f0=0|1a5|1f7,f2=0|451e,f4=0|f5c|1c28,f5=147a|2e14|828f,f6=1c2, f7=1
      auto val=static_cast<int>(input->readULong(2));
      if (val) f << "f" << i << "=" << std::hex << val << std::dec << ",";
    }
  }
  else {
    if (zone.m_type==0x1454) {
      unsigned char col[3];
      for (auto &c : col) c=static_cast<unsigned char>(input->readULong(2)>>8);
      color=MWAWColor(col[0],col[1],col[2]);
      f << color << ",";
    }
    unsigned char col[4];
    for (auto &c : col) c=static_cast<unsigned char>(input->readULong(2)>>8);
    if (zone.m_type==0x106c) {
      color=MWAWColor::colorFromCMYK(col[1],col[2],col[3],col[0]);
      f << color << ",";
    }
  }
  if (zId && m_state->m_zIdToColorMap.find(zId)==m_state->m_zIdToColorMap.end())
    m_state->m_zIdToColorMap[zId]=color;
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  return true;
}

bool FreeHandParser::readDash(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  f.str("");
  if (zId)
    f << "Entries(Dash)[Z" << zId << "]:";
  else
    f << "Entries(Dash):";
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_Dash) {
    MWAW_DEBUG_MSG(("FreeHandParser::readDash: find unexpected zone type for zone %d\n", zId));
  }
  FreeHandParserInternal::StyleHeader zone;
  if (!readStyleHeader(zone)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  int const vers=version();
  if (zone.m_size < 12 || (vers==1 && zone.m_type!=0x1195) || (vers>1 && zone.m_type!=0x157d)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  long endPos;
  if (vers==1)
    endPos=pos+2+zone.m_size;
  else {
    endPos=pos-2+zone.m_size;
    for (int i=0; i<2; ++i) { // 0
      auto val=static_cast<int>(input->readULong(2));
      if (val)
        f << "f" << i << "=" << val << ",";
    }
  }
  f << zone;
  auto N=static_cast<int>(input->readLong(2));
  if (endPos!=input->tell()+2*N || !input->checkPosition(endPos)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << "dash=[";
  std::vector<float> dashes;
  for (int j=0; j<N; ++j) {
    dashes.push_back(float(input->readLong(2))/10.f);
    f << dashes.back() << ",";
  }
  f << "],";
  if (zId && m_state->m_zIdToDashMap.find(zId)==m_state->m_zIdToDashMap.end())
    m_state->m_zIdToDashMap[zId]=dashes;
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(endPos, librevenge::RVNG_SEEK_SET);

  return true;
}

bool FreeHandParser::readFillStyle(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  if (!input->checkPosition(pos+13))
    return false;
  f.str("");
  if (zId)
    f << "Entries(FillStyle)[Z" << zId << "]:";
  else
    f << "Entries(FillStyle):";
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_Fill) {
    MWAW_DEBUG_MSG(("FreeHandParser::readFillStyle: find unexpected zone type for zone %d\n", zId));
  }
  FreeHandParserInternal::StyleHeader zone;
  if (!readStyleHeader(zone)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  FreeHandParserInternal::FillStyle style;
  int expectedSize=0;
  int const vers=version();
  switch (zone.m_type) {
  case 0x10cd:
    f << "basic,";
    if (zone.m_size!=0x12) f << "sz?=" << zone.m_size << ",";
    expectedSize=3;
    break;
  case 0x10d0:
    f << "gradient,";
    style.m_type=MWAWGraphicStyle::G_Linear;
    if (zone.m_size!=0x18) f << "sz?=" << zone.m_size << ",";
    expectedSize=8;
    break;
  case 0x10d1:
    f << "radial,";
    style.m_type=MWAWGraphicStyle::G_Radial;
    if (zone.m_size!=0x14) f << "sz?=" << zone.m_size << ",";
    expectedSize=4;
    break;
  case 0x14b5:
    f << "basic,";
    if (zone.m_size!=0x16) f << "sz?=" << zone.m_size << ",";
    expectedSize=8;
    break;
  case 0x14b7:
    f << "gradient,";
    style.m_type=MWAWGraphicStyle::G_Linear;
    if (zone.m_size!=0x1c) f << "sz?=" << zone.m_size << ",";
    expectedSize=12;
    break;
  case 0x14b8:
    f << "radial,";
    style.m_type=MWAWGraphicStyle::G_Radial;
    if (zone.m_size!=0x1e) f << "sz?=" << zone.m_size << ",";
    expectedSize=14;
    break;
  case 0x14d3:
    f << "pattern,";
    if (zone.m_size!=0x1c) f << "sz?=" << zone.m_size << ",";
    expectedSize=14;
    break;
  case 0x14dd:
    f << "tiled,";
    if (zone.m_size!=0x44) f << "sz?=" << zone.m_size << ",";
    expectedSize=54;
  default:
    break;
  }
  long endPos=input->tell()+expectedSize;
  if (expectedSize==0 || !input->checkPosition(endPos)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << zone;
  if (vers>1) {
    for (int i=0; i<2; ++i) { // always 0
      auto val=static_cast<int>(input->readLong(2));
      if (val) f << "f" << i << "=" << val << ",";
    }
  }
  auto id=static_cast<int>(input->readULong(2));
  if (zone.m_type==0x14dd) {
    if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Group)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    else if (id) {
      static bool first=true;
      if (first) {
        MWAW_DEBUG_MSG(("FreeHandParser::readFillStyle: retrieving tiled style is not implemented\n"));
        first=false;
      }
      f << "group=Z" << id << ",";
    }
    for (int i=0; i<4; ++i) { // always 0
      auto val=static_cast<int>(input->readLong(2));
      if (val) f << "f" << i+2 << "=" << val << ",";
    }
    f << "scale=" << float(input->readLong(4))/65536.f << "x" << float(input->readLong(4))/65536.f << ",";
    f << "decal=" << float(input->readLong(2))/10.f << "x" << float(input->readLong(2))/10.f << ",";
    f << "angle=" << float(input->readLong(2))/10.f << ",";
    f << "fl=" << std::hex << input->readULong(2) << std::dec << ","; // 39|49
    f << "rot=[";
    for (int i=0; i<4; ++i)
      f << float(input->readLong(4))/65536.f << ",";
    f << "],";
    f << "trans=[";
    for (int i=0; i<2; ++i)
      f << float(input->readLong(4))/65536.f/10.f << ",";
    f << "],";
  }
  else if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Color)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  else if (id) {
    style.m_colorId[0]=id;
    f << "color=Z" << id << ",";
  }
  if (zone.m_type==0x10d0 || zone.m_type==0x10d1 || zone.m_type==0x14b7 || zone.m_type==0x14b8) {
    id=static_cast<int>(input->readULong(2));
    if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Color)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    else if (id) {
      style.m_colorId[1]=id;
      f << "color2=Z" << id << ",";
    }
  }
  if (zone.m_type==0x10d0 || zone.m_type==0x14b7) {
    style.m_angle = float(input->readULong(2))/10.f;
    f << "angle=" << style.m_angle << ",";
    auto val=static_cast<int>(input->readULong(vers==1 ? 1 : 2));
    switch (val) {
    case 1:
      f << "linear,";
      break;
    case 2:
      style.m_logarithm=true;
      f << "logarithm,";
      break;
    default:
      MWAW_DEBUG_MSG(("FreeHandParser::readFillStyle: find unexpected gradient type\n"));
      f << "#gradient[type]=" << val << ",";
    }
  }
  else if (zone.m_type==0x14b8) {
    for (int i=0; i<3; ++i) { // always 0
      auto val=static_cast<int>(input->readLong(2));
      if (val) f << "g" << i << "=" << val << ",";
    }
  }
  else if (zone.m_type==0x14d3) {
    MWAWGraphicStyle::Pattern pattern;
    pattern.m_colors[0]=MWAWColor::white();
    pattern.m_colors[1]=MWAWColor::black();
    pattern.m_dim=MWAWVec2i(8,8);
    pattern.m_data.resize(8);
    for (auto &data : pattern.m_data)
      data=static_cast<uint8_t>(input->readULong(1));
    style.m_pattern=pattern;
    f << pattern;
  }
  if ((vers==1 && zone.m_type!=0x10d1) || (vers>1 && zone.m_type==0x14b5)) {
    auto val=static_cast<int>(input->readULong(vers==1 ? 1 : 2)); // always 0
    if (val==1)
      f << "overprint,";
    else
      f << "g0=" << val << ",";
  }
  if (zId && m_state->m_zIdToFillStyleMap.find(zId)==m_state->m_zIdToFillStyleMap.end())
    m_state->m_zIdToFillStyleMap[zId]=style;
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(endPos, librevenge::RVNG_SEEK_SET);

  return true;
}

bool FreeHandParser::readLineStyle(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  if (!input->checkPosition(pos+13))
    return false;
  f.str("");
  if (zId)
    f << "Entries(LinStyle)[Z" << zId << "]:";
  else
    f << "Entries(LinStyle):";
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_LineStyle) {
    MWAW_DEBUG_MSG(("FreeHandParser::readLineStyle: find unexpected zone type for zone %d\n", zId));
  }
  FreeHandParserInternal::StyleHeader zone;
  int const vers=version();
  if (!readStyleHeader(zone)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << zone;
  long endPos=0;
  bool ok=false;
  switch (zone.m_type) {
  case 0x10ce:
    ok=(vers==1);
    endPos=input->tell()+12;
    if (zone.m_size!=0x1c) f << "sz?=" << zone.m_size << ",";
    break;
  case 0x14b6:
    ok=(vers>1);
    endPos=input->tell()+18;
    if (zone.m_size!=0x22) f << "sz?=" << zone.m_size << ",";
    break;
  case 0x14d4:
    f << "pattern,";
    ok=(vers>1);
    endPos=input->tell()+22;
    if (zone.m_size!=0x24) f << "sz?=" << zone.m_size << ",";
    break;
  default:
    ok=false;
    break;
  }
  if (!ok || !input->checkPosition(endPos)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if (vers>1) {
    for (int i=0; i<2; ++i) { // 0
      auto val=static_cast<int>(input->readLong(2));
      if (val)
        f << "f" << i << "=" << val << ",";
    }
  }
  FreeHandParserInternal::LineStyle style;
  auto id=static_cast<int>(input->readULong(2));
  if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Color)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  else if (id) {
    style.m_colorId=id;
    f << "color=Z" << id << ",";
  }
  if (zone.m_type==0x14d4) {
    MWAWGraphicStyle::Pattern pattern;
    pattern.m_colors[0]=MWAWColor::white();
    pattern.m_colors[1]=MWAWColor::black();
    pattern.m_dim=MWAWVec2i(8,8);
    pattern.m_data.resize(8);
    for (auto &data : pattern.m_data)
      data=static_cast<uint8_t>(input->readULong(1));
    style.m_pattern=pattern;
    f << pattern;
  }
  else {
    id=static_cast<int>(input->readULong(2));
    if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Dash)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    else if (id) {
      style.m_dashId=id;
      f << "dash=Z" << id << ",";
    }
  }
  // probably cosecant(miter/2)
  float value=float(input->readLong(4))/65536.f;
  if (value<=-1||value>=1) {
    style.m_miterLimit=float(360./M_PI)*float(std::asin(1/value));
    f << "miter[limit]=" << style.m_miterLimit << ",";
  }
  else if (value<0||value>0) {
    f << "##miter[limit]=2*asin(" << 1/value << "),";
  }
  else
    f << "miter[limit]*,";
  style.m_width=vers==1 ? float(input->readLong(2))/10.0f  : float(input->readLong(4))/65536.f/10.0f;
  f << "width=" << style.m_width << ",";
  if (zone.m_type!=0x14d4) {
    auto val=static_cast<int>(input->readULong(1));
    switch (val) {
    case 0: // default
      break;
    case 1:
      style.m_join=MWAWGraphicStyle::J_Bevel;
      f << "join=bevel,";
      break;
    case 2:
      style.m_join=MWAWGraphicStyle::J_Round;
      f << "join=round,";
      break;
    default:
      MWAW_DEBUG_MSG(("FreeHandParser::readLineStyle: find unknown join\n"));
      f << "#join=" << val << ",";
    }
    val=static_cast<int>(input->readULong(1));
    switch (val) {
    case 0: // default
      break;
    case 1:
      style.m_cap=MWAWGraphicStyle::C_Round;
      f << "cap=round,";
      break;
    case 2:
      style.m_cap=MWAWGraphicStyle::C_Square;
      f << "cap=square,";
      break;
    default:
      MWAW_DEBUG_MSG(("FreeHandParser::readLineStyle: find unknown cap\n"));
      f << "#cap=" << val << ",";
    }
  }
  if (zId && m_state->m_zIdToLineStyleMap.find(zId)==m_state->m_zIdToLineStyleMap.end())
    m_state->m_zIdToLineStyleMap[zId]=style;
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(endPos, librevenge::RVNG_SEEK_SET);

  return true;
}

bool FreeHandParser::readPostscriptStyle(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  if (!input->checkPosition(pos+12))
    return false;
  f.str("");
  if (zId)
    f << "Entries(Postscript)[Z" << zId << "]:";
  else
    f << "Entries(Postscript):";
  if (zId) {
    auto type=m_state->getZoneType(zId);
    if (type!=FreeHandParserInternal::Z_Fill && type!=FreeHandParserInternal::Z_LineStyle) {
      MWAW_DEBUG_MSG(("FreeHandParser::readPostscriptStyle: find unexpected zone type for zone %d\n", zId));
    }
  }
  FreeHandParserInternal::StyleHeader zone;
  if (!readStyleHeader(zone)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << zone;
  long endPos;
  int sSz;
  if (version()==1) {
    if (zone.m_type!=0x10cf) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    if (zone.m_size!=0x12) f << "sz?=" << zone.m_size << ",";
    sSz=static_cast<int>(input->readULong(1));
    endPos=input->tell()+sSz;
  }
  else {
    bool ok=true;
    if (zone.m_type==0x14c9)
      f << "surf,";
    else if (zone.m_type==0x14ca)
      f << "line,";
    else
      ok=false;
    if (!ok || zone.m_size<16) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    for (int i=0; i<2; ++i) { // always 0?
      auto val=static_cast<int>(input->readLong(2));
      if (val) f << "f" << i << "=" << val << ",";
    }
    endPos=pos+zone.m_size-4;
    sSz=int(zone.m_size-16);
  }
  if (!input->checkPosition(endPos)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  std::string text;
  for (int i=0; i<sSz; ++i) text+=char(input->readULong(1));
  if (!text.empty())
    f << "ps=\"" << text << "\",";
  if (zId && m_state->m_zIdToPostscriptMap.find(zId)==m_state->m_zIdToPostscriptMap.end())
    m_state->m_zIdToPostscriptMap[zId]=text;
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(endPos, librevenge::RVNG_SEEK_SET);

  return true;
}

bool FreeHandParser::readBackgroundPicture(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  FreeHandParserInternal::ShapeHeader shape;
  if (!readShapeHeader(shape) || shape.m_type!=0x1007 || !input->checkPosition(input->tell()+32)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  FreeHandParserInternal::Shape res;
  res.m_type=FreeHandParserInternal::Shape::BackgroundPicture;
  res.m_layerId=shape.m_layerId;
  if (zId)
    f << "Entries(BackgroundPicture)[Z" << zId << "]:" << shape;
  else
    f << "Entries(BackgroundPicture):" << shape;
  for (int i=0; i<14; ++i) { // f1=29|39, f2=1, f8=1, f10=0|-5, f12=109|113|118
    auto val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  auto picSize=long(input->readLong(4));
  res.m_picture.setBegin(input->tell());
  res.m_picture.setLength(picSize);
  if (picSize<0 || !input->checkPosition(res.m_picture.end())) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  ascii().skipZone(res.m_picture.begin(), res.m_picture.end()-1);
  input->seek(picSize, librevenge::RVNG_SEEK_CUR);
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  if (zId && m_state->m_zIdToShapeMap.find(zId)==m_state->m_zIdToShapeMap.end())
    m_state->m_zIdToShapeMap.insert(std::map<int,FreeHandParserInternal::Shape>::value_type(zId,res));
  return true;
}

bool FreeHandParser::readPictureZone(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  FreeHandParserInternal::ShapeHeader shape;
  if (!readShapeHeader(shape) || shape.m_type!=0x13f8 || !input->checkPosition(input->tell()+58)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  long endPos=input->tell()+58;
  FreeHandParserInternal::Shape res;
  res.m_type=FreeHandParserInternal::Shape::Picture;
  res.m_layerId=shape.m_layerId;
  if (zId)
    f << "Entries(Picture)[Z" << zId << "]:" << shape;
  else
    f << "Entries(Picture):" << shape;
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_Shape) {
    MWAW_DEBUG_MSG(("FreeHandParser::readPictureZone: find unexpected zone type for zone %d\n", zId));
  }
  for (int i=0; i<2; ++i) {
    auto id=static_cast<int>(input->readULong(2));
    if (!id) continue;
    if (!m_state->addZoneId(id, i==0 ? FreeHandParserInternal::Z_Picture : FreeHandParserInternal::Z_PictureName)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    if (i==0) {
      res.m_dataId=id;
      f << "data=Z" << id << ",";
    }
    else
      f << "name=Z" << id << ",";
  }
  auto val=static_cast<int>(input->readLong(2)); // 0
  if (val) f << "f0=" << val << ",";
  float dim[6];
  for (int i=0; i<2; ++i) dim[i]=float(input->readLong(2))/10.f;
  f << "dim=" << MWAWVec2f(dim[1],dim[0]) << ",";
  // checkme: why are the coord inverted ?
  res.m_box=MWAWBox2f(MWAWVec2f(0,0), MWAWVec2f(dim[1],dim[0]));
  for (int i=0; i<2; ++i) { // 0?
    val=static_cast<int>(input->readLong(2)); // 0
    if (val) f << "f" << i+1 << "=" << val << ",";
  }
  f << "flags=" << std::hex << input->readULong(2) << std::dec << ",";
  f << "rot=[";
  for (int i=0; i<4; ++i) {
    dim[i]=float(input->readLong(4))/65536.f;
    f << dim[i] << ",";
  }
  f << "],";
  f << "trans=[";
  for (int i=0; i<2; ++i) {
    dim[i+4]=float(input->readLong(4))/65536.f/10.f;
    f << dim[i+4] << ",";
  }
  f << "],";
  res.m_transformation=MWAWTransformation(MWAWVec3f(dim[0],dim[2],dim[4]),MWAWVec3f(dim[1],dim[3],dim[5]));
  auto id=static_cast<int>(input->readULong(2));
  if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Color)) {
    MWAW_DEBUG_MSG(("FreeHandParser::readPictureZone: find unexpected colorId\n"));
    f << "###colorId,";
  }
  else if (id)
    f << "color=Z" << id << ",";
  for (int i=0; i<2; ++i) {
    int iDim[4];
    for (auto &d : iDim) d=static_cast<int>(input->readLong(2));
    f << "box" << i << "=" << MWAWBox2i(MWAWVec2i(iDim[0],iDim[1]),MWAWVec2i(iDim[2],iDim[3])) << ",";
  }
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  if (zId && m_state->m_zIdToShapeMap.find(zId)==m_state->m_zIdToShapeMap.end())
    m_state->m_zIdToShapeMap.insert(std::map<int,FreeHandParserInternal::Shape>::value_type(zId,res));
  return true;
}

bool FreeHandParser::readShape(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  FreeHandParserInternal::ShapeHeader shape;
  if (!readShapeHeader(shape)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  bool canHaveMatrix=true, hasDimension=true;
  int dataSize=-1;
  FreeHandParserInternal::Shape res;
  res.m_layerId=shape.m_layerId;
  const int vers=version();
  switch (shape.m_type) {
  case 0x1131:
  case 0x1519:
    if (zId)
      f << "Entries(Rectangle)[Z" << zId << "]:" << shape;
    else
      f << "Entries(Rectangle):" << shape;
    dataSize=4;
    res.m_type=FreeHandParserInternal::Shape::Rectangle;
    break;
  case 0x1132:
  case 0x151a:
    if (zId)
      f << "Entries(Circle)[Z" << zId << "]:" << shape;
    else
      f << "Entries(Circle):" << shape;
    dataSize=0;
    res.m_type=FreeHandParserInternal::Shape::Ellipse;
    break;
  case 0x1134:
  case 0x151c:
    if (zId)
      f << "Entries(Spline)[Z" << zId << "]:" << shape;
    else
      f << "Entries(Spline):" << shape;
    dataSize=4;
    hasDimension=canHaveMatrix=false;
    res.m_type=FreeHandParserInternal::Shape::Path;
    break;
  case 0x1135:
  case 0x151d:
    if (zId)
      f << "Entries(Line)[Z" << zId << "]:" << shape;
    else
      f << "Entries(Line):" << shape;
    dataSize=0;
    canHaveMatrix=false;
    res.m_type=FreeHandParserInternal::Shape::Line;
    break;
  default:
    break;
  }
  if (dataSize<0 || !input->checkPosition(input->tell()+4+(vers>1 ? 6 : 0)+(hasDimension ? 8 : 0)+(canHaveMatrix ? 4 : 0)+dataSize)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << "sz?=" << shape.m_size << ",";
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_Shape) {
    MWAW_DEBUG_MSG(("FreeHandParser::readShape: find unexpected zone type for zone %d\n", zId));
  }
  if (vers>1) {
    auto id=static_cast<int>(input->readULong(2));
    if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Group)) {
      MWAW_DEBUG_MSG(("FreeHandParser::readShapeHeader: find unexpected group id\n"));
      f << "###groupId,";
    }
    else if (id) {
      f << "group=Z" << id << ",";
      res.m_childs.push_back(id);
    }
  }
  auto id=static_cast<int>(input->readLong(2)); // always 0?
  if (id &&  !m_state->addZoneId(id, FreeHandParserInternal::Z_Fill)) {
    MWAW_DEBUG_MSG(("FreeHandParser::readShape: find a bad color\n"));
    f << "###";
  }
  if (id) f << "fill=Z" << id << ",";
  res.m_fillId=id;
  id=static_cast<int>(input->readULong(2));
  if (id &&  !m_state->addZoneId(id, FreeHandParserInternal::Z_LineStyle)) {
    MWAW_DEBUG_MSG(("FreeHandParser::readShape: find a bad style\n"));
    f << "###";
  }
  if (id) f << "line[style]=Z" << id << ",";
  res.m_lineId=id;
  if (vers>1) {
    for (int i=0; i<2; ++i) { // always 0?
      auto val=static_cast<int>(input->readULong(2));
      if (val) f << "f" << i << "=" << val << ",";
    }
  }
  if (hasDimension) {
    float dim[4];
    for (auto &d : dim) d=float(input->readLong(2))/10.f;
    res.m_box=MWAWBox2f(MWAWVec2f(dim[0],dim[1]),MWAWVec2f(dim[2],dim[3]));
    f << "rect=" << res.m_box << ",";
  }
  int dSz=canHaveMatrix ? static_cast<int>(input->readULong(4)) : 0;
  if (dSz<0 || !input->checkPosition(input->tell()+dSz+dataSize)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if (dSz==0x1a) {
    f << "flags=" << std::hex << input->readULong(2) << std::dec << ",";
    float dim[6];
    f << "rot=[";
    for (int i=0; i<4; ++i) {
      dim[i]=float(input->readLong(4))/65536.f;
      f << dim[i] << ",";
    }
    f << "],";
    f << "trans=[";
    for (int i=0; i<2; ++i) {
      dim[i+4]=float(input->readLong(4))/65536.f/10.f;
      f << dim[i+4] << ",";
    }
    f << "],";
    res.m_transformation=MWAWTransformation(MWAWVec3f(dim[0],dim[2],dim[4]),MWAWVec3f(dim[1],dim[3],dim[5]));
  }
  else if (dSz) {
    MWAW_DEBUG_MSG(("FreeHandParser::readShape: find unknown matrix size\n"));
    f << "###matrix,";
    input->seek(dSz, librevenge::RVNG_SEEK_CUR);
  }
  if (shape.m_type==0x1131 || shape.m_type==0x1519) {
    float dim[2];
    for (auto &d : dim) d=float(input->readLong(2))/10.f;
    res.m_corner=MWAWVec2f(dim[0],dim[1]);
    if (res.m_corner!=MWAWVec2f(0,0))
      f << "corner=" << res.m_corner << ",";
  }
  if (shape.m_type==0x1134 || shape.m_type==0x151c) {
    auto val=static_cast<int>(input->readULong(2));
    if (val&1) {
      res.m_closed=true;
      f << "closed,";
    }
    if (val&2) {
      res.m_evenOdd=true;
      f << "even/odd,";
    }
    val &= 0xFFFC;
    // find also 4
    if (val) f << "fl=" << std::hex << val << std::dec << ",";
    auto nPt=static_cast<int>(input->readULong(2));
    f << "N=" << nPt << ",";
    if (!input->checkPosition(input->tell()+16*nPt)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    for (int i=0; i<nPt; ++i) {
      pos=input->tell();
      f.str("");
      f << "Spline-" << i << ":";
      val=static_cast<int>(input->readULong(2));
      switch (val) {
      case 0: // corner
        break;
      case 1:
        f << "connector,";
        break;
      case 2:
        f << "curve,";
        break;
      default:
        // find also 0xf0
        MWAW_DEBUG_MSG(("FreeHandParser::readShape: find unknown point type\n"));
        f << "#type=" << val << ",";
      }
      val=static_cast<int>(input->readULong(2));
      if (val&0x100) f << "no[autoCurvature],";
      val &= 0xFEFF;
      // find unknow [01][4|9|b]
      if (val) f << "fl=" << std::hex << val << std::dec << ",";
      MWAWVec2f coord[3];
      for (auto &pt : coord) {
        float dim[2];
        for (auto &d : dim) d=float(input->readLong(2))/10.f;
        pt=MWAWVec2f(dim[0], dim[1]);
        res.m_vertices.push_back(pt);
      }
      if (coord[0]==coord[1] && coord[0]==coord[2])
        f << coord[0] << ",";
      else
        f << "pts=[" << coord[0] << "," << coord[1] << "," << coord[2] << "],";
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
    }
  }
  else {
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  if (zId && m_state->m_zIdToShapeMap.find(zId)==m_state->m_zIdToShapeMap.end())
    m_state->m_zIdToShapeMap.insert(std::map<int,FreeHandParserInternal::Shape>::value_type(zId,res));
  return true;
}

bool FreeHandParser::readTextboxV1(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  FreeHandParserInternal::ShapeHeader shape;
  if (!readShapeHeader(shape) || shape.m_type!=0x1006 || !input->checkPosition(input->tell()+4+8+54)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if (zId)
    f << "Entries(Textbox)[Z" << zId << "]:" << shape;
  else
    f << "Entries(Textbox):" << shape;
  FreeHandParserInternal::Textbox textbox(zId);
  textbox.m_layerId=shape.m_layerId;
  f << "sz?=" << shape.m_size << ",";
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_Shape) {
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV1: find unexpected zone type for zone %d\n", zId));
  }
  auto val=static_cast<int>(input->readLong(2)); // always 0?
  if (val) f << "f0=" << val << ",";
  auto nbPt=static_cast<int>(input->readULong(2)); // nbPt=4*textSz
  f << "N=" << nbPt << ",";
  long actPos=input->tell();
  if ((nbPt%2) || !input->checkPosition(actPos+3*(nbPt/2)+8+54)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  // probably nbPt/2 float (size of each char)+ nbPt/2 bytes (flag?)
  ascii().skipZone(actPos, actPos+3*(nbPt/2)-1);

  actPos+=3*(nbPt/2);
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  input->seek(actPos, librevenge::RVNG_SEEK_SET);
  f.str("");
  f << "Textbox-A:";
  for (int i=0; i<3; ++i) { // f0=0|2|-82|-123|-225, f1=0|-41|-82|-123|-151, f2=0
    val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  auto sSz=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(input->tell()+sSz+54)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << "text[sz]=" << sSz << ",";
  ascii().addPos(actPos);
  ascii().addNote(f.str().c_str());

  textbox.m_text.setBegin(input->tell());
  textbox.m_text.setLength(sSz);
  input->seek(textbox.m_text.end(), librevenge::RVNG_SEEK_SET);

  actPos=input->tell();
  f.str("");
  f << "Textbox-B:";
  float dim[6];
  for (int i=0; i<4; ++i) dim[i]=float(input->readLong(2))/10.f;
  textbox.m_box=MWAWBox2f(MWAWVec2f(dim[0],dim[1]),MWAWVec2f(dim[2],dim[3]));
  f << "dim=" << textbox.m_box << ",";
  f << "flags=" << std::hex << input->readULong(2) << std::dec << ",";
  f << "rot=[";
  for (int i=0; i<4; ++i) {
    dim[i]=float(input->readLong(4))/65536.f;
    f << dim[i] << ",";
  }
  f << "],";
  f << "trans=[";
  for (int i=0; i<2; ++i) {
    dim[i+4]=float(input->readLong(4))/65536.f/10.f;
    f << dim[i+4] << ",";
  }
  f << "],";
  textbox.m_transformation=MWAWTransformation(MWAWVec3f(dim[0],dim[2],dim[4]),MWAWVec3f(dim[1],dim[3],dim[5]));
  f << "spacing=["; // letter and word
  for (int i=0; i<2; ++i) {
    dim[i]=float(input->readLong(4))/65536.f/10.f;
    f << dim[i] << ",";
  }
  f << "],";
  textbox.m_spacings=MWAWVec2f(dim[0],dim[1]);
  f << "scaling=[";
  for (int i=0; i<2; ++i) {
    dim[i]=float(input->readLong(4))/65536.f;
    f << dim[i] << ",";
  }
  f << "],";
  textbox.m_scalings=MWAWVec2f(dim[0],dim[1]);
  val=static_cast<int>(input->readLong(1)); // 0|1|2
  if (val) f << "f0=" << val << ",";
  val=static_cast<int>(input->readLong(1));
  switch (val) {
  case 0: // left
    break;
  case 1:
    f << "right,";
    textbox.m_justify=MWAWParagraph::JustificationRight;
    break;
  case 2:
    f << "center,";
    textbox.m_justify=MWAWParagraph::JustificationCenter;
    break;
  case 3:
    f << "justify=all,";
    textbox.m_justify=MWAWParagraph::JustificationFull;
    break;
  default:
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV1: find unexpected align\n"));
    f << "###align=" << val << ",";
    break;
  }
  auto nPLC=static_cast<int>(input->readULong(2));
  f << "NPLC=" << nPLC << ",";
  if (!input->checkPosition(input->tell()+18*nPLC)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  ascii().addPos(actPos);
  ascii().addNote(f.str().c_str());
  for (int plc=0; plc<nPLC; ++plc) {
    actPos=input->tell();
    f.str("");
    f << "Textbox-PLC" << plc << ":";
    FreeHandParserInternal::Font font;
    if (plc+1!=nPLC) {
      auto id=static_cast<int>(input->readULong(2));
      if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_String)) {
        MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV1: find bad font name\n"));
        f << "###";
      }
      font.m_nameId=id;
      if (id)
        f << "font[name]=Z" << id << ",";
      id=static_cast<int>(input->readULong(2));
      if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Color)) {
        MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV1: find bad color\n"));
        f << "###";
      }
      if (id)
        f << "color=Z" << id << ",";
      font.m_colorId=id;
      float sz=float(input->readLong(4))/65536.f;
      font.m_font.setSize(sz);
      f << "font[sz]=" << sz << ",";
      val=static_cast<int>(input->readLong(4));
      switch (val) { // useme
      case -2: // solid
        break;
      case -1:
        f << "leading=auto,";
        break;
      default:
        f << "leading=" << float(val)/65536.f << ",";
      }
    }
    else
      input->seek(12, librevenge::RVNG_SEEK_CUR);
    auto cPos=static_cast<int>(input->readULong(2));
    f << "pos=" << cPos << ",";
    uint32_t flags=0;
    val=static_cast<int>(input->readULong(2));
    if (val & 1) {
      flags |= MWAWFont::boldBit;
      f << "bold,";
    }
    if (val & 2) {
      flags |= MWAWFont::italicBit;
      f << "italic,";
    }
    val &= 0xFFFC;
    if (val && plc+1!=nPLC) {
      MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV1: find unknown font flag1\n"));
      f << "##flag1=" << std::hex << val << std::dec << ",";
    }
    val=static_cast<int>(input->readULong(2));
    switch (val) {
    case 1: // solid
      break;
    case 2:
      flags |= MWAWFont::boldBit;
      f << "heavy,";
      break;
    case 3:
      flags |= MWAWFont::italicBit;
      f << "oblique,";
      break;
    case 4:
      flags |= MWAWFont::outlineBit;
      f << "outline,";
      break;
    case 5:
      flags |= MWAWFont::shadowBit;
      f << "shadow,";
      break;
    default:
      if (plc+1==nPLC) break;
      MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV1: find unknown font flag2\n"));
      f << "##flag2=" << val << ",";
      break;
    }
    font.m_font.setFlags(flags);
    textbox.m_posToFontMap[cPos]=font;
    ascii().addPos(actPos);
    ascii().addNote(f.str().c_str());
  }
  if (zId && m_state->m_zIdToTextboxMap.find(zId)==m_state->m_zIdToTextboxMap.end())
    m_state->m_zIdToTextboxMap.insert(std::map<int,FreeHandParserInternal::Textbox>::value_type(zId,textbox));

  return true;
}

bool FreeHandParser::readTextboxV2(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos=input->tell();
  FreeHandParserInternal::ShapeHeader shape;
  if (!readShapeHeader(shape) || shape.m_type!=0x13ee || !input->checkPosition(input->tell()+66)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if (zId)
    f << "Entries(Textbox)[Z" << zId << "]:" << shape;
  else
    f << "Entries(Textbox):" << shape;
  FreeHandParserInternal::Textbox textbox(zId);
  textbox.m_layerId=shape.m_layerId;
  f << "sz?=" << shape.m_size << ",";
  if (zId && m_state->getZoneType(zId)!=FreeHandParserInternal::Z_Shape) {
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV2: find unexpected zone type for zone %d\n", zId));
  }
  for (int i=0; i<6; ++i) { // f3=0|2, f4=0|1
    auto val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  auto val=static_cast<int>(input->readULong(2)); // 1a|7b|c3|eb|f4|fb|114|1af|7d00|7d02
  if (val) f << "f6=" << val << ",";
  float dim[6];
  for (int i=0; i<4; ++i) dim[i]=float(input->readLong(2))/10.f;
  textbox.m_box=MWAWBox2f(MWAWVec2f(dim[0],dim[1]),MWAWVec2f(dim[2],dim[3]));
  f << "dim=" << textbox.m_box << ",";
  val=static_cast<int>(input->readULong(2)); // 0
  if (val) f << "f7=" << val << ",";
  f << "flags=" << std::hex << input->readULong(2) << std::dec << ",";
  f << "rot=[";
  for (int i=0; i<4; ++i) {
    dim[i]=float(input->readLong(4))/65536.f;
    f << dim[i] << ",";
  }
  f << "],";
  f << "trans=[";
  for (int i=0; i<2; ++i) {
    dim[i+4]=float(input->readLong(4))/65536.f/10.f;
    f << dim[i+4] << ",";
  }
  f << "],";
  textbox.m_transformation=MWAWTransformation(MWAWVec3f(dim[0],dim[2],dim[4]),MWAWVec3f(dim[1],dim[3],dim[5]));
  val=static_cast<int>(input->readLong(1));
  switch (val) {
  case 0: // left
    break;
  case 1:
    f << "center,";
    textbox.m_justify=MWAWParagraph::JustificationCenter;
    break;
  case 2:
    f << "right,";
    textbox.m_justify=MWAWParagraph::JustificationRight;
    break;
  case 3:
    f << "justify=all,";
    textbox.m_justify=MWAWParagraph::JustificationFull;
    break;
  default:
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV2: find unexpected align\n"));
    f << "###align=" << val << ",";
    break;
  }
  ascii().addDelimiter(input->tell(), '|');
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(11, librevenge::RVNG_SEEK_CUR);

  pos=input->tell();
  f.str("");
  f << "Textbox-A:";
  auto dSz=static_cast<int>(input->readULong(2));
  auto sSz=static_cast<int>(input->readULong(2));
  long endPos=pos+dSz-18-80;
  if (dSz-18-80<58 || !input->checkPosition(endPos)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f << "text[sz]=" << sSz << ",";
  val=static_cast<int>(input->readULong(2));
  if (val!=sSz) f << "text[pos]=" << val << ",";
  val=static_cast<int>(input->readULong(2)); // always 7ffd ?
  if (val!=0x7ffd) f << "f0=" << val << ",";
  for (int i=0; i<2; ++i) {
    val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i+1 << "=" << val << ",";
  }
  FreeHandParserInternal::Font font;
  auto id=static_cast<int>(input->readULong(2));
  if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_String)) {
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV2: find bad font name\n"));
    f << "###";
  }
  font.m_nameId=id;
  if (id)
    f << "font[name]=Z" << id << ",";
  float sz=float(input->readLong(4))/65536.f;
  font.m_font.setSize(sz);
  f << "font[sz]=" << sz << ",";
  val=static_cast<int>(input->readULong(4));
  // use me
  if (val==static_cast<int>(0xFFFE0000))
    f << "leading=auto,";
  else if (val!=static_cast<int>(0xFFFF0000)) // no solid
    f << "leading=" << float(val)/65536.f << ",";
  val=static_cast<int>(input->readLong(2));
  if (val) f << "f4=" << val << ",";
  uint32_t flags=0;
  val=static_cast<int>(input->readULong(2));
  if (val & 1) {
    flags |= MWAWFont::boldBit;
    f << "bold,";
  }
  if (val & 2) {
    flags |= MWAWFont::italicBit;
    f << "italic,";
  }
  val &= 0xFFFC;
  if (val) {
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV2: find unknown font flag1\n"));
    f << "##flag1=" << std::hex << val << std::dec << ",";
  }
  id=static_cast<int>(input->readULong(2));
  if (id && !m_state->addZoneId(id, FreeHandParserInternal::Z_Color)) {
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV2: find bad color\n"));
    f << "###";
  }
  if (id)
    f << "color=Z" << id << ",";
  font.m_colorId=id;
  val=static_cast<int>(input->readLong(2)); // 0
  if (val) f << "f6=" << val << ",";
  auto special=static_cast<int>(input->readLong(2));
  int specialData[6];
  for (int i=0; i<6; ++i) specialData[i]=static_cast<int>(input->readULong(i>=4 ? 1 : 2));
  if (!m_state->addZoneId(specialData[0], FreeHandParserInternal::Z_Color)) {
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV2: find bad text color\n"));
    f << "###";
  }
  if (specialData[0])
    f << "col2=Z" << specialData[0] << ",";
  switch (special) {
  case 1: // solid
    break;
  case 2:
    flags |= MWAWFont::boldBit;
    f << "heavy,";
    break;
  case 3:
    flags |= MWAWFont::italicBit;
    f << "oblique,";
    break;
  case 4:
    flags |= MWAWFont::outlineBit;
    f << "outline,";
    break;
  case 5:
    flags |= MWAWFont::shadowBit;
    f << "shadow,";
    break;
  case 6:
    f << "fillAndStroke,";
    if (specialData[1]||specialData[2]) {
      f << "stroke[w]=" << float((specialData[1]<<16)+specialData[2])/65536.f << ",";
      specialData[1]=specialData[2]=0;
    }
    if (specialData[3]&0x100) f << "fill[set],";
    if (specialData[3]&0x1) f << "fill[overprint],";
    specialData[3]&=0xfefe;
    for (int i=0; i<2; ++i) {
      if (!specialData[4+i]) continue;

      char const *wh[]= {"stroke[set]", "stroke[overprint]"};
      f << wh[i] << "=" << specialData[4+i] << ",";
      specialData[4+i]=0;
    }
    break;
  case 0x79:
    f << "char,";
    for (int i=1; i<5; ++i) {
      if (!specialData[i]) continue;
      char const *wh[]= {"", "fill[sz]", "line[spacing]", "stroke[width]", "has[stroke]"};
      if (i==3)
        f << wh[i] << "=" << float(specialData[i])/10.f << ",";
      else
        f << wh[i] << "=" << specialData[i] << ",";
      specialData[i]=0;
    }
    break;
  case 0x7a:
    f << "zoom,";
    for (int i=1; i<4; ++i) {
      if (!specialData[i]) continue;
      char const *wh[]= {"", "zoom[horOffset]", "zoom[verOffset]", "zoom[%]"};
      f << wh[i] << "=" << specialData[i] << ",";
      specialData[i]=0;
    }
    break;
  default:
    MWAW_DEBUG_MSG(("FreeHandParser::readTextboxV2: find unknown font flag2\n"));
    f << "##flag2=" << special << ",";
    break;
  }
  for (int i=1; i<6; ++i) {
    if (specialData[i])
      f << "#special" << i << "=" << specialData[i] << ",";
  }
  font.m_font.setFlags(flags);
  textbox.m_posToFontMap[0]=font;
  f << "spacing=["; // letter and word
  for (int i=0; i<2; ++i) {
    dim[i]=float(input->readLong(4))/65536.f;
    f << dim[i] << ",";
  }
  f << "],";
  textbox.m_spacings=MWAWVec2f(dim[0],dim[1]);
  textbox.m_scalings[0]=float(input->readLong(4))/65536.f;
  f << "scalings[hor]=" << textbox.m_scalings[0] << ",";
  textbox.m_baseline=float(input->readLong(4))/65536.f;
  if (textbox.m_baseline<0||textbox.m_baseline>0)
    f << "baseline=" << textbox.m_baseline << ",";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  /* CHECKME: find some blocks here : [12bytes]* followed by [unkn]* and [22bytes]*
     for instance
     00290003007a9ce0000ffed1
     002a00190069046000100000
     0012000...
     001a0000000000c9f280000ac3de00033c22000f0000
     00290000000000690460000ac3de00033c22000f0000
     004d0000000000e2f160000ac3de00033c2200120000
     006000000000007a6860000ac3de00033c22000f0000
   */
  if (endPos!=input->tell()) {
    pos=input->tell();
    f.str("");
    f << "Textbox-B:";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  input->seek(endPos, librevenge::RVNG_SEEK_SET);

  textbox.m_text.setBegin(input->tell());
  textbox.m_text.setLength(sSz);
  input->seek(textbox.m_text.end(), librevenge::RVNG_SEEK_SET);

  if (zId && m_state->m_zIdToTextboxMap.find(zId)==m_state->m_zIdToTextboxMap.end())
    m_state->m_zIdToTextboxMap.insert(std::map<int,FreeHandParserInternal::Textbox>::value_type(zId,textbox));

  return true;
}

bool FreeHandParser::readDataZone(int zId)
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;
  long pos=input->tell();
  if (!input->checkPosition(pos+10)) return false;
  auto dSz=long(input->readULong(4));
  auto opCode=static_cast<int>(input->readULong(2));
  auto dataSize=long(input->readULong(4));
  long endPos=pos+10+dataSize;
  if (opCode!=0x138b || dataSize<0 || !input->checkPosition(endPos)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  auto type=zId ? m_state->getZoneType(zId) : FreeHandParserInternal::Z_Unknown;
  if (type==FreeHandParserInternal::Z_Note) {
    f << "Entries(Note)[Z" << zId << "]:";
    if (dataSize) {
      auto sSz=static_cast<int>(input->readULong(1));
      if (sSz+1>dataSize) {
        MWAW_DEBUG_MSG(("FreeHandParser::readDataZone: can not read the note size\n"));
        f << "##sSz";
      }
      else {
        std::string note;
        for (int i=0; i<sSz; ++i) note+=char(input->readULong(1));
        f << note;
      }
    }
  }
  else if (type==FreeHandParserInternal::Z_PictureName) {
    f << "Picture[name][Z" << zId << "]:";
    if (dataSize<6) {
      MWAW_DEBUG_MSG(("FreeHandParser::readDataZone: can not read the picture name zone\n"));
      f << "##sSz";
    }
    else {
      auto val=static_cast<int>(input->readLong(4)); // disk id?
      if (val) f << "f0=" << std::hex << val << std::dec << ",";
      for (int i=0; i<2; ++i) { // disk name ?, file name
        auto sSz=static_cast<int>(input->readULong(1));
        if (input->tell()+sSz>endPos) {
          MWAW_DEBUG_MSG(("FreeHandParser::readDataZone: can not read some string\n"));
          f << "##sSz";
          break;
        }
        std::string name;
        for (int c=0; c<sSz; ++c) name+=char(input->readULong(1));
        f << name << ",";
      }
    }
  }
  else if (type==FreeHandParserInternal::Z_Picture) {
    f << "Picture[data][Z" << zId << "]:";
    if (dataSize) {
      MWAWEntry entry;
      entry.setBegin(input->tell());
      entry.setLength(dataSize);
      if (zId && m_state->m_zIdToDataMap.find(zId)==m_state->m_zIdToDataMap.end())
        m_state->m_zIdToDataMap[zId]=entry;
      ascii().skipZone(entry.begin(), entry.end()-1);
    }
  }
  else {
    MWAW_DEBUG_MSG(("FreeHandParser::readDataZone: find unknown zone\n"));
    if (zId)
      f << "Entries(DataZone)[Z" << zId << "]:";
    else
      f << "Entries(DataZone):";
    if (dSz!=dataSize+5)
      f << "sz?=" << dSz << ",";
  }
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
//
// send data
//
////////////////////////////////////////////////////////////
bool FreeHandParser::openLayer(int zId)
{
  if (zId<0 || m_state->m_actualLayer>=0 || m_state->m_sendLayerSet.find(zId)!=m_state->m_sendLayerSet.end())
    return false;
  if (!getGraphicListener()) {
    MWAW_DEBUG_MSG(("FreeHandParser::openLayer: can not find the listener\n"));
    return false;
  }
  m_state->m_sendLayerSet.insert(zId);
  librevenge::RVNGString layer;
  layer.sprintf("%d", zId);
  if (!getGraphicListener()->openLayer(layer))
    return false;
  m_state->m_actualLayer=zId;
  return true;
}

void FreeHandParser::closeLayer()
{
  if (m_state->m_actualLayer<0)
    return;
  getGraphicListener()->closeLayer();
  m_state->m_actualLayer=-1;
}

bool FreeHandParser::sendZone(int zId, MWAWTransformation const &transform)
{
  if (!getGraphicListener()) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendZone: can not find the listener\n"));
    return false;
  }
  if (m_state->m_zIdToTextboxMap.find(zId)!=m_state->m_zIdToTextboxMap.end())
    return sendTextbox(m_state->m_zIdToTextboxMap.find(zId)->second, transform);
  if (m_state->m_zIdToShapeMap.find(zId)==m_state->m_zIdToShapeMap.end()) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendZone: can not find the zone %d\n", zId));
    return false;
  }
  auto const &shape=m_state->m_zIdToShapeMap.find(zId)->second;
  shape.m_isSent=true;
  if (shape.m_type==FreeHandParserInternal::Shape::Group ||
      shape.m_type==FreeHandParserInternal::Shape::JoinGroup)
    return sendGroup(shape, transform);
  if (shape.m_type==FreeHandParserInternal::Shape::Picture)
    return sendPicture(shape, transform);
  if (shape.m_type==FreeHandParserInternal::Shape::BackgroundPicture)
    return sendBackgroundPicture(shape, transform);
  if (shape.m_type!=FreeHandParserInternal::Shape::Unknown)
    return sendShape(shape, transform);
  return false;
}

bool FreeHandParser::sendGroup(FreeHandParserInternal::Shape const &group, MWAWTransformation const &transform)
{
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendGroup: can not find the listener\n"));
    return false;
  }
  if (group.m_childs.empty()) return true;
  if (m_state->m_sendIdSet.find(group.m_id)!=m_state->m_sendIdSet.end()) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendGroup: sorry the zone %d is already sent\n", group.m_id));
    return false;
  }
  m_state->m_sendIdSet.insert(group.m_id);
  MWAWTransformation transf=transform*group.m_transformation;
  bool createGroup=group.m_childs.size()>1 && group.m_id!=m_state->m_mainGroupId;
  // TODO check for join group
  bool newLayer=openLayer(group.m_layerId);
  if (createGroup) {
    MWAWPosition pos(MWAWVec2f(0,0), MWAWVec2f(0,0), librevenge::RVNG_POINT);
    pos.m_anchorTo = MWAWPosition::Page;
    listener->openGroup(pos);
  }
  bool checkLayer=m_state->m_actualLayer==-1;
  int actualLayerId=-1;
  for (int cId : group.m_childs) {
    if (checkLayer) {
      int newLayerId=-1;
      if (m_state->m_zIdToTextboxMap.find(cId)!=m_state->m_zIdToTextboxMap.end())
        newLayerId=m_state->m_zIdToTextboxMap.find(cId)->second.m_layerId;
      else if (m_state->m_zIdToShapeMap.find(cId)!=m_state->m_zIdToShapeMap.end())
        newLayerId=m_state->m_zIdToShapeMap.find(cId)->second.m_layerId;
      if (newLayerId!=actualLayerId) {
        if (actualLayerId>=0)
          closeLayer();
        if (openLayer(newLayerId))
          actualLayerId=newLayerId;
        else
          actualLayerId=-1;
      }
    }
    sendZone(cId, transf);
  }
  if (actualLayerId>=0) closeLayer();
  if (createGroup)
    listener->closeGroup();
  m_state->m_sendIdSet.erase(group.m_id);
  if (newLayer) closeLayer();
  return true;
}

bool FreeHandParser::sendBackgroundPicture(FreeHandParserInternal::Shape const &picture, MWAWTransformation const &)
{
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendBackgroundPicture: can not find the listener\n"));
    return false;
  }
  if (!picture.m_picture.valid()) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendBackgroundPicture: can not find the background picture\n"));
    return false;
  }
  MWAWInputStreamPtr input=getInput();
  input->seek(picture.m_picture.begin(), librevenge::RVNG_SEEK_SET);
  librevenge::RVNGBinaryData data;
  if (!input->readDataBlock(picture.m_picture.length(), data) || data.empty()) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendBackgroundPicture: oops the picture is empty\n"));
    return false;
  }
#ifdef DEBUG_WITH_FILES
  static int volatile pictName = 0;
  libmwaw::DebugStream f;
  f << "PICT-" << ++pictName << ".pct";
  libmwaw::Debug::dumpFile(data, f.str().c_str());
#endif
  MWAWPosition pos(MWAWVec2f(float(getPageSpan().getMarginLeft()), float(getPageSpan().getMarginTop())),
                   MWAWVec2f(float(getPageSpan().getPageWidth()), float(getPageSpan().getPageLength())), librevenge::RVNG_INCH);
  pos.m_anchorTo = MWAWPosition::Page;
  pos.setOrder(-1);
  MWAWEmbeddedObject pict(data);
  listener->insertPicture(pos, pict);
  return true;
}

bool FreeHandParser::sendPicture(FreeHandParserInternal::Shape const &picture, MWAWTransformation const &transform)
{
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendPicture: can not find the listener\n"));
    return false;
  }
  if (m_state->m_zIdToDataMap.find(picture.m_dataId)==m_state->m_zIdToDataMap.end() ||
      !m_state->m_zIdToDataMap.find(picture.m_dataId)->second.valid()) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendPicture: can not find the  picture\n"));
    return false;
  }
  MWAWInputStreamPtr input=getInput();
  MWAWEntry entry=m_state->m_zIdToDataMap.find(picture.m_dataId)->second;
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  librevenge::RVNGBinaryData data;
  if (!input->readDataBlock(entry.length(), data) || data.empty()) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendPicture: oops the picture is empty\n"));
    return false;
  }
#ifdef DEBUG_WITH_FILES
  static int volatile pictName = 0;
  libmwaw::DebugStream f;
  f << "PICT-" << ++pictName << ".pct";
  libmwaw::Debug::dumpFile(data, f.str().c_str());
#endif
  MWAWGraphicStyle style=MWAWGraphicStyle::emptyStyle();
  MWAWTransformation finalTransformation=transform*picture.m_transformation;
  MWAWTransformation transf;
  float rotation=0;
  MWAWBox2f box;
  if (decomposeMatrix(finalTransformation,rotation,transf,picture.m_box.center())) {
    box=transf*picture.m_box;
    style.m_rotate=rotation;
  }
  else
    box=finalTransformation*picture.m_box;
  for (int c=0; c<2; ++c) {
    if (box.min()[c]>box.max()[c])
      std::swap(box.min()[c],box.max()[c]);
  }
  MWAWPosition pos(box[0], box.size(), librevenge::RVNG_POINT);
  pos.m_anchorTo = MWAWPosition::Page;
  MWAWEmbeddedObject pict(data);
  listener->insertPicture(pos, pict, style);
  return true;
}

bool FreeHandParser::sendShape(FreeHandParserInternal::Shape const &shape, MWAWTransformation const &transform)
{
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendShape: can not find the listener\n"));
    return false;
  }
  MWAWGraphicStyle style;
  m_state->updateLineStyle(shape.m_lineId, style);
  if (shape.m_type!=FreeHandParserInternal::Shape::Line &&
      (shape.m_type!=FreeHandParserInternal::Shape::Path || shape.m_closed))
    m_state->updateFillStyle(shape.m_fillId, style);
  MWAWTransformation finalTransformation=transform*shape.m_transformation;
  MWAWGraphicShape res;
  if (shape.updateShape(res)) {
    res=res.transform(finalTransformation);
    MWAWPosition pos(res.m_bdBox[0], res.m_bdBox.size(), librevenge::RVNG_POINT);
    pos.m_anchorTo = MWAWPosition::Page;
    listener->insertShape(pos, res, style);
    return true;
  }
  MWAW_DEBUG_MSG(("FreeHandParser::sendShape: found some unexpected shape\n"));
  return false;
}

bool FreeHandParser::sendTextbox(FreeHandParserInternal::Textbox const &textbox, MWAWTransformation const &transform)
{
  textbox.m_isSent=true;
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendTextbox: can not find the listener\n"));
    return false;
  }
  MWAWGraphicStyle style(MWAWGraphicStyle::emptyStyle());
  MWAWTransformation finalTransformation=transform*textbox.m_transformation;
  MWAWTransformation transf;
  float rotation=0;
  MWAWBox2f box;
  if (decomposeMatrix(finalTransformation,rotation,transf,textbox.m_box.center())) {
    box=transf*textbox.m_box;
    style.m_rotate=rotation;
  }
  else
    box=finalTransformation*textbox.m_box;
  for (int c=0; c<2; ++c) {
    if (box.min()[c]>box.max()[c])
      std::swap(box.min()[c],box.max()[c]);
  }
  MWAWPosition pos(box[0], box.size(), librevenge::RVNG_POINT);
  pos.m_anchorTo = MWAWPosition::Page;
  std::shared_ptr<MWAWSubDocument> doc(new FreeHandParserInternal::SubDocument(*this, getInput(), textbox.m_id));
  listener->insertTextBox(pos, doc, style);
  return false;
}

bool FreeHandParser::sendText(int zId)
{
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendText: can not find the listener\n"));
    return false;
  }
  if (m_state->m_zIdToTextboxMap.find(zId)==m_state->m_zIdToTextboxMap.end()) {
    MWAW_DEBUG_MSG(("FreeHandParser::sendText: can not find the text shape\n"));
    return false;
  }
  auto &textbox=m_state->m_zIdToTextboxMap.find(zId)->second;
  MWAWParagraph para;
  para.m_justify = textbox.m_justify;
  listener->setParagraph(para);
  if (!textbox.m_text.valid())
    return true;
  MWAWInputStreamPtr input=getInput();
  input->seek(textbox.m_text.begin(), librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  f << "Textbox[text]:";
  long endPos=textbox.m_text.end();
  int cPos=0;
  float deltaSpacing=textbox.m_spacings[0];
  while (!input->isEnd()) {
    if (input->tell()>=endPos)
      break;
    if (textbox.m_posToFontMap.find(cPos)!=textbox.m_posToFontMap.end()) {
      auto &font=textbox.m_posToFontMap.find(cPos)->second;
      if (font.m_nameId && m_state->m_zIdToStringMap.find(font.m_nameId)!= m_state->m_zIdToStringMap.end())
        font.m_font.setId(getParserState()->m_fontConverter->getId(m_state->m_zIdToStringMap.find(font.m_nameId)->second));
      // color
      if (font.m_colorId && m_state->m_zIdToColorMap.find(font.m_colorId) != m_state->m_zIdToColorMap.end())
        font.m_font.setColor(m_state->m_zIdToColorMap.find(font.m_colorId)->second);
      // spacing
      font.m_font.setDeltaLetterSpacing(deltaSpacing, librevenge::RVNG_POINT);
      // streching
      bool needStreching=false;
      if (textbox.m_scalings[1]<1||textbox.m_scalings[1]>1) {
        font.m_font.setSize(font.m_font.size()*textbox.m_scalings[1]);
        needStreching=true;
      }
      if ((needStreching || textbox.m_scalings[0]<1 || textbox.m_scalings[0]>1) && textbox.m_scalings[1]>0)
        font.m_font.setWidthStreching(textbox.m_scalings[0]/textbox.m_scalings[1]);
      listener->setFont(font.m_font);
      f << "[F]";
    }
    ++cPos;
    auto c = char(input->readULong(1));
    if (c==0) {
      MWAW_DEBUG_MSG(("FreeHandParser::sendText: find char 0\n"));
      f << "#[0]";
      continue;
    }
    f << c;
    switch (c) {
    case 9:
      listener->insertTab();
      break;
    case 0xd:
      listener->insertEOL();
      break;
    default:
      listener->insertCharacter(static_cast<unsigned char>(c), input, endPos);
      break;
    }
  }
  ascii().addPos(textbox.m_text.begin());
  ascii().addNote(f.str().c_str());

  return true;
}

void FreeHandParser::flushExtra()
{
  bool first=true;
  for (auto it : m_state->m_zIdToShapeMap) {
    if (it.second.m_isSent) continue;
    if (first) {
      MWAW_DEBUG_MSG(("FreeHandParser::flushExtra: find some unused shape: %d\n", it.first));
      first=false;
    }
    sendZone(it.first, m_state->m_transform);
  }
  first=true;
  for (auto it : m_state->m_zIdToTextboxMap) {
    if (it.second.m_isSent) continue;
    if (first) {
      MWAW_DEBUG_MSG(("FreeHandParser::flushExtra: find some unsed textbox %d\n", it.first));
      first=false;
    }
    sendZone(it.first, m_state->m_transform);
  }
}

bool FreeHandParser::decomposeMatrix(MWAWTransformation const &matrix, float &rot, MWAWTransformation &transform, MWAWVec2f const &origCenter)
{
  // FIXME: this assumes that there is no skewing, ...
  MWAWVec3f const &yRow=matrix[1];
  if (matrix.isIdentity() || (yRow[0]>=0 && yRow[0]<=0)) return false;
  rot=std::atan2(yRow[0],-yRow[1]);
  rot *= float(180/M_PI);
  transform=MWAWTransformation::rotation(-rot, matrix * origCenter) * matrix;
  return true;
}

// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: