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 <set>
#include <sstream>

#include <librevenge/librevenge.h>

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

#include "NisusWrtGraph.hxx"
#include "NisusWrtStruct.hxx"
#include "NisusWrtText.hxx"

#include "NisusWrtParser.hxx"

/** Internal: the structures of a NisusWrtParser */
namespace NisusWrtParserInternal
{
/** Internal structure: use to store a numbering, a variable or a version */
struct Variable {
  //! Constructor
  explicit Variable(NisusWrtStruct::VariableType type = NisusWrtStruct::V_None)
    : m_type(0)
    , m_containerType(type)
    , m_fieldType(-1)
    , m_refId(-1)
    , m_numberingType(libmwaw::NONE)
    , m_startNumber(1)
    , m_increment(1)
    , m_prefix("")
    , m_suffix("")
    , m_dateFormat(0)
    , m_sample("")
    , m_extra("")
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Variable const &num);
  //! returns true if this is a date
  bool isDate() const
  {
    return m_fieldType == 1 || m_fieldType == 0xf;
  }
  //! returns the date format
  std::string getDateFormat() const
  {
    if (!isDate()) return "";
    switch (m_dateFormat) {
    case 0:
    case 0x20:
      return "%m/%d/%Y";
    case 0x40:
      return "%d/%m/%Y";
    case 1:
    case 0x21:
    case 2:
    case 0x22: // normally DDD, MMM d Y
      return "%A, %B %d %Y";
    case 0x41:
    case 0x42:
      return "%A, %d %B, %Y";
    case 0x81:
    case 0xa1:
    case 0x82:
    case 0xa2: // normally DDD, MMM d Y
      return "%B %d, %Y";
    case 0xc1:
    case 0xc2:
      return "%d %B, %Y";
    default:
      break;
    }
    return "";
  }
  //! the main type
  int m_type;
  //! the container type
  NisusWrtStruct::VariableType m_containerType;
  //! the variable type
  long m_fieldType;
  //! the reference id
  int m_refId;
  //! the numbering type
  libmwaw::NumberingType m_numberingType;
  //! the start number
  int m_startNumber;
  //! the increment
  int m_increment;
  //! the prefix
  std::string m_prefix;
  //! the suffix
  std::string m_suffix;
  //! the date format
  int m_dateFormat;
  //! a sample used in a dialog ?
  std::string m_sample;
  //! some extra debuging information
  std::string m_extra;
};

std::ostream &operator<<(std::ostream &o, Variable const &num)
{
  switch (num.m_type) {
  case 1:
    o << "numbering,";
    break;
  case 2:
    o << "numbering[count],";
    break;
  case 3:
    o << "version,";
    break;
  case 4:
    o << "version[small],";
    break;
  case 5:
    o << "date/time,";
    break;
  case 6:
    o << "docTitle,";
    break;
  default:
    o << "#type=" << num.m_type << ",";
    break;
  }
  switch (num.m_containerType) {
  case NisusWrtStruct::V_Numbering:
    o << "number,";
    break;
  case NisusWrtStruct::V_Variable:
    o << "variable,";
    break;
  case NisusWrtStruct::V_Version:
    o << "version,";
    break;
  case NisusWrtStruct::V_None:
    break;
#if !defined(__clang__)
  default:
    o << "#type[container]=" << int(num.m_containerType) << ",";
    return o;
#endif
  }
  if (num.m_refId >= 0)
    o << "refId=" << num.m_refId << ",";
  switch (num.m_fieldType) {
  case -1:
    break;
  case 0x1:
    o << "date2,";
    break;
  case 0xe:
    o << "version,";
    break;
  case 0xf:
    o << "date,";
    break;
  case 0x10:
    o << "time,";
    break;
  case 0x11:
    o << "docTitle,";
    break;
  case 0x1c:
    o << "footnote,";
    break;
  case 0x1d:
    o << "reference?,";
    break;
  // alway in version variable ?
  case 0x7FFFFFFF:
    o << "none,";
    break;
  // in a variable find also 0xFFFF8014
  default:
    if ((num.m_fieldType>>16)==0x7FFF)
      o << "#fieldType=" << num.m_fieldType -0x7FFFFFFF-1  << ",";
    else if ((num.m_fieldType>>16)==0xFFFF)
      o << "#fieldType=X" << std::hex << num.m_fieldType << std::dec << ",";
    else
      o << "#fieldType=" << num.m_fieldType << ",";
    break;
  }
  std::string type = libmwaw::numberingTypeToString(num.m_numberingType);
  if (type.length())
    o << "type=" << type << ",";
  if (num.m_startNumber != 1) o << "start=" << num.m_startNumber << ",";
  if (num.m_increment != 1) o << "increment=" << num.m_increment << ",";
  if (num.m_prefix.length()) {
    static char const *wh0[] = { "unkn0", "prefix", "name", "comments" };
    o << wh0[num.m_containerType] << "=\"" << num.m_prefix << "\",";
  }
  if (num.m_suffix.length()) {
    static char const *wh2[] = { "unkn2", "suffix", "suffix", "unkn2" };
    o << wh2[num.m_containerType] << "=\"" << num.m_suffix << "\",";
  }
  if (num.m_sample.length()) {
    static char const *wh1[] = { "unkn1", "sample", "sample", "author?" };
    o << wh1[num.m_containerType] << "=\"" << num.m_sample << "\",";
  }
  if (num.m_dateFormat) {
    switch (num.m_dateFormat & 0x9F) {
    case 1:
      o << "format=Day, Month D YYYY,";
      break;
    case 2:
      o << "format=Day, Mon D YYYY,";
      break;
    case 0x81:
      o << "format=Month D, YYYY,";
      break;
    case 0x82:
      o << "format=Mon D, YYYY,";
      break;
    default:
      o << "#format=" << std::hex << (num.m_dateFormat&0x9F) << std::dec << ",";
      break;
    }
    if (num.m_dateFormat & 0x20) o << "[english]";
    if (num.m_dateFormat & 0x40) o << "[european]";
    o << ",";
  }
  if (num.m_extra.length())
    o << num.m_extra;
  return o;
}

/** Internal structure: use to store a mark (reference) */
struct Reference {
  //! constructor
  Reference()
    : m_id(-1)
    , m_textPosition()
    , m_text("")
  {
  }

  // the mark id ?
  int m_id;
  // the text position
  MWAWEntry m_textPosition;
  // the mark text
  std::string m_text;
};

//! internal structure used to stored some zone data
struct Zone {
  //! constructor
  Zone()
    : m_referenceList()
    , m_numberingResetList()
    , m_variableList()
    , m_versionList()
  {
  }
  /** the list of reference */
  std::vector<Reference> m_referenceList;
  //! the list of numbering reset id
  std::vector<int> m_numberingResetList;
  /** the list of variable */
  std::vector<Variable> m_variableList;
  /** the list of versions */
  std::vector<Variable> m_versionList;
};

////////////////////////////////////////
//! Internal: the state of a NisusWrtParser
struct State {
  //! constructor
  State()
    : m_numberingList()
    , m_actPage(0)
    , m_numPages(0)
    , m_numColumns(1)
    , m_columnSep(0.5f)
    , m_footnoteInfo()
    , m_isTextDocument()
  {
  }

  /** the list of numbering */
  std::vector<Variable> m_numberingList;
  /** the main zones : Main, Footnote, HeaderFooter */
  Zone m_zones[3];
  int m_actPage /** the actual page */, m_numPages /** the number of page of the final document */;

  int m_numColumns /** the number of columns */;
  float m_columnSep /** the columns separator */;
  /** the footnote placement */
  NisusWrtStruct::FootnoteInfo m_footnoteInfo;
  /** a bool to know if we examine a text document or another thing glossary ... */
  bool m_isTextDocument;
};
}


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

NisusWrtParser::~NisusWrtParser()
{
}

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

  m_state.reset(new NisusWrtParserInternal::State);

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

  m_graphParser.reset(new NisusWrtGraph(*this));
  m_textParser.reset(new NisusWrtText(*this));
}

MWAWInputStreamPtr NisusWrtParser::rsrcInput()
{
  return getRSRCParser()->getInput();
}

libmwaw::DebugFile &NisusWrtParser::rsrcAscii()
{
  return getRSRCParser()->ascii();
}

////////////////////////////////////////////////////////////
// position and height
////////////////////////////////////////////////////////////
MWAWVec2f NisusWrtParser::getPageLeftTop() const
{
  return MWAWVec2f(float(getPageSpan().getMarginLeft()),
                   float(getPageSpan().getMarginTop()));
}

void NisusWrtParser::getColumnInfo(int &numColumns, float &colSep) const
{
  numColumns = m_state->m_numColumns;
  colSep = m_state->m_columnSep;
}

void NisusWrtParser::getFootnoteInfo(NisusWrtStruct::FootnoteInfo &fInfo) const
{
  fInfo = m_state->m_footnoteInfo;
}

////////////////////////////////////////////////////////////
// interface with the graph parser
////////////////////////////////////////////////////////////
bool NisusWrtParser::sendPicture(int pictId, MWAWPosition const &pictPos)
{
  if (!rsrcInput()) return false;
  long pos = rsrcInput()->tell();
  bool ok=m_graphParser->sendPicture(pictId, true, pictPos);
  rsrcInput()->seek(pos, librevenge::RVNG_SEEK_SET);
  return ok;
}

////////////////////////////////////////////////////////////
// access to variable date
////////////////////////////////////////////////////////////
std::string NisusWrtParser::getDateFormat(NisusWrtStruct::ZoneType zoneId, int vId) const
{
  if (zoneId >= 3) {
    MWAW_DEBUG_MSG(("NisusWrtParser::getDateFormat: bad zone %d\n", zoneId));
    return "";
  }
  NisusWrtParserInternal::Zone const &zone = m_state->m_zones[zoneId];
  if (vId < 0 || vId >= int(zone.m_variableList.size()) ||
      !zone.m_variableList[size_t(vId)].isDate()) {
    // some version 3 files do not contain any variables, so returns the default value...
    if (version()==3 && zone.m_variableList.size()==0)
      return "%m/%d/%Y";
    MWAW_DEBUG_MSG(("NisusWrtParser::getDateFormat: can not find the variable %d\n", vId));
    return "";
  }
  return zone.m_variableList[size_t(vId)].getDateFormat();
}

bool NisusWrtParser::getReferenceData
(NisusWrtStruct::ZoneType zoneId, int vId,
 MWAWField::Type &fType, std::string &content, std::vector<int> &values) const
{
  fType = MWAWField::None;
  content = "";
  if (zoneId >= 3) {
    MWAW_DEBUG_MSG(("NisusWrtParser::getReferenceData: bad zone %d\n", zoneId));
    return false;
  }
  auto const &zone = m_state->m_zones[zoneId];
  if (vId < 0 || vId >= int(zone.m_variableList.size())) {
    MWAW_DEBUG_MSG(("NisusWrtParser::getReferenceData: can not find the variable %d\n", vId));
    return false;
  }
  auto const &var=zone.m_variableList[size_t(vId)];
  if ((var.m_type != 1 && var.m_type != 2) || var.m_refId<=0) {
    MWAW_DEBUG_MSG(("NisusWrtParser::getReferenceData: find a variable with bad type %d\n", vId));
    return false;
  }
  // first special case
  if (var.m_type == 1 && var.m_refId == 14) {
    fType = MWAWField::PageNumber;
    return true;
  }
  if (var.m_type == 2 && var.m_refId == 15) {
    fType = MWAWField::PageCount;
    return true;
  }
  size_t numVar = m_state->m_numberingList.size();
  if (var.m_refId-1 >= int(numVar)) {
    MWAW_DEBUG_MSG(("NisusWrtParser::getReferenceData: can not find numbering variable for %d\n", vId));
    return false;
  }
  // resize values if needed
  for (size_t p = values.size(); p < numVar; p++)
    values.push_back(m_state->m_numberingList[p].m_startNumber
                     -m_state->m_numberingList[p].m_increment);
  auto const &ref=m_state->m_numberingList[size_t(var.m_refId-1)];
  values[size_t(var.m_refId-1)] += ref.m_increment;

  size_t numReset = zone.m_numberingResetList.size();
  if (numReset < numVar+1)
    numReset = numVar+1;
  if (size_t(var.m_refId) < numReset) {
    std::vector<bool> doneValues;
    std::vector<int> toDoValues;
    doneValues.resize(numReset, false);
    doneValues[size_t(var.m_refId)]=true;
    toDoValues.push_back(int(var.m_refId));
    while (toDoValues.size()) {
      auto modId = static_cast<int>(toDoValues.back());
      toDoValues.pop_back();
      for (size_t r = 0; r < numReset; r++) {
        if (zone.m_numberingResetList[r] != modId)
          continue;
        if (r == 0 || doneValues[r]) continue;
        doneValues[r] = true;
        values[r-1] = m_state->m_numberingList[r-1].m_startNumber
                      -m_state->m_numberingList[r-1].m_increment;
        toDoValues.push_back(int(r));
      }
    }
  }
  std::stringstream s;
  std::string str = ref.m_prefix + ref.m_suffix;
  for (char p : str) {
    auto c = static_cast<unsigned char>(p);
    if (c==0 || (c < 0x20 && c > numVar)) {
      MWAW_DEBUG_MSG(("NisusWrtParser::getReferenceData: find unknown variable\n"));
#ifdef DEBUG
      s << "###[" << int(c) << "]";
#endif
    }
    else if (c < 0x20)
      s << libmwaw::numberingValueToString
        (m_state->m_numberingList[size_t(c-1)].m_numberingType,
         values[size_t(c-1)]);
    else s << char(c);
  }
  content=s.str();
  return true;
}

////////////////////////////////////////////////////////////
// new page
////////////////////////////////////////////////////////////
void NisusWrtParser::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);
  }
}

////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void NisusWrtParser::parse(librevenge::RVNGTextInterface *docInterface)
{
  if (!getInput().get() || !getRSRCParser() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = false;
  try {
    // create the asciiFile
    ascii().setStream(getInput());
    ascii().open(asciiName());
    checkHeader(nullptr);
    ok = createZones();
    if (ok) {
      createDocument(docInterface);
      m_graphParser->sendPageGraphics();
      m_textParser->sendMainText();
#ifdef DEBUG
      m_graphParser->flushExtra();
      m_textParser->flushExtra();
#endif
    }
    ascii().reset();
  }
  catch (...) {
    MWAW_DEBUG_MSG(("NisusWrtParser::parse: exception catched when parsing\n"));
    ok = false;
  }

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

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

  // update the page
  m_state->m_actPage = 0;

  // create the page list
  int numPages = 1;
  if (m_graphParser->numPages() > numPages)
    numPages = m_graphParser->numPages();
  if (m_textParser->numPages() > numPages)
    numPages = m_textParser->numPages();
  m_state->m_numPages = numPages;

  std::vector<MWAWPageSpan> pageList;
  std::shared_ptr<MWAWSubDocument> subDoc;
  for (int i = 0; i <= numPages;) {
    MWAWPageSpan ps(getPageSpan());
    int numSim[2]= {1,1};
    subDoc = m_textParser->getHeader(i+1, numSim[0]);
    if (subDoc) {
      MWAWHeaderFooter header(MWAWHeaderFooter::HEADER, MWAWHeaderFooter::ALL);
      header.m_subDocument=subDoc;
      ps.setHeaderFooter(header);
    }
    subDoc = m_textParser->getFooter(i+1, numSim[1]);
    if (subDoc) {
      MWAWHeaderFooter footer(MWAWHeaderFooter::FOOTER, MWAWHeaderFooter::ALL);
      footer.m_subDocument=subDoc;
      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 NisusWrtParser::createZones()
{
  MWAWInputStreamPtr input = getInput();
  std::string type, creator;
  if (input && input->getFinderInfo(type, creator) && type != "TEXT")
    m_state->m_isTextDocument = false;
  auto &entryMap = getRSRCParser()->getEntriesMap();

  // the 128 id
  auto it = entryMap.lower_bound("PREC");
  while (it != entryMap.end()) {
    if (it->first != "PREC")
      break;
    MWAWEntry &entry = it++->second;
    readPrintInfo(entry);
  }
  it = entryMap.lower_bound("CPRC");
  while (it != entryMap.end()) {
    if (it->first != "CPRC")
      break;
    MWAWEntry &entry = it++->second;
    readCPRC(entry);
  }
  it = entryMap.lower_bound("PGLY");
  while (it != entryMap.end()) {
    if (it->first != "PGLY")
      break;
    MWAWEntry &entry = it++->second;
    readPageLimit(entry);
  }
  it = entryMap.lower_bound("INFO");
  while (it != entryMap.end()) {
    if (it->first != "INFO")
      break;
    MWAWEntry &entry = it++->second;
    readINFO(entry);
  }
  it = entryMap.lower_bound("ABBR");
  while (it != entryMap.end()) {
    if (it->first != "ABBR")
      break;
    MWAWEntry &entry = it++->second;
    readABBR(entry);
  }
  it = entryMap.lower_bound("FTA2");
  while (it != entryMap.end()) {
    if (it->first != "FTA2")
      break;
    MWAWEntry &entry = it++->second;
    readFTA2(entry);
  }
  it = entryMap.lower_bound("FnSc");
  while (it != entryMap.end()) {
    if (it->first != "FnSc")
      break;
    MWAWEntry &entry = it++->second;
    readFnSc(entry);
  }
  /** find also the resources :
      - alis: seems to contains data and some strings
      - OPEN: seems to contains one string: author? (size 0x20)
      - INF2: seems to contain some colors and some data (size 0x222)
      - sect: in two files: 010[1|a]0000acc0015f0000000[1|6]000... (size 0x24)
  */

  if (!m_textParser->createZones())
    return false;
  m_graphParser->createZones();

  // numbering, mark, variable, version, ...
  it = entryMap.lower_bound("CNAM"); // numbering name added in v5, v6
  while (it != entryMap.end()) {
    if (it->first != "CNAM")
      break;
    MWAWEntry &entry = it++->second;
    std::vector<std::string> list;
    readStringsList(entry, list, true);
  }

  it = entryMap.lower_bound("DSPL");
  while (it != entryMap.end()) {
    if (it->first != "DSPL")
      break;
    MWAWEntry &entry = it++->second;
    entry.setName("NumberingDef");
    NisusWrtStruct::RecursifData data(NisusWrtStruct::Z_Main, NisusWrtStruct::V_Numbering);
    data.read(*this, entry);
    readVariable(data);
  }
  char const *variableNames[] = { "VARI", "FVAR", "HVAR" };
  for (int z = 0; z < 3; z++) {
    it = entryMap.lower_bound(variableNames[z]);
    while (it != entryMap.end()) {
      if (it->first != variableNames[z])
        break;
      MWAWEntry &entry = it++->second;
      entry.setName("Variable");
      NisusWrtStruct::RecursifData data(NisusWrtStruct::ZoneType(z), NisusWrtStruct::V_Variable);
      data.read(*this, entry);
      readVariable(data);
    }
  }

  char const *cntrNames[] = { "CNTR", "FCNT", "HCNT" };
  for (int z = 0; z < 3; z++) {
    it = entryMap.lower_bound(cntrNames[z]);
    while (it != entryMap.end()) {
      if (it->first != cntrNames[z])
        break;
      MWAWEntry &entry = it++->second;
      readCNTR(entry, NisusWrtStruct::ZoneType(z));
    }
  }
  char const *numbResetNames[] = { "DPND", "FDPN", "HDPN" };
  for (int z = 0; z < 3; z++) {
    it = entryMap.lower_bound(numbResetNames[z]);
    while (it != entryMap.end()) {
      if (it->first != numbResetNames[z])
        break;
      MWAWEntry &entry = it++->second;
      readNumberingReset(entry, NisusWrtStruct::ZoneType(z));
    }
  }
  char const *versionNames[] = { "VRS ", "FVRS", "HVRS" };
  for (int z = 0; z < 3; z++) {
    it = entryMap.lower_bound(versionNames[z]);
    while (it != entryMap.end()) {
      if (it->first != versionNames[z])
        break;
      MWAWEntry &entry = it++->second;
      entry.setName("VariabS");
      NisusWrtStruct::RecursifData data(NisusWrtStruct::ZoneType(z), NisusWrtStruct::V_Version);
      data.read(*this, entry);
      readVariable(data);
    }
  }

  char const *markNames[] = { "MRK7", "FMRK", "HMRK" };
  for (int z = 0; z < 3; z++) {
    it = entryMap.lower_bound(markNames[z]);
    while (it != entryMap.end()) {
      if (it->first != markNames[z])
        break;
      MWAWEntry &entry = it++->second;
      entry.setName("Reference");
      NisusWrtStruct::RecursifData data = NisusWrtStruct::RecursifData(NisusWrtStruct::ZoneType(z));
      data.read(*this, entry);
      readReference(data);
    }
  }
  it = entryMap.lower_bound("XMRK"); // related to mark7 ?
  while (it != entryMap.end()) {
    if (it->first != "XMRK")
      break;
    MWAWEntry &entry = it++->second;
    entry.setName("XMRK");
    NisusWrtStruct::RecursifData data(NisusWrtStruct::Z_Main);
    data.read(*this, entry);
  }
  // unknown ?
  it = entryMap.lower_bound("SGP1");
  while (it != entryMap.end()) {
    if (it->first != "SGP1")
      break;
    MWAWEntry &entry = it++->second;
    entry.setName("SGP1");
    NisusWrtStruct::RecursifData data(NisusWrtStruct::Z_Main);
    data.read(*this, entry);
    readSGP1(data);
  }
  return true;
}

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

////////////////////////////////////////////////////////////
// read the header
////////////////////////////////////////////////////////////
bool NisusWrtParser::checkHeader(MWAWHeader *header, bool /*strict*/)
{
  *m_state = NisusWrtParserInternal::State();
  /** no data fork, may be ok, but this means
      that the file contains no text and no picture, so... */
  MWAWInputStreamPtr input = getInput();
  if (!input || !input->hasDataFork() || !getRSRCParser())
    return false;
  MWAWRSRCParser::Version applVersion, docVersion;
  // read the Nisus version
  MWAWEntry entry = getRSRCParser()->getEntry("vers", 2002);
  if (!entry.valid()) entry = getRSRCParser()->getEntry("vers", 2);
  // read the application format version
  if (!entry.valid() || !getRSRCParser()->parseVers(entry, applVersion)) {
    MWAW_DEBUG_MSG(("NisusWrtParser::checkHeader: can not find the Nisus version\n"));
  }
  // read the file format version
  entry = getRSRCParser()->getEntry("vers", 1);
  if (!entry.valid() || !getRSRCParser()->parseVers(entry, docVersion)) {
    MWAW_DEBUG_MSG(("NisusWrtParser::checkHeader: can not find the Nisus file format version\n"));
    return false;
  }
  switch (docVersion.m_majorVersion) {
  case 3:
  case 4:
    MWAW_DEBUG_MSG(("NisusWrtParser::checkHeader: find Nisus %d file with file format version %d\n", applVersion.m_majorVersion, docVersion.m_majorVersion));
    break;
  default:
    MWAW_DEBUG_MSG(("NisusWrtParser::checkHeader: find Nisus %d file with unknown file format version %d\n", applVersion.m_majorVersion, docVersion.m_majorVersion));
    return false;
  }

  setVersion(docVersion.m_majorVersion);
  if (header)
    header->reset(MWAWDocument::MWAW_T_NISUSWRITER, version());

  return true;
}

////////////////////////////////////////////////////////////
// read a list of string
////////////////////////////////////////////////////////////
bool NisusWrtParser::readStringsList(MWAWEntry const &entry, std::vector<std::string> &list, bool simpleList)
{
  list.resize(0);
  if (!entry.valid() && entry.length()!=0) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readStringsList: the entry is bad\n"));
    return false;
  }
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  f << "Entries(" << entry.type() << ")[" << entry.id() << "]:";
  rsrcAscii().addPos(pos-4);
  rsrcAscii().addNote(f.str().c_str());

  while (!input->isEnd()) {
    pos = input->tell();
    if (pos == entry.end()) break;
    long endPos = entry.end();
    f.str("");
    f << entry.type() << list.size() << ":";

    if (!simpleList) {
      if (pos+2 > entry.end()) {
        f << "###";
        rsrcAscii().addPos(pos);
        rsrcAscii().addNote(f.str().c_str());

        MWAW_DEBUG_MSG(("NisusWrtParser::readStringsList: can not read strings\n"));
        return false;
      }
      auto sz = static_cast<int>(input->readULong(2));
      endPos = pos+2+sz;
      if (pos+2+sz > entry.end()) {
        f.str("");
        f << "###";
        rsrcAscii().addPos(pos);
        rsrcAscii().addNote(f.str().c_str());

        MWAW_DEBUG_MSG(("NisusWrtParser::readStringsList: zone size is bad\n"));
        return false;
      }
    }

    /* checkme: in STNM we can have a list of string, it is general or
       do we need to create a new function NisusWrtParser::readStringsListList
     */
    std::string str("");
    while (input->tell() < endPos-1) {
      auto pSz = static_cast<int>(input->readULong(1));
      if (pSz == 0xFF) {
        f << "_";
        pSz = 0;
      }
      if (input->tell()+pSz > endPos || input->isEnd()) {
        f << "###";
        rsrcAscii().addPos(pos);
        rsrcAscii().addNote(f.str().c_str());
        MWAW_DEBUG_MSG(("NisusWrtParser::readStringsList: string size is too big\n"));
        return false;
      }
      std::string str1("");
      for (int c=0; c < pSz; c++)
        str1 += char(input->readULong(1));
      f << str1 << ",";
      str += str1;
      if (simpleList) continue;
      if ((pSz%2)==0) input->seek(1,librevenge::RVNG_SEEK_CUR);
    }
    list.push_back(str);
    rsrcAscii().addPos(pos);
    rsrcAscii().addNote(f.str().c_str());
    if (simpleList) break;
  }
  return true;
}

////////////////////////////////////////////////////////////
// read DSPL or the VRS zone: numbering definition or version
////////////////////////////////////////////////////////////
bool NisusWrtParser::readVariable(NisusWrtStruct::RecursifData const &data)
{
  if (!data.m_info || data.m_info->m_zoneType >= 3) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readVariable: find unexpected zoneType\n"));
    return false;
  }
  if (!data.m_childList.size())
    return true;

  if (data.m_childList.size() > 1) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readVariable: level 0 node contains more than 1 node\n"));
  }
  if (data.m_childList[0].isLeaf()) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readVariable: level 1 node is a leaf\n"));
    return false;
  }
  auto const &mainData = *data.m_childList[0].m_data;
  auto &zone = m_state->m_zones[int(data.m_info->m_zoneType)];
  MWAWInputStreamPtr input = rsrcInput();
  libmwaw::DebugStream f;
  long val;

  std::vector<NisusWrtParserInternal::Variable> *result = nullptr;
  int lastMaxId = 0;
  switch (data.m_info->m_variableType) {
  case NisusWrtStruct::V_Numbering:
    lastMaxId = 11;
    result = &m_state->m_numberingList;
    break;
  case NisusWrtStruct::V_Version:
    lastMaxId = 8;
    result = &zone.m_versionList;
    break;
  case NisusWrtStruct::V_Variable:
    lastMaxId = 12;
    result = &zone.m_variableList;
    break;
  case NisusWrtStruct::V_None:
#if !defined(__clang__)
  default:
#endif
    MWAW_DEBUG_MSG(("NisusWrtParser::readVariable: find unexpected dataType\n"));
    return false;
  }
  for (auto const &node : mainData.m_childList) {
    static int const minExpectedSz[] = { 1, 4, 1, 2, 2, 2, 4, 4, 1, 4, 1 };
    NisusWrtParserInternal::Variable num(data.m_info->m_variableType);
    num.m_type = node.m_type;

    if (node.isLeaf()) {
      MWAW_DEBUG_MSG(("NisusWrtParser::readVariable: level 2 node is a leaf\n"));
      continue;
    }
    auto const &dt = *node.m_data;
    f.str("");
    int lastId = lastMaxId;
    switch (num.m_type) {
    case 1: // [2..12] with v12 is a string
      lastId = 12;
      f << "numbering,";
      break;
    case 2: // idem
      lastId = 12;
      f << "numbering[count],";
      break;
    case 3: // type = [2, 3.. 8]
      lastId = 8;
      f << "version,";
      break;
    case 4: // type = [2,3,4]
      lastId = 4;
      f << "version[small],";
      break;
    case 5: //type=[2,3,4,4]  second id=4: a small int : Date format/time
      lastId = 4;
      f << "date/time,";
      break;
    case 6:  // type=[2,3,4]
      lastId = 4;
      f << "docTitle,";
      break;
    default:
      f << "#";
      break;
    }
    rsrcAscii().addPos(node.m_entry.begin()-16);
    rsrcAscii().addNote(f.str().c_str());

    for (size_t nDt = 0; nDt < dt.m_childList.size(); nDt++) {
      if (!dt.m_childList[nDt].isLeaf()) {
        MWAW_DEBUG_MSG(("NisusWrtParser::readVariable: level 3 node is not a leaf\n"));
        continue;
      }
      MWAWEntry const &entry = dt.m_childList[nDt].m_entry;
      f.str("");
      input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
      int id = dt.m_childList[nDt].m_type;
      bool checkId = false;
      if (id != int(nDt)+2) {
        if (id == 4 && node.m_type == 5 && entry.length() >= 2) {
          checkId = true;
          id = -4;
        }
        else
          f << "#id=" << id << ",";
      }

      if (!checkId && (id < 2 || id > lastId || entry.length() < minExpectedSz[id-2])) {
        MWAW_DEBUG_MSG(("NisusWrtParser::readVariable: find unexpected size for data %d\n", int(nDt)));
        f << "###";
        rsrcAscii().addPos(entry.begin()-12);
        rsrcAscii().addNote(f.str().c_str());
        continue;
      }
      switch (id) {
      case 2:
      case 4: // display example
      case 10:
      case 12: { // postfix : list of fields
        auto mSz = static_cast<int>(input->readULong(1));
        if (mSz+1 > entry.length()) {
          MWAW_DEBUG_MSG(("NisusWrtParser::readVariable: the dpsl seems to short\n"));
          f << "###Text";
          break;
        }
        std::string text("");
        for (int i = 0; i < mSz; i++)
          text+= char(input->readULong(1));
        static char const *wh0[] = { "unkn0", "prefix", "name", "comments" };
        static char const *wh1[] = { "unkn1", "sample", "sample", "author?" };
        static char const *wh2[] = { "unkn2", "suffix", "suffix", "unkn2" };
        switch (id) {
        case 2:
          num.m_prefix = text;
          f << wh0[num.m_containerType];
          break;
        case 4:
          num.m_sample = text;
          f << wh1[num.m_containerType];
          break;
        case 10:
          num.m_suffix = text;
          f << wh2[num.m_containerType];
          break;
        case 12:
          f << "f12";
          break;
        default:
          f << "###id[" << id << "]";
        }
        f << "=\"" << text << "\"";
        break;
      }
      case -4: { // date format
        num.m_dateFormat = static_cast<int>(input->readULong(2));
        if (!num.m_dateFormat) break; // default
        std::string format("");
        format = num.getDateFormat();
        if (format.length()) f << "format=" << format << ",";
        else f << "#format=" << std::hex << (num.m_dateFormat&0x9F) << std::dec << ",";
        break;
      }
      case 5: // style : 0=arabic, 1=roman, upperroman, abgadhawaz(arabic), alpha, upperalpha, hebrew
        val = long(input->readULong(2));
        f << "type=";
        num.m_numberingType = libmwaw::ARABIC;
        switch (val) {
        case 0:
          f << "arabic,";
          break;
        case 1:
          num.m_numberingType = libmwaw::LOWERCASE_ROMAN;
          f << "roman,";
          break;
        case 2:
          num.m_numberingType = libmwaw::UPPERCASE_ROMAN;
          f << "upperroman,";
          break;
        case 3:
          f << "abgadhawaz,";
          break;
        case 4:
          num.m_numberingType = libmwaw::LOWERCASE;
          f << "alpha,";
          break;
        case 5:
          num.m_numberingType = libmwaw::UPPERCASE;
          f << "upperalpha,";
          break;
        case 6:
          f << "hebrew,";
          break;
        default:
          f << "#" << val << ",";
        }
        break;
      case 6: // start number
        num.m_startNumber = static_cast<int>(input->readLong(2));
        f << "start=" << num.m_startNumber <<",";
        break;
      case 7: // increment
        num.m_increment = static_cast<int>(input->readLong(2));
        f << "increment=" << num.m_increment <<",";
        break;
      case 9: // id ?
        num.m_refId = static_cast<int>(input->readULong(4));
        f << "refId=" << num.m_refId << ",";
        break;
      case 3:
        num.m_fieldType = long(input->readULong(4));
        switch (num.m_fieldType) {
        // 1(type 5) date ?
        case 0xe:
          f << "version,";
          break;
        case 0xf:
          f << "date,";
          break;
        case 0x10:
          f << "time,";
          break;
        case 0x11:
          f << "docTitle,";
          break;
        case 0x14: // find with 0xFFFF8014
          f << "mark,";
          break;
        case 0x1c:
          f << "footnote,";
          break;
        case 0x1d:
          f << "reference?,";
          break;
        // alway in version variable ?
        case 0x7FFFFFFF:
          f << "none,";
          break;
        default:
          if ((num.m_fieldType>>16)==0x7FFF)
            f << "#fieldType=" << num.m_fieldType -0x7FFFFFFF-1  << ",";
          else if ((num.m_fieldType>>16)==0xFFFF)
            f << "#fieldType=X" << std::hex << num.m_fieldType << std::dec << ",";
          else
            f << "#fieldType=" << num.m_fieldType << ",";
          break;
        }
        break;
      case 8: // always 0 ?
        val = long(input->readULong(4));
        f << "g8=" << val << ",";
        break;
      case 11: // find 0-2: if numbering g11=0 means add default data, if not num data?
        val = long(input->readULong(4));
        if (val==0) {
          f << "auto,";
          if (num.m_suffix.length())
            f << "###";
          else
            num.m_suffix += char(num.m_refId);
        }
        else
          f << "numVar?=" << val << ",";
        break;
      default:
        break;
      }

      if (f.str().length()) {
        rsrcAscii().addPos(entry.begin()-12);
        rsrcAscii().addNote(f.str().c_str());
      }
    }
    result->push_back(num);
  }
  return true;
}

////////////////////////////////////////////////////////////
// read the DPND zone ( the numbering reset zone )
////////////////////////////////////////////////////////////
bool NisusWrtParser::readNumberingReset(MWAWEntry const &entry, int zoneId)
{
  // find to 2 times with entry.length()=0x22
  if (!entry.valid()) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readNumberingReset: the entry is bad\n"));
    return false;
  }
  if (zoneId < 0 || zoneId >= 3) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readNumberingReset: find unexpected zoneId: %d\n", zoneId));
    return false;
  }
  auto &zone = m_state->m_zones[zoneId];
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  auto sz = static_cast<int>(input->readULong(2));
  if (sz+2 != entry.length() || sz%2) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readNumberingReset: entry size seems odd\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(NumberingReset)[" << zoneId << "]:";
  auto numElt = size_t(sz/2);
  zone.m_numberingResetList.resize(numElt, 0);
  for (size_t i = 0; i < numElt; i++) {
    auto val = int(input->readULong(2));
    zone.m_numberingResetList[i] = val;
    if (val) f << "reset" << int(i) << "=" << val << ",";
  }
  rsrcAscii().addPos(pos-4);
  rsrcAscii().addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
// read mark zone ( ie the reference structure )
////////////////////////////////////////////////////////////
bool NisusWrtParser::readReference(NisusWrtStruct::RecursifData const &data)
{
  if (!data.m_info || data.m_info->m_zoneType >= 3) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readReference: find unexpected zoneType\n"));
    return false;
  }

  if (!data.m_childList.size())
    return true;

  if (data.m_childList.size() > 1) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readReference: level 0 node contains more than 1 node\n"));
  }
  if (data.m_childList[0].isLeaf()) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readReference: level 1 node is a leaf\n"));
    return false;
  }
  auto const &mainData = *data.m_childList[0].m_data;
  auto &zone = m_state->m_zones[static_cast<int>(data.m_info->m_zoneType)];
  MWAWInputStreamPtr input = rsrcInput();
  libmwaw::DebugStream f, f2;

  size_t n = 0;
  bool pbFound = false;
  size_t numData = mainData.m_childList.size();
  while (n < numData) {
    if (n+1 >= numData) {
      MWAW_DEBUG_MSG(("NisusWrtParser::readReference: find an odd number of data\n"));
      break;
    }

    // ----- First the position -----
    auto const &nd=mainData.m_childList[n++];
    if (nd.isLeaf() || nd.m_type != 0x7FFFFFFF) {
      if (!pbFound) {
        MWAW_DEBUG_MSG(("NisusWrtParser::readReference: oops find bad type for the filePos node\n"));
        pbFound = true;
      }
      continue;
    }
    NisusWrtParserInternal::Reference ref;
    auto const &dt=*nd.m_data;
    if (dt.m_childList.size() != 1 || !dt.m_childList[0].isLeaf()) {
      if (!pbFound) {
        MWAW_DEBUG_MSG(("NisusWrtParser::readReference: the filePos node contain unexpected data\n"));
        pbFound = true;
      }
      zone.m_referenceList.push_back(ref);
      continue;
    }
    MWAWEntry entry = dt.m_childList[0].m_entry;
    if (entry.length() < 8) {
      if (!pbFound) {
        MWAW_DEBUG_MSG(("NisusWrtParser::readReference: the filePos size seem bad\n"));
        pbFound = true;
      }
      zone.m_referenceList.push_back(ref);
      rsrcAscii().addPos(entry.begin()-12);
      rsrcAscii().addNote("###");
      continue;
    }

    // the file position in the text part
    long pos = entry.begin();
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    f.str("");
    f << "Position:";
    ref.m_textPosition.setBegin(long(input->readULong(4)));
    ref.m_textPosition.setEnd(long(input->readULong(4)));
    f << "filePos=" << std::hex
      << ref.m_textPosition.begin() << "<->" << ref.m_textPosition.end() << std::dec << ",";
    rsrcAscii().addPos(pos-12);
    rsrcAscii().addNote(f.str().c_str());

    // ----- Second the data node -----
    auto const &nd1=mainData.m_childList[n++];

    if (nd1.isLeaf() || nd1.m_type == 0x7FFFFFFF) {
      if (!pbFound) {
        MWAW_DEBUG_MSG(("NisusWrtParser::readReference: the date node contain unexpected data\n"));
        pbFound = true;
      }
      zone.m_referenceList.push_back(ref);
      n--;
      continue;
    }

    f.str("");
    switch (nd1.m_type) {
    case 1:
      break; // type=1:id, type=100:text
    case 2:
      f << "unknown,";
      break; // type=1:id, type=-4:?, type=220: ?, type=300: list of chain?
    default:
      f << "#type=" << nd1.m_type << ",";
      break;
    }
    if (f.str().length()) {
      rsrcAscii().addPos(nd1.m_entry.begin()-16);
      rsrcAscii().addNote(f.str().c_str());
    }
    long val;
    auto const &dt1=*nd1.m_data;
    for (auto const &dt1Child : dt1.m_childList) {
      if (!dt1Child.isLeaf()) {
        if (!pbFound) {
          MWAW_DEBUG_MSG(("NisusWrtParser::readReference: find some level 2 data array nodes\n"));
          pbFound = true;
        }
        continue;
      }
      NisusWrtStruct::RecursifData::Node const &childNode = dt1Child;
      entry = childNode.m_entry;
      pos = entry.begin();
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      f.str("");
      switch (childNode.m_type) {
      case 1:
        f << "II:";
        if (entry.length() < 4) {
          f << "###";
          break;
        }
        ref.m_id = static_cast<int>(input->readLong(4)); // some kind of id ?
        f << "id?=" << ref.m_id << ",";
        break;
      case 0x7ffffffc: // find one time with 0
        f << "III:";
        if (entry.length() < 4) {
          f << "###";
          break;
        }
        val = input->readLong(4);
        f << "unkn=" << val << ",";
        break;
      case 100: {
        f << "Text:";
        if (entry.length() < 1) {
          f << "###";
          break;
        }
        auto mSz = static_cast<int>(input->readULong(1));
        if (mSz+1 > entry.length()) {
          f << "###";
          break;
        }
        std::string mark("");
        for (int i = 0; i < mSz; i++)
          mark+=char(input->readULong(1));
        ref.m_text = mark;
        f << mark;
        break;
      }
      case 220: {
        if (entry.length()==0) break;
        if (entry.length()%12) {
          MWAW_DEBUG_MSG(("NisusWrtParser::readReference: unexpected length for type 220\n"));
          f << "###sz";
          break;
        }
        auto N=int(entry.length()/12);
        for (int j = 0; j < N ; j++) {
          long actPos = input->tell();
          f2.str("");
          f2 << "Reference2[" << j << "]:type=220,";
          val = long(input->readULong(4)); // always 0?
          if (val) f << "unkn=" << val << ",";
          // unknown, maybe some dim...
          f2 << "dim?=" << float(input->readLong(4))/65536.f << "<->";
          f2 << float(input->readLong(4))/65536.f << ",";
          rsrcAscii().addPos(actPos);
          rsrcAscii().addNote(f2.str().c_str());
          input->seek(actPos+12, librevenge::RVNG_SEEK_SET);
        }
        break;
      }
      case 300: // find with size 0x28
        break;
      default:
        f << "#type";
        break;
      }
      if (f.str().length()) {
        rsrcAscii().addPos(pos-12);
        rsrcAscii().addNote(f.str().c_str());
      }
    }
    zone.m_referenceList.push_back(ref);
  }
  return true;
}

////////////////////////////////////////////////////////////
// read SGP1 zone: a unknown zone (a type, an id/anchor type? and a bdbox )
////////////////////////////////////////////////////////////
bool NisusWrtParser::readSGP1(NisusWrtStruct::RecursifData const &data)
{
  if (!data.m_info || data.m_info->m_zoneType >= 3) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: find unexpected zoneType\n"));
    return false;
  }

  if (!data.m_childList.size())
    return true;

  if (data.m_childList.size() > 1) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: level 0 node contains more than 1 node\n"));
  }
  if (data.m_childList[0].isLeaf()) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: level 1 node is a leaf\n"));
    return false;
  }
  auto const &mainData = *data.m_childList[0].m_data;
  //  NisusWrtParserInternal::Zone &zone = m_state->m_zones[static_cast<int>(data.m_info->m_zoneType)];
  MWAWInputStreamPtr input = rsrcInput();
  libmwaw::DebugStream f;

  long val;
  for (auto const &mChild : mainData.m_childList) {
    if (mChild.isLeaf()) {
      MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: oops some level 2 node are leaf\n"));
      continue;
    }
    auto const &dt=*mChild.m_data;
    for (auto const &child : dt.m_childList) {
      if (!child.isLeaf()) {
        MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: find an odd level 3 leaf\n"));
        continue;
      }

      long pos = child.m_entry.begin();
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      f.str("");
      switch (child.m_type) {
      case 110: {
        if (child.m_entry.length()==0) break;
        if (child.m_entry.length()%4) {
          MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: unexpected length for type 110\n"));
          f << "###sz";
          break;
        }
        // find some increasing sequence here
        auto N=int(child.m_entry.length()/4);
        f << "unkn=[";
        for (int j = 0; j < N; j++) {
          val = input->readLong(4);
          f << val << ",";
        }
        f << "]";
        break;
      }
      case 120:
      case 200: { /* checkme: always find mSz=0 here */
        if (child.m_entry.length()==0) {
          MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: unexpected length for type %d\n", child.m_type));
          f << "###sz";
          break;
        }
        auto mSz = static_cast<int>(input->readULong(1));
        if (mSz+1 > child.m_entry.length()) {
          MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: the %d text seems to short\n", child.m_type));
          f << "###sz";
          break;
        }
        std::string text("");
        for (int c = 0; c < mSz; c++)
          text+=char(input->readULong(1));
        f << "text=" << text << ",";
        break;
      }
      case 100: {
        if (child.m_entry.length()!=0x24) {
          MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: unexpected length for type 100\n"));
          f << "###sz";
          break;
        }
        // find f1=1, f3=[0|1], f5=0x28, other always 0...
        for (int j = 0; j < 18; j++) {
          val = input->readLong(2);
          if (val) f << "f" << j << "=" << val << ",";
        }
        break;
      }
      case 300: {
        if (child.m_entry.length()!=0x5c) {
          MWAW_DEBUG_MSG(("NisusWrtParser::readSGP1: unexpected length for type 300\n"));
          f << "###sz";
          break;
        }
        val = long(input->readULong(2)); // always 0x8000 ?
        if (val != 0x8000) f << "f0=" << std::hex << val << std::dec << ",";
        for (int j = 0; j < 2; j++) { // find [0,-2]|0
          val = input->readLong(2);
          if (val) f << "f" << j+1 << "=" << val << ",";
        }
        val = long(input->readULong(2)); // find 0 and 1 time 0x7101
        if (val) f << "f3=" << std::hex << val << std::dec << ",";
        auto mSz = static_cast<int>(input->readULong(1));
        if (mSz >= 32) {
          f << "###textSz=" << mSz << ",";
          mSz = 0;
        }
        std::string text("");
        for (int j = 0; j < mSz; j++)
          text += char(input->readULong(1));
        if (text.length()) f << "\"" << text << "\",";
        // checkme: where does the data begins again
        input->seek(child.m_entry.begin()+40, librevenge::RVNG_SEEK_SET);
        for (int j = 0; j < 17; j++) { // find always 0 here
          val = input->readLong(2);
          if (val) f << "g" << j << "=" << val << ",";
        }
        val = long(input->readULong(2)); // 1|b
        f << "unkn=" << val << ",";
        for (int j = 0; j < 8; j++) { // find always 0 here
          val = input->readLong(2);
          if (val) f << "h" << j << "=" << val << ",";
        }
        break;
      }
      default:
        f << "type=" << child.m_type << ",";
        break;
      }
      rsrcAscii().addPos(pos-12);
      rsrcAscii().addNote(f.str().c_str());
    }
  }
  return true;
}

////////////////////////////////////////////////////////////
// read the print info
////////////////////////////////////////////////////////////
bool NisusWrtParser::readPrintInfo(MWAWEntry const &entry)
{
  if (!entry.valid() || entry.length() < 0x78) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readPrintInfo: the entry is bad\n"));
    return false;
  }
  if (entry.id() != 128) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readPrintInfo: the entry id %d is odd\n", entry.id()));
  }
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  // print info
  libmwaw::PrinterInfo info;
  if (!info.read(input)) return false;
  if (entry.id() != 128)
    f << "Entries(PrintInfo)[#" << entry.id() << "]:" << info;
  else
    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.);

  if (entry.length() != 0x78)
    f << "###size=" << entry.length() << ",";
  rsrcAscii().addPos(pos-4);
  rsrcAscii().addNote(f.str().c_str());
  input->seek(pos+0x78, librevenge::RVNG_SEEK_SET);
  if (long(input->tell()) != pos+0x78) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readPrintInfo: file is too short\n"));
    return false;
  }

  return true;
}

////////////////////////////////////////////////////////////
// read the PGLY zone ( page limit )
////////////////////////////////////////////////////////////
bool NisusWrtParser::readPageLimit(MWAWEntry const &entry)
{
  if (!entry.valid() || entry.length() < 0xa2) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readPageLimit: the entry is bad\n"));
    return false;
  }
  if (entry.id() != 128) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readPageLimit: the entry id %d is odd\n", entry.id()));
  }
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  if (entry.id() != 128)
    f << "Entries(Page)[#" << entry.id() << "]:";
  else
    f << "Entries(Page):";

  long val = input->readLong(2); // always 0x88 ?
  if (val != 0x88) f << "f0=" << val << ",";
  val = input->readLong(2); // always 0x901 or two flags ?
  if (val != 0x901) f << "f1=" << val << ",";
  val = input->readLong(2); // always 0x0
  if (val) f << "f2=" << val << ",";

  MWAWBox2i boxes[3];
  for (auto &box : boxes) {
    int dim[4];
    for (auto &d : dim) d = static_cast<int>(input->readLong(2));
    box = MWAWBox2i(MWAWVec2i(dim[1],dim[0]),MWAWVec2i(dim[3],dim[2]));
  }
  f << "page=" << boxes[0] << ",";
  f << "page[text]=" << boxes[1] << ",";
  f << "page[columns]=" << boxes[2] << ",";
  MWAWVec2i pageDim = boxes[0].size();
  MWAWVec2i LT=boxes[1][0]-boxes[0][0];
  MWAWVec2i RB=boxes[0][1]-boxes[1][1];
  bool dimOk=pageDim[0] > 0 && pageDim[1] > 0 &&
             LT[0] >= 0 && LT[1] >= 0 && RB[0] >= 0 && RB[1] >= 0;
  if (!dimOk && m_state->m_isTextDocument) {
    // can be ok in a glossary: in this case, all values can be 0
    MWAW_DEBUG_MSG(("NisusWrtParser::readPageLimit: the page margins seems odd\n"));
    f << "###dim,";
  }
  // all zero expected some time g0=2
  for (int i = 0; i < 3; i++) {
    val = input->readLong(2);
    if (val) f << "g" << i << "=" << val << ",";
  }
  int numColumns = static_cast<int>(input->readLong(2))+1;
  auto colSeps = static_cast<int>(input->readLong(2));
  if (!dimOk) ;
  else if (numColumns <= 0 || numColumns > 8 || colSeps < 0 || colSeps *numColumns >= pageDim[0]) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readPageLimit: the columns definition seems odd\n"));
    f << "###";
  }
  else {
    m_state->m_numColumns = numColumns;
    m_state->m_columnSep = float(colSeps)/72.f;
  }
  if (numColumns != 1) f << "nCols=" << numColumns << ",";
  if (colSeps != 36) f << "colSeps=" << colSeps << "pt,";
  static int const expectedValues[]= { 0, 3, 1};
  // find also, g3=0x10, g4=g5=0 (in v3 file)
  for (int i = 0; i < 3; i++) {
    val = input->readLong(2);
    if (int(val) != expectedValues[i])
      f << "g" << i+3 << "=" << val << ",";
  }
  // find only 0 expected h2=[0|1], h6=[0|-1]
  for (int i = 0; i < 7; i++) {
    val = input->readLong(2);
    if (val)
      f << "h" << i << "=" << val << ",";
  }
  // a series of flags 0 or 1, find frequently fl0=1, and sometimes fl4=fl5=1
  for (int i = 0; i < 8; i++) {
    val = input->readLong(1);
    if (val)
      f << "fl" << i << "=" << val << ",";
  }
  // always 0
  for (int i = 0; i < 3; i++) {
    val = input->readLong(2);
    if (val)
      f << "l" << i << "=" << val << ",";
  }
  rsrcAscii().addPos(pos-4);
  rsrcAscii().addNote(f.str().c_str());

  pos = input->tell();
  f.str("");
  f << "Page[A]:";
  // find 0,3ea0f83e|3ea19dbd|3ea2433b|3ec00000|3ec0a57f
  val = long(input->readULong(4));
  if (val) f << "f0=" << std::hex << val << std::dec << ",";
  // find f3=[0|1|-1]
  for (int i = 1; i < 4; i++) {
    val = input->readLong(2);
    if (val)
      f << "f" << i << "=" << val << ",";
  }
  // 2 times 0x48 ?
  for (int i = 4; i < 6; i++) {
    val = input->readLong(2);
    if (val != 0x48)
      f << "f" << i << "=" << val << ",";
  }
  int dim[4];
  for (auto &d : dim) d = static_cast<int>(input->readLong(2));
  MWAWBox2i realPage = MWAWBox2i(MWAWVec2i(dim[1],dim[0]), MWAWVec2i(dim[3],dim[2]));
  if (dimOk && realPage.size()[0] >= pageDim[0] && realPage.size()[1] >= pageDim[1]) {
    LT -= realPage[0];
    RB += (realPage[1]-boxes[0][1]);
    pageDim = realPage.size();
  }
  else if (dimOk) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readPageLimit: realPage is smaller than page\n"));
    f << "###";
    dimOk = false;
  }
  f << "page[complete]=" << realPage << ",";
  val = input->readLong(1);
  bool portrait = true;
  switch (val) {
  case 0:
    break;
  case 1:
    portrait = false;
    f << "landscape,";
    break;
  default:
    f << "#pageOrientation=" << val << ",";
    break;
  }
  for (int i = 0; i < 5; i++) {
    val = input->readLong(1);
    if (val)
      f << "fl" << i << "=" << val << ",";
  }
  // a size ? often 114,16d
  int sz[2];
  for (auto &s : sz) s=static_cast<int>(input->readLong(2));
  if (sz[0]||sz[1])
    f << "sz=" << sz[1] << "x" << sz[0] << ",";
  // v3: 0,0 or v4: 0x29 0x3 ?
  for (int i = 0; i < 2; i++) {
    val = input->readLong(2);
    if (val)
      f << "g" << i << "=" << val << ",";
  }
  // another sz ? ~153,~1fc
  for (int &i : sz)
    i=static_cast<int>(input->readLong(2));
  if (sz[0]||sz[1])
    f << "sz2=" << sz[1] << "x" << sz[0] << ",";
  // 0|-1, find also g4=0xa
  for (int i = 2; i < 8; i++) {
    val = input->readLong(2);
    if (val)
      f << "g" << i << "=" << val << ",";
  }
  // 0 expect h0=[0|1|22], h1=[0|1|22], h0=h1 ( almost always ? )
  for (int i = 0; i < 7; i++) {
    val = input->readLong(2);
    if (val)
      f << "h" << i << "=" << val << ",";
  }
  // a dim
  for (auto &s : sz) s=static_cast<int>(input->readLong(2));
  if (sz[0]||sz[1])
    f << "dim=" << sz[1] << "x" << sz[0] << ",";
  // all 0 expected k1=[0|1], k2=[0|1] k4=1
  for (int i = 0; i < 9; i++) {
    val = input->readLong(2);
    if (val)
      f << "k" << i << "=" << val << ",";
  }
  if (entry.length()!=0xa2)
    f << "###size=" << entry.length() << ",";
  rsrcAscii().addPos(pos);
  rsrcAscii().addNote(f.str().c_str());

  if (!dimOk) return true;
  getPageSpan().setMarginTop(double(LT[1])/72.0);
  getPageSpan().setMarginBottom(double(RB[1])/72.0);
  getPageSpan().setMarginLeft(double(LT[0])/72.0);
  getPageSpan().setMarginRight(double(RB[0])/72.0);
  getPageSpan().setFormLength(double(pageDim[1])/72.);
  getPageSpan().setFormWidth(double(pageDim[0])/72.);
  if (!portrait)
    getPageSpan().setFormOrientation(MWAWPageSpan::LANDSCAPE);

  return true;
}

////////////////////////////////////////////////////////////
// read the CPRC zone ( unknown )
////////////////////////////////////////////////////////////
bool NisusWrtParser::readCPRC(MWAWEntry const &entry)
{
  if (!entry.valid() || entry.length() < 0xe) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readCPRC: the entry is bad\n"));
    return false;
  }
  if (entry.id() != 128) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readCPRC: the entry id %d is odd\n", entry.id()));
  }
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  if (entry.id() != 128)
    f << "Entries(CPRC)[#" << entry.id() << "]:";
  else
    f << "Entries(CPRC):";

  /** find only 0...
      except one time f0=1[id?], f4=1900 f6=206c [2pos?]
   */
  for (int i = 0; i < int(entry.length())/2; i++) {
    auto val = static_cast<int>(input->readULong(2));
    if (val) f << "#f" << i << "=" << std::hex << val << std::dec << ",";
  }

  if (entry.length()!=0xe)
    f << "###size=" << entry.length() << ",";
  rsrcAscii().addPos(pos-4);
  rsrcAscii().addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
// read the CNTR zone ( a list of related to VRS ?  )
////////////////////////////////////////////////////////////
bool NisusWrtParser::readCNTR(MWAWEntry const &entry, int zoneId)
{
  if (!entry.valid() || entry.length()<20 || (entry.length()%12) != 8) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readCNTR: the entry is bad\n"));
    return false;
  }
  if (zoneId < 0 || zoneId >= 3) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readCNTR: find unexpected zoneId: %d\n", zoneId));
    return false;
  }
  //  NisusWrtParserInternal::Zone &zone = m_state->m_zones[zoneId];
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  int numElt = int(entry.length()/12)-1;
  libmwaw::DebugStream f;
  f << "Entries(VariabCntr)[" << zoneId << "]:N=" << numElt;
  rsrcAscii().addPos(pos-4);
  rsrcAscii().addNote(f.str().c_str());
  for (int i = 0; i < numElt; i++) {
    // unkn[16] * 3, 0[16], refId, 0[16],  unkn[16]
    pos = input->tell();
    f.str("");
    f << "VariabCntr" << i << ":";
    rsrcAscii().addPos(pos);
    rsrcAscii().addNote(f.str().c_str());
    input->seek(pos+12, librevenge::RVNG_SEEK_SET);
  }

  f.str("");
  f << "VariabCntr(II)";
  rsrcAscii().addPos(input->tell());
  rsrcAscii().addNote(f.str().c_str());

  return true;
}

////////////////////////////////////////////////////////////
//! read the ABBR resource: a list of abreviation ?
////////////////////////////////////////////////////////////
bool NisusWrtParser::readABBR(MWAWEntry const &entry)
{
  if (!entry.valid() || entry.length()%32) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readABBR: the entry is bad\n"));
    return false;
  }
  if (entry.id() != 1003) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readABBR: the entry id %d is odd\n", entry.id()));
  }
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  auto numElt = int(entry.length()/32);
  for (int n = 0; n < numElt; n++) {
    pos = input->tell();
    f.str("");
    if (n==0) {
      if (entry.id() != 1003)
        f << "Entries(ABBR)[#" << entry.id() << "]";
      else
        f << "Entries(ABBR)";
    }
    else
      f << "ABBR";
    f << "[" << n << "]:";
    long id = input->readLong(4);
    if (id != long(n))
      f << "#id=" << id << ",";
    auto sSz = int(input->readULong(1));
    if (sSz > 27) {
      MWAW_DEBUG_MSG(("NisusWrtParser::readABBR: the string size is bad\n"));
      f << "##";
    }
    else {
      std::string str("");
      for (int i = 0; i < sSz; i++)
        str += char(input->readULong(1));
      f << "\"" << str << "\",";
    }
    rsrcAscii().addPos(n==0 ? pos-4 : pos);
    rsrcAscii().addNote(f.str().c_str());
    input->seek(pos+32, librevenge::RVNG_SEEK_SET);
  }

  return true;
}

////////////////////////////////////////////////////////////
//! read the FTA2 resource: a list of  ?
////////////////////////////////////////////////////////////
bool NisusWrtParser::readFTA2(MWAWEntry const &entry)
{
  if (!entry.valid() || entry.length()%12) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readFTA2: the entry is bad\n"));
    return false;
  }
  if (entry.id() != 1003) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readFTA2: the entry id %d is odd\n", entry.id()));
  }
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  auto numElt = int(entry.length()/12);
  for (int n = 0; n < numElt; n++) {
    pos = input->tell();
    f.str("");
    if (n==0) {
      if (entry.id() != 1003)
        f << "Entries(FTA2)[#" << entry.id() << "]";
      else
        f << "Entries(FTA2)";
    }
    else
      f << "FTA2";
    f << "[" << n << "]:";
    auto val = int(input->readLong(1)); // 0|ff
    if (val==-1) f << "f0,";
    else if (val) f << "f0=" << val << ",";
    val = int(input->readLong(1)); // 0|6|7|f ( maybe f1=0 if f0=ff )
    if (val)
      f << "f1=" << std::hex << val << std::dec << ",";
    // always 0 excepted f3=0|ff9f|ffc1
    for (int i = 0; i < 5; i++) {
      val = int(input->readLong(2));
      if (val) f << "f" << i+2 << "=" << val << ",";
    }
    rsrcAscii().addPos(n==0 ? pos-4 : pos);
    rsrcAscii().addNote(f.str().c_str());
    input->seek(pos+12, librevenge::RVNG_SEEK_SET);
  }

  return true;
}

////////////////////////////////////////////////////////////
//! read the FnSc resource: a list of  ?
////////////////////////////////////////////////////////////
bool NisusWrtParser::readFnSc(MWAWEntry const &entry)
{
  if (!entry.valid() || entry.length() != 0x42) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readFnSc: the entry is bad\n"));
    return false;
  }
  if (entry.id() != 1003) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readFnSc: the entry id %d is odd\n", entry.id()));
  }
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  if (entry.id() != 1003)
    f << "Entries(FnSc)[#" << entry.id() << "]:";
  else
    f << "Entries(FnSc):";
  long val;
  val = input->readLong(2); // find 0|3|10|14
  if (val) f << "f0=" << val << ",";
  val = long(input->readULong(2)); // find 0|4000|4034
  if (val) f << "f1=" << std::hex << val << std::dec << ",";
  for (int i = 0; i < 31; i++) { // always 0?
    val = input->readLong(2);
    if (val) f << "f" << i+2 << "=" << val << ",";
  }
  rsrcAscii().addPos(pos-4);
  rsrcAscii().addNote(f.str().c_str());

  return true;
}

////////////////////////////////////////////////////////////
//! read the INFO resource: a unknown zone
////////////////////////////////////////////////////////////
bool NisusWrtParser::readINFO(MWAWEntry const &entry)
{
  if (!entry.valid() || entry.length() < 0x23a) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readINFO: the entry is bad\n"));
    return false;
  }
  if (entry.id() != 1003) {
    MWAW_DEBUG_MSG(("NisusWrtParser::readINFO: the entry id %d is odd\n", entry.id()));
  }
  entry.setParsed(true);
  MWAWInputStreamPtr input = rsrcInput();
  long pos = entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  if (entry.id() != 1003)
    f << "Entries(INFO)[#" << entry.id() << "]:";
  else
    f << "Entries(INFO):";
  long val = input->readLong(2);
  if (val != 1) f << "f0=" << val << ",";
  int select[2]; // checkme
  for (auto &sel : select) sel = static_cast<int>(input->readLong(4));
  if (select[0] || select[1]) {
    f << "select=" << select[0];
    if (select[1] != select[0]) f << "x" << select[1];
    f << ",";
  }
  val =  input->readLong(2);
  if (val) f << "f1=" << val << ",";
  // a dim or 2 position ?
  int dim[4];
  for (auto &d : dim) d = static_cast<int>(input->readLong(2));
  if (dim[0] || dim[1] || dim[2] || dim[3])
    f << "dim|pos?=" << dim[0] << "x" << dim[1]
      << "<->" << dim[2] << "x" << dim[3] << ",";
  int sz[2];
  for (auto &s : sz) s = static_cast<int>(input->readLong(2));
  if (sz[0] || sz[1])
    f << "sz=" << sz[0] << "x" << sz[1] << ",";
  for (int i = 0; i < 2; i++) { // always 1|1
    val = input->readLong(1);
    if (val) f << "fl" << i << "=" << val << ",";
  }
  for (int i = 0; i < 8; i++) { // always 0
    val = input->readLong(2);
    if (val) f << "g" << i << "=" << val << ",";
  }
  rsrcAscii().addPos(pos-4);
  rsrcAscii().addNote(f.str().c_str());

  pos = input->tell();
  f.str("");
  f << "INFOA0:";
  for (int i = 0; i < 4; i++) { // find 0-0-0-0 or 1-1-1-1 or 1-1-1-4
    val = input->readLong(2);
    if (val) f << "f" << i << "=" << val << ",";
  }
  // 4 times the same val: 0, aa55, ffff
  auto unkn = static_cast<int>(input->readULong(2));
  if (unkn) f << "unknA0=" << std::hex << unkn << std::dec << ",";
  for (int i = 1; i < 4; i++) {
    val = long(input->readULong(2));
    if (val != unkn)
      f << "unknA" << i << "=" << std::hex << val << std::dec << ",";
  }
  // 4 times the same val: 0, ffff
  unkn = static_cast<int>(input->readULong(2));
  if (unkn) f << "unknB0=" << std::hex << unkn << std::dec << ",";
  for (int i = 1; i < 4; i++) {
    val = long(input->readULong(2));
    if (val != unkn)
      f << "unknB" << i << "=" << std::hex << val << std::dec << ",";
  }
  rsrcAscii().addDelimiter(input->tell(),'|');
  rsrcAscii().addPos(pos);
  rsrcAscii().addNote(f.str().c_str());

  input->seek(pos+0x2c, librevenge::RVNG_SEEK_SET);
  pos = input->tell();
  f.str("");
  f << "INFOB:";
  rsrcAscii().addPos(pos);
  rsrcAscii().addNote(f.str().c_str());

  input->seek(pos+0x112, librevenge::RVNG_SEEK_SET);
  pos = input->tell();
  f.str("");
  f << "INFOA1:";
  rsrcAscii().addPos(pos);
  rsrcAscii().addNote(f.str().c_str());

  input->seek(entry.begin() + 0x194, librevenge::RVNG_SEEK_SET);
  pos = input->tell();
  f.str("");
  f << "INFOC:";

  auto &ftInfo = m_state->m_footnoteInfo;
  ftInfo.m_flags = static_cast<int>(input->readULong(2));
  ftInfo.m_unknown = static_cast<int>(input->readLong(2));
  ftInfo.m_distToDocument = static_cast<int>(input->readLong(2));
  ftInfo.m_distSeparator = static_cast<int>(input->readLong(2));
  ftInfo.m_separatorLength = static_cast<int>(input->readLong(2));
  f << "footnote=[" << ftInfo << "],";
  rsrcAscii().addDelimiter(input->tell(), '|');
  rsrcAscii().addPos(pos);
  rsrcAscii().addNote(f.str().c_str());

  return true;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: