/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libmspub project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "MSPUBParser.h"

#include <algorithm>
#include <cassert>
#include <list>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <utility>

#include <boost/numeric/conversion/cast.hpp>

#include <librevenge-stream/librevenge-stream.h>

#include "Arrow.h"
#include "ColorReference.h"
#include "Coordinate.h"
#include "Dash.h"
#include "EscherContainerType.h"
#include "EscherFieldIds.h"
#include "Fill.h"
#include "FillType.h"
#include "Line.h"
#include "ListInfo.h"
#include "MSPUBBlockID.h"
#include "MSPUBBlockType.h"
#include "MSPUBCollector.h"
#include "MSPUBConstants.h"
#include "MSPUBContentChunkType.h"
#include "MSPUBMetaData.h"
#include "Shadow.h"
#include "ShapeFlags.h"
#include "ShapeType.h"
#include "TableInfo.h"
#include "VerticalAlign.h"
#include "libmspub_utils.h"

namespace libmspub
{

namespace
{

// Well above the ~12 ceiling LibreOffice's PowerPoint Escher exporter
// applies (sd/source/filter/eppt/escherex.cxx
// PptEscherEx::CloseContainer notes PPT itself struggles past 16), well
// below any stack-overflow risk.
constexpr unsigned MAX_SHAPE_GROUP_DEPTH = 100;

Underline readUnderline(const unsigned value)
{
  switch (value & 0xff)
  {
  case 0x0:
    return Underline::None;
  default:
    MSPUB_DEBUG_MSG(("unknown underline type %u\n", value & 0xff));
    MSPUB_FALLTHROUGH;
  case 0x1:
    return Underline::Single;
  case 0x2:
    return Underline::WordsOnly;
  case 0x3:
    return Underline::Double;
  case 0x4:
    return Underline::Dotted;
  case 0x6:
    return Underline::Thick;
  case 0x7:
    return Underline::Dash;
  case 0x9:
    return Underline::DotDash;
  case 0xa:
    return Underline::DotDotDash;
  case 0xb:
    return Underline::Wave;
  case 0x10:
    return Underline::ThickWave;
  case 0x11:
    return Underline::ThickDot;
  case 0x12:
    return Underline::ThickDash;
  case 0x13:
    return Underline::ThickDotDash;
  case 0x14:
    return Underline::ThickDotDotDash;
  case 0x15:
    return Underline::LongDash;
  case 0x16:
    return Underline::ThickLongDash;
  case 0x17:
    return Underline::DoubleWave;
  }
}

}

MSPUBParser::MSPUBParser(librevenge::RVNGInputStream *input, MSPUBCollector *collector)
  : m_input(input),
    m_length(boost::numeric_cast<unsigned>(getLength(input))),
    m_collector(collector),
    m_blockInfo(), m_contentChunks(),
    m_cellsChunkIndices(),
    m_pageChunkIndices(), m_shapeChunkIndices(),
    m_paletteChunkIndices(), m_borderArtChunkIndices(),
    m_fontChunkIndices(),
    m_unknownChunkIndices(), m_documentChunkIndex(),
    m_lastSeenSeqNum(-1), m_lastAddedImage(0),
    m_alternateShapeSeqNums(), m_escherDelayIndices()
{
}

MSPUBParser::~MSPUBParser()
{
}

bool MSPUBParser::lineExistsByFlagPointer(unsigned *flags,
                                          unsigned *geomFlags)
{
  return flags &&
         !(((*flags) & FLAG_USE_LINE) && !((*flags) & FLAG_LINE)) &&
         ((!geomFlags) || !((*geomFlags) & FLAG_GEOM_USE_LINE_OK)
          || ((*geomFlags) & FLAG_GEOM_LINE_OK));

}

unsigned MSPUBParser::getColorIndexByQuillEntry(unsigned entry)
{
  return entry;
}

short MSPUBParser::getBlockDataLength(unsigned type) // -1 for variable-length block with the data length as the first DWORD
{
  switch (type)
  {
  case DUMMY:
  case 0x5:
  case 0x8:
  case 0xa:
    return 0;
  case 0x10:
  case 0x12:
  case 0x18:
  case 0x1a:
  case 0x07:
    return 2;
  case 0x20:
  case 0x22:
  case 0x58:
  case 0x68:
  case 0x70:
  case 0xb8:
    return 4;
  case 0x28:
    return 8;
  case 0x38:
    return 16;
  case 0x48:
    return 24;
  case STRING_CONTAINER:
  case 0x80:
  case 0x82:
  case GENERAL_CONTAINER:
  case 0x8a:
  case 0x90:
  case 0x98:
  case 0xa0:
    return -1;
  }
  //FIXME: Debug assertion here? Should never get here.
  MSPUB_DEBUG_MSG(("Block of unknown type seen!\n"));
  return 0;
}

bool MSPUBParser::parse()
{
  MSPUB_DEBUG_MSG(("***NOTE***: Where applicable, the meanings of block/chunk IDs and Types printed below may be found in:\n\t***MSPUBBlockType.h\n\t***MSPUBBlockID.h\n\t***MSPUBContentChunkType.h\n*****\n"));
  if (!m_input->isStructured())
    return false;
  // No check: metadata are not important enough to fail if they can't be parsed
  parseMetaData();
  std::unique_ptr<librevenge::RVNGInputStream> quill(m_input->getSubStreamByName("Quill/QuillSub/CONTENTS"));
  if (!quill)
  {
    MSPUB_DEBUG_MSG(("Couldn't get quill stream.\n"));
    return false;
  }
  if (!parseQuill(quill.get()))
  {
    MSPUB_DEBUG_MSG(("Couldn't parse quill stream.\n"));
    return false;
  }
  std::unique_ptr<librevenge::RVNGInputStream> contents(m_input->getSubStreamByName("Contents"));
  if (!contents)
  {
    MSPUB_DEBUG_MSG(("Couldn't get contents stream.\n"));
    return false;
  }
  if (!parseContents(contents.get()))
  {
    MSPUB_DEBUG_MSG(("Couldn't parse contents stream.\n"));
    return false;
  }
  std::unique_ptr<librevenge::RVNGInputStream> escherDelay(m_input->getSubStreamByName("Escher/EscherDelayStm"));
  if (escherDelay)
  {
    parseEscherDelay(escherDelay.get());
  }
  std::unique_ptr<librevenge::RVNGInputStream> escher(m_input->getSubStreamByName("Escher/EscherStm"));
  if (!escher)
  {
    MSPUB_DEBUG_MSG(("Couldn't get escher stream.\n"));
    return false;
  }
  if (!parseEscher(escher.get()))
  {
    MSPUB_DEBUG_MSG(("Couldn't parse escher stream.\n"));
    return false;
  }

  return m_collector->go();
}

ImgType MSPUBParser::imgTypeByBlipType(unsigned short type)
{
  switch (type)
  {
  case OFFICE_ART_BLIP_PNG:
    return PNG;
  case OFFICE_ART_BLIP_JPEG:
    return JPEG;
  case OFFICE_ART_BLIP_JPEGCMYK:
    return JPEGCMYK;
  case OFFICE_ART_BLIP_WMF:
    return WMF;
  case OFFICE_ART_BLIP_DIB:
    return DIB;
  case OFFICE_ART_BLIP_EMF:
    return EMF;
  case OFFICE_ART_BLIP_TIFF:
    return TIFF;
  case OFFICE_ART_BLIP_PICT:
    return PICT;
  }
  return UNKNOWN;
}

int MSPUBParser::getStartOffset(ImgType type, unsigned short initial)
{
  bool oneUid = true;
  int offset = 0x11;
  unsigned short recInstance = initial >> 4;
  switch (type)
  {
  case WMF:
    oneUid = recInstance == 0x216;
    offset = 0x34;
    break;
  case EMF:
    oneUid = recInstance == 0x3D4;
    offset = 0x34;
    break;
  case PNG:
    oneUid = recInstance == 0x6E0;
    offset = 0x11;
    break;
  case JPEG:
    oneUid = recInstance == 0x46A || recInstance == 0x6E2;
    offset = 0x11;
    break;
  case JPEGCMYK:
    oneUid = recInstance == 0x46B || recInstance == 0x6E3;
    offset = 33;
    break;
  case DIB:
    oneUid = recInstance == 0x7A8;
    offset = 0x11;
    break;
  case TIFF:
    oneUid = recInstance == 0x6E4;
    offset = 0x11;
    break;
  default:
    break;
  }
  return offset + (oneUid ? 0 : 0x10);
}

bool MSPUBParser::parseEscherDelay(librevenge::RVNGInputStream *input)
{
  while (stillReading(input, (unsigned long)-1))
  {
    EscherContainerInfo info = parseEscherContainer(input);
    const ImgType imgType = imgTypeByBlipType(info.type);
    if (imgType != UNKNOWN)
    {
      librevenge::RVNGBinaryData img;
      unsigned long toRead = info.contentsLength;
      input->seek(input->tell() + getStartOffset(imgType, info.initial), librevenge::RVNG_SEEK_SET);
      while (toRead > 0 && stillReading(input, (unsigned long)-1))
      {
        unsigned long howManyRead = 0;
        const unsigned char *buf = input->read(toRead, howManyRead);
        img.append(buf, howManyRead);
        toRead -= howManyRead;
      }
      if (imgType == WMF || imgType == EMF)
      {
        img = inflateData(img);
      }
      else if (imgType == DIB)
      {
        // Reconstruct BMP header
        // cf. http://en.wikipedia.org/wiki/BMP_file_format , accessed 2012-5-31
        librevenge::RVNGInputStream *buf = img.getDataStream();
        if (img.size() < 0x2E + 4)
        {
          ++m_lastAddedImage;
          MSPUB_DEBUG_MSG(("Garbage DIB at index 0x%x\n", m_lastAddedImage));
          input->seek(info.contentsOffset + info.contentsLength, librevenge::RVNG_SEEK_SET);
          continue;
        }
        buf->seek(0x0E, librevenge::RVNG_SEEK_SET);
        unsigned short bitsPerPixel = readU16(buf);
        buf->seek(0x20, librevenge::RVNG_SEEK_SET);
        unsigned numPaletteColors = readU32(buf);
        if (numPaletteColors == 0 && bitsPerPixel <= 8)
        {
          numPaletteColors = 1;
          for (int i = 0; i < bitsPerPixel; ++i)
          {
            numPaletteColors *= 2;
          }
        }

        librevenge::RVNGBinaryData tmpImg;
        tmpImg.append((unsigned char)0x42);
        tmpImg.append((unsigned char)0x4d);

        tmpImg.append((unsigned char)((img.size() + 14) & 0x000000ff));
        tmpImg.append((unsigned char)(((img.size() + 14) & 0x0000ff00) >> 8));
        tmpImg.append((unsigned char)(((img.size() + 14) & 0x00ff0000) >> 16));
        tmpImg.append((unsigned char)(((img.size() + 14) & 0xff000000) >> 24));

        tmpImg.append((unsigned char)0x00);
        tmpImg.append((unsigned char)0x00);
        tmpImg.append((unsigned char)0x00);
        tmpImg.append((unsigned char)0x00);

        tmpImg.append((unsigned char)(0x36 + 4 * numPaletteColors));
        tmpImg.append((unsigned char)0x00);
        tmpImg.append((unsigned char)0x00);
        tmpImg.append((unsigned char)0x00);
        tmpImg.append(img);
        img = tmpImg;
      }
      m_collector->addImage(++m_lastAddedImage, imgType, img);
    }
    else
    {
      ++m_lastAddedImage;
      MSPUB_DEBUG_MSG(("Image of unknown type at index 0x%x\n", m_lastAddedImage));
    }
    input->seek(info.contentsOffset + info.contentsLength, librevenge::RVNG_SEEK_SET);
  }
  return true;
}

bool MSPUBParser::parseContents(librevenge::RVNGInputStream *input)
{
  MSPUB_DEBUG_MSG(("MSPUBParser::parseContents\n"));
  input->seek(0x1a, librevenge::RVNG_SEEK_SET);
  unsigned trailerOffset = readU32(input);
  MSPUB_DEBUG_MSG(("MSPUBParser: trailerOffset %.8x\n", trailerOffset));
  input->seek(trailerOffset, librevenge::RVNG_SEEK_SET);
  unsigned trailerLength = readU32(input);
  for (unsigned i=0; i<3; i++)
  {
    MSPUBBlockInfo trailerPart = parseBlock(input);
    MSPUB_DEBUG_MSG(("Trailer SubBlock %i, startPosition 0x%lx, id %i, type 0x%x, dataLength 0x%lx\n", i+1, trailerPart.startPosition, trailerPart.id, trailerPart.type, trailerPart.dataLength));
    if (trailerPart.type == TRAILER_DIRECTORY)
    {

      while (stillReading(input, trailerPart.dataOffset + trailerPart.dataLength))
      {
        m_blockInfo.push_back(parseBlock(input));
        ++m_lastSeenSeqNum;
        if (m_blockInfo.back().type == GENERAL_CONTAINER)
        {
          if (parseContentChunkReference(input, m_blockInfo.back()))
          {
            if (m_contentChunks.size() > 1)
            {
              m_contentChunks[m_contentChunks.size() - 2].end = m_contentChunks.back().offset;
            }
          }
        }
        else(skipBlock(input, m_blockInfo.back()));
      }
      if (!m_contentChunks.empty())
      {
        m_contentChunks.back().end = trailerPart.dataOffset + trailerPart.dataLength;
      }
      if (!m_documentChunkIndex)
      {
        return false;
      }
      const ContentChunkReference &documentChunk = m_contentChunks.at(m_documentChunkIndex.get());
      for (unsigned int paletteChunkIndex : m_paletteChunkIndices)
      {
        const ContentChunkReference &paletteChunk = m_contentChunks.at(paletteChunkIndex);
        input->seek(paletteChunk.offset, librevenge::RVNG_SEEK_SET);
        if (! parsePaletteChunk(input, paletteChunk))
        {
          return false;
        }
      }
      for (unsigned int borderArtChunkIndex : m_borderArtChunkIndices)
      {
        const ContentChunkReference &baChunk =
          m_contentChunks.at(borderArtChunkIndex);
        input->seek(baChunk.offset, librevenge::RVNG_SEEK_SET);
        if (!parseBorderArtChunk(input, baChunk))
        {
          return false;
        }
      }
      for (unsigned int shapeChunkIndex : m_shapeChunkIndices)
      {
        const ContentChunkReference &shapeChunk =
          m_contentChunks.at(shapeChunkIndex);
        input->seek(shapeChunk.offset, librevenge::RVNG_SEEK_SET);
        if (!parseShape(input, shapeChunk))
        {
          return false;
        }
      }
      for (unsigned int fontChunkIndex : m_fontChunkIndices)
      {
        const ContentChunkReference &fontChunk =
          m_contentChunks.at(fontChunkIndex);
        input->seek(fontChunk.offset, librevenge::RVNG_SEEK_SET);
        if (!parseFontChunk(input, fontChunk))
        {
          return false;
        }
      }
      input->seek(documentChunk.offset, librevenge::RVNG_SEEK_SET);
      if (!parseDocumentChunk(input, documentChunk))
      {
        return false;
      }
      for (unsigned int pageChunkIndex : m_pageChunkIndices)
      {
        const ContentChunkReference &pageChunk = m_contentChunks.at(pageChunkIndex);
        input->seek(pageChunk.offset, librevenge::RVNG_SEEK_SET);
        if (!parsePageChunk(input, pageChunk))
        {
          return false;
        }
      }
    }
  }
  input->seek(trailerOffset + trailerLength, librevenge::RVNG_SEEK_SET);

  return true;
}

#ifdef DEBUG
bool MSPUBParser::parseDocumentChunk(librevenge::RVNGInputStream *input, const ContentChunkReference &chunk)
#else
bool MSPUBParser::parseDocumentChunk(librevenge::RVNGInputStream *input, const ContentChunkReference &)
#endif
{
  MSPUB_DEBUG_MSG(("parseDocumentChunk: offset 0x%lx, end 0x%lx\n", input->tell(), chunk.end));
  unsigned long begin = input->tell();
  unsigned long len = readU32(input);
  while (stillReading(input, begin + len))
  {
    MSPUBBlockInfo info = parseBlock(input);
    if (info.id == DOCUMENT_SIZE)
    {
      while (stillReading(input, info.dataOffset + info.dataLength))
      {
        MSPUBBlockInfo subInfo = parseBlock(input, true);
        if (subInfo.id == DOCUMENT_WIDTH)
        {
          m_collector->setWidthInEmu(subInfo.data);
        }
        else if (subInfo.id == DOCUMENT_HEIGHT)
        {
          m_collector->setHeightInEmu(subInfo.data);
        }
      }
    }
    else if (info.id == DOCUMENT_PAGE_LIST)
    {
      input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
      while (stillReading(input, info.dataOffset + info.dataLength))
      {
        MSPUBBlockInfo subInfo = parseBlock(input, true);
        if (subInfo.id == 0)
        {
          m_collector->setNextPage(subInfo.data);
        }
      }
    }
    else
    {
      skipBlock(input, info);
    }
  }
  return true; //FIXME: return false for failure
}

bool MSPUBParser::parseFontChunk(
  librevenge::RVNGInputStream *input, const ContentChunkReference &chunk)
{
  unsigned length = readU32(input);
  while (stillReading(input, chunk.offset + length))
  {
    MSPUBBlockInfo info = parseBlock(input, true);
    if (info.id == FONT_CONTAINER_ARRAY)
    {
      input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
      while (stillReading(input, info.dataOffset + info.dataLength))
      {
        MSPUBBlockInfo subInfo = parseBlock(input, true);
        if (subInfo.id == 0)
        {
          boost::optional<librevenge::RVNGString> name;
          boost::optional<unsigned> eotOffset;
          unsigned eotLength = 0;
          input->seek(subInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET);
          while (stillReading(input, subInfo.dataOffset + subInfo.dataLength))
          {
            MSPUBBlockInfo subSubInfo = parseBlock(input, true);
            if (subSubInfo.id == EMBEDDED_FONT_NAME)
            {
              name = librevenge::RVNGString();
              // drop trailing 0
              // TODO: This could be a general problem. Check.
              const std::size_t len = subSubInfo.stringData.size();
              if ((len > 2) && (subSubInfo.stringData[len - 1] == 0) && (subSubInfo.stringData[len - 2] == 0))
              {
                subSubInfo.stringData.pop_back();
                subSubInfo.stringData.pop_back();
              }
              appendCharacters(name.get(), subSubInfo.stringData, "UTF-16LE");
            }
            else if (subSubInfo.id == EMBEDDED_EOT)
            {
              eotOffset = subSubInfo.dataOffset;
              eotLength = subSubInfo.dataLength;
            }
          }
          if (bool(name) && bool(eotOffset))
          {
            // skip length, we've already read that
            // TODO: Why do we not read the data as part of the block info?
            input->seek(eotOffset.get() + 4, librevenge::RVNG_SEEK_SET);
            librevenge::RVNGBinaryData data;
            unsigned long toRead = eotLength;
            while (toRead > 0 && stillReading(input, (unsigned long)-1))
            {
              unsigned long howManyRead = 0;
              const unsigned char *buf = input->read(toRead, howManyRead);
              data.append(buf, howManyRead);
              toRead -= howManyRead;
            }
            m_collector->addEOTFont(name.get(), data);
            input->seek(subInfo.dataOffset + subInfo.dataLength, librevenge::RVNG_SEEK_SET);
          }
        }
      }
    }
  }
  return true;
}

bool MSPUBParser::parseBorderArtChunk(
  librevenge::RVNGInputStream *input, const ContentChunkReference &chunk)
{
  unsigned length = readU32(input);
  while (stillReading(input, chunk.offset + length))
  {
    MSPUBBlockInfo info = parseBlock(input, true);
    if (info.id == BA_ARRAY)
    {
      input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
      unsigned i = 0;
      while (stillReading(input, info.dataOffset + info.dataLength))
      {
        MSPUBBlockInfo entry = parseBlock(input, false);
        while (stillReading(input, entry.dataOffset + entry.dataLength))
        {
          MSPUBBlockInfo subRecord = parseBlock(input, true);
          if (subRecord.id == BA_IMAGE_ARRAY)
          {
            input->seek(subRecord.dataOffset + 4, librevenge::RVNG_SEEK_SET);
            while (stillReading(input, subRecord.dataOffset + subRecord.dataLength))
            {
              MSPUBBlockInfo subSubRecord = parseBlock(input, false);
              if (subSubRecord.id == BA_IMAGE_CONTAINER)
              {
                MSPUBBlockInfo imgRecord = parseBlock(input, false);
                if (imgRecord.id == BA_IMAGE)
                {
                  librevenge::RVNGBinaryData &img = *(m_collector->addBorderImage(
                                                        WMF, i));
                  unsigned long toRead = imgRecord.dataLength;
                  while (toRead > 0 && stillReading(input, (unsigned long)-1))
                  {
                    unsigned long howManyRead = 0;
                    const unsigned char *buf = input->read(toRead, howManyRead);
                    img.append(buf, howManyRead);
                    toRead -= howManyRead;
                  }
                }
              }
            }
          }
          else if (subRecord.id == BA_OFFSET_CONTAINER)
          {
            input->seek(subRecord.dataOffset + 4, librevenge::RVNG_SEEK_SET);
            while (stillReading(
                     input, subRecord.dataOffset + subRecord.dataLength))
            {
              MSPUBBlockInfo subSubRecord = parseBlock(input, true);
              if (subSubRecord.id == BA_OFFSET_ENTRY)
              {
                m_collector->setBorderImageOffset(i, subSubRecord.data);
              }
            }
          }
        }
        ++i;
        input->seek(entry.dataOffset + entry.dataLength, librevenge::RVNG_SEEK_SET);
      }
    }
  }
  return true;
}

bool MSPUBParser::parsePageChunk(librevenge::RVNGInputStream *input, const ContentChunkReference &chunk)
{
  MSPUB_DEBUG_MSG(("parsePageChunk: offset 0x%lx, end 0x%lx, seqnum 0x%x, parent 0x%x\n", input->tell(), chunk.end, chunk.seqNum, chunk.parentSeqNum));
  unsigned long length = readU32(input);
  PageType type = getPageTypeBySeqNum(chunk.seqNum);
  if (type == NORMAL)
  {
    m_collector->addPage(chunk.seqNum);
  }
  while (stillReading(input, chunk.offset + length))
  {
    MSPUBBlockInfo info = parseBlock(input);
    if (info.id == PAGE_BG_SHAPE)
    {
      m_collector->setPageBgShape(chunk.seqNum, info.data);
    }
    else if (info.id == PAGE_SHAPES)
    {
      parsePageShapeList(input, info, chunk.seqNum);
    }
    else if (info.id == THIS_MASTER_NAME)
    {
      for (unsigned char i : info.stringData)
      {
        if (i != 0)
        {
          m_collector->designateMasterPage(chunk.seqNum);
        }
      }
    }
    else if (info.id == APPLIED_MASTER_NAME)
    {
      m_collector->setMasterPage(chunk.seqNum, info.data);
    }
    else
    {
      skipBlock(input, info);
    }
  }
  return true;
}

bool MSPUBParser::parsePageShapeList(librevenge::RVNGInputStream *input, MSPUBBlockInfo info, unsigned pageSeqNum)
{
  MSPUB_DEBUG_MSG(("parsePageShapeList: page seqnum 0x%x\n", pageSeqNum));
  while (stillReading(input, info.dataOffset + info.dataLength))
  {
    MSPUBBlockInfo subInfo = parseBlock(input, true);
    if (subInfo.type == SHAPE_SEQNUM)
    {
      m_collector->setShapePage(subInfo.data, pageSeqNum);
    }
  }
  return true;
}

bool MSPUBParser::parseShape(librevenge::RVNGInputStream *input,
                             const ContentChunkReference &chunk)
{
  MSPUB_DEBUG_MSG(("parseShape: seqNum 0x%x\n", chunk.seqNum));
  unsigned long pos = input->tell();
  unsigned length = readU32(input);
  bool isTable = chunk.type == TABLE;
  bool isGroup = chunk.type == GROUP || chunk.type == LOGO;
  if (isTable)
  {
    boost::optional<unsigned> cellsSeqNum;
    boost::optional<unsigned> numRows;
    boost::optional<unsigned> numCols;
    boost::optional<unsigned> rowcolArrayOffset;
    boost::optional<unsigned> textId;
    while (stillReading(input, pos + length))
    {
      MSPUBBlockInfo info = parseBlock(input, true);
      if (info.id == TABLE_CELLS_SEQNUM)
      {
        cellsSeqNum = info.data;
      }
      else if (info.id == TABLE_NUM_ROWS)
      {
        numRows = info.data;
      }
      else if (info.id == TABLE_NUM_COLS)
      {
        numCols = info.data;
      }
      else if (info.id == TABLE_ROWCOL_ARRAY)
      {
        rowcolArrayOffset = info.dataOffset;
      }
      else if (info.id == SHAPE_TEXT_ID)
      {
        textId = info.data;
      }
    }
    if (bool(cellsSeqNum) && bool(numRows) && bool(numCols) && bool(rowcolArrayOffset))
    {
      unsigned nr = numRows.get();
      unsigned nc = numCols.get();
      unsigned rcao = rowcolArrayOffset.get();
      unsigned csn = cellsSeqNum.get();
      std::vector<unsigned> rowHeightsInEmu;
      std::vector<unsigned> columnWidthsInEmu;
      input->seek(rcao, librevenge::RVNG_SEEK_SET);
      unsigned arrayLength = readU32(input);
      while (stillReading(input, rcao + arrayLength))
      {
        MSPUBBlockInfo info = parseBlock(input, true);
        if (info.id == 0)
        {
          input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
          while (stillReading(input, info.dataOffset + info.dataLength))
          {
            MSPUBBlockInfo subInfo = parseBlock(input, true);
            if (subInfo.id == TABLE_ROWCOL_SIZE)
            {
              if (columnWidthsInEmu.size() < nc)
                columnWidthsInEmu.push_back(subInfo.data);
              else if (rowHeightsInEmu.size() < nr)
                rowHeightsInEmu.push_back(subInfo.data);
            }
          }
        }
      }
      if (rowHeightsInEmu.size() != nr || columnWidthsInEmu.size() != nc)
      {
        MSPUB_DEBUG_MSG(("ERROR: Wrong number of rows or columns found in table definition.\n"));
        return false;
      }
      boost::optional<unsigned> index;
      for (size_t i = 0; i < m_cellsChunkIndices.size(); ++i)
      {
        if (m_contentChunks[m_cellsChunkIndices[i]].seqNum == csn)
        {
          index = i;
          break;
        }
      }

      TableInfo ti(nr, nc);
      ti.m_rowHeightsInEmu = rowHeightsInEmu;
      ti.m_columnWidthsInEmu = columnWidthsInEmu;

      if (!index)
      {
        MSPUB_DEBUG_MSG(("WARNING: Couldn't find cells of seqnum %u corresponding to table of seqnum %u.\n",
                         csn, chunk.seqNum));
        return false;
      }
      else
      {
        const ContentChunkReference &cellsChunk = m_contentChunks[m_cellsChunkIndices[get(index)]];
        input->seek(cellsChunk.offset, librevenge::RVNG_SEEK_SET);
        const unsigned cellsLength = readU32(input);
        boost::optional<unsigned> cellCount;
        while (stillReading(input, cellsChunk.offset + cellsLength))
        {
          MSPUBBlockInfo info = parseBlock(input, true);
          switch (info.id)
          {
          case 0x01:
            cellCount = info.data;
            break;
          case 0x02:
          {
            input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
            while (stillReading(input, info.dataOffset + info.dataLength))
            {
              const MSPUBBlockInfo itemInfo = parseBlock(input, true);
              if (itemInfo.id == 0)
              {
                input->seek(itemInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET);
                CellInfo currentCell;
                while (stillReading(input, itemInfo.dataOffset + itemInfo.dataLength))
                {
                  const MSPUBBlockInfo subInfo = parseBlock(input, true);
                  switch (subInfo.id)
                  {
                  case 0x01:
                    currentCell.m_startRow = subInfo.data;
                    break;
                  case 0x02:
                    currentCell.m_endRow = subInfo.data;
                    break;
                  case 0x03:
                    currentCell.m_startColumn = subInfo.data;
                    break;
                  case 0x04:
                    currentCell.m_endColumn = subInfo.data;
                    break;
                  // TODO: 0x09 - 0x0e: width/height of content + margins?
                  default:
                    break;
                  }
                }
                ti.m_cells.push_back(currentCell);
              }
            }

            break;
          }
          default:
            break;
          }
        }

        if (bool(cellCount) && (get(cellCount) != ti.m_cells.size()))
        {
          MSPUB_DEBUG_MSG(("%u cell records expected, but read %u\n", get(cellCount), unsigned(ti.m_cells.size())));
        }
      }

      m_collector->setShapeTableInfo(chunk.seqNum, ti);
      if (bool(textId))
        m_collector->addTextShape(get(textId), chunk.seqNum);
      return true;
    }
    return false;
  }
  else
  {
    bool isText = false;
    bool shouldStretchBorderArt = true;
    unsigned textId = 0;
    unsigned width = 0;
    unsigned height = 0;
    while (stillReading(input, pos + length))
    {
      MSPUBBlockInfo info = parseBlock(input, true);
      if (info.id == SHAPE_WIDTH)
      {
        width = info.data;
      }
      else if (info.id == SHAPE_HEIGHT)
      {
        height = info.data;
      }
      else if (info.id == SHAPE_BORDER_IMAGE_ID)
      {
        m_collector->setShapeBorderImageId(chunk.seqNum, info.data);
      }
      else if (info.id == SHAPE_DONT_STRETCH_BA)
      {
        shouldStretchBorderArt = false;
      }
      else if (info.id == SHAPE_TEXT_ID)
      {
        textId = info.data;
        isText = true;
      }
      else if (info.id == SHAPE_VALIGN)
      {
        m_collector->setShapeVerticalTextAlign(chunk.seqNum,
                                               static_cast<VerticalAlign>(info.data));
      }
      else if (info.id == SHAPE_CROP && info.data != 0)
      {
        m_collector->setShapeCropType(chunk.seqNum,
                                      static_cast<ShapeType>(info.data));
      }
    }
    if (shouldStretchBorderArt)
    {
      m_collector->setShapeStretchBorderArt(chunk.seqNum);
    }
    bool parseWithoutDimensions = true; //FIXME: Should we ever ignore if height and width not given?
    if (isGroup || (height > 0 && width > 0) || parseWithoutDimensions)
    {
      if (! isGroup)
      {
        if (isText)
        {
          m_collector->addTextShape(textId, chunk.seqNum);
        }
      }
    }
    else
    {
      MSPUB_DEBUG_MSG(("Height and width not both specified, ignoring. (Height: 0x%x, Width: 0x%x)\n", height, width));
    }
    return true;
  }
}

QuillChunkReference MSPUBParser::parseQuillChunkReference(librevenge::RVNGInputStream *input)
{
  QuillChunkReference ret;
  readU16(input); //FIXME: Can we do something sensible if this is not 0x18 ?
  char name[5];
  for (int i = 0; i < 4; ++i)
  {
    name[i] = (char)readU8(input);
  }
  name[4] = '\0';
  ret.name = name;
  ret.id = readU16(input);
  input->seek(input->tell() + 4, librevenge::RVNG_SEEK_SET); //Seek past what is normally 0x01000000. We don't know what this represents.
  char name2[5];
  for (int i = 0; i < 4; ++i)
  {
    name2[i] = (char)readU8(input);
  }
  name2[4] = '\0';
  ret.name2 = name2;
  ret.offset = readU32(input);
  ret.length = readU32(input);
  return ret;
}

std::vector<unsigned> MSPUBParser::parseTableCellDefinitions(
  librevenge::RVNGInputStream *input, const QuillChunkReference &chunk)
{
  std::vector<unsigned> ret;
  unsigned numElements = readU32(input) + 1;
  input->seek(chunk.offset + 0xC, librevenge::RVNG_SEEK_SET);
  for (unsigned i = 0; i < numElements; ++i)
  {
    ret.push_back(readU32(input));
    // compensate for all but the last offset not including the terminating 0x0D00
    if (i != numElements - 1)
    {
      ret.back() += 2;
    }
  }
  return ret;
}

bool MSPUBParser::parseQuill(librevenge::RVNGInputStream *input)
{
  MSPUB_DEBUG_MSG(("MSPUBParser::parseQuill\n"));
  unsigned chunkReferenceListOffset = 0x18;
  std::list<QuillChunkReference> chunkReferences;
  std::set<unsigned> readChunks; // guard against cycle in the chunk list
  while (chunkReferenceListOffset != 0xffffffff)
  {
    input->seek(chunkReferenceListOffset + 2, librevenge::RVNG_SEEK_SET);
    unsigned short numChunks = readU16(input);
    chunkReferenceListOffset = readU32(input);
    if (readChunks.find(chunkReferenceListOffset) != readChunks.end())
    {
      MSPUB_DEBUG_MSG(("Found a cycle in chunk reference list: a broken file!\n"));
      break;
    }
    readChunks.insert(chunkReferenceListOffset);
    for (unsigned i = 0; i < numChunks; ++i)
    {
      QuillChunkReference quillChunkReference = parseQuillChunkReference(input);
      chunkReferences.push_back(quillChunkReference);
    }
  }
  MSPUB_DEBUG_MSG(("Found %u Quill chunks\n", (unsigned)chunkReferences.size()));
  //Make sure we parse the STRS chunk before the TEXT chunk
  std::list<QuillChunkReference>::const_iterator textChunkReference = chunkReferences.end();
  bool parsedStrs = false;
  bool parsedSyid = false;
  bool parsedFdpc = false;
  bool parsedFdpp = false;
  bool parsedStsh = false;
  bool parsedFont = false;
  std::vector<unsigned> textLengths;
  std::vector<unsigned> textIDs;
  std::vector<unsigned> textOffsets;
  std::map<unsigned, std::vector<unsigned> > tableCellTextEnds;
  unsigned textOffsetAccum = 0;
  std::vector<TextSpanReference> spans;
  std::vector<TextParagraphReference> paras;
  unsigned whichStsh = 0;
  for (std::list<QuillChunkReference>::const_iterator i = chunkReferences.begin(); i != chunkReferences.end(); ++i)
  {
    if (i->name == "TEXT")
    {
      textChunkReference = i;
    }
    else if (i->name == "STRS")
    {
      input->seek(i->offset, librevenge::RVNG_SEEK_SET);
      unsigned numLengths = readU32(input); //Assuming the first DWORD is the number of children and that the next is the remaining length before children start. We are unsure that this is correct.
      input->seek(4 + i->offset + readU32(input), librevenge::RVNG_SEEK_SET);
      for (unsigned j = 0; j < numLengths; ++j)
      {
        unsigned length = readU32(input);
        textLengths.push_back(length);
        textOffsets.push_back(textOffsetAccum);
        textOffsetAccum += length * 2;
      }
      parsedStrs = true;
    }
    else if (i->name == "SYID")
    {
      input->seek(i->offset, librevenge::RVNG_SEEK_SET);
      readU32(input); // Don't know what the first DWORD means.
      unsigned numIDs = readU32(input);
      for (unsigned j = 0; j < numIDs; ++j)
      {
        textIDs.push_back(readU32(input));
      }
      parsedSyid = true;
    }
    else if (i->name == "PL  ")
    {
      input->seek(i->offset, librevenge::RVNG_SEEK_SET);
      parseColors(input, *i);
    }
    else if (i->name == "FDPC")
    {
      input->seek(i->offset, librevenge::RVNG_SEEK_SET);
      std::vector<TextSpanReference> thisBlockSpans = parseCharacterStyles(input, *i);
      spans.insert(spans.end(), thisBlockSpans.begin(), thisBlockSpans.end());
      parsedFdpc |= !thisBlockSpans.empty();
    }
    else if (i->name == "FDPP")
    {
      input->seek(i->offset, librevenge::RVNG_SEEK_SET);
      std::vector<TextParagraphReference> thisBlockParas = parseParagraphStyles(input, *i);
      paras.insert(paras.end(), thisBlockParas.begin(), thisBlockParas.end());
      parsedFdpp |= !thisBlockParas.empty();
    }
    else if (i->name == "STSH")
    {
      if (whichStsh++ == 1)
      {
        input->seek(i->offset, librevenge::RVNG_SEEK_SET);
        parseDefaultStyle(input, *i);
        parsedStsh = true;
      }
    }
    else if (i->name == "FONT")
    {
      input->seek(i->offset, librevenge::RVNG_SEEK_SET);
      parseFonts(input, *i);
      parsedFont = true;
    }
    else if (i->name == "TCD ")
    {
      input->seek(i->offset, librevenge::RVNG_SEEK_SET);
      tableCellTextEnds[i->id] = parseTableCellDefinitions(input, *i);
    }
  }
  if (parsedStrs && parsedSyid && parsedFdpc && parsedFdpp && parsedStsh && parsedFont && textChunkReference != chunkReferences.end())
  {
    input->seek(textChunkReference->offset, librevenge::RVNG_SEEK_SET);
    unsigned bytesRead = 0;
    auto currentTextSpan = spans.begin();
    auto currentTextPara = paras.begin();
    for (unsigned j = 0; j < textIDs.size() && j < textLengths.size(); ++j)
    {
      MSPUB_DEBUG_MSG(("Parsing a text block.\n"));
      std::vector<TextParagraph> readParas;
      std::vector<TextSpan> readSpans;
      std::vector<unsigned char> text;
      for (unsigned k = 0; k < textLengths[j] && currentTextPara != paras.end() && currentTextSpan != spans.end(); ++k)
      {
        text.push_back(readU8(input));
        text.push_back(readU8(input));
        bytesRead += 2;
        if (bytesRead >= currentTextSpan->last - textChunkReference->offset)
        {
          if (!text.empty())
          {
            readSpans.push_back(TextSpan(text, currentTextSpan->charStyle));
            MSPUB_DEBUG_MSG(("Saw text span %d in the current text paragraph.\n", (unsigned)readSpans.size()));
          }
          ++currentTextSpan;
          text.clear();
        }
        if (bytesRead >= currentTextPara->last - textChunkReference->offset)
        {
          if (!text.empty())
          {
            readSpans.push_back(TextSpan(text, currentTextSpan->charStyle));
            MSPUB_DEBUG_MSG(("Saw text span %d in the current text paragraph.\n", (unsigned)readSpans.size()));
          }
          text.clear();
          if (!readSpans.empty())
          {
            readParas.push_back(TextParagraph(readSpans, currentTextPara->paraStyle));
            MSPUB_DEBUG_MSG(("Saw paragraph %d in the current text block.\n", (unsigned)readParas.size()));
          }
          ++currentTextPara;
          readSpans.clear();
        }
      }
      if (!text.empty() && currentTextSpan != spans.end())
      {
        readSpans.push_back(TextSpan(text, currentTextSpan->charStyle));
        MSPUB_DEBUG_MSG(("Saw text span %d in the current text paragraph.\n", (unsigned)readSpans.size()));
      }
      text.clear();
      if (!readSpans.empty() && currentTextPara != paras.end())
      {
        readParas.push_back(TextParagraph(readSpans, currentTextPara->paraStyle));
        MSPUB_DEBUG_MSG(("Saw paragraph %d in the current text block.\n", (unsigned)readParas.size()));
      }
      m_collector->addTextString(readParas, textIDs[j]);
      m_collector->setTextStringOffset(textIDs[j], textOffsets[j]);
      const std::map<unsigned, std::vector<unsigned> >::const_iterator it = tableCellTextEnds.find(j);
      if (it != tableCellTextEnds.end())
        m_collector->setTableCellTextEnds(textIDs[j], it->second);
    }
    textChunkReference = chunkReferences.end();
  }
  return true;
}

void MSPUBParser::parseFonts(librevenge::RVNGInputStream *input, const QuillChunkReference &)
{
  readU32(input);
  unsigned numElements = readU32(input);
  input->seek(input->tell() + 12 + 4 * numElements, librevenge::RVNG_SEEK_SET);
  for (unsigned i = 0; i < numElements; ++i)
  {
    unsigned short nameLength = readU16(input);
    if (nameLength > 0)
    {
      std::vector<unsigned char> name;
      readNBytes(input, nameLength * 2, name);
      m_collector->addFont(name);
    }
    readU32(input);
  }
}

void MSPUBParser::parseDefaultStyle(librevenge::RVNGInputStream *input, const QuillChunkReference &chunk)
{
  readU32(input);
  unsigned numElements = std::min(readU32(input), m_length);
  input->seek(input->tell() + 12, librevenge::RVNG_SEEK_SET);
  std::vector<unsigned> offsets;
  offsets.reserve(numElements);
  for (unsigned i = 0; i < numElements; ++i)
  {
    offsets.push_back(readU32(input));
  }
  for (unsigned i = 0; i < numElements; ++i)
  {
    input->seek(chunk.offset + 20 + offsets[i], librevenge::RVNG_SEEK_SET);
    readU16(input);
    if (i % 2 == 0)
    {
      //FIXME: Does STSH2 hold information for associating style indices in FDPP to indices in STSH1 ?
      m_collector->addDefaultCharacterStyle(getCharacterStyle(input));
    }
    else
    {
      m_collector->addDefaultParagraphStyle(getParagraphStyle(input));
    }
  }
}


void MSPUBParser::parseColors(librevenge::RVNGInputStream *input, const QuillChunkReference &)
{
  unsigned numEntries = readU32(input);
  input->seek(input->tell() + 8, librevenge::RVNG_SEEK_SET);
  for (unsigned i = 0; i < numEntries; ++i)
  {
    unsigned blocksOffset = input->tell();
    unsigned len = readU32(input);
    while (stillReading(input, blocksOffset + len))
    {
      MSPUBBlockInfo info = parseBlock(input, true);
      if (info.id == 0x01)
      {
        m_collector->addTextColor(ColorReference(info.data));
      }
    }
  }
}

std::vector<MSPUBParser::TextParagraphReference> MSPUBParser::parseParagraphStyles(librevenge::RVNGInputStream *input, const QuillChunkReference &chunk)
{
  std::vector<TextParagraphReference> ret;
  unsigned short numEntries = readU16(input);
  input->seek(input->tell() + 6, librevenge::RVNG_SEEK_SET);
  std::vector<unsigned> textOffsets;
  textOffsets.reserve(numEntries);
  std::vector<unsigned short> chunkOffsets;
  textOffsets.reserve(numEntries);
  for (unsigned short i = 0; i < numEntries; ++i)
  {
    textOffsets.push_back(readU32(input));
  }
  for (unsigned short i = 0; i < numEntries; ++i)
  {
    chunkOffsets.push_back(readU16(input));
  }
  unsigned currentSpanBegin = 0;
  for (unsigned short i = 0; i < numEntries; ++i)
  {
    input->seek(chunk.offset + chunkOffsets[i], librevenge::RVNG_SEEK_SET);
    ParagraphStyle style = getParagraphStyle(input);
    ret.push_back(TextParagraphReference(currentSpanBegin, textOffsets[i], style));
    currentSpanBegin = textOffsets[i] + 1;
  }
  return ret;
}

std::vector<MSPUBParser::TextSpanReference> MSPUBParser::parseCharacterStyles(librevenge::RVNGInputStream *input, const QuillChunkReference &chunk)
{
  unsigned short numEntries = readU16(input);
  input->seek(input->tell() + 6, librevenge::RVNG_SEEK_SET);
  std::vector<unsigned> textOffsets;
  textOffsets.reserve(numEntries);
  std::vector<unsigned short> chunkOffsets;
  chunkOffsets.reserve(numEntries);
  std::vector<TextSpanReference> ret;
  for (unsigned short i = 0; i < numEntries; ++i)
  {
    textOffsets.push_back(readU32(input));
  }
  for (unsigned short i = 0; i < numEntries; ++i)
  {
    chunkOffsets.push_back(readU16(input));
  }
  unsigned currentSpanBegin = 0;
  for (unsigned short i = 0; i < numEntries; ++i)
  {
    input->seek(chunk.offset + chunkOffsets[i], librevenge::RVNG_SEEK_SET);
    CharacterStyle style = getCharacterStyle(input);
    currentSpanBegin = textOffsets[i] + 1;
    ret.push_back(TextSpanReference(currentSpanBegin, textOffsets[i], style));
  }
  return ret;
}
ParagraphStyle MSPUBParser::getParagraphStyle(librevenge::RVNGInputStream *input)
{
  ParagraphStyle ret;

  bool isList = false;
  unsigned bulletChar = 0;
  NumberingType numberingType = STANDARD_WESTERN;
  NumberingDelimiter numberingDelimiter = NO_DELIMITER;
  boost::optional<unsigned> numberIfRestarted;

  unsigned offset = input->tell();
  unsigned len = readU32(input);
  while (stillReading(input, offset + len))
  {
    MSPUBBlockInfo info = parseBlock(input, true);
    switch (info.id)
    {
    case PARAGRAPH_ALIGNMENT:
      ret.m_align = (Alignment)(info.data & 0xFF); // Is this correct?
      break;
    case PARAGRAPH_DEFAULT_CHAR_STYLE:
      ret.m_defaultCharStyleIndex = info.data;
      break;
    case PARAGRAPH_LINE_SPACING:
      if (info.data & 1)
      {
        // line spacing expressed in points in the UI,
        // in eighths of an emu in the file format.
        // (WTF??)
        ret.m_lineSpacing = LineSpacingInfo(LINE_SPACING_PT,
                                            static_cast<double>(info.data - 1) / 8 * 72 / EMUS_IN_INCH);
      }
      else if (info.data & 2)
      {
        // line spacing expressed in SP in the UI,
        // in what would be EMUs if font size were 96pt in the file format
        // (WTF??)
        ret.m_lineSpacing = LineSpacingInfo(LINE_SPACING_SP,
                                            static_cast<double>(info.data - 2) / EMUS_IN_INCH * 72 / 96);
      }
      break;
    case PARAGRAPH_SPACE_BEFORE:
      ret.m_spaceBeforeEmu = info.data;
      break;
    case PARAGRAPH_SPACE_AFTER:
      ret.m_spaceAfterEmu = info.data;
      break;
    case PARAGRAPH_FIRST_LINE_INDENT:
      ret.m_firstLineIndentEmu = (int)info.data;
      break;
    case PARAGRAPH_LEFT_INDENT:
      ret.m_leftIndentEmu = info.data;
      break;
    case PARAGRAPH_RIGHT_INDENT:
      ret.m_rightIndentEmu = info.data;
      break;
    case PARAGRAPH_TABS:
      input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
      while (stillReading(input, info.dataOffset + info.dataLength))
      {
        MSPUBBlockInfo tabArrayInfo = parseBlock(input, true);
        if (tabArrayInfo.id == TAB_ARRAY)
        {
          input->seek(tabArrayInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET);
          while (stillReading(input, tabArrayInfo.dataOffset + tabArrayInfo.dataLength))
          {
            MSPUBBlockInfo tabEntryInfo = parseBlock(input, true);
            if (tabEntryInfo.type == GENERAL_CONTAINER)
            {
              input->seek(tabEntryInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET);
              MSPUBBlockInfo tabInfo = parseBlock(input, true);
              if (tabInfo.id == TAB_AMOUNT)
              {
                ret.m_tabStopsInEmu.push_back(tabInfo.data);
              }
            }
          }
        }
      }
      break;
    case PARAGRAPH_LIST_INFO:
    {
      isList = true;
      input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
      while (stillReading(input, info.dataOffset + info.dataLength))
      {
        MSPUBBlockInfo listSubInfo = parseBlock(input, true);
        switch (listSubInfo.id)
        {
        case PARAGRAPH_LIST_NUMBERING_TYPE:
          numberingType = static_cast<NumberingType>(info.data);
          break;
        case PARAGRAPH_LIST_BULLET_CHAR:
          bulletChar = info.data;
          break;
        default:
          break;
        }
      }
      break;
    }
    case PARAGRAPH_LIST_NUMBER_RESTART:
      numberIfRestarted = info.data;
      break;
    case PARAGRAPH_DROP_CAP_LINES:
      ret.m_dropCapLines = info.data;
      break;
    case PARAGRAPH_DROP_CAP_LETTERS:
      ret.m_dropCapLetters = info.data;
      break;
    default:
      break;
    }
  }
  if (isList)
  {
    if (bulletChar)
    {
      ret.m_listInfo = ListInfo(bulletChar);
    }
    else
    {
      ret.m_listInfo = ListInfo(numberIfRestarted, numberingType,
                                numberingDelimiter);
    }
  }

  return ret;
}

CharacterStyle MSPUBParser::getCharacterStyle(librevenge::RVNGInputStream *input)
{
  CharacterStyle style;

  bool seenBold1 = false, seenBold2 = false, seenItalic1 = false, seenItalic2 = false;
  int textSize1 = -1, /* textSize2 = -1,*/ colorIndex = -1;
  boost::optional<unsigned> fontIndex;
  unsigned offset = input->tell();
  unsigned len = readU32(input);
  while (stillReading(input, offset + len))
  {
    MSPUBBlockInfo info = parseBlock(input, true);
    switch (info.id)
    {
    case BOLD_1_ID:
      seenBold1 = true;
      break;
    case BOLD_2_ID:
      seenBold2 = true;
      break;
    case ITALIC_1_ID:
      seenItalic1 = true;
      break;
    case ITALIC_2_ID:
      seenItalic2 = true;
      break;
    case UNDERLINE_ID:
      style.underline = readUnderline(info.data);
      break;
    case TEXT_SIZE_1_ID:
      textSize1 = info.data;
      break;
    case TEXT_SIZE_2_ID:
      // textSize2 = info.data;
      break;
    case BARE_COLOR_INDEX_ID:
      colorIndex = info.data;
      break;
    case COLOR_INDEX_CONTAINER_ID:
      colorIndex = getColorIndex(input, info);
      break;
    case FONT_INDEX_CONTAINER_ID:
      fontIndex = getFontIndex(input, info);
      break;
    case SUPER_SUB_TYPE_ID:
      style.superSubType = static_cast<SuperSubType>(info.data);
      break;
    case OUTLINE_ID:
      style.outline = true;
      break;
    case SHADOW_ID:
      style.shadow = true;
      break;
    case SMALL_CAPS_ID:
      style.smallCaps = true;
      break;
    case ALL_CAPS_ID:
      style.allCaps = true;
      break;
    case EMBOSS_ID:
      style.emboss = true;
      break;
    case ENGRAVE_ID:
      style.engrave = true;
      break;
    case SCALING_ID:
      style.textScale = double(info.data) / 10;
      break;
    case LOCALE_ID:
      style.lcid = info.data;
      break;
    default:
      break;
    }
  }
  //FIXME: Figure out what textSize2 is used for. Can we find a document where it differs from textSize1 ?
  // textSize2 = textSize1;
  boost::optional<double> dTextSize;
  if (textSize1 != -1)
  {
    dTextSize = textSize1 * (double(POINTS_IN_INCH) / EMUS_IN_INCH);
  }
  // FIXME: What's this with foo1 && foo2? I've only seen foo1 in 2k2
  // files and either just foo1 or foo1+foo2 in 2k7 files...
  style.italic = seenItalic1; // && seenItalic2;
  style.bold = seenBold1; // && seenBold2;
  (void) seenItalic2;
  (void) seenBold2;
  style.textSizeInPt = dTextSize;
  style.colorIndex = getColorIndexByQuillEntry(colorIndex);
  style.fontIndex = fontIndex;

  return style;
}

unsigned MSPUBParser::getFontIndex(librevenge::RVNGInputStream *input, const MSPUBBlockInfo &info)
{
  MSPUB_DEBUG_MSG(("In getFontIndex\n"));
  input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
  while (stillReading(input, info.dataOffset + info.dataLength))
  {
    MSPUBBlockInfo subInfo = parseBlock(input, true);
    if (subInfo.type == GENERAL_CONTAINER)
    {
      input->seek(subInfo.dataOffset + 4, librevenge::RVNG_SEEK_SET);
      if (stillReading(input, subInfo.dataOffset + subInfo.dataLength))
      {
        MSPUBBlockInfo subSubInfo = parseBlock(input, true);
        skipBlock(input, info);
        return subSubInfo.data;
      }
    }
  }
  return 0;
}

int MSPUBParser::getColorIndex(librevenge::RVNGInputStream *input, const MSPUBBlockInfo &info)
{
  input->seek(info.dataOffset + 4, librevenge::RVNG_SEEK_SET);
  while (stillReading(input, info.dataOffset + info.dataLength))
  {
    MSPUBBlockInfo subInfo = parseBlock(input, true);
    if (subInfo.id == COLOR_INDEX_ID)
    {
      skipBlock(input, info);
      MSPUB_DEBUG_MSG(("Found color index 0x%x\n", (unsigned)subInfo.data));
      return subInfo.data;
    }
  }
  MSPUB_DEBUG_MSG(("Failed to find color index!\n"));
  return -1;
}

bool MSPUBParser::parseEscher(librevenge::RVNGInputStream *input)
{
  MSPUB_DEBUG_MSG(("MSPUBParser::parseEscher\n"));
  EscherContainerInfo fakeroot;
  fakeroot.initial = 0;
  fakeroot.type = 0;
  fakeroot.contentsOffset = input->tell();
  fakeroot.contentsLength = (unsigned long)-1; //FIXME: Get the actual length
  EscherContainerInfo dg, dgg;
  //Note: this assumes that dgg comes before any dg with images.
  if (findEscherContainer(input, fakeroot, dgg, OFFICE_ART_DGG_CONTAINER))
  {
    EscherContainerInfo bsc;
    if (findEscherContainer(input, fakeroot, bsc, OFFICE_ART_B_STORE_CONTAINER))
    {
      unsigned short currentDelayIndex = 1;
      while (stillReading(input, bsc.contentsOffset + bsc.contentsLength))
      {
        unsigned begin = input->tell();
        input->seek(begin + 10, librevenge::RVNG_SEEK_SET);
        if (!(readU32(input) == 0 && readU32(input) == 0 && readU32(input) == 0 && readU32(input) == 0))
        {
          m_escherDelayIndices.push_back(currentDelayIndex++);
        }
        else
        {
          m_escherDelayIndices.push_back(-1);
        }
        input->seek(begin + 44, librevenge::RVNG_SEEK_SET);
      }
    }
    input->seek(dgg.contentsOffset + dgg.contentsLength + getEscherElementTailLength(OFFICE_ART_DGG_CONTAINER), librevenge::RVNG_SEEK_SET);
  }
  while (findEscherContainer(input, fakeroot, dg, OFFICE_ART_DG_CONTAINER))
  {
    EscherContainerInfo spgr;
    while (findEscherContainer(input, dg, spgr, OFFICE_ART_SPGR_CONTAINER))
    {
      Coordinate c1, c2;
      parseShapeGroup(input, spgr, c1, c2);
    }
    input->seek(input->tell() + getEscherElementTailLength(OFFICE_ART_DG_CONTAINER), librevenge::RVNG_SEEK_SET);
  }
  return true;
}

void MSPUBParser::parseShapeGroup(librevenge::RVNGInputStream *input, const EscherContainerInfo &spgr, Coordinate parentCoordinateSystem, Coordinate parentGroupAbsoluteCoord, unsigned depth)
{
  if (depth > MAX_SHAPE_GROUP_DEPTH)
    return;
  EscherContainerInfo shapeOrGroup;
  std::set<unsigned short> types;
  types.insert(OFFICE_ART_SPGR_CONTAINER);
  types.insert(OFFICE_ART_SP_CONTAINER);
  while (findEscherContainerWithTypeInSet(input, spgr, shapeOrGroup, types))
  {
    switch (shapeOrGroup.type)
    {
    case OFFICE_ART_SPGR_CONTAINER:
      m_collector->beginGroup();
      parseShapeGroup(input, shapeOrGroup, parentCoordinateSystem, parentGroupAbsoluteCoord, depth + 1);
      m_collector->endGroup();
      break;
    case OFFICE_ART_SP_CONTAINER:
      parseEscherShape(input, shapeOrGroup, parentCoordinateSystem, parentGroupAbsoluteCoord);
      break;
    }
    input->seek(shapeOrGroup.contentsOffset + shapeOrGroup.contentsLength + getEscherElementTailLength(shapeOrGroup.type), librevenge::RVNG_SEEK_SET);
  }
}

void MSPUBParser::parseEscherShape(librevenge::RVNGInputStream *input, const EscherContainerInfo &sp, Coordinate &parentCoordinateSystem, Coordinate &parentGroupAbsoluteCoord)
{
  Coordinate thisParentCoordinateSystem = parentCoordinateSystem;
  bool definesRelativeCoordinates = false;
  EscherContainerInfo cData;
  EscherContainerInfo cAnchor;
  EscherContainerInfo cFopt;
  EscherContainerInfo cTertiaryFopt;
  EscherContainerInfo cFsp;
  EscherContainerInfo cFspgr;
  unsigned shapeFlags = 0;
  bool isGroupLeader = false;
  ShapeType st = RECTANGLE;
  if (findEscherContainer(input, sp, cFspgr, OFFICE_ART_FSPGR))
  {
    input->seek(cFspgr.contentsOffset, librevenge::RVNG_SEEK_SET);
    parentCoordinateSystem.m_xs = readU32(input);
    parentCoordinateSystem.m_ys = readU32(input);
    parentCoordinateSystem.m_xe = readU32(input);
    parentCoordinateSystem.m_ye = readU32(input);
    parentCoordinateSystem.arrange();
    definesRelativeCoordinates = true;
  }
  input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET);
  if (findEscherContainer(input, sp, cFsp, OFFICE_ART_FSP))
  {
    st = (ShapeType)(cFsp.initial >> 4);
    std::map<unsigned short, unsigned> fspData = extractEscherValues(input, cFsp);
    input->seek(cFsp.contentsOffset + 4, librevenge::RVNG_SEEK_SET);
    shapeFlags = readU32(input);
    isGroupLeader = shapeFlags & SF_GROUP;
  }
  input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET);
  if (findEscherContainer(input, sp, cData, OFFICE_ART_CLIENT_DATA))
  {
    std::map<unsigned short, unsigned> dataValues = extractEscherValues(input, cData);
    unsigned *shapeSeqNum = getIfExists(dataValues, FIELDID_SHAPE_ID);
    if (shapeSeqNum)
    {
      m_collector->setShapeType(*shapeSeqNum, st);
      m_collector->setShapeFlip(*shapeSeqNum, shapeFlags & SF_FLIP_V, shapeFlags & SF_FLIP_H);
      input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET);
      if (isGroupLeader)
      {
        m_collector->setCurrentGroupSeqNum(*shapeSeqNum);
      }
      else
      {
        m_collector->setShapeOrder(*shapeSeqNum);
      }
      std::set<unsigned short> anchorTypes;
      anchorTypes.insert(OFFICE_ART_CLIENT_ANCHOR);
      anchorTypes.insert(OFFICE_ART_CHILD_ANCHOR);
      bool foundAnchor;
      if ((foundAnchor = findEscherContainerWithTypeInSet(input, sp, cAnchor, anchorTypes)) || isGroupLeader)
      {
        bool rotated90 = false;
        MSPUB_DEBUG_MSG(("Found Escher data for %s of seqnum 0x%x\n", isGroupLeader ? "group" : "shape", *shapeSeqNum));
        boost::optional<std::map<unsigned short, unsigned> > maybe_tertiaryFoptValues;
        input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET);
        if (findEscherContainer(input, sp, cTertiaryFopt, OFFICE_ART_TERTIARY_FOPT))
        {
          maybe_tertiaryFoptValues = extractEscherValues(input, cTertiaryFopt);
        }
        if (bool(maybe_tertiaryFoptValues))
        {
          const std::map<unsigned short, unsigned> &tertiaryFoptValues =
            maybe_tertiaryFoptValues.get();
          const unsigned *ptr_pictureRecolor = getIfExists_const(tertiaryFoptValues,
                                                                 FIELDID_PICTURE_RECOLOR);
          if (ptr_pictureRecolor)
          {
            m_collector->setShapePictureRecolor(*shapeSeqNum,
                                                ColorReference(*ptr_pictureRecolor));
          }
        }
        input->seek(sp.contentsOffset, librevenge::RVNG_SEEK_SET);
        if (findEscherContainer(input, sp, cFopt, OFFICE_ART_FOPT))
        {
          FOPTValues foptValues = extractFOPTValues(input, cFopt);
          unsigned *pxId = getIfExists(foptValues.m_scalarValues, FIELDID_PXID);
          if (pxId)
          {
            MSPUB_DEBUG_MSG(("Current Escher shape has pxId %d\n", *pxId));
            if (*pxId > 0 && *pxId <= m_escherDelayIndices.size() && m_escherDelayIndices[*pxId - 1] >= 0)
            {
              m_collector->setShapeImgIndex(*shapeSeqNum, m_escherDelayIndices[*pxId - 1]);
            }
            else
            {
              MSPUB_DEBUG_MSG(("Couldn't find corresponding escherDelay index\n"));
            }
            unsigned *ptr_pictureBrightness = getIfExists(foptValues.m_scalarValues, FIELDID_PICTURE_BRIGHTNESS);
            if (ptr_pictureBrightness)
            {
              m_collector->setShapePictureBrightness(*shapeSeqNum, (int)(*ptr_pictureBrightness));
            }
            unsigned *ptr_pictureContrast = getIfExists(foptValues.m_scalarValues, FIELDID_PICTURE_CONTRAST);
            if (ptr_pictureContrast)
            {
              m_collector->setShapePictureContrast(*shapeSeqNum, (int)(*ptr_pictureContrast));
            }
          }
          unsigned *ptr_lineBackColor =
            getIfExists(foptValues.m_scalarValues, FIELDID_LINE_BACK_COLOR);
          if (ptr_lineBackColor &&
              static_cast<int>(*ptr_lineBackColor) != -1)
          {
            m_collector->setShapeLineBackColor(
              *shapeSeqNum, ColorReference(*ptr_lineBackColor));
          }
          unsigned *ptr_lineColor = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_COLOR);
          unsigned *ptr_lineFlags = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_STYLE_BOOL_PROPS);
          unsigned *ptr_geomFlags = getIfExists(
                                      foptValues.m_scalarValues, FIELDID_GEOM_BOOL_PROPS);
          bool useLine = lineExistsByFlagPointer(
                           ptr_lineFlags, ptr_geomFlags);
          bool skipIfNotBg = false;
          std::shared_ptr<Fill> ptr_fill = getNewFill(foptValues.m_scalarValues, skipIfNotBg, foptValues.m_complexValues);
          unsigned lineWidth = 0;
          if (useLine)
          {
            if (ptr_lineColor)
            {
              unsigned *ptr_lineWidth = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_WIDTH);
              lineWidth = ptr_lineWidth ? *ptr_lineWidth : 9525;
              m_collector->addShapeLine(*shapeSeqNum, Line(ColorReference(*ptr_lineColor), lineWidth, true));
            }
            else
            {
              if (bool(maybe_tertiaryFoptValues))
              {
                std::map<unsigned short, unsigned> &tertiaryFoptValues =
                  maybe_tertiaryFoptValues.get();
                unsigned *ptr_tertiaryLineFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_STYLE_BOOL_PROPS);
                if (lineExistsByFlagPointer(ptr_tertiaryLineFlags))
                {
                  unsigned *ptr_topColor = getIfExists(tertiaryFoptValues, FIELDID_LINE_TOP_COLOR);
                  unsigned *ptr_topWidth = getIfExists(tertiaryFoptValues, FIELDID_LINE_TOP_WIDTH);
                  unsigned *ptr_topFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_TOP_BOOL_PROPS);
                  unsigned *ptr_rightColor = getIfExists(tertiaryFoptValues, FIELDID_LINE_RIGHT_COLOR);
                  unsigned *ptr_rightWidth = getIfExists(tertiaryFoptValues, FIELDID_LINE_RIGHT_WIDTH);
                  unsigned *ptr_rightFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_RIGHT_BOOL_PROPS);
                  unsigned *ptr_bottomColor = getIfExists(tertiaryFoptValues, FIELDID_LINE_BOTTOM_COLOR);
                  unsigned *ptr_bottomWidth = getIfExists(tertiaryFoptValues, FIELDID_LINE_BOTTOM_WIDTH);
                  unsigned *ptr_bottomFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_BOTTOM_BOOL_PROPS);
                  unsigned *ptr_leftColor = getIfExists(tertiaryFoptValues, FIELDID_LINE_LEFT_COLOR);
                  unsigned *ptr_leftWidth = getIfExists(tertiaryFoptValues, FIELDID_LINE_LEFT_WIDTH);
                  unsigned *ptr_leftFlags = getIfExists(tertiaryFoptValues, FIELDID_LINE_LEFT_BOOL_PROPS);

                  bool topExists = ptr_topColor && lineExistsByFlagPointer(ptr_topFlags);
                  bool rightExists = ptr_rightColor && lineExistsByFlagPointer(ptr_rightFlags);
                  bool bottomExists = ptr_bottomColor && lineExistsByFlagPointer(ptr_bottomFlags);
                  bool leftExists = ptr_leftColor && lineExistsByFlagPointer(ptr_leftFlags);
                  if (ptr_topWidth)
                  {
                    lineWidth = *ptr_topWidth;
                  }

                  m_collector->addShapeLine(*shapeSeqNum,
                                            topExists ? Line(ColorReference(*ptr_topColor), ptr_topWidth ? *ptr_topWidth : 9525, true) :
                                            Line(ColorReference(0), 0, false));
                  m_collector->addShapeLine(*shapeSeqNum,
                                            rightExists ? Line(ColorReference(*ptr_rightColor), ptr_rightWidth ? *ptr_rightWidth : 9525, true) :
                                            Line(ColorReference(0), 0, false));
                  m_collector->addShapeLine(*shapeSeqNum,
                                            bottomExists ? Line(ColorReference(*ptr_bottomColor), ptr_bottomWidth ? *ptr_bottomWidth : 9525, true) :
                                            Line(ColorReference(0), 0, false));
                  m_collector->addShapeLine(*shapeSeqNum,
                                            leftExists ? Line(ColorReference(*ptr_leftColor), ptr_leftWidth ? *ptr_leftWidth : 9525, true) :
                                            Line(ColorReference(0), 0, false));

                  // Amazing feat of Microsoft engineering:
                  // The detailed interaction of four flags describes ONE true/false property!

                  if (ptr_leftFlags &&
                      (*ptr_leftFlags & FLAG_USE_LEFT_INSET_PEN) &&
                      (!(*ptr_leftFlags & FLAG_USE_LEFT_INSET_PEN_OK) || (*ptr_leftFlags & FLAG_LEFT_INSET_PEN_OK)) &&
                      (*ptr_leftFlags & FLAG_LEFT_INSET_PEN))
                  {
                    m_collector->setShapeBorderPosition(*shapeSeqNum, INSIDE_SHAPE);
                  }
                  else
                  {
                    m_collector->setShapeBorderPosition(*shapeSeqNum, HALF_INSIDE_SHAPE);
                  }
                }
              }
            }
          }
          if (ptr_fill)
          {
            m_collector->setShapeFill(*shapeSeqNum, ptr_fill, skipIfNotBg);
          }
          int *ptr_adjust1 = (int *)getIfExists(foptValues.m_scalarValues, FIELDID_ADJUST_VALUE_1);
          int *ptr_adjust2 = (int *)getIfExists(foptValues.m_scalarValues, FIELDID_ADJUST_VALUE_2);
          int *ptr_adjust3 = (int *)getIfExists(foptValues.m_scalarValues, FIELDID_ADJUST_VALUE_3);
          if (ptr_adjust1)
          {
            m_collector->setAdjustValue(*shapeSeqNum, 0, *ptr_adjust1);
          }
          if (ptr_adjust2)
          {
            m_collector->setAdjustValue(*shapeSeqNum, 1, *ptr_adjust2);
          }
          if (ptr_adjust3)
          {
            m_collector->setAdjustValue(*shapeSeqNum, 2, *ptr_adjust3);
          }
          int *ptr_rotation = (int *)getIfExists(foptValues.m_scalarValues, FIELDID_ROTATION);
          if (ptr_rotation)
          {
            double rotation = doubleModulo(toFixedPoint(*ptr_rotation), 360);
            m_collector->setShapeRotation(*shapeSeqNum, short(rotation));
            //FIXME : make MSPUBCollector handle double shape rotations
            rotated90 = (rotation >= 45 && rotation < 135) || (rotation >= 225 && rotation < 315);

          }
          unsigned *ptr_left = getIfExists(foptValues.m_scalarValues, FIELDID_DY_TEXT_LEFT);
          unsigned *ptr_top = getIfExists(foptValues.m_scalarValues, FIELDID_DY_TEXT_TOP);
          unsigned *ptr_right = getIfExists(foptValues.m_scalarValues, FIELDID_DY_TEXT_RIGHT);
          unsigned *ptr_bottom = getIfExists(foptValues.m_scalarValues, FIELDID_DY_TEXT_BOTTOM);
          m_collector->setShapeMargins(*shapeSeqNum, ptr_left ? *ptr_left : DEFAULT_MARGIN,
                                       ptr_top ? *ptr_top : DEFAULT_MARGIN,
                                       ptr_right ? *ptr_right : DEFAULT_MARGIN,
                                       ptr_bottom ? *ptr_bottom : DEFAULT_MARGIN);
          unsigned *ptr_lineDashing = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_DASHING);
          unsigned *ptr_lineEndcapStyle = getIfExists(foptValues.m_scalarValues, FIELDID_LINE_ENDCAP_STYLE);
          DotStyle dotStyle = RECT_DOT;
          if (ptr_lineEndcapStyle)
          {
            switch (*ptr_lineEndcapStyle)
            {
            case 0:
              dotStyle = ROUND_DOT;
              break;
            default:
              break;
            }
          }
          if (ptr_lineDashing)
          {
            m_collector->setShapeDash(*shapeSeqNum, getDash(
                                        static_cast<MSPUBDashStyle>(*ptr_lineDashing), lineWidth,
                                        dotStyle));
          }

          if (bool(maybe_tertiaryFoptValues))
          {
            std::map<unsigned short, unsigned> &tertiaryFoptValues = maybe_tertiaryFoptValues.get();
            unsigned *ptr_numColumns = getIfExists(tertiaryFoptValues, FIELDID_NUM_COLUMNS);
            if (ptr_numColumns)
            {
              m_collector->setShapeNumColumns(*shapeSeqNum, *ptr_numColumns);
            }
            unsigned *ptr_columnSpacing = getIfExists(tertiaryFoptValues, FIELDID_COLUMN_SPACING);
            if (ptr_columnSpacing)
            {
              m_collector->setShapeColumnSpacing(*shapeSeqNum, *ptr_columnSpacing);
            }
          }
          unsigned *ptr_beginArrowStyle = getIfExists(foptValues.m_scalarValues,
                                                      FIELDID_BEGIN_ARROW_STYLE);
          unsigned *ptr_beginArrowWidth = getIfExists(foptValues.m_scalarValues,
                                                      FIELDID_BEGIN_ARROW_WIDTH);
          unsigned *ptr_beginArrowHeight = getIfExists(foptValues.m_scalarValues,
                                                       FIELDID_BEGIN_ARROW_HEIGHT);
          m_collector->setShapeBeginArrow(*shapeSeqNum, Arrow(
                                            ptr_beginArrowStyle ? (ArrowStyle)(*ptr_beginArrowStyle) :
                                            NO_ARROW,
                                            ptr_beginArrowWidth ? (ArrowSize)(*ptr_beginArrowWidth) :
                                            MEDIUM,
                                            ptr_beginArrowHeight ? (ArrowSize)(*ptr_beginArrowHeight) :
                                            MEDIUM));
          unsigned *ptr_endArrowStyle = getIfExists(foptValues.m_scalarValues,
                                                    FIELDID_END_ARROW_STYLE);
          unsigned *ptr_endArrowWidth = getIfExists(foptValues.m_scalarValues,
                                                    FIELDID_END_ARROW_WIDTH);
          unsigned *ptr_endArrowHeight = getIfExists(foptValues.m_scalarValues,
                                                     FIELDID_END_ARROW_HEIGHT);
          m_collector->setShapeEndArrow(*shapeSeqNum, Arrow(
                                          ptr_endArrowStyle ? (ArrowStyle)(*ptr_endArrowStyle) :
                                          NO_ARROW,
                                          ptr_endArrowWidth ? (ArrowSize)(*ptr_endArrowWidth) :
                                          MEDIUM,
                                          ptr_endArrowHeight ? (ArrowSize)(*ptr_endArrowHeight) :
                                          MEDIUM));

          unsigned *shadowBoolProps = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_BOOL_PROPS);
          if (shadowBoolProps)
          {
            unsigned shadowProps = *shadowBoolProps;
            if ((shadowProps & FLAG_USE_FSHADOW) && (shadowProps & FLAG_USE_SHADOW))
            {
              unsigned *ptr_shadowType = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_TYPE);
              auto shadowType = static_cast<ShadowType>(ptr_shadowType ? *ptr_shadowType : 0);
              unsigned *shadowColor = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_COLOR);
              unsigned *shadowHColor = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_HIGHLIGHT);
              unsigned *shadowOpacity = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_OPACITY);
              unsigned *shadowOffsetX = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_OFFSET_X);
              unsigned *shadowOffsetY = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_OFFSET_Y);
              unsigned *shadowOffsetX2 = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_SECOND_OFFSET_X);
              unsigned *shadowOffsetY2 = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_SECOND_OFFSET_Y);
              unsigned *shadowOriginX = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_ORIGIN_X);
              unsigned *shadowOriginY = getIfExists(foptValues.m_scalarValues, FIELDID_SHADOW_ORIGIN_Y);
              m_collector->setShapeShadow(*shapeSeqNum, Shadow(shadowType,
                                                               shadowOffsetX ? static_cast<int>(*shadowOffsetX) : 0x6338,
                                                               shadowOffsetY ? static_cast<int>(*shadowOffsetY) : 0x6338,
                                                               shadowOffsetX2 ? static_cast<int>(*shadowOffsetX2) : 0,
                                                               shadowOffsetY2 ? static_cast<int>(*shadowOffsetY2) : 0,
                                                               shadowOriginX ? toFixedPoint(static_cast<int>(*shadowOriginX)) : 0,
                                                               shadowOriginY ? toFixedPoint(static_cast<int>(*shadowOriginY)) : 0,
                                                               toFixedPoint(shadowOpacity ? static_cast<int>(*shadowOpacity) : 0x10000),
                                                               ColorReference(shadowColor ? *shadowColor : 0x00808080),
                                                               ColorReference(shadowHColor ? *shadowHColor : 0x00CBCBCB)
                                                              ));

            }
          }

          const std::vector<unsigned char> vertexData = foptValues.m_complexValues[FIELDID_P_VERTICES];
          if (!vertexData.empty())
          {
            unsigned *p_geoRight = getIfExists(foptValues.m_scalarValues,
                                               FIELDID_GEO_RIGHT);
            unsigned *p_geoBottom = getIfExists(foptValues.m_scalarValues,
                                                FIELDID_GEO_BOTTOM);
            const std::vector<unsigned char> segmentData = foptValues.m_complexValues[FIELDID_P_SEGMENTS];
            const std::vector<unsigned char> guideData = foptValues.m_complexValues[FIELDID_P_GUIDES];
            m_collector->setShapeCustomPath(*shapeSeqNum, getDynamicCustomShape(vertexData, segmentData,
                                                                                guideData, p_geoRight ? *p_geoRight : 21600,
                                                                                p_geoBottom ? *p_geoBottom : 21600));
          }
          const std::vector<unsigned char> wrapVertexData = foptValues.m_complexValues[FIELDID_P_WRAPPOLYGONVERTICES];
          if (!wrapVertexData.empty())
          {
            std::vector<Vertex> ret = parseVertices(wrapVertexData);
            m_collector->setShapeClipPath(*shapeSeqNum, ret);
          }
        }
        if (foundAnchor)
        {
          Coordinate absolute;
          if (cAnchor.type == OFFICE_ART_CLIENT_ANCHOR)
          {
            std::map<unsigned short, unsigned> anchorData = extractEscherValues(input, cAnchor);
            absolute = Coordinate(anchorData[FIELDID_XS],
                                  anchorData[FIELDID_YS], anchorData[FIELDID_XE],
                                  anchorData[FIELDID_YE]);
          }
          else if (cAnchor.type == OFFICE_ART_CHILD_ANCHOR)
          {
            input->seek(cAnchor.contentsOffset, librevenge::RVNG_SEEK_SET);
            int coordSystemWidth = int64_t(thisParentCoordinateSystem.m_xe) - thisParentCoordinateSystem.m_xs;
            if (coordSystemWidth == 0)
              coordSystemWidth = 1;
            int coordSystemHeight = thisParentCoordinateSystem.m_ye - thisParentCoordinateSystem.m_ys;
            if (coordSystemHeight == 0)
              coordSystemHeight = 1;
            int groupWidth = int64_t(parentGroupAbsoluteCoord.m_xe) - parentGroupAbsoluteCoord.m_xs;
            int groupHeight = int64_t(parentGroupAbsoluteCoord.m_ye) - parentGroupAbsoluteCoord.m_ys;
            double widthScale = (double)groupWidth / coordSystemWidth;
            double heightScale = (double)groupHeight / coordSystemHeight;
            int xs = (readU32(input) - thisParentCoordinateSystem.m_xs) * widthScale + parentGroupAbsoluteCoord.m_xs;
            int ys = (readU32(input) - thisParentCoordinateSystem.m_ys) * heightScale + parentGroupAbsoluteCoord.m_ys;
            int xe = (readU32(input) - thisParentCoordinateSystem.m_xs) * widthScale + parentGroupAbsoluteCoord.m_xs;
            int ye = (readU32(input) - thisParentCoordinateSystem.m_ys) * heightScale + parentGroupAbsoluteCoord.m_ys;

            absolute = Coordinate(xs, ys, xe, ye);
          }
          if (rotated90)
          {
            int initialWidth = int64_t(absolute.m_xe) - absolute.m_xs;
            int initialHeight = int64_t(absolute.m_ye) - absolute.m_ys;
            int centerX = int64_t(absolute.m_xs) + initialWidth / 2;
            int centerY = int64_t(absolute.m_ys) + initialHeight / 2;
            int xs = centerX - initialHeight / 2;
            int ys = centerY - initialWidth / 2;
            int xe = xs + initialHeight;
            int ye = ys + initialWidth;
            absolute = Coordinate(xs, ys, xe, ye);
          }
          m_collector->setShapeCoordinatesInEmu(*shapeSeqNum,
                                                absolute.m_xs,
                                                absolute.m_ys,
                                                absolute.m_xe,
                                                absolute.m_ye);
          if (definesRelativeCoordinates)
          {
            parentGroupAbsoluteCoord = absolute;
          }
        }
      }
    }
  }
}

std::shared_ptr<Fill> MSPUBParser::getNewFill(const std::map<unsigned short, unsigned> &foptProperties,
                                              bool &skipIfNotBg, std::map<unsigned short, std::vector<unsigned char> > &foptValues)
{
  const FillType *ptr_fillType = (FillType *)getIfExists_const(foptProperties, FIELDID_FILL_TYPE);
  FillType fillType = ptr_fillType ? *ptr_fillType : SOLID;
  switch (fillType)
  {
  case SOLID:
  {
    const unsigned *ptr_fillColor = getIfExists_const(foptProperties, FIELDID_FILL_COLOR);
    const unsigned *ptr_fieldStyleProps = getIfExists_const(foptProperties, FIELDID_FIELD_STYLE_BOOL_PROPS);
    skipIfNotBg = ptr_fieldStyleProps && (*ptr_fieldStyleProps & 0xF0) == 0;
    if (ptr_fillColor && !skipIfNotBg)
    {
      const unsigned *ptr_fillOpacity = getIfExists_const(foptProperties, FIELDID_FILL_OPACITY);
      return std::shared_ptr<Fill>(new SolidFill(ColorReference(*ptr_fillColor), ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1, m_collector));
    }
    return std::shared_ptr<Fill>();
  }
  case SHADE_SHAPE:
  case SHADE_CENTER:
  case SHADE:
  case SHADE_SCALE:
  {
    int angle;
    const int *ptr_angle = (const int *)getIfExists_const(foptProperties, FIELDID_FILL_ANGLE);
    const unsigned *ptr_fillColor = getIfExists_const(foptProperties, FIELDID_FILL_COLOR);
    const unsigned *ptr_fillBackColor = getIfExists_const(foptProperties, FIELDID_FILL_BACK_COLOR);
    unsigned fill = ptr_fillColor ? *ptr_fillColor : 0x00FFFFFFF;
    unsigned fillBack = ptr_fillBackColor ? *ptr_fillBackColor : 0x00FFFFFF;
    ColorReference firstColor(fill, fill);
    ColorReference secondColor(fill, fillBack);
    const unsigned *ptr_fillOpacity = getIfExists_const(foptProperties, FIELDID_FILL_OPACITY);
    const unsigned *ptr_fillBackOpacity = getIfExists_const(foptProperties, FIELDID_FILL_BACK_OPACITY);
    const unsigned *ptr_fillFocus = getIfExists_const(foptProperties, FIELDID_FILL_FOCUS);
    short fillFocus = ptr_fillFocus ? int((*ptr_fillFocus << 16) >> 16) : 0;
    angle = ptr_angle ? *ptr_angle : 0;
    angle >>= 16; //it's actually only 16 bits
    // Don't try to figure out what sense the following switch statement makes.
    // The angles are just offset by 90 degrees in the file format in some cases.
    // It seems totally arbitrary -- maybe an MS bug ?
    switch (angle)
    {
    case -135:
      angle = -45;
      break;
    case -45:
      angle = 225;
      break;
    default:
      break;
    }
    double fillLeftVal = 0.0;
    const unsigned *ptr_fillLeft = getIfExists_const(foptProperties, FIELDID_FILL_TO_LEFT);
    if (ptr_fillLeft)
      fillLeftVal = toFixedPoint(*ptr_fillLeft);
    double fillTopVal = 0.0;
    const unsigned *ptr_fillTop = getIfExists_const(foptProperties, FIELDID_FILL_TO_TOP);
    if (ptr_fillTop)
      fillTopVal = toFixedPoint(*ptr_fillTop);
    double fillRightVal = 0.0;
    const unsigned *ptr_fillRight = getIfExists_const(foptProperties, FIELDID_FILL_TO_RIGHT);
    if (ptr_fillRight)
      fillRightVal = toFixedPoint(*ptr_fillRight);
    double fillBottomVal = 0.0;
    const unsigned *ptr_fillBottom = getIfExists_const(foptProperties, FIELDID_FILL_TO_BOTTOM);
    if (ptr_fillBottom)
      fillBottomVal = toFixedPoint(*ptr_fillBottom);

    std::shared_ptr<GradientFill> ret(new GradientFill(m_collector, angle, (int)fillType));
    ret->setFillCenter(fillLeftVal, fillTopVal, fillRightVal, fillBottomVal);

    const unsigned *ptr_fillGrad = getIfExists_const(foptProperties, FIELDID_FILL_SHADE_COMPLEX);
    if (ptr_fillGrad)
    {
      const std::vector<unsigned char> gradientData = foptValues[FIELDID_FILL_SHADE_COMPLEX];
      if (gradientData.size() > 6)
      {
        unsigned short numEntries = gradientData[0] | (gradientData[1] << 8);
        unsigned offs = 6;
        const unsigned maxEntries = (gradientData.size() - offs) / 8;
        if (numEntries > maxEntries)
          numEntries = maxEntries;
        for (unsigned i = 0; i < numEntries; ++i)
        {
          unsigned color = gradientData[offs] | (unsigned(gradientData[offs + 1]) << 8) | (unsigned(gradientData[offs + 2]) << 16) | (unsigned(gradientData[offs + 3]) << 24);
          offs += 4;
          auto posi = (int)(toFixedPoint(gradientData[offs] | (unsigned(gradientData[offs + 1]) << 8) | (unsigned(gradientData[offs + 2]) << 16) | (unsigned(gradientData[offs + 3]) << 24)) * 100);
          offs += 4;
          ColorReference sColor(color, color);
          if (fillFocus ==  0)
            ret->addColor(sColor, posi, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
          else if (fillFocus == 100)
            ret->addColorReverse(sColor, 100 - posi, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
          else if (fillFocus > 0)
            ret->addColor(sColor, posi / 2, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
          else if (fillFocus < 0)
            ret->addColorReverse(sColor, (100 - posi) / 2, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
        }
        if ((fillFocus < 0) || ((fillFocus > 0) && (fillFocus < 100)))
          ret->completeComplexFill();
      }
    }
    else
    {
      if (fillFocus ==  0)
      {
        ret->addColor(firstColor, 0, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
        ret->addColor(secondColor, 100, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1);
      }
      else if (fillFocus == 100)
      {
        ret->addColor(secondColor, 0, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1);
        ret->addColor(firstColor, 100, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
      }
      else if (fillFocus > 0)
      {
        ret->addColor(secondColor, 0, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1);
        ret->addColor(firstColor, fillFocus, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
        ret->addColor(secondColor, 100, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1);

//        ret->addColor(firstColor, 0, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
//        ret->addColor(secondColor, fillFocus, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1);
//        ret->addColor(firstColor, 100, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
      }
      else if (fillFocus < 0)
      {
        ret->addColor(firstColor, 0, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
        ret->addColor(secondColor, 100 + fillFocus, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1);
        ret->addColor(firstColor, 100, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);

//        ret->addColor(secondColor, 0, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1);
//        ret->addColor(firstColor, 100 + fillFocus, ptr_fillOpacity ? (double)(*ptr_fillOpacity) / 0xFFFF : 1);
//        ret->addColor(secondColor, 100, ptr_fillBackOpacity ? (double)(*ptr_fillBackOpacity) / 0xFFFF : 1);
      }
    }
    return ret;
  }
  case TEXTURE:
  case BITMAP:
  {
    // in the case the shape is rotated we must rotate the image too
    int rotation = 0;
    const int *ptr_rotation = (const int *)getIfExists_const(foptProperties, FIELDID_ROTATION);
    if (ptr_rotation)
      rotation = (int)doubleModulo(toFixedPoint(*ptr_rotation), 360);
    const unsigned *ptr_bgPxId = getIfExists_const(foptProperties, FIELDID_BG_PXID);
    if (ptr_bgPxId && *ptr_bgPxId > 0 && *ptr_bgPxId <= m_escherDelayIndices.size() && m_escherDelayIndices[*ptr_bgPxId - 1] >= 0)
    {
      return std::shared_ptr<Fill>(new ImgFill(m_escherDelayIndices[*ptr_bgPxId - 1], m_collector, fillType == TEXTURE, rotation));
    }
    return std::shared_ptr<Fill>();
  }
  case PATTERN:
  {
    const unsigned *ptr_bgPxId = getIfExists_const(foptProperties, FIELDID_BG_PXID);
    const unsigned *ptr_fillColor = getIfExists_const(foptProperties, FIELDID_FILL_COLOR);
    const unsigned *ptr_fillBackColor = getIfExists_const(foptProperties, FIELDID_FILL_BACK_COLOR);
    ColorReference fill = ptr_fillColor ? ColorReference(*ptr_fillColor) : ColorReference(0x00FFFFFF);
//    ColorReference back = ptr_fillBackColor ? ColorReference(*ptr_fillBackColor) : ColorReference(0x08000000);
    ColorReference back = ptr_fillBackColor ? ColorReference(*ptr_fillBackColor) : ColorReference(0x00FFFFFF);
    if (ptr_bgPxId && *ptr_bgPxId > 0 && *ptr_bgPxId <= m_escherDelayIndices.size() && m_escherDelayIndices[*ptr_bgPxId - 1] >= 0)
    {
      return std::shared_ptr<Fill>(new PatternFill(m_escherDelayIndices[*ptr_bgPxId - 1], m_collector, fill, back));
    }
    return std::shared_ptr<Fill>();
  }
  case BACKGROUND:
  default:
    return std::shared_ptr<Fill>();
  }
}

DynamicCustomShape MSPUBParser::getDynamicCustomShape(
  const std::vector<unsigned char> &vertexData, const std::vector<unsigned char> &segmentData,
  const std::vector<unsigned char> &guideData, unsigned geoWidth,
  unsigned geoHeight)
{
  DynamicCustomShape ret(geoWidth, geoHeight);
  ret.m_vertices = parseVertices(vertexData);
  ret.m_elements = parseSegments(segmentData);
  ret.m_calculations = parseGuides(guideData);
  return ret;
}

std::vector<unsigned short> MSPUBParser::parseSegments(
  const std::vector<unsigned char> &segmentData)
{
  std::vector<unsigned short> ret;
  if (segmentData.size() < 6)
  {
    return ret;
  }
  // assume that the entry size is 2.
  unsigned short numEntries = segmentData[0] | (segmentData[1] << 8);
  unsigned offset = 6;
  for (unsigned i = 0; i < numEntries; ++i)
  {
    if (offset + 2 > segmentData.size())
    {
      break;
    }
    ret.push_back(segmentData[offset] | (segmentData[offset + 1] << 8));
    offset += 2;
  }
  return ret;
}

std::vector<Calculation> MSPUBParser::parseGuides(
  const std::vector<unsigned char> &/* guideData */)
{
  std::vector<Calculation> ret;

  //FIXME : implement this function.

  return ret;
}

std::vector<Vertex> MSPUBParser::parseVertices(
  const std::vector<unsigned char> &vertexData)
{
  std::vector<Vertex> ret;
  if (vertexData.size() < 6)
  {
    return ret;
  }
  unsigned short numVertices = vertexData[0] | (vertexData[1] << 8);
  unsigned short entrySize = vertexData[4] | (vertexData[5] << 8);
  if (entrySize == 0xFFF0)
  {
    entrySize = 4;
  }
  if (!(entrySize == 2 || entrySize == 4 || entrySize == 8))
  {
    MSPUB_DEBUG_MSG(("Incomprehensible entry size %u in vertex complex data!\n", entrySize));
    return ret;
  }
  unsigned offset = 6;
  ret.reserve(numVertices);
  for (unsigned i = 0; i < numVertices; ++i)
  {
    if (offset + entrySize > vertexData.size())
    {
      break;
    }
    int x, y;
    switch (entrySize)
    {
    case 2:
      x = vertexData[offset];
      y = vertexData[offset + 1];
      break;
    case 4:
      x = vertexData[offset] | (unsigned(vertexData[offset + 1]) << 8);
      y = vertexData[offset + 2] | (unsigned(vertexData[offset + 3]) << 8);
      break;
    case 8:
      x = vertexData[offset] | (unsigned(vertexData[offset + 1]) << 8) |
          (unsigned(vertexData[offset + 2]) << 16) | (unsigned(vertexData[offset + 3]) << 24);
      y = vertexData[offset + 4] | (unsigned(vertexData[offset + 5]) << 8) |
          (unsigned(vertexData[offset + 6]) << 16) | (unsigned(vertexData[offset + 7]) << 24);
      break;
    default: // logically shouldn't be able to get here.
      x = 0;
      y = 0;
      break;
    }
    Vertex v = {x, y};
    ret.push_back(v);
    offset += entrySize;
  }
  return ret;
}

unsigned MSPUBParser::getEscherElementTailLength(unsigned short type)
{
  switch (type)
  {
  case OFFICE_ART_DGG_CONTAINER:
  case OFFICE_ART_DG_CONTAINER:
    return 4;
  default:
    return 0;
  }
}

unsigned MSPUBParser::getEscherElementAdditionalHeaderLength(unsigned short type)
{
  switch (type)
  {
  case OFFICE_ART_CLIENT_ANCHOR:
  case OFFICE_ART_CLIENT_DATA: //account for the fact that the length appears twice, for whatever reason
    return 4;
  }
  return 0;
}

bool MSPUBParser::findEscherContainerWithTypeInSet(librevenge::RVNGInputStream *input, const EscherContainerInfo &parent, EscherContainerInfo &out, std::set<unsigned short> types)
{
  while (stillReading(input, parent.contentsOffset + parent.contentsLength))
  {
    EscherContainerInfo next = parseEscherContainer(input);
    if (types.find(next.type) != types.end())
    {
      out = next;
      return true;
    }
    input->seek(next.contentsOffset + next.contentsLength + getEscherElementTailLength(next.type), librevenge::RVNG_SEEK_SET);
  }
  return false;
}

bool MSPUBParser::findEscherContainer(librevenge::RVNGInputStream *input, const EscherContainerInfo &parent, EscherContainerInfo &out, unsigned short desiredType)
{
  MSPUB_DEBUG_MSG(("At offset 0x%lx, attempting to find escher container of type 0x%x\n", input->tell(), desiredType));
  while (stillReading(input, parent.contentsOffset + parent.contentsLength))
  {
    EscherContainerInfo next = parseEscherContainer(input);
    if (next.type == desiredType)
    {
      out = next;
      return true;
    }
    input->seek(next.contentsOffset + next.contentsLength + getEscherElementTailLength(next.type), librevenge::RVNG_SEEK_SET);
  }
  return false;
}

FOPTValues MSPUBParser::extractFOPTValues(librevenge::RVNGInputStream *input, const EscherContainerInfo &record)
{
  FOPTValues ret;
  input->seek(record.contentsOffset, librevenge::RVNG_SEEK_SET);
  unsigned short numValues = record.initial >> 4;
  std::vector<unsigned short> complexIds;
  for (unsigned short i = 0; i < numValues; ++i)
  {
    if (!stillReading(input, record.contentsOffset + record.contentsLength))
    {
      break;
    }
    unsigned short id = readU16(input);
    unsigned value  = readU32(input);
    ret.m_scalarValues[id] = value;
    bool complex = id & 0x8000;
    if (complex)
    {
      complexIds.push_back(id);
    }
  }
  for (unsigned short id : complexIds)
  {
    if (!stillReading(input, record.contentsOffset + record.contentsLength))
    {
      break;
    }
    unsigned length = ret.m_scalarValues[id];
    if (!length)
    {
      continue;
    }
    unsigned short numEntries = readU16(input);
    input->seek(2, librevenge::RVNG_SEEK_CUR);
    unsigned short entryLength = readU16(input);
    if (entryLength == 0xFFF0)
    {
      entryLength = 4;
    }
    input->seek(-6, librevenge::RVNG_SEEK_CUR);
    readNBytes(input, static_cast<unsigned long>(entryLength) * numEntries + 6, ret.m_complexValues[id]);
  }
  return ret;
}

std::map<unsigned short, unsigned> MSPUBParser::extractEscherValues(librevenge::RVNGInputStream *input, const EscherContainerInfo &record)
{
  std::map<unsigned short, unsigned> ret;
  input->seek(record.contentsOffset + getEscherElementAdditionalHeaderLength(record.type), librevenge::RVNG_SEEK_SET);
  while (stillReading(input, record.contentsOffset + record.contentsLength))
  {
    unsigned short id = readU16(input);
    if (id == 0)
    {
      if (!stillReading(input, record.contentsOffset + record.contentsLength))
        break;
      MSPUB_DEBUG_MSG(("found escher value with ID 0!\n"));
    }
    unsigned value = readU32(input);
    ret[id] = value;
  }
  return ret;
}


bool MSPUBParser::parseContentChunkReference(librevenge::RVNGInputStream *input, const MSPUBBlockInfo block)
{
  //input should be at block.dataOffset + 4 , that is, at the beginning of the list of sub-blocks
  MSPUB_DEBUG_MSG(("Parsing chunk reference 0x%x\n", m_lastSeenSeqNum));
  unsigned type = UNKNOWN_CHUNK;
  unsigned long offset = 0;
  unsigned parentSeqNum = 0;
  bool seenType = false;
  bool seenOffset = false;
  bool seenParentSeqNum = false;
  while (stillReading(input, block.dataOffset + block.dataLength))
  {
    MSPUBBlockInfo subBlock = parseBlock(input, true);
    //FIXME: Warn if multiple of these blocks seen.
    if (subBlock.id == CHUNK_TYPE)
    {
      type = subBlock.data;
      seenType = true;
    }
    else if (subBlock.id == CHUNK_OFFSET)
    {
      offset = subBlock.data;
      seenOffset = true;
    }
    else if (subBlock.id == CHUNK_PARENT_SEQNUM)
    {
      parentSeqNum = subBlock.data;
      seenParentSeqNum = true;
    }
  }
  if (seenType && seenOffset) //FIXME: What if there is an offset, but not a type? Should we still set the end of the preceding chunk to that offset?
  {
    if (type == PAGE)
    {
      MSPUB_DEBUG_MSG(("page chunk: offset 0x%lx, seqnum 0x%x\n", offset, m_lastSeenSeqNum));
      m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0));
      m_pageChunkIndices.push_back(unsigned(m_contentChunks.size() - 1));
      return true;
    }
    else if (type == DOCUMENT)
    {
      MSPUB_DEBUG_MSG(("document chunk: offset 0x%lx, seqnum 0x%x\n", offset, m_lastSeenSeqNum));
      m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0));
      m_documentChunkIndex = unsigned(m_contentChunks.size() - 1);
      return true;
    }
    else if (type == SHAPE || type == ALTSHAPE || type == GROUP || type == TABLE || type == LOGO)
    {
      MSPUB_DEBUG_MSG(("shape chunk: offset 0x%lx, seqnum 0x%x, parent seqnum: 0x%x\n", offset, m_lastSeenSeqNum, parentSeqNum));
      m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0));
      m_shapeChunkIndices.push_back(unsigned(m_contentChunks.size() - 1));
      if (type == ALTSHAPE)
      {
        m_alternateShapeSeqNums.push_back(m_lastSeenSeqNum);
      }
      return true;
    }
    else if (type == CELLS)
    {
      m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0));
      m_cellsChunkIndices.push_back(unsigned(m_contentChunks.size() - 1));
      return true;
    }
    else if (type == PALETTE)
    {
      m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0));
      m_paletteChunkIndices.push_back(unsigned(m_contentChunks.size() - 1));
      return true;
    }
    else if (type == BORDER_ART)
    {
      m_contentChunks.push_back(ContentChunkReference(type, offset, 0,
                                                      m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0));
      m_borderArtChunkIndices.push_back(
        unsigned(m_contentChunks.size() - 1));
      return true;
    }
    else if (type == FONT)
    {
      m_contentChunks.push_back(ContentChunkReference(type, offset, 0,
                                                      m_lastSeenSeqNum,
                                                      seenParentSeqNum ? parentSeqNum : 0));
      m_fontChunkIndices.push_back(unsigned(m_contentChunks.size() - 1));
      return true;
    }
    m_contentChunks.push_back(ContentChunkReference(type, offset, 0, m_lastSeenSeqNum, seenParentSeqNum ? parentSeqNum : 0));
    m_unknownChunkIndices.push_back(unsigned(m_contentChunks.size() - 1));
  }
  return false;
}

bool MSPUBParser::isBlockDataString(unsigned type)
{
  return type == STRING_CONTAINER;
}
void MSPUBParser::skipBlock(librevenge::RVNGInputStream *input, MSPUBBlockInfo block)
{
  input->seek(block.dataOffset + block.dataLength, librevenge::RVNG_SEEK_SET);
}

EscherContainerInfo MSPUBParser::parseEscherContainer(librevenge::RVNGInputStream *input)
{
  EscherContainerInfo info;
  info.initial = readU16(input);
  info.type = readU16(input);
  info.contentsLength = readU32(input);
  info.contentsOffset = input->tell();
  MSPUB_DEBUG_MSG(("Parsed escher container: type 0x%x, contentsOffset 0x%lx, contentsLength 0x%lx\n", info.type, info.contentsOffset, info.contentsLength));
  return info;
}

MSPUBBlockInfo MSPUBParser::parseBlock(librevenge::RVNGInputStream *input, bool skipHierarchicalData)
{
  MSPUBBlockInfo info;
  info.startPosition = input->tell();
  info.id = readU8(input);
  info.type = readU8(input);
  info.dataOffset = input->tell();
  int len = getBlockDataLength(info.type);
  bool varLen = len < 0;
  if (varLen)
  {
    info.dataLength = readU32(input);
    if (isBlockDataString(info.type))
    {
      info.stringData = std::vector<unsigned char>();
      readNBytes(input, info.dataLength - 4, info.stringData);
    }
    else if (skipHierarchicalData)
    {
      skipBlock(input, info);
    }
    info.data = 0;
  }
  else
  {
    info.dataLength = len;
    switch (info.dataLength)
    {
    case 1:
      info.data = readU8(input);
      break;
    case 2:
      info.data = readU16(input);
      break;
    case 4:
      info.data = readU32(input);
      break;
    case 8:
    case 16:
    case 24:
      //FIXME: Not doing anything with this data for now.
      skipBlock(input, info);
      MSPUB_FALLTHROUGH;
    default:
      info.data = 0;
    }
  }
  MSPUB_DEBUG_MSG(("parseBlock dataOffset 0x%lx, id 0x%x, type 0x%x, dataLength 0x%lx, integral data 0x%x\n", info.dataOffset, info.id, info.type, info.dataLength, info.data));
  return info;
}

PageType MSPUBParser::getPageTypeBySeqNum(unsigned seqNum)
{
  switch (seqNum)
  {
  case 0x10d:
  case 0x110:
  case 0x113:
  case 0x117:
    return DUMMY_PAGE;
  default:
    return NORMAL;
  }
}

bool MSPUBParser::parsePaletteChunk(librevenge::RVNGInputStream *input, const ContentChunkReference &chunk)
{
  unsigned length = readU32(input);
  while (stillReading(input, chunk.offset + length))
  {
    MSPUBBlockInfo info = parseBlock(input);
    if (info.type == 0xA0)
    {
      while (stillReading(input, info.dataOffset + info.dataLength))
      {
        MSPUBBlockInfo subInfo = parseBlock(input);
        if (subInfo.type == GENERAL_CONTAINER)
        {
          parsePaletteEntry(input, subInfo);
        }
        else if (subInfo.type == DUMMY)
        {
          m_collector->addPaletteColor(Color());
        }
        skipBlock(input, subInfo);
      }
    }
    skipBlock(input, info);
  }
  return true;
}

void MSPUBParser::parsePaletteEntry(librevenge::RVNGInputStream *input, MSPUBBlockInfo info)
{
  while (stillReading(input, info.dataOffset + info.dataLength))
  {
    MSPUBBlockInfo subInfo = parseBlock(input, true);
    if (subInfo.id == 0x01)
    {
      m_collector->addPaletteColor(Color(subInfo.data & 0xFF, (subInfo.data >> 8) & 0xFF, (subInfo.data >> 16) & 0xFF));
    }
  }
}

bool MSPUBParser::parseMetaData()
{
  m_input->seek(0, librevenge::RVNG_SEEK_SET);
  MSPUBMetaData metaData;

  std::unique_ptr<librevenge::RVNGInputStream> sumaryInfo(m_input->getSubStreamByName("\x05SummaryInformation"));
  if (sumaryInfo)
  {
    metaData.parse(sumaryInfo.get());
  }

  std::unique_ptr<librevenge::RVNGInputStream> docSumaryInfo(m_input->getSubStreamByName("\005DocumentSummaryInformation"));
  if (docSumaryInfo)
  {
    metaData.parse(docSumaryInfo.get());
  }

  m_input->seek(0, librevenge::RVNG_SEEK_SET);
  metaData.parseTimes(m_input);
  m_collector->collectMetaData(metaData.getMetaData());

  return true;
}


}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
