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

#include <librevenge/librevenge.h>

#include "MWAWTextListener.hxx"
#include "MWAWHeader.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWPosition.hxx"
#include "MWAWPictMac.hxx"
#include "MWAWPrinter.hxx"
#include "MWAWSubDocument.hxx"

#include "MacWrtProStructures.hxx"

#include "MacWrtProParser.hxx"

// set this flag to true to create an ascii file of the original
#define DEBUG_RECONSTRUCT 0

/** Internal: the structures of a MacWrtProParser */
namespace MacWrtProParserInternal
{
////////////////////////////////////////
//! Internal: a struct used to store a zone
struct Zone {
  Zone()
    : m_type(-1)
    , m_blockId(0)
    , m_data()
    , m_input()
    , m_asciiFile(MWAWInputStreamPtr())
    , m_parsed(false)
  {
  }
  ~Zone()
  {
    ascii().reset();
  }

  //! returns the debug file
  libmwaw::DebugFile &ascii()
  {
    return m_asciiFile;
  }

  //! the type : 0(text), 1(graphic)
  int m_type;

  //! the first block id
  int m_blockId;

  //! the storage
  librevenge::RVNGBinaryData m_data;

  //! the main input
  MWAWInputStreamPtr m_input;

  //! the debug file
  libmwaw::DebugFile m_asciiFile;

  //! true if the zone is sended
  bool m_parsed;
};

//! Internal: a struct used to store a text zone
struct TextZoneData {
  TextZoneData()
    : m_type(-1)
    , m_length(0)
    , m_id(0)
  {
  }
  friend std::ostream &operator<<(std::ostream &o, TextZoneData const &tData)
  {
    switch (tData.m_type) {
    case 0:
      o << "C" << tData.m_id << ",";
      break;
    case 1:
      o << "P" << tData.m_id << ",";
      break;
    default:
      o << "type=" << tData.m_type << ",id=" << tData.m_id << ",";
      break;
    }
    o << "nC=" << tData.m_length << ",";
    return o;
  }
  //! the type
  int m_type;
  //! the text length
  int m_length;
  //! an id
  int m_id;
};

//! Internal: a struct used to store a text zone
struct Token {
  Token()
    : m_type(-1)
    , m_length(0)
    , m_blockId(-1)
    , m_box()
  {
    for (auto &fl : m_flags) fl = 0;
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Token const &tkn)
  {
    o << "nC=" << tkn.m_length << ",";
    switch (tkn.m_type) {
    case 1:
      o << "pagenumber,";
      break;
    case 2:
      o << "footnote(pos),";
      break;
    case 3:
      o << "footnote(content),";
      break;
    case 4:
      o << "figure,";
      break;
    case 5:
      o << "hyphen,";
      break;
    case 6:
      o << "date,";
      break;
    case 7:
      o << "time,";
      break;
    case 8:
      o << "title,";
      break;
    case 9:
      o << "revision,";
      break;
    case 10:
      o << "sectionnumber,";
      break;
    default:
      o << "#type=" << tkn.m_type << ",";
    }
    if (tkn.m_blockId >= 0) o << "blockId=" << tkn.m_blockId << ",";
    for (int i = 0; i < 4; i++) {
      if (tkn.m_flags[i]) o << "fl" << i << "=" << std::hex << tkn.m_flags[i] << ",";
    }
    return o;
  }
  //! the type
  int m_type;
  //! the text length
  int m_length;
  //! the block id
  int m_blockId;
  //! the bdbox ( filled in MWII for figure)
  MWAWBox2f m_box;
  //! some flags
  int m_flags[4];
};

//! Internal: a struct used to store a text zone
struct TextZone {
  TextZone()
    : m_textLength(0)
    , m_entries()
    , m_tokens()
    , m_parsed(false)
  {
  }

  //! the text length
  int m_textLength;

  //! the list of entries
  std::vector<MWAWEntry> m_entries;

  //! two vector list of id ( charIds, paragraphIds)
  std::vector<TextZoneData> m_ids[2];

  //! the tokens list
  std::vector<Token> m_tokens;

  //! true if the zone is sended
  bool m_parsed;
};


////////////////////////////////////////
//! Internal: the state of a MacWrtProParser
struct State {
  //! constructor
  State()
    : m_blocksMap()
    , m_dataMap()
    , m_textMap()
    , m_blocksCallByTokens()
    , m_col(1)
    , m_actPage(0)
    , m_numPages(0)
  {
  }

  //! the list of retrieved block : block -> new address
  std::map<int,long> m_blocksMap;

  //! the list of blockId->data zone
  std::map<int, std::shared_ptr<Zone> > m_dataMap;

  //! the list of blockId->text zone
  std::map<int, std::shared_ptr<TextZone> > m_textMap;

  //! the list of blockId called by tokens
  std::vector<int> m_blocksCallByTokens;

  int m_col /** the number of columns in MWII */;
  int m_actPage /** the actual page */, m_numPages /** the number of page of the final document */;
};

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

  //! destructor
  ~SubDocument() final {}

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

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

protected:
  //! the subdocument id
  int m_id;
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType /*type*/)
{
  if (m_id == -3) return; // empty block
  if (!listener.get()) {
    MWAW_DEBUG_MSG(("MacWrtProParserInternal::SubDocument::parse: no listener\n"));
    return;
  }
  auto *parser = dynamic_cast<MacWrtProParser *>(m_parser);
  if (!parser) {
    MWAW_DEBUG_MSG(("MacWrtProParserInternal::SubDocument::parse: no parser\n"));
    return;
  }

  long pos = m_input->tell();
  if (parser->m_structures.get())
    parser->m_structures->send(m_id);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}

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


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

MacWrtProParser::~MacWrtProParser()
{
}

void MacWrtProParser::init()
{
  resetTextListener();
  setAsciiName("main-1");

  m_state.reset(new MacWrtProParserInternal::State);
  m_structures.reset(new MacWrtProStructures(*this));

  // reduce the margin (in case, the page is not defined)
  getPageSpan().setMargins(0.1);
}

////////////////////////////////////////////////////////////
// position and height
////////////////////////////////////////////////////////////
int MacWrtProParser::numColumns() const
{
  if (m_state->m_col <= 1) return 1;
  return m_state->m_col;
}

////////////////////////////////////////////////////////////
// new page
////////////////////////////////////////////////////////////
void MacWrtProParser::newPage(int number, bool softBreak)
{
  if (number <= m_state->m_actPage) return;
  if (number > m_state->m_numPages) {
    MWAW_DEBUG_MSG(("MacWrtProParser::newPage: can not create new page\n"));
    return;
  }

  while (m_state->m_actPage < number) {
    m_state->m_actPage++;
    if (!getTextListener() || m_state->m_actPage == 1)
      continue;
    if (softBreak)
      getTextListener()->insertBreak(MWAWTextListener::SoftPageBreak);
    else
      getTextListener()->insertBreak(MWAWTextListener::PageBreak);
  }
}

std::vector<int> const &MacWrtProParser::getBlocksCalledByToken() const
{
  return m_state->m_blocksCallByTokens;
}


////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void MacWrtProParser::parse(librevenge::RVNGTextInterface *docInterface)
{
  if (!getInput().get() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = true;
  try {
    m_state->m_blocksMap.clear();

    // create the asciiFile
    ascii().setStream(getInput());
    ascii().open(asciiName());

    checkHeader(nullptr);

    ok = createZones();
#if DEBUG_RECONSTRUCT && defined(DEBUG_WITH_FILES)
    saveOriginal(getInput());
#endif
    if (ok) {
      createDocument(docInterface);
      if (m_structures) {
        m_structures->sendMainZone();
        m_structures->flushExtra();
      }
    }

    std::vector<int> freeList;
    if (getFreeZoneList(2, freeList) && freeList.size() > 1) {
      for (size_t i = 1; i < freeList.size(); i++) {
        ascii().addPos(freeList[i]*0x100);
        ascii().addNote("Entries(Free)");
      }
    }
#ifdef DEBUG
    checkUnparsed();
#endif

    ascii().reset();
  }
  catch (...) {
    MWAW_DEBUG_MSG(("MacWrtProParser::parse: exception catched when parsing\n"));
    ok = false;
  }

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

////////////////////////////////////////////////////////////
// returns a data zone which corresponds to a zone id
////////////////////////////////////////////////////////////
bool MacWrtProParser::getZoneData(librevenge::RVNGBinaryData &data, int blockId)
{
  data.clear();
  if (blockId < 1) {
    MWAW_DEBUG_MSG(("MacWrtProParser::getZoneData: block %d is invalid\n", blockId));
    return false;
  }
  MWAWInputStreamPtr input = getInput();
  input->seek((blockId-1)*0x100, librevenge::RVNG_SEEK_SET);

  unsigned long read;
  long first=blockId-1, last=blockId-1;
  int const linkSz = version()<= 0 ? 2 : 4;
  while (!input->isEnd()) {
    bool ok = true;
    for (long i=first; i<= last; i++) {
      if (m_state->m_blocksMap.find(static_cast<int>(i)) != m_state->m_blocksMap.end()) {
        MWAW_DEBUG_MSG(("MacWrtProParser::getZoneData: block %ld already seems\n", i));
        ok = false;
        break;
      }
      m_state->m_blocksMap[static_cast<int>(i)] = long(data.size())+(i-first)*0x100;
    }
    if (!ok) break;
    long endPos = (last+1)*0x100 - linkSz;
    long pos = input->tell();
    input->seek(endPos, librevenge::RVNG_SEEK_SET);
    long limit = input->tell();
    if (limit <= pos) break;
    input->seek(pos, librevenge::RVNG_SEEK_SET);

    const unsigned char *dt = input->read(size_t(limit-pos), read);
    data.append(dt, read);
    ascii().skipZone(first*0x100, (last+1)*0x100-1);

    if (long(read) != limit-pos) {
      MWAW_DEBUG_MSG(("MacWrtProParser::getZoneData: can not read all data\n"));
      break;
    }
    if (limit < endPos)
      break;
    input->seek(limit, librevenge::RVNG_SEEK_SET);

    long act = last;
    long val = input->readLong(linkSz);
    if (val == 0) break;
    if (val < 0)
      first = -val-1;
    else
      first = val-1;
    last = first;

    if (first != act+1) {
      input->seek(first*0x100, librevenge::RVNG_SEEK_SET);
      if (long(input->tell()) != first*0x100) {
        MWAW_DEBUG_MSG(("MacWrtProParser::getZoneData: can not go to %ld block\n", first));
        break;
      }
    }
    if (val < 0) {
      auto num = long(input->readULong(linkSz));
      last = first+(num-1);
    }
    if (last-first > 2) {
      pos = input->tell();
      input->seek((last-1)*0x100, librevenge::RVNG_SEEK_SET);
      if (long(input->tell()) != (last-1)*0x100) {
        MWAW_DEBUG_MSG(("MacWrtProParser::getZoneData: num %ld if odd\n", last));
        last = input->tell();
        last = (last>>8)+1;  // set last to the last block
      }
      input->seek(pos, librevenge::RVNG_SEEK_SET);
    }
  }
  return data.size() != 0;
}

////////////////////////////////////////////////////////////
// return the chain list of block ( used to get free blocks)
////////////////////////////////////////////////////////////
bool MacWrtProParser::getFreeZoneList(int blockId, std::vector<int> &blockLists)
{
  blockLists.clear();
  if (blockId < 1) {
    MWAW_DEBUG_MSG(("MacWrtProParser::getFreeZoneList: block %d is invalid\n", blockId));
    return false;
  }
  MWAWInputStreamPtr input = getInput();

  long first=blockId-1, last=blockId-1;
  while (1) {
    bool ok = true;
    auto firstIt = m_state->m_blocksMap.upper_bound(int(first));
    auto lastIt = m_state->m_blocksMap.lower_bound(int(last));
    if (firstIt != lastIt) {
      MWAW_DEBUG_MSG(("MacWrtProParser::getFreeZoneList: some blocks in range (%ld, %ld) already seen\n", first, last));
      ok = false;
      if (firstIt != m_state->m_blocksMap.end())
        last = firstIt->first-1;
    }
    if (last < first)
      break;

    long endPos = (last+1)*0x100 - 4;
    if (!input->checkPosition(endPos)) {
      MWAW_DEBUG_MSG(("MacWrtProParser::getFreeZoneList: position %ld is beoynd the end\n", endPos));
      break;
    }

    blockLists.reserve(blockLists.size() + static_cast<size_t>(last - first + 1));
    for (long i=first; i<= last; i++) {
      blockLists.push_back(static_cast<int>(i));
      m_state->m_blocksMap[static_cast<int>(i)] = 0;
    }

    if (!ok) break;
    input->seek(endPos, librevenge::RVNG_SEEK_SET);

    long act = last;
    long val = input->readLong(4);
    if (val == 0) break;
    if (val < 0)
      first = (-val)-1;
    else
      first = val-1;
    last = first;

    if (val < 0) {
      if (first != act+1) {
        if (input->seek(first*0x100, librevenge::RVNG_SEEK_SET) != 0) {
          MWAW_DEBUG_MSG(("MacWrtProParser::getFreeZoneList: can not go to %ld block\n", first));
          break;
        }
      }

      auto num = long(input->readULong(4));
      last = first+(num-1);
    }
  }
  return blockLists.size() != 0;
}

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

  // update the page
  m_state->m_actPage = 0;
  int numPages = m_structures ? m_structures->numPages() : 0;
  if (numPages <= 0) numPages = 1;
  m_state->m_numPages = numPages;

  // create the page list
  std::vector<MWAWPageSpan> pageList;

  int actHeaderId = 0, actFooterId = 0;
  std::shared_ptr<MacWrtProParserInternal::SubDocument> headerSubdoc, footerSubdoc;
  for (int i = 0; i < m_state->m_numPages;) {
    int numSim[2]= {1,1};
    int headerId =  m_structures->getHeaderId(i+1, numSim[0]);
    if (headerId != actHeaderId) {
      actHeaderId = headerId;
      if (actHeaderId == 0)
        headerSubdoc.reset();
      else
        headerSubdoc.reset
        (new MacWrtProParserInternal::SubDocument(*this, getInput(), headerId));
    }
    int footerId =  m_structures->getFooterId(i+1, numSim[1]);
    if (footerId != actFooterId) {
      actFooterId = footerId;
      if (actFooterId == 0)
        footerSubdoc.reset();
      else
        footerSubdoc.reset
        (new MacWrtProParserInternal::SubDocument(*this, getInput(), footerId));
    }

    MWAWPageSpan ps(getPageSpan());
    if (headerSubdoc) {
      MWAWHeaderFooter header(MWAWHeaderFooter::HEADER, MWAWHeaderFooter::ALL);
      header.m_subDocument=headerSubdoc;
      ps.setHeaderFooter(header);
    }
    if (footerSubdoc) {
      MWAWHeaderFooter footer(MWAWHeaderFooter::FOOTER, MWAWHeaderFooter::ALL);
      footer.m_subDocument=footerSubdoc;
      ps.setHeaderFooter(footer);
    }
    if (numSim[1] < numSim[0]) numSim[0]=numSim[1];
    if (numSim[0] < 1) numSim[0]=1;
    ps.setPageSpan(numSim[0]);
    i+=numSim[0];
    pageList.push_back(ps);
  }

  //
  MWAWTextListenerPtr listen(new MWAWTextListener(*getParserState(), pageList, documentInterface));
  setTextListener(listen);
  listen->startDocument();
}


////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool MacWrtProParser::createZones()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();

  if (!readPrintInfo()) {
    // bad sign, but we can try to recover
    ascii().addPos(pos);
    ascii().addNote("###PrintInfo");
    input->seek(pos+0x78, librevenge::RVNG_SEEK_SET);
  }

  pos = input->tell();
  if (!readDocHeader()) {
    ascii().addPos(pos);
    ascii().addNote("##Entries(Data0)");
  }

  // ok now ask the structure manager to retrieve its data
  return m_structures->createZones();
}

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

////////////////////////////////////////////////////////////
// read the header
////////////////////////////////////////////////////////////
bool MacWrtProParser::checkHeader(MWAWHeader *header, bool strict)
{
  *m_state = MacWrtProParserInternal::State();

  MWAWInputStreamPtr input = getInput();
  if (!input || !input->hasDataFork())
    return false;

  libmwaw::DebugStream f;
  int const headerSize=4;
  if (!input->checkPosition(headerSize+0x78)) {
    MWAW_DEBUG_MSG(("MacWrtProParser::checkHeader: file is too short\n"));
    return false;
  }
  input->seek(0,librevenge::RVNG_SEEK_SET);

  auto vers = static_cast<int>(input->readULong(2));
  auto val = static_cast<int>(input->readULong(2));

  f << "FileHeader:";
  switch (vers) {
  case 0x2e:
    vers = 0;
    if (val != 0x2e)
      return false;
    break;
  case 4:
    vers = 1;
    if (val != 4) {
#ifdef DEBUG
      if (strict || val < 3 || val > 5)
        return false;
      f << "#unk=" << val << ",";
#else
      return false;
#endif
    }
    break;
  default:
    MWAW_DEBUG_MSG(("MacWrtProParser::checkHeader: unknown version\n"));
    return false;
  }
  setVersion(vers);
  f << "vers=" << vers << ",";
  if (strict) {
    if (vers) {
      input->seek(0xdd, librevenge::RVNG_SEEK_SET);
      // "MP" seems always in this position
      if (input->readULong(2) != 0x4d50)
        return false;
    }
    else if (!readPrintInfo()) { // last chance, check DocHeader
      input->seek(4+0x78, librevenge::RVNG_SEEK_SET);
      if (input->readULong(2)) return false;
      val=static_cast<int>(input->readULong(2));
      if ((val&0x0280)!=0x0280) return false;
      for (int i=0; i<4; ++i) {
        val=static_cast<int>(input->readLong(1));
        if (val<-1 || val>1) return false;
      }
    }
  }


  // ok, we can finish initialization
  if (header)
    header->reset(MWAWDocument::MWAW_T_MACWRITEPRO, version());

  //
  input->seek(headerSize, librevenge::RVNG_SEEK_SET);

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

  return true;
}

////////////////////////////////////////////////////////////
// read the print info
////////////////////////////////////////////////////////////
bool MacWrtProParser::readPrintInfo()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  libmwaw::DebugStream f;
  // print info
  libmwaw::PrinterInfo info;
  if (!info.read(input)) return false;
  f << "Entries(PrintInfo):"<< info;

  MWAWVec2i paperSize = info.paper().size();
  MWAWVec2i pageSize = info.page().size();
  if (pageSize.x() <= 0 || pageSize.y() <= 0 ||
      paperSize.x() <= 0 || paperSize.y() <= 0) return false;

  // define margin from print info
  MWAWVec2i lTopMargin= -1 * info.paper().pos(0);
  MWAWVec2i rBotMargin=info.paper().pos(1) - info.page().pos(1);

  // move margin left | top
  int decalX = lTopMargin.x() > 14 ? lTopMargin.x()-14 : 0;
  int decalY = lTopMargin.y() > 14 ? lTopMargin.y()-14 : 0;
  lTopMargin -= MWAWVec2i(decalX, decalY);
  rBotMargin += MWAWVec2i(decalX, decalY);

  // decrease right | bottom
  int rightMarg = rBotMargin.x() -10;
  if (rightMarg < 0) rightMarg=0;
  int botMarg = rBotMargin.y() -50;
  if (botMarg < 0) botMarg=0;

  getPageSpan().setMarginTop(lTopMargin.y()/72.0);
  getPageSpan().setMarginBottom(botMarg/72.0);
  getPageSpan().setMarginLeft(lTopMargin.x()/72.0);
  getPageSpan().setMarginRight(rightMarg/72.0);
  getPageSpan().setFormLength(paperSize.y()/72.);
  getPageSpan().setFormWidth(paperSize.x()/72.);

  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+0x78, librevenge::RVNG_SEEK_SET);
  if (long(input->tell()) != pos+0x78) {
    MWAW_DEBUG_MSG(("MacWrtProParser::readPrintInfo: file is too short\n"));
    return false;
  }
  ascii().addPos(input->tell());

  return true;
}

////////////////////////////////////////////////////////////
// read the document header
////////////////////////////////////////////////////////////
bool MacWrtProParser::readDocHeader()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  libmwaw::DebugStream f;

  f << "Entries(Data0):";
  long val;
  if (version()==0) {
    val = input->readLong(2); // always 0 ?
    if (val) f << "f0=" << val << ",";
    /* fl0=[2|6|82|86], fl1=[80|a0|a4], other 0|1|-1 */
    for (int i = 0; i < 9; i++) {
      val = (i<2) ? int(input->readULong(1)) : input->readLong(1);
      if (!val) continue;
      if (i < 2)
        f << "fl" << i << "=" << std::hex << val << std::dec << ",";
      else
        f << "fl" << i << "=" << val << ",";
    }
    val = input->readLong(2); // always 612 ?
    if (val != 0x612) f << "f1=" << val << ",";
    val = input->readLong(1); // always 1 ?
    if (val != 1) f << "f2=" << val << ",";
    val = input->readLong(2); // always 2 ?
    if (val != 2) f << "f3=" << val << ",";
    val = input->readLong(2); // always 12c ?
    if (val != 0x12c) f << "f4=" << val << ",";
    for (int i = 0; i < 4; i++) { // 0, 0, 3c, a small number
      val = input->readLong(2);
      if (val) f << "g" << i << "=" << val << ",";
    }
    /* then
      0009000020000000fd803333000600000000000120 |
      000c000020000000fd803333000600000000000180 |
      000c000020000000fd8033330006000000000001a0 |
      000c0000200e0000fd8033330006000000000001a0 |
      00240000200e0000fd8033330006000000000001a0

      and
      000001000000016f66000000000000000800090001000000
     */
  }
  else {
    val = input->readLong(1); // always 0 ?
    if (val) f << "unkn=" << val << ",";
    auto N=static_cast<int>(input->readLong(2)); // find 2, a, 9e, 1a
    f << "N?=" << N << ",";
    N = static_cast<int>(input->readLong(1)); // almost always 0, find one time 6 ?
    if (N) f << "N1?=" << N << ",";
    val = static_cast<int>(input->readLong(2)); // almost always 0x622, find also 0 and 12
    f << "f0=" << std::hex << val << std::dec << ",";
    val = static_cast<int>(input->readLong(1)); // always 0 ?
    if (val) f << "unkn1=" << val << ",";
    N = static_cast<int>(input->readLong(2));
    f << "N2?=" << N << ",";
    val = input->readLong(1); // almost always 1 ( find one time 2)
    f << "f1=" << val << ",";
    int const defVal[] = { 0x64, 0/*small number between 1 and 8*/, 0x24 };
    for (int i = 0; i < 3; i++) {
      val = input->readLong(2);
      if (val != defVal[i])
        f << "f" << i+2 << "=" << val << ",";
    }
    for (int i = 5; i < 10; i++) { // always 0 ?
      val = input->readLong(1);
      if (val)
        f << "f" << i << "=" << val << ",";
    }
    val = input->readLong(2); // always 480 ?
    if (val != 0x480) f << "f10=" << val << ",";
    val = long(input->readULong(1)); // always 0 ?
    if (val) f << "f11=" << val << ",";
  }
  float dim[6];
  bool ok = true;
  for (auto &d : dim) {
    d = float(input->readLong(4))/65356.f;
    if (d<0) ok=false;
  }
  if (ok) ok = dim[0] > dim[2]+dim[3] && dim[1] > dim[4]+dim[5];

  if (ok) {
    getPageSpan().setMarginTop(double(dim[2])/72.0);
    getPageSpan().setMarginLeft(double(dim[4])/72.0);
    /* decrease a little the right/bottom margin to allow fonts discrepancy*/
    getPageSpan().setMarginBottom((dim[3]<36) ? 0.0 : double(dim[3])/72.0-0.5);
    getPageSpan().setMarginRight((dim[5]<18) ? 0.0 : double(dim[5])/72.0-0.25);
    getPageSpan().setFormLength(double(dim[0])/72.);
    getPageSpan().setFormWidth(double(dim[1])/72.);
  }
  else {
    MWAW_DEBUG_MSG(("MacWrtProParser::readDocHeader: find odd page dimensions, ignored\n"));
    f << "#";
  }
  f << "dim=" << dim[1] << "x" << dim[0] << ",";
  f << "margins=["; // top, bottom, left, right
  for (int i = 2; i < 6; i++) f << dim[i] << ",";
  f << "],";
  if (version()==0) {
    m_state->m_col = static_cast<int>(input->readLong(2));
    if (m_state->m_col != 1) f << "col=" << m_state->m_col << ",";
  }

  ascii().addDelimiter(input->tell(), '|');
  /** then find
      000000fd0000000000018200000100002f00
      44[40|80] followed by something like a7c3ec07|a7c4c3c6 : 2 ptrs ?
      6f6600000000000000080009000105050506010401
   */
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  f.str("");
  f << "Data0-A:";
  if (version()==0) {
    input->seek(pos+120, librevenge::RVNG_SEEK_SET);
    pos = input->tell();
    for (int i = 0; i < 3; i++) { // f0=f1, f2=0
      val = long(input->readULong(4));
      if (val) f << "f" << i << "=" << std::hex << val << std::dec << ",";
    }
  }
  else {
    input->seek(pos+97, librevenge::RVNG_SEEK_SET);
    pos = input->tell();
    val = long(input->readULong(2));
    if (val != 0x4d50) // MP
      f << "#keyWord=" << std::hex << val <<std::dec;
    //always 4, 4, 6 ?
    for (int i = 0; i < 3; i++) {
      val = input->readLong(1);
      if ((i==2 && val!=6) || (i < 2 && val != 4))
        f << "f" << i << "=" << val << ",";
    }
    for (int i = 3; i < 9; i++) { // always 0 ?
      val = input->readLong(2);
      if (val) f << "f"  << i << "=" << val << ",";
    }
  }
  // some dim ?
  f << "dim=[";
  for (int i = 0; i < 4; i++)
    f << input->readLong(2) << ",";
  f << "],";
  // always 0x48 0x48
  for (int i = 0; i < 2; i++) {
    val = input->readLong(2);
    if (val != 0x48) f << "g"  << i << "=" << val << ",";
  }
  // always 0 ?
  for (int i = 2; i < 42; i++) {
    val = long(input->readULong(2));
    if (val) f << "g"  << i << "=" << std::hex << val << std::dec << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  // then junk ? (ie. find a string portion, a list of 0...),
  pos = input->tell();
  f.str("");
  f << "Data0-B:";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  // interesting data seems to begin again in 0x200...
  input->seek(0x200, librevenge::RVNG_SEEK_SET);
  ascii().addPos(input->tell());
  ascii().addNote("_");
  return true;
}

////////////////////////////////////////////////////////////
// try to parse a data zone
////////////////////////////////////////////////////////////
bool MacWrtProParser::parseDataZone(int blockId, int type)
{
  if (m_state->m_dataMap.find(blockId) != m_state->m_dataMap.end())
    return true;
  if (blockId < 1) {
    MWAW_DEBUG_MSG(("MacWrtProParser::parseDataZone: block %d seems bad\n", blockId));
    return false;
  }
  if (m_state->m_blocksMap.find(blockId-1) != m_state->m_blocksMap.end()) {
    MWAW_DEBUG_MSG(("MacWrtProParser::parseDataZone: block %d is already parsed\n", blockId));
    return false;
  }

  std::shared_ptr<MacWrtProParserInternal::Zone> zone(new MacWrtProParserInternal::Zone);
  zone->m_blockId = blockId;
  zone->m_type = type;

  if (!getZoneData(zone->m_data, blockId))
    return false;
  zone->m_input=MWAWInputStream::get(zone->m_data, false);
  if (!zone->m_input)
    return false;

  zone->m_asciiFile.setStream(zone->m_input);
  std::stringstream s;
  s << "DataZone" << std::hex << blockId << std::dec;
  zone->m_asciiFile.open(s.str());
  m_state->m_dataMap[blockId] = zone;

  // ok init is done
  if (type == 0)
    parseTextZone(zone);
  else if (type == 1)
    ;
  else {
    libmwaw::DebugStream f;
    f << "Entries(DataZone):type" << type;
    zone->m_asciiFile.addPos(0);
    zone->m_asciiFile.addNote(f.str().c_str());
  }
  return true;
}

bool MacWrtProParser::parseTextZone(std::shared_ptr<MacWrtProParserInternal::Zone> zone)
{
  if (!zone) return false;
  if (zone->m_type != 0) {
    MWAW_DEBUG_MSG(("MacWrtProParser::parseTextZone: not a picture date\n"));
    return false;
  }

  MWAWInputStreamPtr input = zone->m_input;
  MWAWInputStreamPtr fileInput = getInput();
  libmwaw::DebugFile &asciiFile = zone->m_asciiFile;
  libmwaw::DebugStream f;

  std::shared_ptr<MacWrtProParserInternal::TextZone> text(new MacWrtProParserInternal::TextZone);

  long pos = 0;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(TextZone):";
  text->m_textLength = static_cast<int>(input->readLong(4));
  f << "textLength=" << text->m_textLength << ",";

  asciiFile.addPos(pos);
  asciiFile.addNote(f.str().c_str());

  if (!readTextEntries(zone, text->m_entries, text->m_textLength))
    return false;
  m_state->m_textMap[zone->m_blockId] = text;

  int n=0;
  for (auto &entry : text->m_entries) {
    n++;
    if (!fileInput->checkPosition(entry.begin())) {
      MWAW_DEBUG_MSG(("MacWrtProParser::parseTextZone: bad block id for block %ld\n", long(n-1)));
      entry.setBegin(-1);
    }
  }
  for (int i = 0; i < 2; i++) {
    if (!readTextIds(zone, text->m_ids[i], text->m_textLength, i))
      return true;
  }

  if (!readTextTokens(zone, text->m_tokens, text->m_textLength))
    return true;

  asciiFile.addPos(input->tell());
  asciiFile.addNote("TextZone(end)");

  return true;
}

bool MacWrtProParser::readTextEntries(std::shared_ptr<MacWrtProParserInternal::Zone> zone,
                                      std::vector<MWAWEntry> &res, int textLength)
{
  res.resize(0);
  int vers = version();
  int expectedSize = vers == 0 ? 4 : 6;
  MWAWInputStreamPtr input = zone->m_input;
  libmwaw::DebugFile &asciiFile = zone->m_asciiFile;
  libmwaw::DebugStream f;
  long pos = input->tell();

  auto val = static_cast<int>(input->readULong(2));
  auto sz = static_cast<int>(input->readULong(2));
  if ((sz%expectedSize) != 0) {
    MWAW_DEBUG_MSG(("MacWrtProParser::readTextEntries: find an odd size\n"));
    return false;
  }
  long endPos = pos+sz+4;

  int numElt = sz/expectedSize;
  f << "TextZone:entry(header),N=" << numElt << ",";
  if (val) f << "unkn=" << val << ",";
  asciiFile.addPos(pos);
  asciiFile.addNote(f.str().c_str());

  int remainLength = textLength;
  for (int i = 0; i < numElt; i++) {
    pos = input->tell();
    f.str("");
    f << "TextZone-" << i << ":entry,";
    int unkn = 0;
    if (vers >= 1) {
      unkn = static_cast<int>(input->readLong(2));
      if (unkn) f << "unkn=" << unkn << ",";
    }
    auto bl = static_cast<int>(input->readLong(2));
    f << "block=" << std::hex << bl << std::dec << ",";
    auto nChar = static_cast<int>(input->readULong(2));
    f << "blockSz=" << nChar;

    if (nChar > remainLength || nChar > 256) {
      MWAW_DEBUG_MSG(("MacWrtProParser::readTextEntries: bad size for block %d\n", i));
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    remainLength -= nChar;
    bool ok = bl >= 3 && m_state->m_blocksMap.find(bl-1) == m_state->m_blocksMap.end();
    if (!ok) {
      MWAW_DEBUG_MSG(("MacWrtProParser::readTextEntries: bad block id for block %d\n", i));
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }

    m_state->m_blocksMap[bl-1] = 0;
    asciiFile.addPos(pos);
    asciiFile.addNote(f.str().c_str());
    if (nChar==0) continue;

    MWAWEntry entry;
    entry.setId(unkn);
    entry.setBegin((bl-1)*0x100);
    entry.setLength(nChar);
    res.push_back(entry);
  }

  if (remainLength) {
    MWAW_DEBUG_MSG(("MacWrtProParser::readTextEntries: can not find %d characters\n", remainLength));
    asciiFile.addPos(input->tell());
    asciiFile.addNote("TextEntry-#");
  }

  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  return long(input->tell() == endPos) && res.size() != 0;
}

bool MacWrtProParser::readTextIds(std::shared_ptr<MacWrtProParserInternal::Zone> zone,
                                  std::vector<MacWrtProParserInternal::TextZoneData> &res,
                                  int textLength, int type)
{
  res.resize(0);
  MWAWInputStreamPtr input = zone->m_input;
  libmwaw::DebugFile &asciiFile = zone->m_asciiFile;
  libmwaw::DebugStream f;
  long pos = input->tell();

  auto val = static_cast<int>(input->readULong(2));
  auto sz = static_cast<int>(input->readULong(2));
  if (sz == 0) {
    asciiFile.addPos(pos);
    asciiFile.addNote("_");
    return true;
  }

  if ((sz%6) != 0) {
    MWAW_DEBUG_MSG(("MacWrtProParser::readTextIds: find an odd size\n"));
    return false;
  }
  long endPos = pos+sz+4;

  int numElt = sz/6;
  f << "TextZone:type=" << type << "(header),N=" << numElt << ",";
  if (val) f << "unkn=" << val << ",";
  asciiFile.addPos(pos);
  asciiFile.addNote(f.str().c_str());

  long remainLength = textLength;
  for (int i = 0; i < numElt; i++) {
    MacWrtProParserInternal::TextZoneData data;
    data.m_type = type;
    pos = input->tell();
    data.m_id = static_cast<int>(input->readLong(2));
    auto nChar = long(input->readULong(4));
    data.m_length = static_cast<int>(nChar);
    f.str("");
    f << "TextZone-" << i<< ":" << data;

    if (nChar > remainLength) {
      MWAW_DEBUG_MSG(("MacWrtProParser::readTextIds: bad size for block %d\n", i));
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    remainLength -= nChar;
    asciiFile.addPos(pos);
    asciiFile.addNote(f.str().c_str());
    if (nChar==0) continue;

    res.push_back(data);
  }

  if (remainLength) {
    MWAW_DEBUG_MSG(("MacWrtProParser::readTextIds: can not find %ld characters\n", remainLength));
    asciiFile.addPos(input->tell());
    asciiFile.addNote("TextZone:id-#");
  }

  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  return long(input->tell() == endPos) && res.size() != 0;
}

bool MacWrtProParser::readTextTokens(std::shared_ptr<MacWrtProParserInternal::Zone> zone,
                                     std::vector<MacWrtProParserInternal::Token> &res,
                                     int textLength)
{
  res.resize(0);
  int vers = version();
  int expectedSz = vers==0 ? 8 : 10;
  MWAWInputStreamPtr input = zone->m_input;
  libmwaw::DebugFile &asciiFile = zone->m_asciiFile;
  libmwaw::DebugStream f;
  long pos = input->tell();

  auto val = static_cast<int>(input->readULong(2));
  if (val && vers == 0) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    asciiFile.addPos(pos);
    asciiFile.addNote("_");
    return true;
  }
  long sz = static_cast<int>(input->readULong(2));
  if (sz == 0) {
    asciiFile.addPos(pos);
    asciiFile.addNote("_");
    return true;
  }

  if ((sz%expectedSz) != 0) {
    MWAW_DEBUG_MSG(("MacWrtProParser::readTextTokens: find an odd size\n"));
    return false;
  }
  long endPos = pos+sz+4;

  auto numElt = int(sz/expectedSz);
  f << "TextZone:token(header),N=" << numElt << ",";
  if (val) f << "unkn=" << val << ",";
  asciiFile.addPos(pos);
  asciiFile.addNote(f.str().c_str());

  long remainLength = textLength;
  int numFootnotes = 0;
  std::vector<int> pictPos;
  for (int i = 0; i < numElt; i++) {
    f.str("");
    pos = input->tell();

    MacWrtProParserInternal::Token data;
    data.m_type = static_cast<int>(input->readULong(1));
    if (vers==0) { // check me
      switch (data.m_type) {
      case 2:  // page number
        data.m_type=1;
        break;
      case 3:  // footnote content
        break;
      case 4: // figure
        break;
      case 5: // footnote pos
        data.m_type=2;
        data.m_blockId = ++numFootnotes; // for MW2
        break;
      case 0x15: // Fixme: must find other date
      case 0x17: // date alpha
        data.m_type=6;
        break;
      case 0x1a: // time
        data.m_type=7;
        break;
      default:
        MWAW_DEBUG_MSG(("MacWrtProParser::readTextTokens: unknown block type %d\n", data.m_type));
        f << "#type=" << data.m_type << ",";
        data.m_type = -1;
        break;
      }
    }
    data.m_flags[0] = static_cast<int>(input->readULong(1));
    auto nChar = long(input->readULong(vers == 0 ? 2 : 4));
    data.m_length = static_cast<int>(nChar);
    for (int j = 1; j < 3; j++) data.m_flags[j] = static_cast<int>(input->readULong(1));
    if (vers == 0)
      data.m_flags[3] = static_cast<int>(input->readULong(2));
    else
      data.m_blockId = static_cast<int>(input->readULong(2));
    f << "TextZone-" << i<< ":token," << data;
    if (nChar > remainLength) {
      MWAW_DEBUG_MSG(("MacWrtProParser::readTextTokens: bad size for block %d\n", i));
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    remainLength -= nChar;
    asciiFile.addPos(pos);
    asciiFile.addNote(f.str().c_str());
    if (data.m_type == 4) pictPos.push_back(static_cast<int>(res.size()));
    res.push_back(data);

    if (vers == 1 && data.m_blockId && (data.m_type == 2 || data.m_type == 4))
      m_state->m_blocksCallByTokens.push_back(data.m_blockId);
  }
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  if (vers == 0 && pictPos.size()) {
    size_t numPict = pictPos.size();
    // checkme always inverted ?
    for (size_t i = numPict; i > 0; i--) {
      MacWrtProParserInternal::Token &token = res[size_t(pictPos[i-1])];
      pos = input->tell();
      f.str("");
      f << "TextZone-pict" << i-1<< ":";
      val = static_cast<int>(input->readLong(2));
      if (val) f << "unkn=" << val << ",";
      auto blockId = static_cast<int>(input->readULong(2));
      if (blockId) {
        token.m_blockId = blockId;
        f << "block=" << blockId << ",";
        parseDataZone(blockId,1);
      }
      sz = long(input->readULong(4));
      f << "sz=" << std::hex << sz << std::dec << ",";
      int dim[4];
      for (auto &d : dim) d = static_cast<int>(input->readLong(2));
      token.m_box = MWAWBox2f(MWAWVec2f(float(dim[1]),float(dim[0])), MWAWVec2f(float(dim[3]),float(dim[2])));
      f << "dim=[" << dim[1] << "x" << dim[0] << "-"
        << dim[3] << "x" << dim[2] << ",";
      for (auto &d : dim) d = static_cast<int>(input->readLong(2));
      f << "dim2=[" << dim[1] << "x" << dim[0] << "-"
        << dim[3] << "x" << dim[2] << ",";
      // followed by junk ?
      ascii().addDelimiter(input->tell(),'|');
      input->seek(pos+62, librevenge::RVNG_SEEK_SET);
      asciiFile.addPos(pos);
      asciiFile.addNote(f.str().c_str());
    }
  }

  return long(input->tell() == endPos) && res.size() != 0;
}

////////////////////////////////////////////////////////////
// try to send a empty zone
////////////////////////////////////////////////////////////
bool MacWrtProParser::sendEmptyFrameZone(MWAWPosition const &pos, MWAWGraphicStyle const &style)
{
  std::shared_ptr<MacWrtProParserInternal::SubDocument> subdoc
  (new MacWrtProParserInternal::SubDocument(*this, getInput(), -3));
  if (getTextListener())
    getTextListener()->insertTextBox(pos, subdoc, style);
  return true;
}

////////////////////////////////////////////////////////////
// try to send a text
////////////////////////////////////////////////////////////
int MacWrtProParser::findNumHardBreaks(int blockId)
{
  auto it = m_state->m_textMap.find(blockId);
  if (it == m_state->m_textMap.end()) {
    MWAW_DEBUG_MSG(("MacWrtProParser::findNumHardBreaks: can not find text zone\n"));
    return 0;
  }
  return findNumHardBreaks(it->second);
}

int MacWrtProParser::findNumHardBreaks(std::shared_ptr<MacWrtProParserInternal::TextZone> zone)
{
  if (!zone->m_entries.size()) return 0;
  int num = 0;
  MWAWInputStreamPtr input = getInput();
  for (auto const &entry : zone->m_entries) {
    input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
    for (int j = 0; j < entry.length(); j++) {
      switch (input->readULong(1)) {
      case 0xc: // hard page
      case 0xb: // difficult to differentiate column/page break so...
        num++;
        break;
      default:
        break;
      }
    }
  }
  return num;
}

////////////////////////////////////////////////////////////
// try to send a text
////////////////////////////////////////////////////////////
bool MacWrtProParser::sendTextZone(int blockId, bool mainZone)
{
  auto it = m_state->m_textMap.find(blockId);
  if (it == m_state->m_textMap.end()) {
    MWAW_DEBUG_MSG(("MacWrtProParser::sendTextZone: can not find text zone\n"));
    return false;
  }
  sendText(it->second, mainZone);
  return true;
}

bool MacWrtProParser::sendTextBoxZone(int blockId, MWAWPosition const &pos, MWAWGraphicStyle const &style)
{
  std::shared_ptr<MacWrtProParserInternal::SubDocument> subdoc
  (new MacWrtProParserInternal::SubDocument(*this, getInput(), blockId));
  if (getTextListener())
    getTextListener()->insertTextBox(pos, subdoc, style);
  return true;
}

namespace MacWrtProParserInternal
{
/** Internal and low level: structure used to sort the position of data */
struct DataPosition {
  //! constructor
  DataPosition(int type=-1, int id=-1, long pos=0)
    : m_type(type)
    , m_id(id)
    , m_pos(pos)
  {
  }
  //! the type
  int m_type;
  //! an id
  int m_id;
  //! the position
  long m_pos;
  //! the comparison structure
  struct Compare {
    //! comparaison function
    bool operator()(DataPosition const &p1, DataPosition const &p2) const
    {
      long diff = p1.m_pos - p2.m_pos;
      if (diff) return (diff < 0);
      diff = p1.m_type - p2.m_type;
      if (diff) return (diff < 0);
      diff = p1.m_id - p2.m_id;
      return (diff < 0);
    }
  };
};
}

bool MacWrtProParser::sendText(std::shared_ptr<MacWrtProParserInternal::TextZone> zone, bool mainZone)
{
  if (!zone->m_entries.size()) {
    MWAW_DEBUG_MSG(("MacWrtProParser::sendText: can not find the text entries\n"));
    return false;
  }
  int vers = version();
  MacWrtProStructuresListenerState listenerState(m_structures, mainZone);
  MacWrtProParserInternal::DataPosition::Compare compareFunction;
  std::set<MacWrtProParserInternal::DataPosition, MacWrtProParserInternal::DataPosition::Compare>
  set(compareFunction);
  long cPos = 0;
  for (size_t i = 0; i < zone->m_entries.size(); i++) {
    set.insert(MacWrtProParserInternal::DataPosition(3, static_cast<int>(i), cPos));
    cPos += zone->m_entries[i].length();
  }
  set.insert(MacWrtProParserInternal::DataPosition(4, 0, cPos));
  cPos = 0;
  for (size_t i = 0; i < zone->m_tokens.size(); i++) {
    cPos += zone->m_tokens[i].m_length;
    set.insert(MacWrtProParserInternal::DataPosition(2, static_cast<int>(i), cPos));
  }
  for (int id = 0; id < 2; id++) {
    cPos = 0;
    for (size_t i = 0; i < zone->m_ids[id].size(); i++) {
      set.insert(MacWrtProParserInternal::DataPosition(1-id, static_cast<int>(i), cPos));
      cPos += zone->m_ids[id][i].m_length;
    }
  }
  std::vector<int> pageBreaks=listenerState.getPageBreaksPos();
  for (size_t i = 0; i < pageBreaks.size(); i++) {
    if (pageBreaks[i]<=0 || pageBreaks[i] >= zone->m_textLength) {
      MWAW_DEBUG_MSG(("MacWrtProParser::sendText: page breaks seems bad\n"));
      break;
    }
    set.insert(MacWrtProParserInternal::DataPosition(-1, static_cast<int>(i), pageBreaks[i]));
  }

  MWAWInputStreamPtr input = getInput();
  long pos = zone->m_entries[0].begin();
  long asciiPos = pos;
  if (pos > 0)
    input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f, f2;
  cPos = 0;
  for (auto const &data : set) {
    long oldPos = pos;
    if (data.m_pos < cPos) {
      MWAW_DEBUG_MSG(("MacWrtProParser::sendText: position go backward, stop...\n"));
      break;
    }
    if (data.m_pos != cPos) {
      if (pos > 0) {
        std::string text("");
        for (long i = cPos; i < data.m_pos && !input->isEnd(); i++) {
          auto ch = char(input->readULong(1));
          if (!ch)
            text+= "#";
          else {
            listenerState.sendChar(ch);
            if (ch > 0 && ch < 20 && ch != 0xd && ch != 0x9) text+="#";
            text+=ch;
          }
        }
        f << "'" << text << "'";
      }

      if (pos > 0 && f.str().length()) {
        f2.str("");
        f2 << "Entries(TextContent):" << f.str();
        f.str("");
        ascii().addPos(asciiPos);
        ascii().addNote(f2.str().c_str());
        pos += (data.m_pos-cPos);
      }

      cPos = data.m_pos;
    }
    switch (data.m_type) {
    case -1:
      listenerState.insertSoftPageBreak();
      break;
    case 4:
    case 3:
      if (pos > 0 && (pos&0xFF))
        ascii().addDelimiter(pos,'|');
      if (data.m_type == 3) {
        pos = zone->m_entries[size_t(data.m_id)].begin();
        if (pos > 0)
          input->seek(pos, librevenge::RVNG_SEEK_SET);
      }
      break;
    case 2: {
      // save the position because we read some extra data ( footnote, table, textbox)
      long actPos = input->tell();
      switch (zone->m_tokens[size_t(data.m_id)].m_type) {
      case 1:
        if (getTextListener()) getTextListener()->insertField(MWAWField(MWAWField::PageNumber));
        break;
      case 2:
        if (vers == 1 && listenerState.isSent(zone->m_tokens[size_t(data.m_id)].m_blockId)) {
          MWAW_DEBUG_MSG(("MacWrtProParser::sendText: footnote is already sent...\n"));
        }
        else {
          int id = zone->m_tokens[size_t(data.m_id)].m_blockId;
          if (vers == 0) id = -id;
          MWAWSubDocumentPtr subdoc(new MacWrtProParserInternal::SubDocument(*this, getInput(), id));
          getTextListener()->insertNote(MWAWNote(MWAWNote::FootNote), subdoc);
        }
        break;
      case 3:
        break; // footnote content, ok
      case 4:
        if (vers==0) {
          MWAWPosition pictPos(MWAWVec2f(0,0), zone->m_tokens[size_t(data.m_id)].m_box.size(), librevenge::RVNG_POINT);
          pictPos.setRelativePosition(MWAWPosition::Char, MWAWPosition::XLeft, MWAWPosition::YBottom);
          sendPictureZone(zone->m_tokens[size_t(data.m_id)].m_blockId, pictPos);
        }
        else {
          listenerState.send(zone->m_tokens[size_t(data.m_id)].m_blockId);
          listenerState.resendAll();
        }
        break;
      case 5:
        break; // hyphen ok
      case 6:
        if (getTextListener()) getTextListener()->insertField(MWAWField(MWAWField::Date));
        break;
      case 7:
        if (getTextListener()) getTextListener()->insertField(MWAWField(MWAWField::Time));
        break;
      case 8:
        if (getTextListener()) getTextListener()->insertField(MWAWField(MWAWField::Title));
        break;
      case 9:
        if (getTextListener()) getTextListener()->insertUnicodeString(librevenge::RVNGString("#REVISION#"));
        break;
      case 10:
        if (getTextListener()) {
          int numSection = listenerState.numSection()+1;
          std::stringstream s;
          s << numSection;
          getTextListener()->insertUnicodeString(librevenge::RVNGString(s.str().c_str()));
        }
        break;
      default:
        break;
      }
      f << "token[" << zone->m_tokens[size_t(data.m_id)] << "],";
      input->seek(actPos, librevenge::RVNG_SEEK_SET);
      break;
    }
    case 1:
      if (m_structures) {
        listenerState.sendFont(zone->m_ids[0][size_t(data.m_id)].m_id);
        f << "[" << listenerState.getFontDebugString(zone->m_ids[0][size_t(data.m_id)].m_id) << "],";
      }
      else
        f << "[" << zone->m_ids[0][size_t(data.m_id)] << "],";
      break;
    case 0:
      if (m_structures) {
        listenerState.sendParagraph(zone->m_ids[1][size_t(data.m_id)].m_id);
        f << "[" << listenerState.getParagraphDebugString(zone->m_ids[1][size_t(data.m_id)].m_id) << "],";
      }
      else
        f << "[" << zone->m_ids[1][size_t(data.m_id)] << "],";
      break;
    default: {
      static bool firstError = true;
      if (firstError) {
        MWAW_DEBUG_MSG(("MacWrtProParser::sendText: find unexpected data type...\n"));
        firstError = false;
      }
      f << "#";
      break;
    }

    }
    if (pos >= 0 && pos != oldPos)
      asciiPos = pos;
  }

  return true;
}


////////////////////////////////////////////////////////////
// try to send a picture
////////////////////////////////////////////////////////////
bool MacWrtProParser::sendPictureZone(int blockId, MWAWPosition const &pictPos,
                                      MWAWGraphicStyle const &style)
{
  auto it = m_state->m_dataMap.find(blockId);
  if (it == m_state->m_dataMap.end()) {
    MWAW_DEBUG_MSG(("MacWrtProParser::sendPictureZone: can not find picture zone\n"));
    return false;
  }
  sendPicture(it->second, pictPos, style);
  return true;
}

bool MacWrtProParser::sendPicture(std::shared_ptr<MacWrtProParserInternal::Zone> zone,
                                  MWAWPosition pictPos, MWAWGraphicStyle const &style)
{
  if (!zone) return false;
  if (zone->m_type != 1) {
    MWAW_DEBUG_MSG(("MacWrtProParser::sendPicture: not a picture date\n"));
    return false;
  }

  zone->m_parsed = true;

  // ok init is done
  MWAWInputStreamPtr input = zone->m_input;
  libmwaw::DebugFile &asciiFile = zone->m_asciiFile;
  libmwaw::DebugStream f;

  f << "Entries(PICT),";
  asciiFile.addPos(0);
  asciiFile.addNote(f.str().c_str());

  input->seek(0, librevenge::RVNG_SEEK_SET);
  auto pictSize = long(input->readULong(4));
  if (pictSize < 10 || pictSize > long(zone->m_data.size())) {
    MWAW_DEBUG_MSG(("MacWrtProParser::sendPicture: oops a pb with pictSize\n"));
    asciiFile.addPos(4);
    asciiFile.addNote("#PICT");
    return false;
  }
  std::shared_ptr<MWAWPict> pict(MWAWPictData::get(input, static_cast<int>(pictSize)));
  if (!pict) {
    // sometimes this just fails because the pictSize is not correct
    input->seek(14, librevenge::RVNG_SEEK_SET);
    if (input->readULong(2) == 0x1101) { // try to force the size to be ok
      librevenge::RVNGBinaryData data;
      input->seek(0, librevenge::RVNG_SEEK_SET);
      input->readDataBlock(4+pictSize, data);
      auto *dataPtr=const_cast<unsigned char *>(data.getDataBuffer());
      if (!dataPtr) {
        MWAW_DEBUG_MSG(("MacWrtProParser::sendPicture: oops where is the picture...\n"));
        return false;
      }

      dataPtr[4]=dataPtr[2];
      dataPtr[5]=dataPtr[3];

      MWAWInputStreamPtr pictInput=MWAWInputStream::get(data, false);
      if (!pictInput) {
        MWAW_DEBUG_MSG(("MacWrtProParser::sendPicture: oops where is the picture input...\n"));
        return false;
      }

      pictInput->seek(4, librevenge::RVNG_SEEK_SET);
      pict.reset(MWAWPictData::get(pictInput, static_cast<int>(pictSize)));
    }
  }

#ifdef DEBUG_WITH_FILES
  asciiFile.skipZone(4, 4+pictSize-1);
  librevenge::RVNGBinaryData file;
  input->seek(4, librevenge::RVNG_SEEK_SET);
  input->readDataBlock(pictSize, file);
  static int volatile pictName = 0;
  f.str("");
  f << "PICT-" << ++pictName;
  libmwaw::Debug::dumpFile(file, f.str().c_str());
  asciiFile.addPos(4+pictSize);
  asciiFile.addNote("PICT(end)");
#endif

  if (!pict) { // ok, we can not do anything except sending the data...
    MWAW_DEBUG_MSG(("MacWrtProParser::parseDataZone: no sure this is a picture\n"));
    if (pictPos.size().x() <= 0 || pictPos.size().y() <= 0)
      pictPos=MWAWPosition(MWAWVec2f(0,0),MWAWVec2f(100.,100.), librevenge::RVNG_POINT);
    if (getTextListener()) {
      librevenge::RVNGBinaryData data;
      input->seek(4, librevenge::RVNG_SEEK_SET);
      input->readDataBlock(pictSize, data);
      getTextListener()->insertPicture(pictPos, MWAWEmbeddedObject(data, "image/pict"), style);
    }
    return true;
  }

  if (pictPos.size().x() <= 0 || pictPos.size().y() <= 0) {
    pictPos.setOrigin(MWAWVec2f(0,0));
    pictPos.setSize(pict->getBdBox().size());
    pictPos.setUnit(librevenge::RVNG_POINT);
  }
  if (pict->getBdBox().size().x() > 0 && pict->getBdBox().size().y() > 0)
    pictPos.setNaturalSize(pict->getBdBox().size());

  if (getTextListener()) {
    MWAWEmbeddedObject picture;
    if (pict->getBinary(picture))
      getTextListener()->insertPicture(pictPos, picture, style);
  }
  return true;
}

////////////////////////////////////////////////////////////
// some debug functions
////////////////////////////////////////////////////////////

#ifdef DEBUG
void MacWrtProParser::saveOriginal(MWAWInputStreamPtr input)
{
  libmwaw::DebugStream f;

  int ptSz = version()==0 ? 2 : 4;
  libmwaw::DebugFile orig(input);
  orig.open("orig");
  int bl = 0;
  while (1) {
    long pos = bl*0x100;
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    if (long(input->tell()) != pos)
      break;
    f.str("");
    if (bl) {
      input->seek(-ptSz, librevenge::RVNG_SEEK_CUR);
      long val = input->readLong(ptSz);
      long next = val > 0 ? val : -val;
      if (next > 0 && next < 0x1000) {
        f << "next=" << std::hex << (next-1)*0x100 << std::dec << ",";
        if (val < 0)
          f << "N=?" << input->readLong(ptSz) << ",";
        orig.addPos(pos-ptSz);
        orig.addNote(f.str().c_str());
      }
    }
    orig.addPos(input->tell());

    if (m_state->m_blocksMap.find(bl) == m_state->m_blocksMap.end())
      orig.addNote("unparsed*");
    else {
      f.str("");
      f << std::hex << "(" << m_state->m_blocksMap.find(bl)->second << ")"
        << std::dec;
      orig.addNote(f.str().c_str());
    }
    bl++;
  }
  orig.reset();
}
#endif

void MacWrtProParser::checkUnparsed()
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;

  long pos;
  std::stringstream notParsed;
  for (int bl = 3; bl < 1000; bl++) {
    if (m_state->m_blocksMap.find(bl) != m_state->m_blocksMap.end())
      continue;

    pos = bl*0x100;
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    if (input->isEnd()) break;
    notParsed << std::hex <<  bl << std::dec << ",";

    // normaly there must remains only text entry...
    f.str("");
    f << "Entries(Unparsed):";

    std::string text("");
    bool findZero = false;
    for (int c = 0; c < 256; c++) {
      auto ch = char(input->readULong(1));
      if (!ch) {
        if (findZero) {
          input->seek(-1, librevenge::RVNG_SEEK_CUR);
          break;
        }
        findZero = true;
        continue;
      }
      if (findZero) {
        text += "#";
        findZero = false;
      }
      text+=ch;
    }
    f << text;
    if (long(input->tell()) != pos+256)
      ascii().addDelimiter(input->tell(),'|');
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  if (!notParsed.str().empty()) {
    MWAW_DEBUG_MSG(("MacWrtProParser::checkUnparsed: not parsed %s\n", notParsed.str().c_str()));
  }
}

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