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 <sstream>

#include <librevenge/librevenge.h>

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

#include "MsWrd1Parser.hxx"

/** Internal: the structures of a MsWrd1Parser */
namespace MsWrd1ParserInternal
{
/** different types
 *
 * - FONT: font
 * - RULER: ruler
 * - PAGE: page break
 * - FOOTNOTE: footnote marker
 * - ZONE: unknown(zone4)
 */
enum PLCType { FONT=0, RULER, FOOTNOTE, PAGE, ZONE, UNKNOWN};

/** Internal: class to store the PLC: Pointer List Content ? */
struct PLC {
  //! constructor
  explicit PLC(PLCType type=UNKNOWN)
    : m_type(type)
    , m_id(-1)
    , m_extras("")
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, PLC const &plc);
  //! the type
  PLCType m_type;
  //! the id
  int m_id;
  //! a string used to store the parsing extrass
  std::string m_extras;
};

std::ostream &operator<<(std::ostream &o, PLC const &plc)
{
  switch (plc.m_type) {
  case FONT:
    o << "F";
    break;
  case RULER:
    o << "P";
    break;
  case FOOTNOTE:
    o << "Fn";
    break;
  case PAGE:
    o << "Page";
    break;
  case ZONE:
    o << "Z";
    break;
  case UNKNOWN:
#if !defined(__clang__)
  default:
#endif
    o << "#type" << int(plc.m_type);
    break;
  }
  if (plc.m_id != -1) o << plc.m_id;
  else o << "_";
  if (!plc.m_extras.empty()) o << ":" << plc.m_extras;
  return o;
}

////////////////////////////////////////
//! Internal: the font of a MsWrd1Parser
struct Font {
  //! constructor
  Font()
    : m_font()
    , m_type(0)
    , m_extras("")
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Font const &ft);
  //! the basic font property
  MWAWFont m_font;
  //! a unknown int, maybe 0x80 means defined font
  int m_type;
  //! a string used to store the parsing extrass
  std::string m_extras;
};

std::ostream &operator<<(std::ostream &o, Font const &ft)
{
  if (ft.m_type) o << "type=" << std::hex << ft.m_type << std::dec << ",";
  if (!ft.m_extras.empty()) o << ft.m_extras;
  return o;
}

////////////////////////////////////////
//! Internal: the paragraph of a MsWrd1Parser
struct Paragraph final : public MWAWParagraph {
  //! constructor
  Paragraph()
    : MWAWParagraph()
    , m_type(0)
    , m_type2(0)
  {
  }
  //! destructor
  ~Paragraph() final;
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Paragraph const &ft);
  //! the initial type
  int m_type;
  //! another type
  int m_type2;
};

Paragraph::~Paragraph()
{
}

std::ostream &operator<<(std::ostream &o, Paragraph const &para)
{
  o << static_cast<MWAWParagraph const &>(para);
  // 0|80 frequent: means redefine paragraph? find also a7 in a footnote
  if (para.m_type) o << "type=" << std::hex << para.m_type << std::dec << ",";

  if (para.m_type2 & 0xF0) {
    bool foot = (para.m_type2 & 0x10);
    if (foot) o << "footer/footnote[";
    else o << "header[";
    if (para.m_type2 & 0x20) o << (foot ? "even," : "odd,");
    if (para.m_type2 & 0x40) o << (foot ? "odd," : "even,");
    if (para.m_type2 & 0x80) o << "first,";
    o << "]";
  }
  if (para.m_type2 & 0xF)
    o << "#type2=" << std::hex << (para.m_type2 & 0xF) << std::dec << ",";

  return o;
}

////////////////////////////////////////
//! Internal: the state of a MsWrd1Parser
struct State {
  //! constructor
  State()
    : m_eot(-1)
    , m_numColumns(1)
    , m_columnsSep(0)
    , m_textZonesList()
    , m_mainTextZonesList()
    , m_fontsList()
    , m_paragraphsList()
    , m_endNote(false)
    , m_footnotesList()
    , m_plcMap()
    , m_actPage(0)
    , m_numPages(1)
    , m_headersId()
    , m_footersId()
  {
    for (auto &limit : m_fileZonesLimit) limit = -1;
  }

  //! end of text
  long m_eot;
  //! the number of columns
  int m_numColumns;
  //! the column separator
  float m_columnsSep;
  //! the zones limits
  int m_fileZonesLimit[7];
  //! the list of text zones
  std::vector<MWAWVec2l> m_textZonesList;
  //! the list of main text zones
  std::vector<int> m_mainTextZonesList;
  //! the list of fonts
  std::vector<Font> m_fontsList;
  //! the list of paragraph
  std::vector<Paragraph> m_paragraphsList;
  //! a flag to know if we send endnote or footnote
  bool m_endNote;
  //! the footnote positions ( list of beginPos, endPos)
  std::vector<MWAWVec2l> m_footnotesList;
  //! the text correspondance zone ( filepos, plc )
  std::multimap<long, PLC> m_plcMap;

  int m_actPage /** the actual page */, m_numPages /** the number of page of the final document */;
  /** the list of header id which corresponds to each page */
  std::vector<int> m_headersId;
  /** the list of footer id which corresponds to each page */
  std::vector<int> m_footersId;
};

////////////////////////////////////////
//! Internal: the subdocument of a MsWrdParser
class SubDocument final : public MWAWSubDocument
{
public:
  //! constructor for footnote, header
  SubDocument(MsWrd1Parser &pars, MWAWInputStreamPtr const &input, MWAWEntry const &position)
    : MWAWSubDocument(&pars, input, position)
  {
  }

  //! destructor
  ~SubDocument() final {}

  //! operator!=
  bool operator!=(MWAWSubDocument const &doc) const final
  {
    return MWAWSubDocument::operator!=(doc);
  }

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

protected:
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType)
{
  if (!listener.get()) {
    MWAW_DEBUG_MSG(("MsWrd1ParserInternal::SubDocument::parse: no listener\n"));
    return;
  }
  auto *parser=dynamic_cast<MsWrd1Parser *>(m_parser);
  if (!parser) {
    MWAW_DEBUG_MSG(("MsWrd1ParserInternal::SubDocument::parse: no parser\n"));
    return;
  }

  if (!m_zone.valid()) {
    listener->insertChar(' ');
    return;
  }
  long pos = m_input->tell();
  parser->sendText(m_zone);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}
}


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

MsWrd1Parser::~MsWrd1Parser()
{
}

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

  m_state.reset(new MsWrd1ParserInternal::State);

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

////////////////////////////////////////////////////////////
// new page
////////////////////////////////////////////////////////////
void MsWrd1Parser::newPage(int number)
{
  if (number <= m_state->m_actPage || number > m_state->m_numPages)
    return;

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

void MsWrd1Parser::removeLastCharIfEOL(MWAWEntry &entry)
{
  if (!entry.valid()) return;
  MWAWInputStreamPtr input = getInput();
  long actPos = input->tell();
  input->seek(entry.end()-1, librevenge::RVNG_SEEK_SET);
  if (input->readLong(1)==0xd)
    entry.setLength(entry.length()-1);
  input->seek(actPos, librevenge::RVNG_SEEK_SET);
}
////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void MsWrd1Parser::parse(librevenge::RVNGTextInterface *docInterface)
{
  if (!getInput().get() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = true;
  try {
    // create the asciiFile
    ascii().setStream(getInput());
    ascii().open(asciiName());
    checkHeader(nullptr);
    ok = createZones();
    if (ok) {
      createDocument(docInterface);
      sendMain();
    }

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

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

////////////////////////////////////////////////////////////
// send the main zone
////////////////////////////////////////////////////////////
void MsWrd1Parser::sendMain()
{
  for (auto id : m_state->m_mainTextZonesList) {
    if (id < 0 || id >= int(m_state->m_textZonesList.size()))
      continue;
    MWAWEntry entry;
    entry.setBegin(m_state->m_textZonesList[size_t(id)][0]);
    entry.setEnd(m_state->m_textZonesList[size_t(id)][1]);
    sendText(entry, true);
  }
  // maybe need if we have no text ; if not, nobody will see it
  if (getTextListener())
    getTextListener()->insertChar(' ');
}

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

  // update the page
  m_state->m_actPage = 0;
  // create the page list
  std::vector<MWAWPageSpan> pageList;
  auto numHeaders=int(m_state->m_headersId.size());
  auto numFooters=int(m_state->m_footersId.size());
  for (int i = 0; i <= m_state->m_numPages;) {
    int numSim[2]= {1,1};
    MWAWPageSpan ps(getPageSpan());
    while (i < numHeaders) {
      int id = m_state->m_headersId[size_t(i)];
      if (id < 0 || id >= int(m_state->m_textZonesList.size()))
        break;
      MWAWEntry entry;
      entry.setBegin(m_state->m_textZonesList[size_t(id)][0]);
      entry.setEnd(m_state->m_textZonesList[size_t(id)][1]);
      removeLastCharIfEOL(entry);
      if (!entry.valid()) break;
      MWAWHeaderFooter header(MWAWHeaderFooter::HEADER, MWAWHeaderFooter::ALL);
      header.m_subDocument.reset
      (new MsWrd1ParserInternal::SubDocument(*this, getInput(), entry));
      ps.setHeaderFooter(header);
      int j = i+1;
      while (j < numHeaders && m_state->m_headersId[size_t(j)]==id) {
        numSim[0]++;
        j++;
      }
      break;
    }
    while (i < int(numFooters)) {
      int id = m_state->m_footersId[size_t(i)];
      if (id < 0 || id >= int(m_state->m_textZonesList.size()))
        break;
      MWAWEntry entry;
      entry.setBegin(m_state->m_textZonesList[size_t(id)][0]);
      entry.setEnd(m_state->m_textZonesList[size_t(id)][1]);
      removeLastCharIfEOL(entry);
      if (!entry.valid()) break;
      MWAWHeaderFooter footer(MWAWHeaderFooter::FOOTER, MWAWHeaderFooter::ALL);
      footer.m_subDocument.reset
      (new MsWrd1ParserInternal::SubDocument(*this, getInput(), entry));
      ps.setHeaderFooter(footer);
      int j = i+1;
      while (j < numFooters && m_state->m_footersId[size_t(j)]==id) {
        numSim[1]++;
        j++;
      }
      break;
    }
    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
////////////////////////////////////////////////////////////

// create the different zones
bool MsWrd1Parser::createZones()
{
  libmwaw::DebugStream f;
  if (m_state->m_eot < 0x80) return false;

  ascii().addPos(0x80);
  ascii().addNote("TextContent");

  ascii().addPos(m_state->m_eot);
  ascii().addNote("_");

  MWAWInputStreamPtr input = getInput();
  for (int z = 5; z >= 0; z--) {
    if (m_state->m_fileZonesLimit[z] == m_state->m_fileZonesLimit[z+1])
      continue;
    if (!input->checkPosition(m_state->m_fileZonesLimit[z+1]*0x80) ||
        m_state->m_fileZonesLimit[z] > m_state->m_fileZonesLimit[z+1]) {
      f.str("");
      f << "Entries(Zone" << z << "):###";
      MWAW_DEBUG_MSG(("MsWrd1Parser::createZones: zone %d is too long\n",z));
      ascii().addPos(m_state->m_fileZonesLimit[z]*0x80);
      ascii().addNote(f.str().c_str());
      break;
    }
    MWAWVec2i limit(m_state->m_fileZonesLimit[z],m_state->m_fileZonesLimit[z+1]);
    bool done = false;
    switch (z) {
    case 0:
    case 1:
      done = readPLC(limit,z);
      break;
    case 2:
      done = readFootnoteCorrespondance(limit);
      break;
    case 3:
      done = readDocInfo(limit);
      break;
    case 4:
      done = readZones(limit);
      break;
    case 5:
      done = readPageBreak(limit);
      break;
    default:
      break;
    }
    if (done) continue;
    for (int p = m_state->m_fileZonesLimit[z], i=0; p < m_state->m_fileZonesLimit[z+1]; p++, i++) {
      f.str("");
      f << "Entries(Zone" << z << ")[" << i << "]:";
      ascii().addPos(p*0x80);
      ascii().addNote(f.str().c_str());
    }
    ascii().addPos(m_state->m_fileZonesLimit[z+1]*0x80);
    ascii().addNote("_");
  }
  prepareTextZones();
  return true;
}

// try to read retrieve the header/footer zones ...
bool MsWrd1Parser::prepareTextZones()
{
  m_state->m_numPages = 1;
  m_state->m_textZonesList.resize(0);
  m_state->m_mainTextZonesList.resize(0);
  m_state->m_headersId.resize(0);
  m_state->m_footersId.resize(0);
  long endMain = m_state->m_eot;
  for (auto const &footnote : m_state->m_footnotesList) {
    long pos = footnote[0];
    if (pos >= 0x80 && pos < endMain)
      endMain = pos;
  }
  if (endMain < 0x80) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::sendText: oops problem computing the limit of the main section"));
    m_state->m_textZonesList.push_back(MWAWVec2l(0x80, m_state->m_eot));
    m_state->m_mainTextZonesList.push_back(0);
    return false;
  }

  auto plcIt = m_state->m_plcMap.begin();
  long pos = 0x80, prevMainPos=pos;
  int actPage = 1;
  int actType = 0;
  MWAWVec2i headerId(-1,-1), footerId(-1,-1);
  int firstHeaderId=-1, firstFooterId=-1;
  while (pos < endMain) {
    int newType = 0;
    if (plcIt == m_state->m_plcMap.end() || plcIt->first>=endMain) {
      pos = endMain;
      newType = -1;
    }
    else {
      pos = plcIt->first;
      MsWrd1ParserInternal::PLC const &plc = plcIt++->second;
      if (plc.m_type==MsWrd1ParserInternal::PAGE && pos!=0x80) {
        if (actPage> int(m_state->m_headersId.size())) {
          m_state->m_headersId.resize(size_t(actPage),-1);
          m_state->m_headersId[size_t(actPage)-1] = headerId[(actPage%2)];
        }
        if (actPage> int(m_state->m_footersId.size())) {
          m_state->m_footersId.resize(size_t(actPage),-1);
          m_state->m_footersId[size_t(actPage)-1] = footerId[(actPage%2)];
        }
        actPage++;
      }
      if (plc.m_type!=MsWrd1ParserInternal::RULER) continue;
      if (plc.m_id >= 0 && plc.m_id < int(m_state->m_paragraphsList.size()))
        newType = (m_state->m_paragraphsList[size_t(plc.m_id)].m_type2>>4);
      if (newType == actType)
        continue;
    }
    if (pos==prevMainPos) {
      actType = newType;
      continue;
    }

    auto id = int(m_state->m_textZonesList.size());
    m_state->m_textZonesList.push_back(MWAWVec2l(prevMainPos, pos));
    prevMainPos=pos;
    if (actType==0) {
      m_state->m_mainTextZonesList.push_back(id);
      actType = newType;
      continue;
    }
    if (actType&1) {
      if (actType&2) footerId[1]=id;
      if (actType&4) footerId[0]=id;
      if (actType&8) firstFooterId=id;
      m_state->m_footersId.resize(size_t(actPage),-1);
      m_state->m_footersId[size_t(actPage)-1] =
        (actPage==1 && firstFooterId >= 0) ? firstFooterId :
        (actPage%2) ? footerId[1] : footerId[0];
    }
    else {
      if (actType&2) headerId[0]=id;
      if (actType&4) headerId[1]=id;
      if (actType&8) firstHeaderId=id;
      m_state->m_headersId.resize(size_t(actPage),-1);
      m_state->m_headersId[size_t(actPage)-1] =
        (actPage==1 && firstHeaderId >= 0) ? firstHeaderId :
        (actPage%2) ? headerId[1] : headerId[0];
    }
    actType = newType;
  }
  if (actPage> int(m_state->m_headersId.size())) {
    m_state->m_headersId.resize(size_t(actPage),-1);
    m_state->m_headersId[size_t(actPage)-1] = headerId[(actPage%2)];
  }
  if (actPage> int(m_state->m_footersId.size())) {
    m_state->m_footersId.resize(size_t(actPage),-1);
    m_state->m_footersId[size_t(actPage)-1] = footerId[(actPage%2)];
  }
  m_state->m_numPages = actPage;
  return true;
}

////////////////////////////////////////////////////////////
// try to read the different zones
////////////////////////////////////////////////////////////

// read the character property
bool MsWrd1Parser::readFont(long fPos, MsWrd1ParserInternal::Font &font)
{
  font = MsWrd1ParserInternal::Font();
  libmwaw::DebugStream f;
  MWAWInputStreamPtr input = getInput();
  input->seek(fPos, librevenge::RVNG_SEEK_SET);
  auto sz = static_cast<int>(input->readLong(1));
  if (sz < 1 || sz > 0x7f || !input->checkPosition(fPos+1+sz)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readFont: the zone size seems bad\n"));
    return false;
  }
  font.m_type = static_cast<int>(input->readULong(1));
  int val;
  uint32_t flags=0;
  if (sz >= 2) {
    val = static_cast<int>(input->readULong(1));
    if (val & 0x80) flags |= MWAWFont::boldBit;
    if (val & 0x40) flags |= MWAWFont::italicBit;
    if (val & 0x3f)
      font.m_font.setId((val & 0x3f));
  }
  if (sz >= 3) {
    val = static_cast<int>(input->readULong(1));
    if (val) font.m_font.setSize(float(val)/2.f);
  }
  if (sz >= 4) {
    val = static_cast<int>(input->readULong(1));
    if (val & 0x80) font.m_font.setUnderlineStyle(MWAWFont::Line::Simple);
    switch ((val&0xc)>>2) {
    case 0:
      break;
    case 3:
      flags |= MWAWFont::uppercaseBit;
      break;
    default:
      f << "#capBits=" << int((val&0xc)>>2) << ",";
    }
    // find also &2 for footnote
    if (val & 0x73)
      f << "#flags1=" << std::hex << (val & 0x73) << std::dec << ",";
  }
  if (sz >= 5) {
    val = static_cast<int>(input->readULong(1));
    if (val & 0x10) flags |= MWAWFont::embossBit;
    if (val & 0x8) flags |= MWAWFont::shadowBit;
    if (val & 0xe7)
      f << "#flags2=" << std::hex << (val & 0xe7) << std::dec << ",";
  }
  if (sz >= 6) { // vdepl
    val = static_cast<int>(input->readLong(1));
    if (val > 0) font.m_font.set(MWAWFont::Script::super100());
    else if (val < 0) font.m_font.set(MWAWFont::Script::sub100());
  }
  if (sz >= 7) {
    f << "###";
    ascii().addDelimiter(input->tell(),'|');
  }
  font.m_font.setFlags(flags);
  font.m_extras = f.str();

  return true;
}

/* read the paragraph property */
bool MsWrd1Parser::readParagraph(long fPos, MsWrd1ParserInternal::Paragraph &para)
{
  para = MsWrd1ParserInternal::Paragraph();
  libmwaw::DebugStream f;
  MWAWInputStreamPtr input = getInput();
  input->seek(fPos, librevenge::RVNG_SEEK_SET);
  auto sz = static_cast<int>(input->readLong(1));
  if (sz < 1 || sz > 0x7f || !input->checkPosition(fPos+1+sz)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readParagraph: the zone size seems bad\n"));
    return false;
  }
  para.m_type = static_cast<int>(input->readULong(1));
  int val;
  if (sz >= 2) {
    val = static_cast<int>(input->readULong(1));
    switch (val>>6) {
    case 0:
      break; // left
    case 1:
      para.m_justify = MWAWParagraph::JustificationCenter;
      break;
    case 2:
      para.m_justify = MWAWParagraph::JustificationRight;
      break;
    case 3:
      para.m_justify = MWAWParagraph::JustificationFull;
      break;
    default:
      break;
    }
    if (val & 0x10) f << "dontbreak[para],";
    if (val & 0x10) f << "dontbreak[line],";
    if (val & 0xf)
      f << "#justify=" << std::hex << (val & 0xf) << std::dec << ",";
  }
  if (sz >= 4) { // find always 0 here
    val = static_cast<int>(input->readLong(2));
    if (val) f << "#f0=" << val << ",";
  }
  if (sz >= 6) {
    val = static_cast<int>(input->readLong(2));
    if (val)
      para.m_margins[2] = double(val)/1440.0;
  }
  if (sz >= 8) {
    val = static_cast<int>(input->readLong(2));
    if (val)
      para.m_margins[0] = double(val)/1440.0;
  }
  if (sz >= 10) {
    val = static_cast<int>(input->readLong(2));
    if (val && !para.m_margins[0].isSet())
      para.m_margins[1] = double(val)/1440.0;
    else if (val)
      para.m_margins[1] = para.m_margins[0].get()+double(val)/1440.0;
  }
  if (sz >= 12) {
    val = static_cast<int>(input->readLong(2));
    if (val)
      para.setInterline(double(val)/1440.0, librevenge::RVNG_INCH);
  }
  if (sz >= 14) {
    val = static_cast<int>(input->readLong(2));
    if (val)
      para.m_spacings[1] = double(val)/1440.0;
  }
  if (sz >= 16) {
    val = static_cast<int>(input->readLong(2));
    if (val)
      para.m_spacings[2] = double(val)/1440.0;
  }
  if (sz >= 17)
    para.m_type2 = static_cast<int>(input->readULong(1));
  // checkme: not sure what is the exact decomposition of the following
  if (sz >= 22) { // find always 0 here
    for (int i = 0; i < 5; i++) {
      val = static_cast<int>(input->readLong(1));
      if (val) f << "#f" << i+1 << "=" << val << ",";
    }
  }
  if (sz >= 26) {
    int numTabs = (sz-26)/4;
    for (int i = 0; i < numTabs; i++) {
      MWAWTabStop newTab;
      newTab.m_position = double(input->readLong(2))/1440.;
      auto flags = static_cast<int>(input->readULong(1));
      switch ((flags>>5)&3) {
      case 0:
        break;
      case 1:
        newTab.m_alignment = MWAWTabStop::CENTER;
        break;
      case 2:
        newTab.m_alignment = MWAWTabStop::RIGHT;
        break;
      case 3:
        newTab.m_alignment = MWAWTabStop::DECIMAL;
        break;
      default:
        break;
      }
      switch ((flags>>2)&3) {
      case 0:
        break;
      case 1:
        newTab.m_leaderCharacter = '.';
        break;
      case 2:
        newTab.m_leaderCharacter = '-';
        break;
      case 3:
        newTab.m_leaderCharacter = '_';
        break;
      default:
        break;
      }
      if (flags & 0x93)
        f << "#tabs" << i << "[fl1=" << std::hex << (flags & 0x93) << std::dec << ",";
      val = static_cast<int>(input->readULong(1));
      if (val)
        f << "#tabs" << i << "[fl2=" << std::hex << val << std::dec << ",";
      para.m_tabs->push_back(newTab);
    }
  }
  if (input->tell() != fPos+1+sz)
    ascii().addDelimiter(input->tell(), '|');
  para.m_extra = f.str();
  return true;
}

/* read the page break separation */
bool MsWrd1Parser::readPageBreak(MWAWVec2i limits)
{
  MWAWInputStreamPtr input = getInput();
  if (limits[1] <= limits[0] || !input->checkPosition(limits[1]*0x80)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readPageBreak: the zone is not well defined\n"));
    return false;
  }
  libmwaw::DebugStream f;
  long pos = limits[0]*0x80;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(PageBreak):";
  auto N = static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  if (N==0 || 4+6*N > (limits[1]-limits[0])*0x80) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readPageBreak: the number of element seems odds\n"));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  long val = static_cast<int>(input->readULong(2)); // 1|a
  f << "unkn=" << val << ",";
  MsWrd1ParserInternal::PLC plc(MsWrd1ParserInternal::PAGE);
  for (int i = 0; i < N; i++) {
    auto pg = static_cast<int>(input->readULong(2));
    long textPos = long(input->readULong(4))+0x80;
    f << "Page" << i << "=" << std::hex << textPos << std::dec;
    if (pg != i+1) f << "[page=" << pg << "]";
    if (textPos < m_state->m_eot) {
      plc.m_id = pg;
      m_state->m_plcMap.insert
      (std::multimap<long,MsWrd1ParserInternal::PLC>::value_type(textPos, plc));
    }
    else if (i != N-1)
      f << "###";
    f << ",";
  }
  if (input->tell() != limits[1]*0x80)
    ascii().addDelimiter(input->tell(),'|');
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

/* read the footnote zone */
bool MsWrd1Parser::readFootnoteCorrespondance(MWAWVec2i limits)
{
  MWAWInputStreamPtr input = getInput();
  if (limits[1] <= limits[0] || !input->checkPosition(limits[1]*0x80)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readFootnoteCorrespondance: the zone is not well defined\n"));
    return false;
  }
  libmwaw::DebugStream f;

  long textEnd = m_state->m_eot;
  MsWrd1ParserInternal::PLC plc(MsWrd1ParserInternal::FOOTNOTE);
  long pos = limits[0]*0x80;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(Footnote):";
  auto N = static_cast<int>(input->readULong(2));
  auto N1 = static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  if (N!=N1) f << "N1=" << N1 << ",";
  if (N!=N1 || N==0 || 4+8*N > (limits[1]-limits[0])*0x80) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readFootnoteCorrespondance: the number of element seems odds\n"));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  std::map<long, int> footnoteMap;
  for (int i = 0; i < N; i++) {
    long textPos = long(input->readULong(4))+0x80;
    long notePos = long(input->readULong(4))+0x80;
    bool ok = textPos <= textEnd && notePos <= textEnd;
    f << "Fn" << i << ":" << std::hex << textPos << "<->" << notePos << std::dec << ",";
    if (!ok) {
      if (i==N-1) break;
      f << "###";
      continue;
    }
    plc.m_id = int(footnoteMap.size());
    footnoteMap[notePos]=plc.m_id;
    m_state->m_plcMap.insert
    (std::multimap<long,MsWrd1ParserInternal::PLC>::value_type(textPos, plc));
    m_state->m_plcMap.insert
    (std::multimap<long,MsWrd1ParserInternal::PLC>::value_type(notePos, plc));
  }
  m_state->m_footnotesList.resize(footnoteMap.size(),MWAWVec2l(0,0));
  for (auto fIt=footnoteMap.begin(); fIt!=footnoteMap.end();) {
    MWAWVec2l fPos;
    fPos[0] = fIt->first;
    int id = fIt++->second;
    fPos[1] = fIt==footnoteMap.end() ? m_state->m_eot : fIt->first;
    if (id >= int(m_state->m_footnotesList.size()))
      m_state->m_footnotesList.resize(size_t(id)+1,MWAWVec2l(0,0));
    m_state->m_footnotesList[size_t(id)]=fPos;
  }
  ascii().addDelimiter(input->tell(),'|');
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  return true;
}

/* read the zone4: a list of main zone ( headers, footers ) ? */
bool MsWrd1Parser::readZones(MWAWVec2i limits)
{
  MWAWInputStreamPtr input = getInput();
  if (limits[1] <= limits[0] || !input->checkPosition(limits[1]*0x80)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readZones: the zone is not well defined\n"));
    return false;
  }
  libmwaw::DebugStream f;

  MsWrd1ParserInternal::PLC plc(MsWrd1ParserInternal::ZONE);
  long pos = limits[0]*0x80;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(Zones):";
  auto N = static_cast<int>(input->readULong(2));
  auto N1 = static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  if (N!=N1) f << "N1=" << N1 << ",";
  if (N!=N1 || N==0 || 4+10*N > (limits[1]-limits[0])*0x80) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readZones: the number of element seems odds\n"));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  for (int i = 0; i < N; i++) {
    long textPos = long(input->readULong(4))+0x80;
    f << std::hex << textPos << std::dec;
    f << ":f0=" << input->readLong(2); // find 1|2|3
    auto val = static_cast<int>(input->readLong(4)); // find -1, 0x900, 0xa00
    if (val!=-1) f << ":f1=" << std::hex << val << std::dec;
    if (textPos < m_state->m_eot) {
      plc.m_id = i;
      m_state->m_plcMap.insert
      (std::multimap<long,MsWrd1ParserInternal::PLC>::value_type(textPos, plc));
    }
    else if (textPos != m_state->m_eot && i != N-1)
      f << "###";
    f << ",";
  }
  ascii().addDelimiter(input->tell(),'|');
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  return true;
}

/* read the document information */
bool MsWrd1Parser::readDocInfo(MWAWVec2i limits)
{
  MWAWInputStreamPtr input = getInput();
  if (limits[1] != limits[0]+1 || !input->checkPosition(limits[1]*0x80)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readDocInfo: the zone is not well defined\n"));
    return false;
  }

  libmwaw::DebugStream f;
  long pos = limits[0]*0x80;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(DocInfo):";
  int val;
  for (int i=0; i < 2; i++) { // find 66|0
    val = static_cast<int>(input->readULong(1));
    if (val)
      f << "f" << i << "=" << std::hex << val << std::dec << ",";
  }
  auto flags = static_cast<int>(input->readULong(1));
  switch (flags>>5) {
  case 0:
    f << "division=no,";
    break;
  case 1:
    f << "division=columns,";
    break;
  case 2:
    f << "division=page,";
    break; // default
  case 3:
    f << "division=evenpage,";
    break;
  case 4:
    f << "division=oddpage,";
    break;
  default:
    f << "#division=" << (flags>>5) << ",";
    break;
  }
  switch ((flags>>2)&7) {
  case 0: // default (numeric)
    break;
  case 1:
    f << "numbering=roman[upper],";
    break;
  case 2:
    f << "numbering=roman[lower],";
    break;
  case 3:
    f << "numbering=alpha[upper],";
    break;
  case 4:
    f << "numbering=alpha[lower],";
    break;
  default:
    f << "#numbering[type]=" << ((flags>>2)&7) << ",";
    break;
  }
  if (flags&3) f << "flags=" << (flags&3) << ",";

  float pageDim[2];
  for (auto &d : pageDim) d = float(input->readULong(2))/1440.f;
  f << "dim=[" << pageDim[1] << "x" << pageDim[0] << "],";
  val = static_cast<int>(input->readLong(2));
  if (val != -1) f << "firstPage=" << val << ",";
  // check me
  float pagePos[2][2]; // [Y|X][header|size]
  char const *wh[] = {"TopMargin", "Y[page]", "LeftMargin", "X[page]" };
  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 2; j++) {
      pagePos[i][j] = float(input->readULong(2))/1440.f;
      f << wh[i*2+j] << "=" << pagePos[i][j] << ",";
    }
  }
  flags = static_cast<int>(input->readULong(1));
  bool endNote = false;
  if (flags&1) {
    f << "endnote,";
    endNote = true;
  }
  if (flags&2)
    f << "autonumbering,";
  if (flags&0xFC)
    f << "flags2=" << std::hex << (flags&0xFC) << std::dec << ",";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  pos = input->tell();
  f.str("");
  f << "DocInfo(II):";
  auto numCols = static_cast<int>(input->readULong(1));
  if (numCols != 1) {
    f << "nCols=" << numCols << ",";
    if (numCols < 1 || numCols > 6) {
      f << "###";
      numCols = 1;
    }
  }
  float hfLength[2];
  for (auto &hf : hfLength) hf = float(input->readULong(2))/1440.f;
  hfLength[1]=pageDim[0]-hfLength[1];

  f << "headerLength=" << hfLength[0] << ",";
  f << "footerLength=" << hfLength[1] << ",";
  float colSep = float(input->readULong(2))/1440.f;
  f << "colSep=" << colSep << ",";
  val = static_cast<int>(input->readLong(2));
  if (val)
    f << "f3=" << val << ",";
  f << "distToHeader=" << float(input->readULong(2))/1440.f << ",";
  f << "distToNote=" << float(input->readULong(2))/1440.f << ",";
  // probably follows by other distance

  if (pageDim[0] > 0 && pageDim[1] > 0 &&
      pagePos[0][0]>=0 && pagePos[0][1]>=0 && pageDim[0] >= pagePos[0][0]+pagePos[0][1] &&
      pagePos[1][0]>=0 && pagePos[1][1]>=0 && pageDim[1] >= pagePos[1][0]+pagePos[1][1] &&
      pageDim[1] >= float(numCols)*pagePos[1][1]) {
    getPageSpan().setMarginTop(double(pagePos[0][0]));
    getPageSpan().setMarginLeft(double(pagePos[1][0]));
    getPageSpan().setFormLength(double(pageDim[0]));
    getPageSpan().setFormWidth(double(pageDim[1]));
    m_state->m_endNote = endNote;
    m_state->m_numColumns = numCols;
    m_state->m_columnsSep = colSep;
  }
  else {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readDocInfo: some dimension do not look good\n"));
  }
  ascii().addDelimiter(input->tell(),'|');
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  ascii().addPos(pos+53);
  ascii().addNote("DocInfo(III)");
  return true;
}

// read a plc zone (char or paragraph properties )
bool MsWrd1Parser::readPLC(MWAWVec2i limits, int wh)
{
  MWAWInputStreamPtr input = getInput();
  if (limits[1] <= limits[0] || !input->checkPosition(limits[1]*0x80)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::readPLC: the zone is not well defined\n"));
    return false;
  }
  libmwaw::DebugStream f, f2;

  std::map<long, int> posIdMap;
  MsWrd1ParserInternal::PLC plc(wh==0 ? MsWrd1ParserInternal::FONT :
                                MsWrd1ParserInternal::RULER);
  char const *what = wh==0 ? "Char" : "Para";

  for (int z = limits[0], n=0; z < limits[1]; z++, n++) {
    f.str("");
    f << "Entries(" << what << ")[" << n << "]:";
    long pos = z*0x80;
    input->seek(pos+0x7f, librevenge::RVNG_SEEK_SET);
    auto N = static_cast<int>(input->readULong(1));
    f << "N=" << N << ",";
    if (4+N*6 > 0x7f) {
      f << "###";
      MWAW_DEBUG_MSG(("MsWrd1Parser::readPLC: the number of element seems to big\n"));
      ascii().addDelimiter(input->tell(),'|');
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      continue;
    }
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    auto fPos = long(input->readULong(4));

    for (int i = 0; i < N; i++) {
      f << "fPos=" << std::hex << fPos;
      auto newPos = long(input->readULong(4));
      f << "->" << newPos << std::dec;

      auto depl = static_cast<int>(input->readLong(2));
      if (depl == -1)
        plc.m_id = -1;
      else if (depl < N*6 || 4+depl >= 0x7f) {
        f << "[###pos=" << std::hex << depl << std::dec << "]";
        plc.m_id = -1;
      }
      else {
        long dataPos = pos+depl+4;
        long actPos = input->tell();
        if (posIdMap.find(dataPos) == posIdMap.end()) {
          f2.str("");
          f2 << what << "-";
          if (wh == 0) {
            MsWrd1ParserInternal::Font font;
            if (readFont(dataPos, font)) {
              plc.m_id=int(m_state->m_fontsList.size());
              m_state->m_fontsList.push_back(font);
              f2 << plc.m_id << ":";
#ifdef DEBUG
              f2 << font.m_font.getDebugString(getFontConverter()) << font;
#endif
            }
            else {
              plc.m_id = -1;
              f2 << "###";
            }
            ascii().addPos(dataPos);
            ascii().addNote(f2.str().c_str());
          }
          else {
            MsWrd1ParserInternal::Paragraph para;
            if (readParagraph(dataPos, para)) {
              plc.m_id=int(m_state->m_paragraphsList.size());
              m_state->m_paragraphsList.push_back(para);
              f2 << plc.m_id << ":" << para;
            }
            else {
              plc.m_id = -1;
              f2 << "###";
            }
            ascii().addPos(dataPos);
            ascii().addNote(f2.str().c_str());
          }
          posIdMap[dataPos] = plc.m_id;
        }
        else
          plc.m_id = posIdMap.find(dataPos)->second;
        input->seek(actPos, librevenge::RVNG_SEEK_SET);
      }
      m_state->m_plcMap.insert
      (std::multimap<long,MsWrd1ParserInternal::PLC>::value_type(fPos, plc));
      fPos = newPos;
      f << ":" << plc << ",";
    }
    ascii().addDelimiter(input->tell(),'|');
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }

  return true;
}

////////////////////////////////////////////////////////////
// try to read a text entry
////////////////////////////////////////////////////////////
bool MsWrd1Parser::sendText(MWAWEntry const &textEntry, bool isMain)
{
  if (!textEntry.valid()) return false;
  if (!getTextListener()) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::sendText: can not find a listener!"));
    return true;
  }
  if (isMain) {
    int numCols = m_state->m_numColumns;
    if (numCols > 1 && !getTextListener()->isSectionOpened()) {
      MWAWSection sec;
      sec.setColumns(numCols, getPageWidth()/double(numCols), librevenge::RVNG_INCH, double(m_state->m_columnsSep));
      getTextListener()->openSection(sec);
    }
  }
  long pos = textEntry.begin();
  MWAWInputStreamPtr input = getInput();

  input->seek(pos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "TextContent:";
  int actFId=-1, actRId = -1, actPage=0;
  auto plcIt = m_state->m_plcMap.begin();
  while (plcIt != m_state->m_plcMap.end() && plcIt->first < pos) {
    MsWrd1ParserInternal::PLC const &plc = plcIt++->second;
    if (plc.m_type == MsWrd1ParserInternal::FONT)
      actFId = plc.m_id;
    else if (plc.m_type == MsWrd1ParserInternal::RULER)
      actRId = plc.m_id;
    else if (plc.m_type == MsWrd1ParserInternal::PAGE)
      actPage++;
  }
  // new page can be in header, ..., so sometimes we must force a new page...
  if (isMain && actPage > m_state->m_actPage)
    newPage(actPage);
  MsWrd1ParserInternal::Font actFont, defFont;
  defFont.m_font = MWAWFont(3,12);
  if (actFId>=0 && actFId < int(m_state->m_fontsList.size()))
    actFont = m_state->m_fontsList[size_t(actFId)];
  else
    actFont = defFont;
  bool rulerNotSent = actRId != -1, fontNotSent = true;
  while (!input->isEnd() && input->tell() < textEntry.end()) {
    long actPos = input->tell();
    bool firstPlc = true;
    while (plcIt != m_state->m_plcMap.end() && plcIt->first <= actPos) {
      if (firstPlc) {
        ascii().addPos(pos);
        ascii().addNote(f.str().c_str());
        pos = actPos;
        f.str("");
        f << "TextContent:";
        firstPlc = false;
      }

      auto const &plc = plcIt++->second;
      switch (plc.m_type) {
      case MsWrd1ParserInternal::FONT:
        if (plc.m_id >= 0 && plc.m_id < int(m_state->m_fontsList.size()))
          getTextListener()->setFont(m_state->m_fontsList[size_t(plc.m_id)].m_font);
        else
          getTextListener()->setFont(defFont.m_font);
        actFont.m_font = getTextListener()->getFont();
        fontNotSent = false;
        break;
      case MsWrd1ParserInternal::RULER:
        actRId = plc.m_id;
        rulerNotSent = true;
        break;
      case MsWrd1ParserInternal::PAGE:
        if (isMain) newPage(++actPage);
        break;
      case MsWrd1ParserInternal::FOOTNOTE: {
        if (!isMain) break;
        if (plc.m_id < 0 || plc.m_id >= int(m_state->m_footnotesList.size())) {
          MWAW_DEBUG_MSG(("MsWrd1Parser::sendText: oops, can not find a footnote!\n"));
          break;
        }
        MWAWEntry entry;
        entry.setBegin(m_state->m_footnotesList[size_t(plc.m_id)][0]);
        entry.setEnd(m_state->m_footnotesList[size_t(plc.m_id)][1]);
        removeLastCharIfEOL(entry);
        std::shared_ptr<MWAWSubDocument> subdoc
        (new MsWrd1ParserInternal::SubDocument(*this, getInput(), entry));
        getTextListener()->insertNote(MWAWNote(m_state->m_endNote ? MWAWNote::EndNote : MWAWNote::FootNote), subdoc);
        break;
      }
      case MsWrd1ParserInternal::ZONE:
      case MsWrd1ParserInternal::UNKNOWN:
#if !defined(__clang__)
      default:
#endif
        break;
      }
      f << "[" << plc << "]";
    }
    if (rulerNotSent) {
      if (actRId >= 0 && actRId < int(m_state->m_paragraphsList.size()))
        setProperty(m_state->m_paragraphsList[size_t(actRId)]);
      else
        setProperty(MsWrd1ParserInternal::Paragraph());
      rulerNotSent = false;
    }
    if (fontNotSent) getTextListener()->setFont(actFont.m_font);
    auto c = static_cast<unsigned char>(input->readULong(1));
    f << char(c);
    switch (c) {
    case 1:
      getTextListener()->insertUnicodeString(librevenge::RVNGString("(picture)"));
      break;
    case 5: // footnote mark
    case 0xc: // end of file
      break;
    case 0x9:
      getTextListener()->insertTab();
      break;
    case 0xd:
      getTextListener()->insertEOL();
      break;
    default:
      getTextListener()->insertCharacter(static_cast<unsigned char>(c), input, textEntry.end());
      break;
    }
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

// send the ruler properties
void MsWrd1Parser::setProperty(MsWrd1ParserInternal::Paragraph const &para)
{
  if (!getTextListener()) return;
  getTextListener()->setParagraph(para);
}

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

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

  libmwaw::DebugStream f;
  if (!input->checkPosition(0x80)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::checkHeader: file is too short\n"));
    return false;
  }
  long pos = 0;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  auto val = static_cast<int>(input->readULong(2));
  switch (val) {
  case 0xfe32:
    switch (input->readULong(2)) {
    case 0x0:
      setVersion(1);
      break;
    default:
      return false;
    }
    break;
  default:
    return false;
  }

  f << "FileHeader:";
  val = static_cast<int>(input->readULong(1)); // v1: ab other 0 ?
  if (val) f << "f0=" << val << ",";
  for (int i = 1; i < 3; i++) { // always 0
    val = static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  for (int i = 0; i < 5; i++) { // always 0 ?
    val = static_cast<int>(input->readLong(1));
    if (val) f << "g" << i << "=" << val << ",";
  }

  m_state->m_eot = long(input->readULong(4));
  f << "text=" << std::hex << 0x80 << "<->" << m_state->m_eot << ",";
  if (0x80 > m_state->m_eot || !input->checkPosition(m_state->m_eot)) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::checkHeader: problem with text position must stop\n"));
    return false;
  }

  m_state->m_fileZonesLimit[0] = int((m_state->m_eot+0x7f)/0x80);
  f << "zonesPos=[" << std::hex;
  for (int i = 0; i < 6; i++) {
    m_state->m_fileZonesLimit[i+1] = static_cast<int>(input->readLong(2));
    if (m_state->m_fileZonesLimit[i]==m_state->m_fileZonesLimit[i+1]) {
      f << "_,";
      continue;
    }
    if (m_state->m_fileZonesLimit[i]<m_state->m_fileZonesLimit[i+1]) {
      f << m_state->m_fileZonesLimit[i]*0x80 << "<->"
        << m_state->m_fileZonesLimit[i+1]*0x80 << ",";
      continue;
    }
    MWAW_DEBUG_MSG(("MsWrd1Parser::checkHeader: problem reading the zones positions\n"));
    if (strict) return false;
    f << "###" << m_state->m_fileZonesLimit[i+1]*0x80 << ",";
    m_state->m_fileZonesLimit[i+1] = m_state->m_fileZonesLimit[i];
  }
  f << std::dec << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  pos = input->tell();
  f.str("");
  f << "FileHeader[A]:";
  for (int i = 0; i < 17; i++) {
    val = static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  long textSize[2];
  for (auto &tSize : textSize) tSize = input->readLong(4);
  if (textSize[0] != textSize[1] || 0x80+textSize[0] != m_state->m_eot) {
    MWAW_DEBUG_MSG(("MsWrd1Parser::checkHeader: problem with text position length\n"));
    if (strict) return false;
    f << "##textSize=" << std::hex << textSize[0] << ":" << textSize[1] << std::dec << ",";
    if (textSize[1] > textSize[0]) textSize[0] = textSize[1];
    if (0x80+textSize[0] > m_state->m_eot && input->checkPosition(0x80+textSize[0]))
      m_state->m_eot = 0x80+textSize[0];
  }
  pos=input->tell();
  f.str("");
  f << "FileHeader[B]:";
  for (int i = 0; i < 28; i++) { // always 0
    val = static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  if (header)
    header->reset(MWAWDocument::MWAW_T_MICROSOFTWORD, 1);
  return true;
}

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