Blob Blame History Raw
/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */

/* libmwaw
* Version: MPL 2.0 / LGPLv2+
*
* The contents of this file are subject to the Mozilla Public License Version
* 2.0 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2002 William Lachance (wrlach@gmail.com)
* Copyright (C) 2002,2004 Marc Maurer (uwog@uwog.net)
* Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch)
* Copyright (C) 2006, 2007 Andrew Ziem
* Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr)
*
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
* in which case the provisions of the LGPLv2+ are applicable
* instead of those above.
*/

#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <sstream>

#include <librevenge/librevenge.h>

#include "MWAWDebug.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWListener.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWParser.hxx"
#include "MWAWSubDocument.hxx"

#include "MsWksDocument.hxx"

#include "MsWks3Text.hxx"

/** Internal: the structures of a MsWks3Text */
namespace MsWks3TextInternal
{
////////////////////////////////////////
//! Internal: header zone
struct LineZone {
  //! the constructor
  LineZone()
    : m_type(-1)
    , m_pos()
    , m_id(0)
    , m_flags()
    , m_height(0)
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, LineZone const &z)
  {
    switch (z.m_type>>5) {
    case 0:
    case 1:
    case 2:
    case 3:
      o << "Text,";
      break;
    case 4:
      o << "Tabs,";
      break;
    default:
      o << "##type=" << std::hex << (z.m_type >> 5) << std::dec << ",";
    }
    if (z.m_type&0x8) o << "note,";
    if (z.m_type&0x7) o << "#type=" << std::hex << (z.m_type&0x7) << std::dec << ",";
    if (z.m_id) o  << "id=" << z.m_id << ",";
    if (z.m_flags&1) o << "softBreak,";
    if (z.m_flags&2) o << "hardBreak,";
    if (!(z.m_flags&4)) o << "beginDoc,";
    if (z.m_flags&8) o << "graphics,";
    if (z.m_flags&0x20) o << "noteFl,";
    if (z.m_flags&0x40) o << "fields,";
    if (z.m_flags&0x90) o << "#flags=" << std::hex << (z.m_flags&0x90) << std::dec << ",";
    if (z.m_height) o << "h=" << z.m_height << ",";
    return o;
  }
  //! return true if this is a note
  bool isNote() const
  {
    return m_type&0x8;
  }
  //! return true if this is a tabs
  bool isRuler() const
  {
    return (m_type&0xE0)==0x80;
  }
  //! the type
  int m_type;
  //! the file position
  MWAWEntry m_pos;
  //! the id
  int m_id;
  //! the zone flags
  int m_flags;
  //! the height
  int m_height;
};

////////////////////////////////////////
//! Internal: the fonts
struct Font {
  //! the constructor
  Font(): m_font(), m_extra("")
  {
    for (int &flag : m_flags) flag = 0;
  }

  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Font const &font)
  {
    for (int i = 0; i < 3; i++) {
      if (!font.m_flags[i]) continue;
      o << "ft" << i << "=";
      if (i == 0) o << std::hex;
      o << font.m_flags[i] << std::dec << ",";
    }
    if (font.m_extra.length())
      o << font.m_extra << ",";
    return o;
  }

  //! the font
  MWAWFont m_font;
  //! some unknown flag
  int m_flags[3];
  //! extra data
  std::string m_extra;
};

////////////////////////////////////////
//! Internal: the text zone
struct TextZone {
  enum Type { Header, Footer, Main, Unknown };
  //! constructor
  TextZone()
    : m_type(Unknown)
    , m_id(0)
    , m_zonesList()
    , m_linesHeight()
    , m_pagesHeight()
    , m_pagesPosition()
    , m_footnoteMap()
    , m_text("")
    , m_isSent(false)
  {
  }

  //! return true if this is the main zone
  bool isMain() const
  {
    return m_type == Main;
  }
  //! the zone type;
  int m_type;
  //! the zone id
  int m_id;
  //! the list of zones
  std::vector<LineZone> m_zonesList;
  //! the line height
  std::vector<int> m_linesHeight;
  //! the pages height
  std::vector<int> m_pagesHeight;
  //! the zone id -> hard break
  std::map<int, bool> m_pagesPosition;
  //! the note id -> zone limit
  std::map<int, MWAWVec2i> m_footnoteMap;
  //! a string used to store v1-2 files header/footer
  std::string m_text;
  //! flag to know if the zone is send or not
  bool m_isSent;
};

////////////////////////////////////////
//! Internal: the state of a MsWks3Text
struct State {
  //! constructor
  State()
    : m_version(-1)
    , m_zones()
    , m_numPages(1)
    , m_actualPage(1)
  {
  }
  //! the file version
  mutable int m_version;
  //! the main zone
  std::vector<TextZone> m_zones;

  int m_numPages /* the number of pages */, m_actualPage /* the actual page */;
};

////////////////////////////////////////
//! Internal: the subdocument of a MsWks3Text
class SubDocument final : public MWAWSubDocument
{
public:
  enum Type { Zone, Text };
  SubDocument(MsWks3Text &pars, MWAWInputStreamPtr const &input, int zoneId, int noteId)
    : MWAWSubDocument(pars.m_mainParser, input, MWAWEntry())
    , m_textParser(&pars)
    , m_id(zoneId)
    , m_noteId(noteId)
  {
  }

  //! destructor
  ~SubDocument() final {}

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

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

protected:
  /** the text parser */
  MsWks3Text *m_textParser;
  /** the subdocument id*/
  int m_id;
  /** the note id */
  int m_noteId;
private:
  SubDocument(SubDocument const &orig) = delete;
  SubDocument operator=(SubDocument const &orig) = delete;
};

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

  long pos = m_input->tell();
  m_textParser->sendNote(m_id, m_noteId);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}

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

}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
MsWks3Text::MsWks3Text(MsWksDocument &document)
  : m_parserState()
  , m_state(new MsWks3TextInternal::State)
  , m_mainParser(&document.getMainParser())
  , m_document(document)
{
  m_parserState=m_mainParser->getParserState();
}

MsWks3Text::~MsWks3Text()
{
}

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

int MsWks3Text::numPages(int zoneId) const
{
  if (zoneId < 0 || zoneId >= int(m_state->m_zones.size())) {
    MWAW_DEBUG_MSG(("MsWks3Text::numPages: unknown zone %d\n", zoneId));
    return 0;
  }
  auto const &zone = m_state->m_zones[size_t(zoneId)];
  int nPages = 1 + int(zone.m_pagesPosition.size());
  if (zone.isMain()) {
    m_state->m_actualPage = 1;
    m_state->m_numPages = nPages;
  }
  else {
    MWAW_DEBUG_MSG(("MsWks3Text::numPages: called on no main zone: %d\n", zoneId));
  }
  return nPages;
}

bool MsWks3Text::getLinesPagesHeight
(int zoneId, std::vector<int> &lines, std::vector<int> &pages)
{
  lines.resize(0);
  pages.resize(0);
  if (zoneId < 0 || zoneId >= int(m_state->m_zones.size())) {
    MWAW_DEBUG_MSG(("MsWks3Text::getLinesPagesHeight: unknown zone %d\n", zoneId));
    return false;
  }
  lines = m_state->m_zones[size_t(zoneId)].m_linesHeight;
  pages = m_state->m_zones[size_t(zoneId)].m_pagesHeight;
  return true;
}

////////////////////////////////////////////////////////////
// Intermediate level
////////////////////////////////////////////////////////////
int MsWks3Text::getHeader() const
{
  for (size_t i = 0; i < m_state->m_zones.size(); i++)
    if (m_state->m_zones[i].m_type == MsWks3TextInternal::TextZone::Header)
      return int(i);
  return -1;
}

int MsWks3Text::getFooter() const
{
  for (size_t i = 0; i < m_state->m_zones.size(); i++)
    if (m_state->m_zones[i].m_type == MsWks3TextInternal::TextZone::Footer)
      return int(i);
  return -1;
}

////////////////////////////////////////////////////////////
// try to find the different zone
////////////////////////////////////////////////////////////
int MsWks3Text::createZones(int numLines, bool mainZone)
{
  MsWks3TextInternal::LineZone zone;
  auto zoneId = int(m_state->m_zones.size());
  m_state->m_zones.push_back(MsWks3TextInternal::TextZone());
  auto &actualZone = m_state->m_zones.back();
  actualZone.m_id = zoneId;
  if (mainZone)
    actualZone.m_type = MsWks3TextInternal::TextZone::Main;
  bool hasNote=false;
  int firstNote=0;
  MWAWInputStreamPtr input=m_document.getInput();
  while (!input->isEnd()) {
    if (numLines==0) break;
    if (numLines>0) numLines--;
    long pos = input->tell();
    if (!readZoneHeader(zone)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    if (!hasNote && zone.isNote()) {
      firstNote = int(actualZone.m_zonesList.size());
      hasNote = true;
    }
    actualZone.m_zonesList.push_back(zone);
    input->seek(zone.m_pos.end(), librevenge::RVNG_SEEK_SET);
  }
  auto numLineZones = int(actualZone.m_zonesList.size());
  if (numLineZones == 0) {
    m_state->m_zones.pop_back();
    return -1;
  }

  update(actualZone);
  if (hasNote)
    updateNotes(actualZone, firstNote);
  return zoneId;
}

void MsWks3Text::update(MsWks3TextInternal::TextZone &zone)
{
  size_t numLineZones = zone.m_zonesList.size();
  if (numLineZones == 0) return;

  auto textHeight = int(72.0*m_mainParser->getPageSpan().getPageLength());

  int actH = 0, actualPH = 0;
  zone.m_linesHeight.push_back(0);
  for (size_t i = 0; i < numLineZones; i++) {
    auto &z = zone.m_zonesList[i];
    if (z.isNote()) continue; // a note
    actH += z.m_height;
    zone.m_linesHeight.push_back(actH);
    bool newPage = ((z.m_flags&1) && actualPH) || (z.m_flags&2);
    actualPH += z.m_height;
    if (newPage || (actualPH > textHeight && textHeight > 0)) {
      zone.m_pagesPosition[int(i)]=(z.m_flags&2);
      zone.m_pagesHeight.push_back(actualPH-z.m_height);
      actualPH=z.m_height;
    }
  }
}

void MsWks3Text::updateNotes(MsWks3TextInternal::TextZone &zone, int firstNote)
{
  auto numLineZones = int(zone.m_zonesList.size());
  if (firstNote < 0 || firstNote >= numLineZones) {
    MWAW_DEBUG_MSG(("MsWks3Text::updateNotes: can not find first note position\n"));
    return;
  }

  MWAWInputStreamPtr input=m_document.getInput();
  MsWks3TextInternal::Font font;
  int noteId = -1;
  long lastIndentPos = -1;
  MWAWVec2i notePos;

  for (int n = firstNote; n < numLineZones; n++) {
    auto const &z = zone.m_zonesList[size_t(n)];
    if (!z.isNote()) {
      noteId=-1;
      MWAW_DEBUG_MSG(("MsWks3Text::updateNotes: find extra data in notes, stop recording note\n"));
      break;
    }
    if (z.isRuler()) {
      lastIndentPos = n;
      continue;
    }
    if (z.m_pos.length() < 8) continue;
    long actPos = z.m_pos.begin();
    input->seek(actPos+6,librevenge::RVNG_SEEK_SET);

    auto c = static_cast<int>(input->readULong(1));
    if ((c == 1 || c == 2) && readFont(font, z.m_pos.end())) {
      if (long(input->tell())+2 > z.m_pos.end())
        continue;
      c = static_cast<int>(input->readULong(1));
      if (c <= 4) {
        if (long(input->tell())+2 > z.m_pos.end())
          continue;
        c = static_cast<int>(input->readULong(1));
      }
    }
    if (c != 0x14) continue;
    if (noteId >= 0) {
      notePos[1] = (lastIndentPos != -1) ? int(lastIndentPos) : n;
      if (zone.m_footnoteMap.find(noteId)==zone.m_footnoteMap.end())
        zone.m_footnoteMap[noteId] = notePos;
      else {
        MWAW_DEBUG_MSG(("MsWks3Text::updateNotes: note %d is already defined, ignored\n", noteId));
      }
    }
    noteId = static_cast<int>(input->readULong(2));
    notePos[0] = (lastIndentPos != -1) ? int(lastIndentPos) : n;
    lastIndentPos = -1;
  }
  if (noteId >= 0) {
    notePos[1] = numLineZones;
    if (zone.m_footnoteMap.find(noteId)==zone.m_footnoteMap.end())
      zone.m_footnoteMap[noteId] = notePos;
    else {
      MWAW_DEBUG_MSG(("MsWks3Text::updateNotes: note %d is already defined, ignored\n", noteId));
    }
  }
}

bool MsWks3Text::readZoneHeader(MsWks3TextInternal::LineZone &zone) const
{
  zone = MsWks3TextInternal::LineZone();
  MWAWInputStreamPtr input=m_document.getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+6)) return false;
  zone.m_pos.setBegin(pos);
  zone.m_type = static_cast<int>(input->readULong(1));
  if (zone.m_type & 0x17) return false;
  zone.m_id = static_cast<int>(input->readULong(1));
  zone.m_flags = static_cast<int>(input->readULong(1));
  zone.m_height = static_cast<int>(input->readULong(1));
  zone.m_pos.setLength(6+long(input->readULong(2)));
  if (!input->checkPosition(zone.m_pos.end())) return false;
  return true;
}

////////////////////////////////////////////////////////////
// the text:
////////////////////////////////////////////////////////////
bool MsWks3Text::sendText(MsWks3TextInternal::LineZone &zone, int zoneId)
{
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("MsWks3Text::sendText: can not find the listener\n"));
    return true;
  }
  MWAWInputStreamPtr input=m_document.getInput();
  input->seek(zone.m_pos.begin()+6, librevenge::RVNG_SEEK_SET);
  int vers = version();
  libmwaw::DebugFile &ascFile = m_document.ascii();
  libmwaw::DebugStream f;
  f << "Entries(TextZone):" << zone << ",";
  MsWks3TextInternal::Font font;
  if (listener && zone.m_height > 0) {
    MWAWParagraph para=listener->getParagraph();
    para.setInterline(zone.m_height, librevenge::RVNG_POINT);
    listener->setParagraph(para);
  }
  while (!input->isEnd()) {
    long pos = input->tell();
    if (pos >= zone.m_pos.end()) break;
    auto c = static_cast<int>(input->readULong(1));
    if ((c == 1 || c == 2) && readFont(font, zone.m_pos.end())) {
      listener->setFont(font.m_font);
      f << "[" << font.m_font.getDebugString(m_parserState->m_fontConverter) << font << "]";
      continue;
    }
    if (c == 0) {
      f << "#";
      continue;
    }
    f << char(c);
    if (!listener)
      continue;
    switch (c) {
    case 0x9:
      listener->insertTab();
      break;
    case 0x10: // cursor pos
    case 0x11:
      break;
    default:
      if (c >= 0x14 && c <= 0x19 && vers >= 3) {
        int sz = (c==0x19) ? 0 : (c == 0x18) ? 1 : 2;
        int id = (sz && pos+1+sz <=  zone.m_pos.end()) ? int(input->readLong(sz)) : 0;
        if (id) f << "[" << id << "]";
        switch (c) {
        case 0x19:
          listener->insertField(MWAWField(MWAWField::Title));
          break;
        case 0x18:
          listener->insertField(MWAWField(MWAWField::PageNumber));
          break;
        case 0x16:
          listener->insertField(MWAWField(MWAWField::Time));
          break;
        case 0x17: // id = 0 : short date ; id=9 : long date
          listener->insertField(MWAWField(MWAWField::Date));
          break;
        case 0x15:
          MWAW_DEBUG_MSG(("MsWks3Text::sendText: find unknown field type 0x15\n"));
          break;
        case 0x14:
          if (!zone.isNote()) {
            MWAWSubDocumentPtr subdoc
            (new MsWks3TextInternal::SubDocument(*this, m_document.getInput(), zoneId, id));
            listener->insertNote(MWAWNote(MWAWNote::FootNote), subdoc);
          }
          break;
        default:
          break;
        }
      }
      else if (c <= 0x1f) {
        f << "#" << std::hex << c << std::dec << "]";
        ascFile.addDelimiter(pos,'#');
        MWAW_DEBUG_MSG(("MsWks3Text::sendText: find char=%x\n", static_cast<unsigned int>(c)));
      }
      else
        listener->insertCharacter(static_cast<unsigned char>(c), input, zone.m_pos.end());
      break;
    }
  }
  if (listener)
    listener->insertEOL();
  ascFile.addPos(zone.m_pos.begin());
  ascFile.addNote(f.str().c_str());
  return true;
}

bool MsWks3Text::sendString(std::string &str)
{
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener)
    return true;
  MsWks3TextInternal::Font defFont;
  defFont.m_font = MWAWFont(20,12);
  listener->setFont(defFont.m_font);

  for (auto c : str) {
    switch (c) {
    case 0x9:
      listener->insertTab();
      break;
    case 0x10: // cursor pos
    case 0x11:
      break;
    case 0x19:
      listener->insertField(MWAWField(MWAWField::Title));
      break;
    case 0x18:
      listener->insertField(MWAWField(MWAWField::PageNumber));
      break;
    case 0x16:
      listener->insertField(MWAWField(MWAWField::Time));
      break;
    case 0x17: // id = 0 : short date ; id=9 : long date
      listener->insertField(MWAWField(MWAWField::Date));
      break;
    case 0x15:
      MWAW_DEBUG_MSG(("MsWks3Text::sendString: find unknown field type 0x15\n"));
      break;
    case 0x14: // fixme
      MWAW_DEBUG_MSG(("MsWks3Text::sendString: footnote are not implemented\n"));
      break;
    default:
      listener->insertCharacter(static_cast<unsigned char>(c));
      break;
    }
  }
  return true;
}

////////////////////////////////////////////////////////////
// the font:
////////////////////////////////////////////////////////////
bool MsWks3Text::readFont(MsWks3TextInternal::Font &font, long endPos)
{
  int vers = version();
  font = MsWks3TextInternal::Font();
  MWAWInputStreamPtr input=m_document.getInput();
  long pos  = input->tell();
  input->seek(-1, librevenge::RVNG_SEEK_CUR);
  auto type = static_cast<int>(input->readLong(1));
  if ((type != 1 && type != 2) || pos+type+3 > endPos) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  libmwaw::DebugStream f;
  auto flag = static_cast<int>(input->readULong(1)); // check or font ?
  if (flag) f << "#f0=" << flag << ",";
  font.m_font.setId(static_cast<int>(input->readULong(1)));
  font.m_font.setSize(float(input->readULong(1)));
  flag = static_cast<int>(input->readULong(1));
  uint32_t flags = 0;
  if (flag & 0x1) flags |= MWAWFont::boldBit;
  if (flag & 0x2) flags |= MWAWFont::italicBit;
  if (flag & 0x4) font.m_font.setUnderlineStyle(MWAWFont::Line::Simple);
  if (flag & 0x8) flags |= MWAWFont::embossBit;
  if (flag & 0x10) flags |= MWAWFont::shadowBit;
  if (flag & 0x20) {
    if (vers==1)
      font.m_font.set(MWAWFont::Script(20,librevenge::RVNG_PERCENT,80));
    else
      font.m_font.set(MWAWFont::Script::super100());
  }
  if (flag & 0x40) {
    if (vers==1)
      font.m_font.set(MWAWFont::Script(-20,librevenge::RVNG_PERCENT,80));
    else
      font.m_font.set(MWAWFont::Script::sub100());
  }
  if ((flag & 0x80) && !(flag & 0x60)) f << "fFl80#,";
  font.m_font.setFlags(flags);
  int color = 1;
  if (type == 2) {
    color=static_cast<int>(input->readULong(1));
  }
  else if (pos+type+5 <= endPos) {
    auto val = static_cast<int>(input->readULong(1));
    if (val == 0)
      f << "end0#,";
    else
      input->seek(-1, librevenge::RVNG_SEEK_CUR);
  }
  if (color != 1) {
    MWAWColor col;
    if (m_document.getColor(color,col,vers))
      font.m_font.setColor(col);
    else
      f << "#fColor=" << color << ",";
  }
  font.m_extra = f.str();
  return true;
}

////////////////////////////////////////////////////////////
// the tabulations:
////////////////////////////////////////////////////////////
bool MsWks3Text::readParagraph(MsWks3TextInternal::LineZone &zone, MWAWParagraph &parag)
{
  int dataSize = int(zone.m_pos.length())-6;
  if (dataSize < 15) return false;
  MWAWInputStreamPtr input=m_document.getInput();
  input->seek(zone.m_pos.begin()+6, librevenge::RVNG_SEEK_SET);

  parag = MWAWParagraph();
  libmwaw::DebugFile &ascFile = m_document.ascii();
  libmwaw::DebugStream f;

  int fl[2];
  bool firstFlag = (dataSize & 1) == 0;
  fl[0] = firstFlag ? static_cast<int>(input->readULong(1)) : 0x4c;
  switch (fl[0]) {
  case 0x4c:
    break;
  case 0x43:
    parag.m_justify = MWAWParagraph::JustificationCenter;
    break;
  case 0x52:
    parag.m_justify = MWAWParagraph::JustificationRight;
    break;
  case 0x46:
    parag.m_justify = MWAWParagraph::JustificationFull;
    break;
  default:
    f << "#align=" << std::hex << fl[0] << ",";
    MWAW_DEBUG_MSG(("MsWks3Text::readParagraph: unknown alignment %x\n", static_cast<unsigned int>(fl[0])));
    break;
  }
  fl[1] = static_cast<int>(input->readULong(1));
  if (fl[1])
    f << "fl0=" <<fl[1] << std::dec << ",";
  int dim[3];
  bool ok = true;
  for (int i = 0; i < 3; i++) {
    dim[i] = static_cast<int>(input->readULong(2));
    if (i==0&&(dim[0]&0x8000)) {
      dim[0] &= 0x7FFF;
      f << "6linesByInches,";
    }
    if (dim[i] > 3000) ok = false;
  }
  if (!ok) {
    MWAW_DEBUG_MSG(("MsWks3Text::readParagraph: size is very odd\n"));
    f << "##";
  }
  if (dim[0] || dim[1] || dim[2]) {
    f << "size=" << dim[1] << "x" << dim[0];
    if (dim[2]) f << "[" << dim[2] << "]";
    f << ",";
  }

  int fl2[2];
  for (auto &flag : fl2) // between -16 and 16
    flag = static_cast<int>(input->readULong(1));
  if (fl2[0] || fl2[1])
    f << "fl2=(" << std::hex << fl2[0] << ", " <<fl2[1] << ")" << std::dec << ",";

  for (int i = 0; i < 3; i++) {
    auto val = static_cast<int>(input->readULong(2));
    int flag = (val & 0xc000) >> 14;
    val = (val & 0x3fff);
    if (val > 8000 || flag) {
      MWAW_DEBUG_MSG(("MsWks3Text::readParagraph: find odd margin %d\n", i));
      f << "#margin" << i << "=" << val  << "(" << flag << "),";
    }
    if (val > 8000) continue;
    // i = 0 (last), i = 1 (firstL), i=2 (nextL)
    parag.m_margins[2-i] = val/72.0;
  }
  *(parag.m_margins[0]) -= *(parag.m_margins[1]);
  if (parag.m_margins[2].get() > 0.0)
    parag.m_margins[2] = m_mainParser->getPageWidth()-*(parag.m_margins[2]);
  if (parag.m_margins[2].get() > 56./72.) *(parag.m_margins[2]) -= 28./72.;
  else if (parag.m_margins[2].get() >=0.0) *(parag.m_margins[2]) *= 0.5;
  else parag.m_margins[2] = 0.0;
  int numVal = (dataSize-9)/2-3;
  parag.m_tabs->resize(size_t(numVal));
  size_t numTabs = 0;

  // checkme: in order to avoid x_tabs > textWidth (appears sometimes when i=0)
  auto maxWidth = long(m_mainParser->getPageWidth()*72-36);
  if (dim[1] > maxWidth) maxWidth = dim[1];

  for (int i = 0; i < numVal; i++) {
    auto val = static_cast<int>(input->readULong(2));
    MWAWTabStop::Alignment align = MWAWTabStop::LEFT;
    switch (val >> 14) {
    case 1:
      align = MWAWTabStop::DECIMAL;
      break;
    case 2:
      align = MWAWTabStop::RIGHT;
      break;
    case 3:
      align = MWAWTabStop::CENTER;
      break;
    default:
      break;
    }
    val = (val & 0x3fff);
    if (val > maxWidth) {
      static bool first = true;
      if (first) {
        MWAW_DEBUG_MSG(("MsWks3Text::readParagraph: finds some odd tabs (ignored)\n"));
        first = false;
      }
      f << "#tabs" << i << "=" << val << ",";
      continue;
    }
    (*parag.m_tabs)[numTabs].m_alignment = align;
    (*parag.m_tabs)[numTabs++].m_position = val/72.0;
  }
  if (int(numTabs)!=numVal) parag.m_tabs->resize(numTabs);
  parag.m_extra = f.str();

  f.str("");
  f << "Entries(Paragraph):" << zone << "," << parag << ",";
  ascFile.addPos(zone.m_pos.begin());
  ascFile.addNote(f.str().c_str());

  return true;
}

////////////////////////////////////////////////////////////
// read v1-v2 header/footer string
////////////////////////////////////////////////////////////
std::string MsWks3Text::readHeaderFooterString(bool header)
{
  std::string res("");
  MWAWInputStreamPtr input=m_document.getInput();
  auto numChar = static_cast<int>(input->readULong(1));
  if (!numChar) return res;
  for (int i = 0; i < numChar; i++) {
    auto c = static_cast<unsigned char>(input->readULong(1));
    if (c == 0) {
      input->seek(-1, librevenge::RVNG_SEEK_CUR);
      break;
    }
    if (c == '&') {
      auto nextC = static_cast<unsigned char>(input->readULong(1));
      bool field = true;
      switch (nextC) {
      case 'F':
        res += char(0x19);
        break; // file name
      case 'D':
        res += char(0x17);
        break; // date
      case 'P':
        res += char(0x18);
        break; // page
      case 'T':
        res += char(0x16);
        break; // time
      default:
        field = false;
      }
      if (field) continue;
      input->seek(-1, librevenge::RVNG_SEEK_CUR);
    }
    res += char(c);
  }
  if (res.length()) {
    m_state->m_zones.push_back(MsWks3TextInternal::TextZone());
    auto &zone = m_state->m_zones.back();
    zone.m_id = int(m_state->m_zones.size())-1;
    zone.m_type = header ? MsWks3TextInternal::TextZone::Header :
                  MsWks3TextInternal::TextZone::Footer;
    zone.m_text = res;
  }
  return res;
}

////////////////////////////////////////////////////////////
//
// Low level
//
////////////////////////////////////////////////////////////
void MsWks3Text::send(MsWks3TextInternal::TextZone &zone, MWAWVec2i limit)
{
  auto numZones = int(zone.m_zonesList.size());
  // set the default font
  if (m_parserState->getMainListener())
    m_parserState->getMainListener()->setFont(MWAWFont(20,12));
  if (numZones == 0 && zone.m_text.length()) {
    sendString(zone.m_text);
    zone.m_isSent = true;
    return;
  }
  bool isMain = false;
  MWAWVec2i notePos(-1,-1);
  if (limit[0] < 0) {
    limit = MWAWVec2i(0,numZones);
    isMain = zone.isMain();
    // find the notes in the text zones
    for (auto noteIt : zone.m_footnoteMap) {
      if (notePos[0]==-1) {
        notePos = noteIt.second;
        continue;
      }
      if (notePos[0] > noteIt.second[0])
        notePos[0] = noteIt.second[0];
      if (notePos[1] < noteIt.second[1])
        notePos[1] = noteIt.second[1];
    }
  }

  for (int i = limit[0]; i < limit[1]; i++) {
    if (i == notePos[0]) {
      i = notePos[1]-1;
      continue;
    }
    if (isMain && zone.m_pagesPosition.find(i) != zone.m_pagesPosition.end()) {
      ++m_state->m_actualPage;
      m_document.newPage(m_state->m_actualPage, zone.m_pagesPosition[i]);
    }
    auto &z = zone.m_zonesList[size_t(i)];
    if (z.m_type & 0x80) {
      MWAWParagraph parag;
      if (readParagraph(z, parag) && m_parserState->getMainListener())
        m_parserState->getMainListener()->setParagraph(parag);
    }
    else
      sendText(z, zone.m_id);
  }
  zone.m_isSent = true;
}

void MsWks3Text::sendNote(int zoneId, int noteId)
{
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (zoneId < 0 || zoneId >= int(m_state->m_zones.size())) {
    if (listener) listener->insertChar(' ');
    MWAW_DEBUG_MSG(("MsWks3Text::sendNote: unknown zone %d\n", zoneId));
    return;
  }
  auto &zone=m_state->m_zones[size_t(zoneId)];
  auto noteIt = zone.m_footnoteMap.find(noteId);
  if (noteIt==zone.m_footnoteMap.end()) {
    MWAW_DEBUG_MSG(("MsWks3Text::sendNote: unknown note %d-%d\n", zoneId, noteId));
    if (listener) listener->insertChar(' ');
  }
  else
    send(zone, noteIt->second);
}

void MsWks3Text::sendZone(int zoneId)
{
  if (zoneId < 0 || zoneId >= int(m_state->m_zones.size())) {
    MWAW_DEBUG_MSG(("MsWks3Text::sendZone: unknown zone %d\n", zoneId));
    return;
  }
  send(m_state->m_zones[size_t(zoneId)]);
}


void MsWks3Text::flushExtra()
{
  for (auto &zone : m_state->m_zones) {
    if (zone.m_isSent)
      continue;
    send(zone);
  }
}

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