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

#include <librevenge/librevenge.h>

#include "MWAWDebug.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWGraphicListener.hxx"
#include "MWAWListener.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWParser.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSection.hxx"
#include "MWAWRSRCParser.hxx"

#include "GreatWksDocument.hxx"

#include "GreatWksText.hxx"

/** Internal: the structures of a GreatWksText */
namespace GreatWksTextInternal
{
/** the different plc type */
enum PLCType { P_Font, P_Page,  P_Ruler, P_Token, P_Unknown};

/** Internal : a PLC: used to store change of properties in GreatWksTextInternal::Zone */
struct PLC {
  /// the constructor
  PLC()
    : m_type(P_Unknown)
    , m_id(-1)
    , m_extra("")
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, PLC const &plc);
  /** the PLC types */
  PLCType m_type;
  /** the id */
  int m_id;
  /** extra data */
  std::string m_extra;
};
std::ostream &operator<<(std::ostream &o, PLC const &plc)
{
  switch (plc.m_type) {
  case P_Font:
    o << "F";
    break;
  case P_Page:
    o << "Pg";
    break;
  case P_Ruler:
    o << "R";
    break;
  case P_Token:
    o << "Tn";
    break;
  case P_Unknown:
#if !defined(__clang__)
  default:
#endif
    o << "#Unkn";
    break;
  }
  if (plc.m_id >= 0) o << plc.m_id;
  else o << "_";
  if (plc.m_extra.length()) o << ":" << plc.m_extra;
  return o;
}

////////////////////////////////////////
//! Internal and low level: structure which stores a token for GreatWksText
struct Token {
  //! constructor
  Token()
    : m_type(-1)
    , m_format(0)
    , m_pictEntry()
    , m_dataSize(0)
    , m_dim(0,0)
    , m_date(0xFFFFFFFF)
    , m_extra("")
  {
  }
  //! returns a field format
  std::string getDTFormat() const;
  //! try to send the token to the listener
  bool sendTo(MWAWListener &listener) const;
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Token const &tkn);
  //! the token type
  int m_type;
  //! the token format
  int m_format;
  //! the picture entry
  MWAWEntry m_pictEntry;
  //! the picture data size
  long m_dataSize;
  //! the picture dimension
  MWAWVec2f m_dim;
  //! the token date (0xFFFFFFFF means actual date)
  uint32_t m_date;
  //! extra data
  std::string m_extra;
};

bool Token::sendTo(MWAWListener &listener) const
{
  switch (m_type) {
  case 2:
    switch (m_format) {
    case 1:
    case 3: // must be roman
      listener.insertField(MWAWField(MWAWField::PageNumber));
      break;
    case 2:
    case 4: // must be roman
      listener.insertField(MWAWField(MWAWField::PageNumber));
      listener.insertUnicodeString(librevenge::RVNGString(" of "));
      listener.insertField(MWAWField(MWAWField::PageCount));
      break;
    default:
      listener.insertField(MWAWField(MWAWField::PageNumber));
      break;
    }
    return true;
  case 0x15:
  case 0x16: {
    MWAWField field(m_type==0x15 ? MWAWField::Date : MWAWField::Time);
    field.m_DTFormat=getDTFormat();
    listener.insertField(field);
    return true;
  }
  default:
    break;
  }
  return false;
}

std::string Token::getDTFormat() const
{
  switch (m_type) {
  case 0x15:
    switch (m_format) {
    case 0xa:
      return "%m/%d/%y";
    case 0xb:
      return "%b %d, %Y";
    case 0xc:
      return "%b %Y";
    case 0xd:
      return "%b %d";
    case 0xe:
      return "%B %d, %Y";
    case 0xf:
      return "%B %Y";
    case 0x10:
      return "%B %d";
    case 0x11:
      return "%a, %b %d, %Y";
    case 0x12:
      return "%A, %B %d, %Y";
    default:
      break;
    }
    break;
  case 0x16:
    switch (m_format) {
    case 0x14:
      return "%I:%M %p";
    case 0x15:
      return "%I:%M:%S %p";
    case 0x16:
      return "%I:%M";
    case 0x17:
      return "%I:%M:%S";
    case 0x18:
      return "%H:%M";
    case 0x19:
      return "%H:%M:%S";
    default:
      break;
    }
    break;
  default:
    break;
  }
  return "";
}

std::ostream &operator<<(std::ostream &o, Token const &token)
{
  switch (token.m_type) {
  case 0: // none
    break;
  case 2:
    switch (token.m_format) {
    case 0:
      o << "page,";
      break;
    case 1:
      o << "page/pagecount,";
      break;
    case 2:
      o << "page[roman],";
      break;
    case 3:
      o << "page/pagecount[roman],";
      break;
    default:
      o << "page[#m_format=" << token.m_format << "],";
      break;
    }
    break;
  case 4:
    o << "pict,dim="<< token.m_dim << ",sz=" << std::hex << token.m_dataSize << std::dec << ",";
    break;
  case 0x15:
  case 0x16: {
    o << (token.m_type==0x15 ? "date" : "time");
    std::string format=token.getDTFormat();
    if (!format.empty())
      o << "[" << format << "]";
    else
      o << "[#format=" << token.m_format << "]";
    if (token.m_date != 0xFFFFFFFF)
      o << ":val=" << std::hex << token.m_date << std::dec;
    o << ",";
    break;
  }
  default:
    MWAW_DEBUG_MSG(("GreatWksTextInternal::Token: unknown type=%d\n", token.m_type));
    o << "#type=" << token.m_type << ",";
    if (token.m_format)
      o << "#format=" << token.m_format << ",";
  }
  o << token.m_extra;
  return o;
}
////////////////////////////////////////
//! Internal and low level: structure which stores a text position for GreatWksText
struct Frame {
  //! constructor
  Frame()
    : m_pos()
    , m_page(0)
    , m_extra("")
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Frame const &frm)
  {
    o << "dim=" << frm.m_pos << ",";
    if (frm.m_page) o << "page=" << frm.m_page << ",";
    o << frm.m_extra;
    return o;
  }

  //! the frame position
  MWAWBox2f m_pos;
  //! the page
  int m_page;
  //! extra data
  std::string m_extra;
};
////////////////////////////////////////
//! Internal and low level: structure which stores a text zone header for GreatWksText
struct Zone {
  //! constructor
  Zone()
    : m_type(-1)
    , m_numFonts(0)
    , m_numRulers(0)
    , m_numLines(0)
    , m_numTokens(0)
    , m_numChar(0)
    , m_numCharPLC(0)
    , m_numFrames(0)
    , m_fontList()
    , m_rulerList()
    , m_tokenList()
    , m_frameList()
    , m_textEntry()
    , m_posPLCMap()
    , m_parsed(false)
    , m_extra("")
  {
  }
  //! returns true if this is the main zone
  bool isMain() const
  {
    return m_type==3;
  }
  //! check if the data read are or not ok
  bool ok() const
  {
    if (m_type<0 || m_type > 5)
      return false;
    if (m_numFonts<=0 || m_numRulers<=0 || m_numLines<0 || m_numTokens<=0 || m_numChar<0 ||
        m_numCharPLC<=0 || m_numFrames<0)
      return false;
    return true;
  }
  //! returns the data size
  long size() const
  {
    return 22*m_numFonts+192*m_numRulers+6*m_numCharPLC
           +18*m_numTokens+14*m_numLines+22*m_numFrames+m_numChar;
  }
  //! returns true if the data has graphic
  bool hasGraphics() const
  {
    for (auto const &token : m_tokenList) {
      if (token.m_type==4)
        return true;
    }
    return false;
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Zone const &fr)
  {
    switch (fr.m_type) {
    case 1: // in draw=textbox
      o << "header/footer,";
      break;
    case 2:
      o << "textbox,";
      break;
    case 3:
      o << "main,";
      break;
    default:
      o << "#type=" << fr.m_type << ",";
      break;
    }
    if (fr.m_numFonts)
      o << "nFonts=" << fr.m_numFonts << ",";
    if (fr.m_numRulers)
      o << "nRulers=" << fr.m_numRulers << ",";
    if (fr.m_numChar)
      o << "length[text]=" << fr.m_numChar << ",";
    if (fr.m_numCharPLC)
      o << "char[plc]=" << fr.m_numCharPLC << ",";
    if (fr.m_numLines)
      o << "nLines=" << fr.m_numLines << ",";
    if (fr.m_numTokens)
      o << "nTokens=" << fr.m_numTokens << ",";
    if (fr.m_numFrames)
      o << "nFrames=" << fr.m_numFrames << ",";
    o << fr.m_extra;
    return o;
  }
  //! the main type: 1=auxi, 3=main
  int m_type;
  //! the number of fonts
  int m_numFonts;
  //! the number of rulers
  int m_numRulers;
  //! the number of lines
  int m_numLines;
  //! the number of token
  int m_numTokens;
  //! the number of character
  long m_numChar;
  //! the number of char plc
  int m_numCharPLC;
  //! the number of frames (ie. one by column and one by pages )
  int m_numFrames;
  //! the list of font
  std::vector<MWAWFont> m_fontList;
  //! the list of ruler
  std::vector<MWAWParagraph> m_rulerList;
  //! the list of token
  std::vector<Token> m_tokenList;
  //! the list of frame token
  std::vector<Frame> m_frameList;
  //! the text entry list
  MWAWEntry m_textEntry;
  //! a map text pos -> PLC
  std::multimap<long,PLC> m_posPLCMap;
  //! a bool to know if the data are send to the listener
  mutable bool m_parsed;
  //! extra data
  std::string m_extra;
};

////////////////////////////////////////
//! Internal: the state of a GreatWksText
struct State {
  //! constructor
  State()
    : m_fileIdFontIdMap()
    , m_zonesList()
    , m_version(-1)
    , m_numPages(-1)
  {
  }
  //! returns the font id corresponding to a fId
  int getFId(int fileId) const
  {
    if (m_fileIdFontIdMap.find(fileId)==m_fileIdFontIdMap.end())
      return fileId;
    return m_fileIdFontIdMap.find(fileId)->second;
  }
  //! a global map file font id-> fontconverter id
  std::map<int,int> m_fileIdFontIdMap;
  //! a list of text zone
  std::vector<Zone> m_zonesList;
  //! the file version
  mutable int m_version;
  int m_numPages /* the number of pages */;
};
}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
GreatWksText::GreatWksText(GreatWksDocument &document)
  : m_document(document)
  , m_parserState(document.m_parserState)
  , m_state(new GreatWksTextInternal::State)
  , m_mainParser(&document.getMainParser())
{
}

GreatWksText::~GreatWksText()
{ }

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

int GreatWksText::numPages() const
{
  if (m_state->m_numPages >= 0)
    return m_state->m_numPages;

  int nPages=1;
  for (auto const &zone : m_state->m_zonesList) {
    if (!zone.isMain() || zone.m_frameList.empty())
      continue;
    if (zone.m_frameList.back().m_page>1)
      nPages = zone.m_frameList.back().m_page;
    break;
  }

  return m_state->m_numPages = nPages;
}

int GreatWksText::getFontId(int fileId) const
{
  return m_state->getFId(fileId);
}

int GreatWksText::numHFZones() const
{
  int nHF=0;
  for (auto const &zone : m_state->m_zonesList) {
    if (zone.isMain())
      break;
    nHF++;
  }
  return nHF;
}

bool GreatWksText::canSendTextBoxAsGraphic(MWAWEntry const &entry)
{
  if (!entry.valid())
    return false;
  MWAWInputStreamPtr &input= m_parserState->m_input;
  long pos=input->tell();
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  GreatWksTextInternal::Zone zone;
  bool ok=!readZone(zone) ||!zone.hasGraphics();
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  return ok;
}

bool GreatWksText::sendTextbox(MWAWEntry const &entry, MWAWListenerPtr listener)
{
  if (!listener && !m_parserState->getMainListener()) {
    MWAW_DEBUG_MSG(("GreatWksText::sendTextbox: can not find a listener\n"));
    return false;
  }
  if (!entry.valid())
    return false;
  MWAWInputStreamPtr &input= m_parserState->m_input;
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  GreatWksTextInternal::Zone zone;
  if (readZone(zone)) {
    sendZone(zone, listener);
    return true;
  }

  return sendSimpleTextbox(entry, listener);
}

////////////////////////////////////////////////////////////
// Intermediate level
////////////////////////////////////////////////////////////
bool GreatWksText::createZones(int expectedHF)
{
  MWAWInputStreamPtr &input= m_parserState->m_input;
  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;

  long pos = input->tell();
  f << "Entries(TZoneHeader):";
  auto val=static_cast<int>(input->readULong(2));
  if (val)
    f << "numPages=" << val << ",";
  val=static_cast<int>(input->readULong(2));
  if (val) // related to number of header/footer?
    f << "f0=" << val << ",";
  f << "height[total]=" << input->readLong(4) << ","; // checkme
  ascFile.addDelimiter(input->tell(),'|');
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  pos += 68;
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  if (!readFontNames()) {
    MWAW_DEBUG_MSG(("GreatWksText::createZones: can not find the font names\n"));
    input->seek(pos, librevenge::RVNG_SEEK_SET);
  }

  bool findMainZone=false;
  int nAuxi=0;
  while (!input->isEnd()) {
    pos=input->tell();
    GreatWksTextInternal::Zone zone;
    if (!readZone(zone)) {
      input->seek(pos,librevenge::RVNG_SEEK_SET);
      if (findMainZone)
        break;

      if (!findNextZone() || !readZone(zone)) {
        input->seek(pos,librevenge::RVNG_SEEK_SET);
        break;
      }
    }
    m_state->m_zonesList.push_back(zone);
    if (zone.isMain())
      findMainZone=true;
    else
      nAuxi++;
  }
  if (nAuxi!=expectedHF) {
    MWAW_DEBUG_MSG(("GreatWksText::createZones: unexpected HF zones: %d/%d\n", nAuxi, expectedHF));
  }
  return findMainZone;
}

bool GreatWksText::findNextZone()
{
  MWAWInputStreamPtr &input= m_parserState->m_input;

  long searchPos=input->tell(), pos=searchPos;
  int const headerSize=24+22+184;
  if (!input->checkPosition(pos+headerSize))
    return false;

  // first look for ruler
  input->seek(pos+headerSize, librevenge::RVNG_SEEK_SET);
  while (true) {
    if (input->isEnd())
      return false;
    pos = input->tell();
    unsigned long val=input->readULong(4);
    if (val==0x20FFFF)
      input->seek(pos, librevenge::RVNG_SEEK_SET);
    else if (val==0x20FFFFFF)
      input->seek(pos-1, librevenge::RVNG_SEEK_SET);
    else if (val==0xFFFFFFFF)
      input->seek(pos-2, librevenge::RVNG_SEEK_SET);
    else if (val==0xFFFFFF2E)
      input->seek(pos-3, librevenge::RVNG_SEEK_SET);
    else
      continue;
    if (input->readULong(4)!=0x20FFFF || input->readULong(4)!=0xFFFF2E00) {
      input->seek(pos+4, librevenge::RVNG_SEEK_SET);
      continue;
    }
    // ok a empty tabs stop
    while (!input->isEnd()) {
      pos = input->tell();
      if (input->readULong(4)!=0x20FFFF || input->readULong(4)!=0xFFFF2E00) {
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        break;
      }
    }
    break;
  }

  pos=input->tell();
  int nFonts=0;
  GreatWksTextInternal::Zone zone;
  while (true) {
    long hSize=headerSize+22*nFonts++;
    if (pos-hSize < searchPos)
      break;
    input->seek(pos-hSize, librevenge::RVNG_SEEK_SET);
    if (input->readLong(4))
      continue;
    auto val=static_cast<int>(input->readULong(2));
    if (val & 0xFEFE)
      continue;
    input->seek(2,librevenge::RVNG_SEEK_CUR);
    if (input->readLong(2)!=nFonts)
      continue;
    input->seek(pos-hSize, librevenge::RVNG_SEEK_SET);
    if (readZone(zone)) {
      input->seek(pos-hSize, librevenge::RVNG_SEEK_SET);
      return true;
    }
  }

  MWAW_DEBUG_MSG(("GreatWksText::findNextZone: can not find begin of zone for pos=%lx\n", static_cast<long unsigned int>(pos)));
  input->seek(searchPos, librevenge::RVNG_SEEK_SET);
  return false;
}

bool GreatWksText::readZone(GreatWksTextInternal::Zone &zone)
{
  zone=GreatWksTextInternal::Zone();
  MWAWInputStreamPtr &input= m_parserState->m_input;
  long pos=input->tell();
  long endPos=pos+24;
  if (!input->checkPosition(endPos))
    return false;

  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  if (input->readLong(4)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  for (int i=0; i < 2; ++i) {
    auto val=static_cast<int>(input->readLong(1));
    if (val==0) continue;
    if (val!=1)
      return false;
    if (i==0)
      f << "smartquote,";
    else
      f << "hidepict,";
  }
  zone.m_type=static_cast<int>(input->readLong(1));
  if (input->readLong(1)) { // simple|complex field
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  zone.m_numFonts=static_cast<int>(input->readULong(2));
  zone.m_numRulers=static_cast<int>(input->readULong(2));
  zone.m_numCharPLC=static_cast<int>(input->readULong(2));
  zone.m_numTokens=static_cast<int>(input->readULong(2));
  zone.m_numLines=static_cast<int>(input->readULong(2));
  zone.m_numFrames=static_cast<int>(input->readULong(2));
  zone.m_numChar=long(input->readULong(4));
  if (!zone.ok() || !input->checkPosition(endPos+zone.size())) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  zone.m_extra=f.str();
  f.str("");
  f << "Entries(HeaderText):" << zone;
  if (input->tell()!=endPos) {
    ascFile.addDelimiter(input->tell(),'|');
    input->seek(endPos, librevenge::RVNG_SEEK_SET);
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  for (int i=0; i<zone.m_numFonts; ++i) {
    pos = input->tell();
    f.str("");
    f << "Entries(FontDef)-F" << i << ":";

    MWAWFont font;
    if (!readFont(font)) {
      f << "###";
      font = MWAWFont();
      input->seek(pos+22, librevenge::RVNG_SEEK_SET);
    }
    zone.m_fontList.push_back(font);
    f << font.getDebugString(m_parserState->m_fontConverter) << ",";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  MWAWParagraph para;
  for (int i=0; i<zone.m_numRulers; ++i) {
    pos = input->tell();
    f.str("");
    f << "Entries(RulerDef)-R" << i << ":";
    if (!readRuler(para)) {
      f << "###";
      para = MWAWParagraph();
      input->seek(pos+192, librevenge::RVNG_SEEK_SET);
    }
    zone.m_rulerList.push_back(para);
    f << para;
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }

  pos=input->tell();
  f.str("");
  f << "Entries(CharPLC):";
  GreatWksTextInternal::PLC plc;
  plc.m_type = GreatWksTextInternal::P_Font;
  long textPos = 0;
  for (int i=0; i < zone.m_numCharPLC; ++i) {
    plc.m_id=static_cast<int>(input->readULong(2));
    zone.m_posPLCMap.insert
    (std::multimap<long,GreatWksTextInternal::PLC>::value_type(textPos, plc));
    f << "F" << plc.m_id << ":" << std::hex << textPos << std::dec << ",";
    textPos+= static_cast<int>(input->readULong(4));
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  // the token
  plc.m_type = GreatWksTextInternal::P_Token;
  textPos = 0;
  for (int i=0; i < zone.m_numTokens; ++i) {
    pos = input->tell();
    f.str("");
    plc.m_id=i;
    f << "Entries(Token)-Tn" << i << ":";
    GreatWksTextInternal::Token tkn;
    long numC;
    if (!readToken(tkn,numC)) {
      f << "###";
      tkn = GreatWksTextInternal::Token();
      input->seek(pos+2, librevenge::RVNG_SEEK_SET);
      numC=input->readLong(2);
    }
    zone.m_posPLCMap.insert
    (std::multimap<long,GreatWksTextInternal::PLC>::value_type(textPos, plc));
    zone.m_tokenList.push_back(tkn);
    f << tkn << ",pos=" << std::hex << textPos << std::dec;
    textPos+= numC;
    input->seek(pos+18, librevenge::RVNG_SEEK_SET);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  if (!readZonePositions(zone))
    return false;
  // fill the text entry
  pos=input->tell();
  zone.m_textEntry.setBegin(pos);
  zone.m_textEntry.setLength(zone.m_numChar);
  ascFile.addPos(pos);
  ascFile.addNote("_");
  pos += zone.m_numChar;
  ascFile.addPos(pos);
  ascFile.addNote("_");

  for (auto &tkn : zone.m_tokenList) {
    if (tkn.m_type != 4)
      continue;
    if (tkn.m_dataSize <= 0 || !input->checkPosition(pos+tkn.m_dataSize)) {
      MWAW_DEBUG_MSG(("GreatWksText::readZone: can not determine the picture size\n"));
      break;
    }
    tkn.m_pictEntry.setBegin(pos);
    tkn.m_pictEntry.setLength(tkn.m_dataSize);
    pos+=tkn.m_dataSize;
  }
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  return true;
}

bool GreatWksText::readZonePositions(GreatWksTextInternal::Zone &zone)
{
  MWAWInputStreamPtr &input= m_parserState->m_input;
  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;

  // the line
  GreatWksTextInternal::PLC plc;
  plc.m_type = GreatWksTextInternal::P_Ruler;
  long textPos = 0;
  std::vector<long> linesPos;
  linesPos.push_back(0);
  for (int i=0; i < zone.m_numLines; ++i) {
    long pos=input->tell();
    f.str("");
    plc.m_id=static_cast<int>(input->readULong(2));
    auto numC = long(input->readULong(4));
    f << "y=" << double(input->readLong(4))/65536.;
    f << "->" << double(input->readLong(4))/65536.;
    plc.m_extra=f.str();
    zone.m_posPLCMap.insert
    (std::multimap<long,GreatWksTextInternal::PLC>::value_type(textPos, plc));
    f.str("");
    f << "Entries(Line)-L" << i << ":" << plc <<  ":"
      << std::hex << textPos << std::dec;
    textPos+=numC;
    linesPos.push_back(textPos);
    input->seek(pos+14, librevenge::RVNG_SEEK_SET);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  // the page dimension
  plc.m_type = GreatWksTextInternal::P_Page;
  for (int i=0; i < zone.m_numFrames; ++i) {
    GreatWksTextInternal::Frame frame;
    long pos=input->tell();
    plc.m_id=i;
    f.str("");
    float dim[4];
    for (auto &d : dim) d=float(input->readLong(4))/65536.f;
    frame.m_pos=MWAWBox2f(MWAWVec2f(dim[1],dim[0]),MWAWVec2f(dim[3],dim[2]));
    auto val=static_cast<int>(input->readLong(2)); // always 0
    if (val) f << "#unkn=" << val << ",";
    frame.m_page=static_cast<int>(input->readLong(2));
    auto line=static_cast<int>(input->readLong(2));
    plc.m_extra=f.str();
    if (line >= 0 && line < int(linesPos.size())) {
      textPos=linesPos[size_t(line)];
      zone.m_posPLCMap.insert
      (std::multimap<long,GreatWksTextInternal::PLC>::value_type(textPos, plc));
      if (textPos)
        f << "pos=" << std::hex << textPos << std::dec;
    }
    else {
      MWAW_DEBUG_MSG(("GreatWksText::readZone: can not find begin pos for page %d\n",line));
      f << "##pos[line]=" << line << ",";
    }
    frame.m_extra=f.str();
    zone.m_frameList.push_back(frame);
    f.str("");
    f << "Entries(TFrames)-" << i << ":" << frame;
    input->seek(pos+22, librevenge::RVNG_SEEK_SET);
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  return true;
}

//////////////////////////////////////////////
// Fonts
//////////////////////////////////////////////
bool GreatWksText::readFontNames()
{
  MWAWInputStreamPtr &input= m_parserState->m_input;
  long pos=input->tell();
  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  f << "Entries(FontNames):";
  auto sz= long(input->readULong(4));
  long endPos = input->tell()+sz;
  if (sz < 2 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("GreatWksText::readFontNames: can not read field size\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  auto N=static_cast<int>(input->readLong(2));
  f << "N=" << N << ",";
  if (N*5+2 > sz) {
    MWAW_DEBUG_MSG(("GreatWksText::readFontNames: can not read N\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    return false;
  }
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  for (int i=0; i < N; ++i) {
    pos = input->tell();
    f.str("");
    f << "FontNames-" << i << ":";
    auto fId=static_cast<int>(input->readULong(2));
    f << "fId=" << fId << ",";
    auto val=static_cast<int>(input->readLong(2)); // always 0 ?
    if (val)
      f << "unkn=" << val << ",";
    auto fSz=static_cast<int>(input->readULong(1));
    if (pos+5+fSz>endPos) {
      MWAW_DEBUG_MSG(("GreatWksText::readFontNames: can not read font %d\n", i));
      f << "###";
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      input->seek(endPos, librevenge::RVNG_SEEK_SET);
      return i>0;
    }
    std::string name("");
    for (int c=0; c < fSz; ++c)
      name+=char(input->readULong(1));
    if (!name.empty())
      m_state->m_fileIdFontIdMap[fId]=m_parserState->m_fontConverter->getId(name);
    if ((fSz%2)==0)
      input->seek(1, librevenge::RVNG_SEEK_CUR);

    f << "\"" << name << "\",";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
  }
  pos = input->tell();
  if (pos!=endPos) {
    MWAW_DEBUG_MSG(("GreatWksText::readFontNames: find extra data\n"));
    ascFile.addPos(pos);
    ascFile.addNote("FontNames:###");
    input->seek(endPos, librevenge::RVNG_SEEK_SET);
  }
  return true;
}

bool GreatWksText::readFont(MWAWFont &font)
{
  font=MWAWFont();
  MWAWInputStreamPtr &input= m_parserState->m_input;
  long pos=input->tell();
  long endPos=pos+22;
  if (!input->checkPosition(endPos))
    return false;

  libmwaw::DebugStream f;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  auto val=static_cast<int>(input->readLong(2)); // small number between 0 and 5
  if (val==0) f << "unused,";
  else if (val!=1) f << "nbUsed=" << val << ",";
  // some dim dim[0]>dim[1]: minH, maxH ?
  int dim[2];
  for (auto &d : dim) d=static_cast<int>(input->readLong(2));
  f << "dim?=" << dim[1] << "x" << dim[0] << ",";

  for (int i=0; i < 2; ++i) { // f0=0|4(with italic), f1=0
    val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  font.setId(m_state->getFId(static_cast<int>(input->readULong(2))));
  auto flag =static_cast<int>(input->readULong(2));
  uint32_t flags=0;
  if (flag&0x1) flags |= MWAWFont::boldBit;
  if (flag&0x2) flags |= MWAWFont::italicBit;
  if (flag&0x4) font.setUnderlineStyle(MWAWFont::Line::Simple);
  if (flag&0x8) flags |= MWAWFont::embossBit;
  if (flag&0x10) flags |= MWAWFont::shadowBit;
  if (flag&0x20) font.setDeltaLetterSpacing(-1);
  if (flag&0x40) font.setDeltaLetterSpacing(1);
  if (flag&0x100) font.set(MWAWFont::Script::super100());
  if (flag&0x200) font.set(MWAWFont::Script::sub100());
  if (flag&0x800) font.setStrikeOutStyle(MWAWFont::Line::Simple);
  if (flag&0x2000) {
    font.setUnderlineStyle(MWAWFont::Line::Simple);
    font.setUnderlineType(MWAWFont::Line::Double);
  }
  flag &=0xD480;
  if (flag) f << "#fl=" << std::hex << flag << std::dec << ",";
  font.setFlags(flags);
  font.setSize(float(input->readULong(2)));
  unsigned char color[3];
  for (auto &c : color) c = static_cast<unsigned char>(input->readULong(2)>>8);
  font.setColor(MWAWColor(color[0],color[1],color[2]));
  font.m_extra=f.str();

  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  return true;
}
//////////////////////////////////////////////
// Ruler
//////////////////////////////////////////////
bool GreatWksText::readRuler(MWAWParagraph &para)
{
  para = MWAWParagraph();
  MWAWInputStreamPtr &input= m_parserState->m_input;
  long pos=input->tell();
  long endPos=pos+192;
  if (!input->checkPosition(endPos))
    return false;

  libmwaw::DebugStream f;
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  auto val=static_cast<int>(input->readLong(2));
  if (val==0) f << "unused,";
  else if (val!=1) f << "nbUsed=" << val << ",";
  val=static_cast<int>(input->readLong(2));
  switch (val) {
  case 0:
    break;
  case 1:
    para.m_justify = MWAWParagraph::JustificationCenter;
    break;
  case 2:
    para.m_justify = MWAWParagraph::JustificationRight ;
    break;
  case 3:
    para.m_justify = MWAWParagraph::JustificationFull;
    break;
  default:
    f << "#align" << val << ",";
    break;
  }
  para.m_marginsUnit=librevenge::RVNG_POINT;
  for (auto &margin : para.m_margins)
    margin = double(input->readLong(4))/65536.;
  para.m_margins[0]=*para.m_margins[0]-*para.m_margins[1];
  double spacing[3];
  for (double &i : spacing)
    i = double(input->readLong(4))/65536.;
  int unit[3];
  for (auto &u : unit) u = static_cast<int>(input->readLong(1));
  switch (unit[0]) {
  case 1:
  case 2:
  case 3:
  case 4:
  case 5:
    para.setInterline(spacing[0], librevenge::RVNG_POINT);
    break;
  case 6:
    para.setInterline(spacing[0], librevenge::RVNG_PERCENT);
    break;
  default:
    f << "#interline=" << spacing[0] << "[unit=" << unit[0] << "],";
  }
  for (int w=1; w < 3; ++w) {
    if (unit[w]==6)
      para.m_spacings[w]=spacing[w]*12./72.;
    else if (unit[w]>0 && unit[w]<6)
      para.m_spacings[w]=spacing[w]/72.;
    else
      f << "#spac" << w << "=" << spacing[w] << "[unit=" << unit[w] << "],";
  }
  val = static_cast<int>(input->readLong(1));
  if (val) f << "#f0=" << val << ",";

  for (int i=0; i < 20; ++i) {
    MWAWTabStop tab;
    val = static_cast<int>(input->readLong(1));
    switch (val) {
    case 0: // left
      break;
    case 1:
      tab.m_alignment = MWAWTabStop::CENTER;
      break;
    case 2:
      tab.m_alignment = MWAWTabStop::RIGHT;
      break;
    case 3:
      tab.m_alignment = MWAWTabStop::DECIMAL;
      break;
    default:
      f << "#tab" << i << "[align]=" << val << ",";
      break;
    }
    auto leaderChar = char(input->readULong(1));
    if (leaderChar) {
      int unicode= m_parserState->m_fontConverter->unicode(3, static_cast<unsigned char>(leaderChar));
      if (unicode==-1)
        tab.m_leaderCharacter = uint16_t(leaderChar);
      else
        tab.m_leaderCharacter = uint16_t(unicode);
    }
    long tPos=input->readLong(4);
    if (tPos==-1) {
      input->seek(2, librevenge::RVNG_SEEK_CUR);
      continue;
    }
    tab.m_position=double(tPos)/72./65536.;
    auto decimalChar = char(input->readULong(1));
    if (decimalChar) {
      int unicode= m_parserState->m_fontConverter->unicode(3, static_cast<unsigned char>(decimalChar));
      if (unicode==-1)
        tab.m_decimalCharacter = uint16_t(decimalChar);
      else
        tab.m_decimalCharacter = uint16_t(unicode);
    }
    val = static_cast<int>(input->readLong(1));
    if (val) f << "#tab" << i << "[f0=" << val << ",";
    para.m_tabs->push_back(tab);
  }
  para.m_extra=f.str();

  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  return true;
}

//////////////////////////////////////////////
// Token
//////////////////////////////////////////////
bool GreatWksText::readToken(GreatWksTextInternal::Token &token, long &nChar)
{
  token = GreatWksTextInternal::Token();
  MWAWInputStreamPtr &input= m_parserState->m_input;
  long pos=input->tell();
  long endPos=pos+18;
  if (!input->checkPosition(endPos))
    return false;

  libmwaw::DebugStream f;
  token.m_type=static_cast<int>(input->readULong(1));
  token.m_format=static_cast<int>(input->readULong(1));
  nChar=input->readLong(4);
  if (token.m_type==0x15||token.m_type==0x16)
    token.m_date=static_cast<uint32_t>(input->readULong(4));
  else if (token.m_type==4) {
    token.m_dataSize = input->readLong(4);
    float dim[2];
    for (auto &d : dim) d = float(input->readLong(4))/65536.f;
    token.m_dim=MWAWVec2f(dim[0],dim[1]); // checkme
  }
  int nUnread=int(endPos-input->tell())/2;
  for (int i=0; i < nUnread; i++) {
    auto val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  token.m_extra=f.str();

  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  return true;
}

//////////////////////////////////////////////
// send data
//////////////////////////////////////////////
bool GreatWksText::sendMainText()
{
  for (auto const &zone : m_state->m_zonesList) {
    if (!zone.isMain())
      continue;
    return sendZone(zone);
  }
  return false;
}

bool GreatWksText::sendHF(int id)
{
  for (auto const &zone : m_state->m_zonesList) {
    if (zone.isMain())
      continue;
    if (id--==0)
      return sendZone(zone);
  }
  MWAW_DEBUG_MSG(("GreatWksText::sendHF: can not find a header/footer\n"));
  return false;
}

void GreatWksText::flushExtra()
{
  MWAWListenerPtr listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("GreatWksText::flushExtra: can not find a listener\n"));
    return;
  }
  for (auto const &zone : m_state->m_zonesList) {
    if (zone.m_parsed)
      continue;
    sendZone(zone);
  }
}

bool GreatWksText::sendSimpleTextbox(MWAWEntry const &entry, MWAWListenerPtr listener)
{
  if (!listener)
    listener=m_parserState->getMainListener();
  if (!listener || !listener->canWriteText()) {
    MWAW_DEBUG_MSG(("GreatWksText::sendSimpleTextbox: can not find a listener\n"));
    return false;
  }
  if (entry.length()<51) {
    MWAW_DEBUG_MSG(("GreatWksText::sendSimpleTextbox: the entry seems to short\n"));
    return false;
  }
  MWAWInputStreamPtr &input= m_parserState->m_input;
  long pos=entry.begin();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  f << "Entries(Texbox):";
  bool ok = input->readLong(4)==0;
  for (int i=0; ok && i < 2; ++i) {
    auto val=static_cast<int>(input->readLong(1));
    if (val==0) continue;
    if (val!=1) {
      f << "#fl" << i << "=" << val << ",";
      ok = false;
      break;
    }
    if (i==0)
      f << "smartquote,";
    else
      f << "hidepict,";
  }
  auto type=static_cast<int>(input->readLong(1));
  if (ok) {
    if (type<0 || type>5) {
      f << "#type=" << type << ",";
      ok = false;
    }
    else  if (type==1)
      f << "textbox[draw],";
    else if (type!=2)
      f << "type=" << type << ",";
  }
  if (ok) ok=input->readLong(1)==1;

  if (!ok) {
    MWAW_DEBUG_MSG(("GreatWksText::sendSimpleTextbox: the header seems bad\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  MWAWFont font;
  font.setId(m_state->getFId(static_cast<int>(input->readULong(2))));
  auto flag =static_cast<int>(input->readULong(2));
  uint32_t flags=0;
  if (flag&0x1) flags |= MWAWFont::boldBit;
  if (flag&0x2) flags |= MWAWFont::italicBit;
  if (flag&0x4) font.setUnderlineStyle(MWAWFont::Line::Simple);
  if (flag&0x8) flags |= MWAWFont::embossBit;
  if (flag&0x10) flags |= MWAWFont::shadowBit;
  if (flag&0x20) font.setDeltaLetterSpacing(-1);
  if (flag&0x40) font.setDeltaLetterSpacing(1);
  if (flag&0x100) font.set(MWAWFont::Script::super100());
  if (flag&0x200) font.set(MWAWFont::Script::sub100());
  if (flag&0x800) font.setStrikeOutStyle(MWAWFont::Line::Simple);
  if (flag&0x2000) {
    font.setUnderlineStyle(MWAWFont::Line::Simple);
    font.setUnderlineType(MWAWFont::Line::Double);
  }
  flag &=0xD480;
  if (flag) f << "font[#fl]=" << std::hex << flag << std::dec << ",";
  font.setFlags(flags);
  font.setSize(float(input->readULong(2)));
  unsigned char color[3];
  for (auto &c : color) c = static_cast<unsigned char>(input->readULong(2)>>8);
  font.setColor(MWAWColor(color[0],color[1],color[2]));
  f << "font=[" << font.getDebugString(m_parserState->m_fontConverter) << "],";
  listener->setFont(font);

  int val;
  f << "unkn=[" << std::hex;
  for (int i=0; i<6; i++) { // junk ?
    val=static_cast<int>(input->readULong(2));
    if (val)
      f << val << ",";
    else
      f << "_,";
  }
  f << std::dec << "],";
  MWAWParagraph para;
  val=static_cast<int>(input->readLong(2));
  switch (val) {
  case 0:
    break;
  case 1:
    para.m_justify = MWAWParagraph::JustificationCenter;
    break;
  case 2:
    para.m_justify = MWAWParagraph::JustificationRight ;
    break;
  case 3:
    para.m_justify = MWAWParagraph::JustificationFull;
    break;
  default:
    f << "#align" << val << ",";
    break;
  }
  f << para <<",";
  listener->setParagraph(para);
  double dim[4];
  for (auto &d : dim) d=double(input->readLong(4))/65536.;
  f << "dim=" << dim[1] << "x" << dim[0] << "<->" << dim[3] << "x" << dim[2] << "," ;
  auto fSz=static_cast<int>(input->readULong(1));
  if (50+fSz > entry.length()) {
    MWAW_DEBUG_MSG(("GreatWksText::sendSimpleTextbox: the text size seems too big\n"));
    f << "###";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }

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

  pos=input->tell();
  f.str("");
  f << "Entries(Text):";
  for (int i=0; i < fSz; ++i) {
    auto c = char(input->readULong(1));
    f << c;
    switch (c) {
    case 0x9:
      listener->insertTab();
      break;
    case 0xd:
      listener->insertEOL();
      break;
    default:
      listener->insertCharacter(static_cast<unsigned char>(c));
      break;
    }
  }
  input->seek(entry.end(), librevenge::RVNG_SEEK_SET);
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  return true;
}

bool GreatWksText::sendZone(GreatWksTextInternal::Zone const &zone, MWAWListenerPtr listener)
{
  bool inLocalZone=false;
  if (listener)
    inLocalZone=true;
  else
    listener=m_parserState->getMainListener();
  if (!listener || !listener->canWriteText()) {
    MWAW_DEBUG_MSG(("GreatWksText::sendZone: can not find a listener\n"));
    return false;
  }
  bool isMain = zone.isMain();
  int actPage = 1, actCol = 0, numCol=1;
  if (isMain && !listener->canOpenSectionAddBreak()) {
    MWAW_DEBUG_MSG(("GreatWksText::sendZone: in a main zone, but can not open section\n"));
    isMain = false;
  }
  else if (isMain) {
    m_document.newPage(1);
    MWAWSection sec=m_document.getMainSection();
    numCol = sec.numColumns();
    if (numCol>1) {
      if (listener->isSectionOpened())
        listener->closeSection();
      listener->openSection(sec);
    }
  }
  if (!zone.m_textEntry.valid())
    return true;
  MWAWInputStreamPtr &input= m_parserState->m_input;
  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  f << "Entries(Text):";
  zone.m_parsed=true;
  long pos = zone.m_textEntry.begin(), endPos = zone.m_textEntry.end();
  input->seek(pos, librevenge::RVNG_SEEK_SET);

  long cPos=0;
  while (1) {
    long actPos = input->tell();
    bool done = input->isEnd() || actPos==endPos;

    char c = done ? char(0) : char(input->readULong(1));
    if (pos!=actPos && (c==0xd || done)) {
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
      f.str("");
      f << "Text:";
      pos = actPos+1;
    }
    if (done) break;

    auto pIt=zone.m_posPLCMap.find(cPos);
    GreatWksTextInternal::Token token;
    while (pIt!=zone.m_posPLCMap.end() && pIt->first==cPos) {
      GreatWksTextInternal::PLC const &plc=(pIt++)->second;
      f << "[" << plc << "]";
      switch (plc.m_type) {
      case GreatWksTextInternal::P_Font:
        if (plc.m_id < 0 || plc.m_id >= static_cast<int>(zone.m_fontList.size())) {
          MWAW_DEBUG_MSG(("GreatWksText::sendZone: can not find font %d\n", plc.m_id));
          break;
        }
        listener->setFont(zone.m_fontList[size_t(plc.m_id)]);
        break;
      case GreatWksTextInternal::P_Ruler:
        if (plc.m_id < 0 || plc.m_id >= static_cast<int>(zone.m_rulerList.size())) {
          MWAW_DEBUG_MSG(("GreatWksText::sendZone: can not find ruler %d\n", plc.m_id));
          break;
        }
        listener->setParagraph(zone.m_rulerList[size_t(plc.m_id)]);
        break;
      case GreatWksTextInternal::P_Token:
        if (plc.m_id < 0 || plc.m_id >= static_cast<int>(zone.m_tokenList.size())) {
          MWAW_DEBUG_MSG(("GreatWksText::sendZone: can not find token %d\n", plc.m_id));
          break;
        }
        token=zone.m_tokenList[size_t(plc.m_id)];
        break;
      case GreatWksTextInternal::P_Page:
      case GreatWksTextInternal::P_Unknown:
#if !defined(__clang__)
      default:
#endif
        break;
      }
    }
    if (c!=0xd) f << c;
    switch (c) {
    case 0x4: {
      f << "[pict]";
      if (token.m_type!=4 || !token.m_pictEntry.valid()) {
        MWAW_DEBUG_MSG(("GreatWksText::sendZone: can not find a picture\n"));
        f << "###";
        break;
      }
      if (inLocalZone) {
        MWAW_DEBUG_MSG(("GreatWksText::sendZone: oops, can not send a picture in a graphic zone\n"));
        break;
      }
      MWAWPosition pictPos(MWAWVec2f(0,0), token.m_dim, librevenge::RVNG_POINT);
      pictPos.setRelativePosition(MWAWPosition::Char, MWAWPosition::XLeft, MWAWPosition::YBottom);
      m_document.sendPicture(token.m_pictEntry, pictPos);
      break;
    }
    case 0x9:
      listener->insertTab();
      break;
    case 0xb:
      f << "[colbreak]";
      if (!isMain) {
        MWAW_DEBUG_MSG(("GreatWksText::sendZone: find column break in auxilliary block\n"));
        break;
      }
      if (actCol < numCol-1 && numCol > 1) {
        listener->insertBreak(MWAWListener::ColumnBreak);
        actCol++;
      }
      else {
        actCol = 0;
        m_document.newPage(++actPage);
      }
      break;
    case 0xc:
      f << "[pagebreak]";
      if (!isMain) {
        f << "###";
        MWAW_DEBUG_MSG(("GreatWksText::sendZone: find page break in auxilliary zone\n"));
        break;
      }
      m_document.newPage(++actPage);
      actCol = 0;
      break;
    case 0xd:
      listener->insertEOL();
      break;
    case 2: // page number
    case 0x15: // date
    case 0x16: // time
      if (token.sendTo(*listener))
        break;
      f << "###";
      MWAW_DEBUG_MSG(("GreatWksText::sendZone: can not send token\n"));
      break;
    default:
      listener->insertCharacter(static_cast<unsigned char>(c));
      break;
    }
    cPos++;
  }
  return true;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: