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

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

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

#include <librevenge/librevenge.h>

#include "MWAWGraphicListener.hxx"
#include "MWAWGraphicShape.hxx"
#include "MWAWGraphicStyle.hxx"
#include "MWAWHeader.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPictBitmap.hxx"
#include "MWAWPictData.hxx"
#include "MWAWPrinter.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSubDocument.hxx"

#include "SuperPaintParser.hxx"

/** Internal: the structures of a SuperPaintParser */
namespace SuperPaintParserInternal
{
////////////////////////////////////////
//! Internal: the shape of a SuperPaintParser
struct Shape {
  //! the type
  enum Type { GraphicShape, Group, Picture, TextBox };
  //! constructor
  Shape(Type type, MWAWBox2f const &box)
    : m_type(type)
    , m_box(box)
    , m_entry()
    , m_shape()
    , m_style()
    , m_font()
    , m_justify(MWAWParagraph::JustificationLeft)
    , m_interline(1)
  {
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Shape const &shape);
  //! the shape type
  Type m_type;
  //! the bdbox
  MWAWBox2f m_box;
  //! the picture/textbox entry
  MWAWEntry m_entry;
  //! the graphic shape
  MWAWGraphicShape m_shape;
  //! the graphic style
  MWAWGraphicStyle m_style;
  //! the textbox font
  MWAWFont m_font;
  //! the textbox justification
  MWAWParagraph::Justification m_justify;
  //! the interline in percent
  double m_interline;
};

std::ostream &operator<<(std::ostream &o, Shape const &shape)
{
  switch (shape.m_type) {
  case Shape::GraphicShape:
    o << "shape," << shape.m_shape << ",";
    break;
  case Shape::Group:
    o << "group,box=" << shape.m_box << ",";
    break;
  case Shape::Picture:
    o << "picture,box=" << shape.m_box << ",";
    break;
  case Shape::TextBox:
    o << "textbox,box=" << shape.m_box << ",";
    break;
#if !defined(__clang__)
  default:
    MWAW_DEBUG_MSG(("SuperPaintParserInternal::Shape::operator<<: find unknown type"));
    break;
#endif
  }
  o << shape.m_style;
  return o;
}

////////////////////////////////////////
//! Internal: the state of a SuperPaintParser
struct State {
  //! constructor
  State()
    : m_kind(MWAWDocument::MWAW_K_DRAW)
    , m_bitmap()
    , m_shapeList()
  {
  }
  //! try to return the color corresponding to an id
  static bool getColor(int id, MWAWColor &color);
  //! the file type
  MWAWDocument::Kind m_kind;
  /// the bitmap (v1)
  std::shared_ptr<MWAWPict> m_bitmap;
  //! the list of shapes
  std::vector<Shape> m_shapeList;
};

bool State::getColor(int id, MWAWColor &color)
{
  switch (id) {
  case 0:
    color=MWAWColor(0,0,0);
    break;
  case 1:
    color=MWAWColor(255,255,255);
    break;
  case 2:
    color=MWAWColor(255,0,0);
    break;
  case 3:
    color=MWAWColor(0,255,0);
    break;
  case 4:
    color=MWAWColor(0,0,255);
    break;
  case 5: // orange
    color=MWAWColor(255, 165, 0);
    break;
  case 6: // purple
    color=MWAWColor(128,0,128);
    break;
  case 7: // yellow
    color=MWAWColor(255,255,0);
    break;
  default:
    MWAW_DEBUG_MSG(("SuperPaintParserInternal::State::getColor: can not determine color for id=%d\n", id));
    return false;
  }
  return true;
}

////////////////////////////////////////
//! Internal: the subdocument of a SuperPaintParser
class SubDocument final : public MWAWSubDocument
{
public:
  SubDocument(SuperPaintParser &pars, MWAWInputStreamPtr const &input, int zoneId)
    : MWAWSubDocument(&pars, input, MWAWEntry())
    , m_id(zoneId)
  {
  }

  //! destructor
  ~SubDocument() final {}

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

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

protected:
  //! the subdocument id
  int m_id;
private:
  SubDocument(SubDocument const &orig) = delete;
  SubDocument &operator=(SubDocument const &orig) = delete;
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType)
{
  if (!listener || !listener->canWriteText()) {
    MWAW_DEBUG_MSG(("SuperPaintParserInternal::SubDocument::parse: no listener\n"));
    return;
  }
  if (!m_parser) {
    MWAW_DEBUG_MSG(("SuperPaintParserInternal::SubDocument::parse: no parser\n"));
    return;
  }
  long pos = m_input->tell();
  static_cast<SuperPaintParser *>(m_parser)->sendText(m_id);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}


}

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

SuperPaintParser::~SuperPaintParser()
{
}

void SuperPaintParser::init()
{
  resetGraphicListener();
  setAsciiName("main-1");

  m_state.reset(new SuperPaintParserInternal::State);

  getPageSpan().setMargins(0.1);
}

////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void SuperPaintParser::parse(librevenge::RVNGDrawingInterface *docInterface)
{
  if (!getInput().get() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = false;
  try {
    // create the asciiFile
    ascii().setStream(getInput());
    ascii().open(asciiName());
    checkHeader(nullptr);
    ok = createZones();
    if (ok) {
      createDocument(docInterface);
      if (m_state->m_kind==MWAWDocument::MWAW_K_PAINT)
        sendBitmap();
      else
        sendPictures();
    }
    ascii().reset();
  }
  catch (...) {
    MWAW_DEBUG_MSG(("SuperPaintParser::parse: exception catched when parsing\n"));
    ok = false;
  }

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

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

  // create the page list
  MWAWPageSpan ps(getPageSpan());
  ps.setPageSpan(1);
  std::vector<MWAWPageSpan> pageList(1,ps);
  MWAWGraphicListenerPtr listen(new MWAWGraphicListener(*getParserState(), pageList, documentInterface));
  setGraphicListener(listen);
  listen->startDocument();
}


////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool SuperPaintParser::createZones()
{
  MWAWInputStreamPtr input = getInput();
  readHeader();
  input->seek(512,librevenge::RVNG_SEEK_SET);
  bool ok=true;
  long pos=input->tell();
  if (m_state->m_kind==MWAWDocument::MWAW_K_DRAW) {
    ok = readPictures();
  }
  else if (readBitmap(true)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    ok = readBitmap();
  }

  pos=input->tell();
  if (input->size()==pos+2 && input->readLong(2)==0) {
    ascii().addPos(pos);
    ascii().addNote("_");
  }
  else if (input->size()!=pos) {
    MWAW_DEBUG_MSG(("SuperPaintParser::createZones: find some extra data\n"));
    ascii().addPos(input->tell());
    ascii().addNote("Entries(End):###");
  }
  return ok;
}

////////////////////////////////////////////////////////////
// send shapes: vector graphic document
////////////////////////////////////////////////////////////
bool SuperPaintParser::sendPictures()
{
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("SuperPaintParser::sendPictures: can not find the listener\n"));
    return false;
  }
  MWAWInputStreamPtr input = getInput();

  for (size_t i=m_state->m_shapeList.size(); i>0;) {
    auto const &shape=m_state->m_shapeList[--i];
    MWAWPosition pos(shape.m_box[0], shape.m_box.size(), librevenge::RVNG_POINT);
    pos.setPage(1);
    pos.m_anchorTo=MWAWPosition::Page;
    switch (shape.m_type) {
    case SuperPaintParserInternal::Shape::Group:
      break;
    case SuperPaintParserInternal::Shape::TextBox: {
      std::shared_ptr<MWAWSubDocument> doc(new SuperPaintParserInternal::SubDocument(*this, input, static_cast<int>(i)));
      listener->insertTextBox(pos, doc, MWAWGraphicStyle::emptyStyle());
      break;
    }
    case SuperPaintParserInternal::Shape::Picture:
      if (!shape.m_entry.valid()) {
        MWAW_DEBUG_MSG(("SuperPaintParser::sendPictures: the picture entry seems bad\n"));
        break;
      }
      else {
        input->seek(shape.m_entry.begin(), librevenge::RVNG_SEEK_SET);
        std::shared_ptr<MWAWPict> thePict(MWAWPictData::get(input, static_cast<int>(shape.m_entry.length())));
        MWAWEmbeddedObject picture;
        if (thePict && thePict->getBinary(picture))
          listener->insertPicture(pos, picture);
        else {
          MWAW_DEBUG_MSG(("SuperPaintParser::sendPictures: can not check the picture data\n"));
          break;
        }
      }
      break;
    case SuperPaintParserInternal::Shape::GraphicShape:
      listener->insertShape(pos, shape.m_shape, shape.m_style);
      break;
#if !defined(__clang__)
    default:
      MWAW_DEBUG_MSG(("SuperPaintParser::sendPictures: find unknown shape type\n"));
      break;
#endif
    }
  }
  return true;
}

bool SuperPaintParser::sendText(int id)
{
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener || !listener->canWriteText()) {
    MWAW_DEBUG_MSG(("SuperPaintParser::sendText: can not find the listener\n"));
    return false;
  }
  if (id<0 || id>=static_cast<int>(m_state->m_shapeList.size())) {
    MWAW_DEBUG_MSG(("SuperPaintParser::sendText: can not find the textbox\n"));
    return false;
  }
  auto const &shape=m_state->m_shapeList[size_t(id)];
  if (!shape.m_entry.valid() || shape.m_type != SuperPaintParserInternal::Shape::TextBox) {
    MWAW_DEBUG_MSG(("SuperPaintParser::sendText: the textbox seems invalid\n"));
    return false;
  }
  MWAWParagraph para;
  para.setInterline(shape.m_interline, librevenge::RVNG_PERCENT);
  para.m_justify=shape.m_justify;
  listener->setParagraph(para);
  MWAWFont font=shape.m_font;
  listener->setFont(font);

  MWAWInputStreamPtr input = getInput();
  input->seek(shape.m_entry.begin(), librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "Shape-Text:";
  for (long i = 0; i < shape.m_entry.length(); i++) {
    auto c = static_cast<unsigned char>(input->readULong(1));
    f << c;
    switch (c) {
    case 0x9:
      listener->insertTab();
      break;
    case 0xd:
      // this is marks the end of a paragraph
      listener->insertEOL();
      break;
    default:
      listener->insertCharacter(c);
      break;
    }
  }
  ascii().addPos(shape.m_entry.begin());
  ascii().addNote(f.str().c_str());

  return true;
}

////////////////////////////////////////////////////////////
// read shapes: vector graphic document
////////////////////////////////////////////////////////////
bool SuperPaintParser::readPictures()
{
  MWAWInputStreamPtr input = getInput();
  input->seek(512, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  while (!input->isEnd()) {
    long pos=input->tell();
    if (readShape()) continue;
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    f.str("");
    f << "Entries(Picture):";
    auto dSz=static_cast<int>(input->readULong(2));
    if (!input->checkPosition(pos+2+dSz)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    input->seek(pos+2+dSz, librevenge::RVNG_SEEK_SET);
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  return !m_state->m_shapeList.empty();
}

bool SuperPaintParser::readShape()
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();

  libmwaw::DebugStream f;
  f << "Entries(Shape):";
  auto dSz=static_cast<int>(input->readULong(2));
  if (dSz==0xFFFF) {
    f << "endGroup";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return true;
  }
  if (dSz<50 || !input->checkPosition(pos+2+dSz)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  int dim[4];
  for (auto &d : dim) d=static_cast<int>(input->readLong(2));
  MWAWBox2f box(MWAWVec2f(static_cast<float>(dim[1]),static_cast<float>(dim[0])),MWAWVec2f(static_cast<float>(dim[3]),static_cast<float>(dim[2])));
  f << "box=" << dim[1] << "x" << dim[0] << "<->" << dim[3] << "x" << dim[2] << ",";
  int val;
  for (int i=0; i<4; ++i) { // always 0?
    val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  val=static_cast<int>(input->readLong(1)); // always 0?
  if (val) f << "f4=" << val << ",";
  auto type=static_cast<int>(input->readLong(1));
  MWAWGraphicStyle style;
  MWAWColor fontColor(MWAWColor::black());
  if (type) {
    val=static_cast<int>(input->readULong(1));
    bool hasSurfaceColor=false;
    if (val==1)
      hasSurfaceColor=true;
    else if (val)
      f << "#hasColor=" << val << ",";
    // small number: pattern?
    val=static_cast<int>(input->readULong(1));
    if (val) f << "g0=" << val << ",";
    val=static_cast<int>(input->readULong(1));
    if (val==1) f << "select,";
    else if (val) f << "#select=val,";
    val=static_cast<int>(input->readULong(1)); // always 0
    if (val) f << "g1=" << val << ",";
    val=static_cast<int>(input->readULong(2)); // g3=a big number
    if (val) f << "g2=" << std::hex << val << std::dec << ",";
    int penSize[2];
    for (auto &pSize : penSize) pSize=static_cast<int>(input->readULong(2));
    if (penSize[0]!=1||penSize[1]!=1) f << "pen[size]=" << penSize[0] << "x" << penSize[1] << ",";
    style.m_lineWidth=0.5f*float(penSize[0]+penSize[1]);
    MWAWGraphicStyle::Pattern pat[2];
    for (auto &p : pat) {
      p.m_dim=MWAWVec2i(8,8);
      p.m_data.resize(8);
      for (auto &data : p.m_data) data=static_cast<uint8_t>(input->readULong(1));
    }
    f << "line[pattern]=";
    MWAWColor color;
    if (pat[0].getUniqueColor(color))
      f << color << ",";
    else
      f << "[" << pat[0] << "],";
    f << "surface[pattern]=";
    if (pat[1].getUniqueColor(color))
      f << color << ",";
    else
      f << "[" << pat[1] << "],";
    val=static_cast<int>(input->readULong(2));
    if (m_state->getColor((val>>13)&7, color)) {
      pat[0].m_colors[1]=color;
      if (!color.isBlack())
        f << "line0[col]=" << color << ",";
    }
    if (m_state->getColor((val>>10)&7, color)) {
      pat[0].m_colors[0]=color;
      if (!color.isWhite())
        f << "line1[col]=" << color << ",";
    }
    if (m_state->getColor((val>>7)&7, color)) {
      pat[1].m_colors[1]=fontColor=color;
      if (!color.isBlack())
        f << "surf0[col]=" << color << ",";
    }
    if (m_state->getColor(val&7, color)) {
      pat[1].m_colors[0]=color;
      if (!color.isWhite())
        f << "surf1[col]=" << color << ",";
    }
    if (pat[0].getAverageColor(color))
      style.m_lineColor=color;
    if (hasSurfaceColor) {
      if (pat[1].getUniqueColor(color))
        style.setSurfaceColor(color);
      else
        style.setPattern(pat[1]);
    }
    if (val&0x78) f << "g4=" << ((val&0x78)>>3) << ",";
  }
  int numDatas=0;
  SuperPaintParserInternal::Shape::Type shapeType=SuperPaintParserInternal::Shape::GraphicShape;
  switch (type) {
  case 0:
    shapeType=SuperPaintParserInternal::Shape::Group;
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, box));
    f << "group,";
    break;
  case 0x2:
    shapeType=SuperPaintParserInternal::Shape::Picture;
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, box));
    f << "pict,";
    numDatas=2;
    break;
  case 0x17: {
    f << "textbox,";
    val=static_cast<int>(input->readULong(4));
    if (val) f << "id?=" << std::hex << val << ",";
    shapeType=SuperPaintParserInternal::Shape::TextBox;
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, box));
    numDatas=2;
    if (dSz<0x4a) {
      MWAW_DEBUG_MSG(("SuperPaintParser::readShape: textbox size seems to short\n"));
      f << "###";
      break;
    }
    MWAWFont font;
    font.setId(static_cast<int>(input->readULong(2)));
    font.setSize(float(input->readULong(2)));
    font.setColor(fontColor);
    val=static_cast<int>(input->readULong(2));
    switch (val&3) {
    case 0: // left
      break;
    case 1:
      m_state->m_shapeList.back().m_justify=MWAWParagraph::JustificationCenter;
      f << "center,";
      break;
    case 2:
      m_state->m_shapeList.back().m_justify=MWAWParagraph::JustificationRight;
      f << "right,";
      break;
    default:
      f << "#align=3,";
      break;
    }
    if ((val&0xFFFC)!=8) f << "#fl0=" << std::hex << (val&0xFFFC) << std::dec << ",";
    f << "height[line]?=" << input->readLong(2) << ","; // checkme
    val=static_cast<int>(input->readULong(2));
    switch (val&3) {
    case 0: // basic interline
      break;
    case 1:
      m_state->m_shapeList.back().m_interline=1.5;
      f << "interline=150%,";
      break;
    case 2:
      m_state->m_shapeList.back().m_interline=2;
      f << "interline=200%,";
      break;
    default:
      break;
    }
    if ((val&0xFFFC)!=0xc) f << "#fl1=" << std::hex << (val&0xFFFC) << std::dec << ",";
    val=static_cast<int>(input->readULong(2)); // no sure about this one, seems sometimes related to the next font flags...
    if (val) f << "fl2="<< std::hex << val << std::dec << ",";
    auto flag=static_cast<int>(input->readULong(1));
    uint32_t flags=0;
    if (flag&0x1) flags |= MWAWFont::boldBit;
    if (flag&0x2) flags |= MWAWFont::italicBit;
    if (flag&0x4) font.setUnderlineStyle(MWAWFont::Line::Simple);
    if (flag&0x8) flags |= MWAWFont::embossBit;
    if (flag&0x10) flags |= MWAWFont::shadowBit;
    if (flag&0xE0) f << "#fFlag=" << std::hex << (flag&0xE0) << std::dec << ",";
    font.setFlags(flags);

    m_state->m_shapeList.back().m_font=font;
    f << "font=[" << font.getDebugString(getParserState()->m_fontConverter) << "],";
    break;
  }
  case 0x6: // axis line
  case 0x18: { // normal
    if (type==6)
      f << "line[axis],";
    else
      f << "line,";
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, box));
    m_state->m_shapeList.back().m_shape=MWAWGraphicShape::line(box[0],box[1]);
    // now check the box size
    m_state->m_shapeList.back().m_box=m_state->m_shapeList.back().m_shape.getBdBox();
    break;
  }
  case 0x19:
    f << "rect,";
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, box));
    m_state->m_shapeList.back().m_shape=MWAWGraphicShape::rectangle(box);
    break;
  case 0x1a:
    f << "roundRect,";
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, box));
    m_state->m_shapeList.back().m_shape=MWAWGraphicShape::rectangle(box, MWAWVec2f(5,5));
    break;
  case 0x1b:
  case 0x20:
    if (type==0x20)
      f << "circle[ortho],";
    else
      f << "circle,";
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, box));
    m_state->m_shapeList.back().m_shape=MWAWGraphicShape::circle(box);
    break;
  case 0x1c: {
    f << "arc,";
    int fileAngle[2];
    for (auto &angl : fileAngle) angl = static_cast<int>(input->readLong(2));
    int angle[2] = { 90-fileAngle[0]-fileAngle[1], 90-fileAngle[0] };
    while (angle[1] > 360) {
      angle[0]-=360;
      angle[1]-=360;
    }
    while (angle[0] < -360) {
      angle[0]+=360;
      angle[1]+=360;
    }
    MWAWVec2f center = box.center();
    MWAWVec2f axis = 0.5f*MWAWVec2f(box.size());
    // we must compute the real bd box
    float minVal[2] = { 0, 0 }, maxVal[2] = { 0, 0 };
    int limitAngle[2];
    for (int i = 0; i < 2; i++)
      limitAngle[i] = (angle[i] < 0) ? int(angle[i]/90)-1 : int(angle[i]/90);
    for (int bord = limitAngle[0]; bord <= limitAngle[1]+1; bord++) {
      float ang = (bord == limitAngle[0]) ? float(angle[0]) :
                  (bord == limitAngle[1]+1) ? float(angle[1]) : float(90 * bord);
      ang *= float(M_PI/180.);
      float actVal[2] = { axis[0] *std::cos(ang), -axis[1] *std::sin(ang)};
      if (actVal[0] < minVal[0]) minVal[0] = actVal[0];
      else if (actVal[0] > maxVal[0]) maxVal[0] = actVal[0];
      if (actVal[1] < minVal[1]) minVal[1] = actVal[1];
      else if (actVal[1] > maxVal[1]) maxVal[1] = actVal[1];
    }
    MWAWBox2f realBox(MWAWVec2f(center[0]+minVal[0],center[1]+minVal[1]),
                      MWAWVec2f(center[0]+maxVal[0],center[1]+maxVal[1]));
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, realBox));
    m_state->m_shapeList.back().m_shape = MWAWGraphicShape::pie(realBox, box, MWAWVec2f(float(angle[0]),float(angle[1])));
    break;
  }
  case 0x1d:
  case 0x1e:
    if (type==0x1d)
      f << "poly,";
    else
      f << "poly[hand],";
    m_state->m_shapeList.push_back(SuperPaintParserInternal::Shape(shapeType, box));
    m_state->m_shapeList.back().m_shape.m_type=MWAWGraphicShape::Polygon;
    numDatas=2;
    break;
  default:
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if (m_state->m_shapeList.empty() || m_state->m_shapeList.back().m_type!=shapeType) {
    MWAW_DEBUG_MSG(("SuperPaintParser::readShape: the shape list seems bad\n"));
    f.str("");
    f << "Shape:type=" << type << ",###";
    input->seek(pos+2+dSz, librevenge::RVNG_SEEK_SET);
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return true;
  }
  if (input->tell()!=pos+2+dSz) {
    ascii().addDelimiter(input->tell(),'|');
    input->seek(pos+2+dSz, librevenge::RVNG_SEEK_SET);
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  auto &shape=m_state->m_shapeList.back();
  shape.m_style=style;
  if (!numDatas) return true;

  // first zone always 0 (or it is a 4 bytes pointer ) ?
  pos=input->tell();
  dSz=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(pos+2+dSz)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  if (dSz==0) {
    ascii().addPos(pos);
    ascii().addNote("_");
  }
  else {
    MWAW_DEBUG_MSG(("SuperPaintParser::readShape: find some data for type %d\n", type));
    f.str("");
    f << "Shape-Data0:type=" << type << ",###";
    input->seek(pos+2+dSz, librevenge::RVNG_SEEK_SET);
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  if (numDatas<=1) return true;

  // now the real data
  pos=input->tell();
  dSz=static_cast<int>(input->readULong(2));
  if (!input->checkPosition(pos+2+dSz)) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  switch (shapeType) {
  case SuperPaintParserInternal::Shape::Picture:
    shape.m_entry.setBegin(pos+2);
    shape.m_entry.setLength(dSz);
    ascii().skipZone(pos+2,pos+1+dSz);
    break;
  case SuperPaintParserInternal::Shape::TextBox:
    shape.m_entry.setBegin(pos+2);
    shape.m_entry.setLength(dSz);
    break;
  case SuperPaintParserInternal::Shape::GraphicShape: {
    f.str("");
    f << "Shape-Point:";
    if (shape.m_shape.m_type==MWAWGraphicShape::Polygon)
      f << "poly,";
    else {
      m_state->m_shapeList.pop_back();
      MWAW_DEBUG_MSG(("SuperPaintParser::readShape: unexpected type\n"));
      f << "###";
      input->seek(pos+2+dSz, librevenge::RVNG_SEEK_SET);
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      return true;
    }
    if (dSz<6 || (dSz%4)!=2) {
      m_state->m_shapeList.pop_back();
      MWAW_DEBUG_MSG(("SuperPaintParser::readShape: can not compute the number of polygon points\n"));
      f << "###";
      input->seek(pos+2+dSz, librevenge::RVNG_SEEK_SET);
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      return true;
    }
    val=static_cast<int>(input->readLong(2));
    if (val!=dSz) f << "#sz=" << val << ",";
    f << "bdbox=[";
    for (int i=0; i< 4; ++i) f << input->readLong(2) << ",";
    f << "],";
    int N=(dSz-6)/4;
    f << "N=" << N << ",pts=[";
    std::vector<MWAWVec2f> vertices(static_cast<size_t>(N));
    for (int i=0; i<N; ++i) {
      int coord[2];
      for (auto &c : coord) c=static_cast<int>(input->readLong(2));
      f << MWAWVec2i(coord[1],coord[0]) << ",";
      vertices[size_t(i)]=MWAWVec2f(static_cast<float>(coord[1]),static_cast<float>(coord[0]))-box[0];
    }
    f << "],";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    // the last vertices is sometimes bad, so...
    if (!vertices.empty())
      vertices.pop_back();
    shape.m_shape.m_vertices=vertices;
    break;
  }
  case SuperPaintParserInternal::Shape::Group:
#if !defined(__clang__)
  default:
#endif
    MWAW_DEBUG_MSG(("SuperPaintParser::readShape: call with unexpeced type\n"));
    f.str("");
    f << "Shape-Data1:type=" << type << ",###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  input->seek(pos+2+dSz, librevenge::RVNG_SEEK_SET);

  if (numDatas<=2) return true;
  // must not appear
  MWAW_DEBUG_MSG(("SuperPaintParser::readShape: find numData=%d\n", numDatas));
  for (int i=2; i<numDatas; ++i) {
    pos=input->tell();
    f.str("");
    f << "Shape-Data" << i << ":##";
    dSz=static_cast<int>(input->readULong(2));
    if (!input->checkPosition(pos+2+dSz)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      return false;
    }
    input->seek(pos+2+dSz, librevenge::RVNG_SEEK_SET);
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  return true;
}

////////////////////////////////////////////////////////////
// read/send a bitmap ( paint document)
////////////////////////////////////////////////////////////
bool SuperPaintParser::sendBitmap()
{
  MWAWGraphicListenerPtr listener=getGraphicListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("SuperPaintParser::sendBitmap: can not find the listener\n"));
    return false;
  }

  MWAWEmbeddedObject picture;
  if (!m_state->m_bitmap || !m_state->m_bitmap->getBinary(picture)) return false;

  MWAWPageSpan const &page=getPageSpan();
  MWAWPosition pos(MWAWVec2f(float(page.getMarginLeft()),float(page.getMarginRight())),
                   MWAWVec2f(float(page.getPageWidth()),float(page.getPageLength())), librevenge::RVNG_INCH);
  pos.setRelativePosition(MWAWPosition::Page);
  pos.m_wrapping = MWAWPosition::WNone;
  listener->insertPicture(pos, picture);
  return true;
}

bool SuperPaintParser::readBitmap(bool onlyCheck)
{
  MWAWInputStreamPtr input = getInput();
  input->seek(512, librevenge::RVNG_SEEK_SET);
  long pos=input->tell();
  libmwaw::DebugStream f;
  f << "Entries(Bitmap):";
  if (!input->checkPosition(pos+48)) {
    MWAW_DEBUG_MSG(("SuperPaintParser::readBitmap: the bitmap header seems too short\n"));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  int val;
  for (int i=0; i<11; ++i) {
    static int const expected[]= {0x11,1,0xa0,0x62,0xc,0xa0,0,0x8e,1,0,0xa};
    val=static_cast<int>(input->readULong(1));
    if (val!=expected[i])
      f << "f" << i << "=" << val << ",";
  }
  int pictDim[4];
  for (auto &pDim : pictDim) pDim=static_cast<int>(input->readLong(2));
  f << "dim=" << pictDim[1] << "x" << pictDim[0] << "<->" << pictDim[3] << "x" << pictDim[2] << ",";
  if (pictDim[0]<0 || pictDim[1]<0 || pictDim[0]>pictDim[2] || pictDim[1]>pictDim[3]) {
    MWAW_DEBUG_MSG(("SuperPaintParser::readBitmap: the picture dimension seems bad\n"));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+19, librevenge::RVNG_SEEK_SET);

  std::shared_ptr<MWAWPictBitmapIndexed> pict;
  if (!onlyCheck) {
    pict.reset(new MWAWPictBitmapIndexed(MWAWVec2i(pictDim[3],pictDim[2])));
    std::vector<MWAWColor> colors(2);
    colors[0]=MWAWColor::white();
    colors[1]=MWAWColor::black();
    pict->setColors(colors);
  }
  while (!input->isEnd()) {
    pos=input->tell();
    f.str("");
    f << "Bitmap-Row:";
    val=static_cast<int>(input->readULong(1));
    if (val!=0x98||!input->checkPosition(pos+29)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    auto numColByRow=static_cast<int>(input->readLong(2)); // 20|2c|2a
    f << "numCol[byRow]=" << numColByRow << ",";
    int dim[4];
    for (auto &d : dim) d=static_cast<int>(input->readULong(2));
    f << "dim?=" << dim[1] << "x" << dim[0] << "<->" <<  dim[3] << "x" << dim[2] << ",";
    if (dim[2]<dim[0] || numColByRow*8 < dim[3]-dim[1] || dim[1]<0 || dim[3]>pictDim[3]+8) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    for (int j=0; j<2; ++j) {
      // normally the real picture position (before alignment to 8)
      int dim2[4];
      for (auto &d : dim2) d=static_cast<int>(input->readULong(2));
      if (dim[0]!=dim2[0]||dim[2]!=dim2[2]||(dim[1]>dim2[1]||dim[1]+8<dim2[1])||
          (dim[3]<dim2[3]||dim[3]>dim2[3]+8))
        f << "dim" << j << "?=" << dim2[1] << "x" << dim2[0] << "<->" <<  dim2[3] << "x" << dim2[2] << ",";
    }
    val=static_cast<int>(input->readLong(2));
    if (val!=1) f << "f1=" << val << ",";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());

    for (int r=dim[0]; r<dim[2]; ++r) {
      pos=input->tell();
      auto len=static_cast<int>(input->readULong(1));
      long rEndPos=pos+1+len;
      if (!len || !input->checkPosition(rEndPos)) {
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        break;
      }
      f.str("");
      f << "Bitmap-R" << r << ":";
      int col=dim[1];
      while (input->tell()<rEndPos) {
        auto wh=static_cast<int>(input->readULong(1));
        if (wh>=0x81) {
          auto color=static_cast<int>(input->readULong(1));
          if (onlyCheck) {
            col+=8*(0x101-wh);
          }
          else {
            for (int j=0; j < 0x101-wh; ++j) {
              for (int b=7; b>=0; --b) {
                if (col<pictDim[3])
                  pict->set(col, r, (color>>b)&1);
                ++col;
              }
            }
          }
        }
        else {
          if (input->tell()+wh+1>rEndPos) {
            MWAW_DEBUG_MSG(("SuperPaintParser::readBitmap: can not read row %d\n", r));
            f << "###";
            ascii().addPos(pos);
            ascii().addNote(f.str().c_str());
            return false;
          }
          if (onlyCheck) {
            col+=8*(wh+1);
            input->seek(wh+1, librevenge::RVNG_SEEK_CUR);
          }
          else {
            for (int j=0; j < wh+1; ++j) {
              auto color=static_cast<int>(input->readULong(1));
              for (int b=7; b>=0; --b) {
                if (col<pictDim[3])
                  pict->set(col, r, (color>>b)&1);
                ++col;
              }
            }
          }
        }
        if (col>pictDim[3]+8) {
          MWAW_DEBUG_MSG(("SuperPaintParser::readBitmap: can not read row %d\n", r));
          f << "###";
          ascii().addPos(pos);
          ascii().addNote(f.str().c_str());
          return false;
        }
      }
      f << "col=" << col << ",";
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      input->seek(rEndPos, librevenge::RVNG_SEEK_SET);
    }
  }
  pos=input->tell();
  f.str("");
  f << "Bitmap-End:";
  if (!input->checkPosition(pos+6) || input->readULong(1)!=0xa0) {
    MWAW_DEBUG_MSG(("SuperPaintParser::readBitmap: problem when reading the bitmap\n"));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  if (!onlyCheck)
    m_state->m_bitmap=pict;
  for (int i=0; i<3; ++i) {
    int const expected[]= {0,0x8F,0xFF};
    val=static_cast<int>(input->readULong(1));
    if (val!=expected[i])
      f << "f" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  if (!onlyCheck)
    m_state->m_bitmap=pict;
  return true;
}

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

  int const vers=1;
  libmwaw::DebugStream f;
  f << "FileHeader:";
  input->seek(0, librevenge::RVNG_SEEK_SET);
  if (input->readULong(2)!=0x1000) return false;
  int dim[4];
  for (auto &d : dim) d=static_cast<int>(input->readLong(2));
  if (dim[0]||dim[1]||dim[2]||dim[3])
    f << "bitmap[content]=" << dim[1] << "x" << dim[0] << "<->" << dim[3] << "x" << dim[2] << ",";
  auto val=static_cast<int>(input->readLong(2));
  switch (val) {
  case 1:
    m_state->m_kind=MWAWDocument::MWAW_K_PAINT;
    f << "paint,";
    break;
  case 2: // not yet implemented
    f << "draw,";
    break;
  default:
    return false;
  }
  val=static_cast<int>(input->readLong(1));
  if (val==1) f << "hasPrintInfo?,";
  else if (val) f << "#f0=" << val << ",";
  val=static_cast<int>(input->readLong(1));
  if (val==8) f << "color?,";
  else if (val) f << "#f1=" << val << ",";
  ascii().addPos(0);
  ascii().addNote(f.str().c_str());

  if (strict) {
    if (m_state->m_kind==MWAWDocument::MWAW_K_PAINT) {
      if (!readBitmap(true))
        return false;
    }
    else {
      input->seek(512,librevenge::RVNG_SEEK_SET);
      for (int i=0; i<4; ++i) {
        if (input->isEnd()) break;
        long pos=input->tell();
        auto dSz=long(input->readULong(2));
        if (!input->checkPosition(pos+2+dSz)) return false;
        input->seek(pos+2+dSz,librevenge::RVNG_SEEK_SET);
      }
    }
  }

  setVersion(vers);
  if (header)
    header->reset(MWAWDocument::MWAW_T_SUPERPAINT, vers, m_state->m_kind);
  input->seek(512,librevenge::RVNG_SEEK_SET);

  return true;
}

////////////////////////////////////////////////////////////
// try to read the header zone
////////////////////////////////////////////////////////////
bool SuperPaintParser::readHeader()
{
  MWAWInputStreamPtr input = getInput();
  if (!input->checkPosition(512)) {
    MWAW_DEBUG_MSG(("SuperPaintParser::readHeader: the header zone seems too short\n"));
    ascii().addPos(14);
    ascii().addNote("Entries(Header):#");
    return false;
  }
  input->seek(14,librevenge::RVNG_SEEK_SET);
  if (!readPrintInfo()) {
    ascii().addPos(14);
    ascii().addNote("Entries(PrintInfo):#");
    input->seek(14+120, librevenge::RVNG_SEEK_SET);
  }
  long pos=input->tell();
  libmwaw::DebugStream f;
  f << "Entries(Header):";
  int val;
  for (int i=0; i<6; ++i) {
    int const expected[]= {0x24,1,0,0/*or3*/,0,0x24 /* or 32*/};
    val=static_cast<int>(input->readLong(1));
    if (val != expected[i]) f << "f" << i << "=" << val << ",";
  }
  for (int i=0; i<60; ++i) { // always 0?
    val=static_cast<int>(input->readLong(2));
    if (val) f << "g" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  pos=input->tell();
  f.str("");
  f << "Header-II:";
  for (int i=0; i<126; ++i) { // always 0?
    val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
// try to read the print info zone
////////////////////////////////////////////////////////////
bool SuperPaintParser::readPrintInfo()
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  long endPos=pos+120;
  if (!input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("SuperPaintParser::readPrintInfo: file seems too short\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(PrintInfo):";
  libmwaw::PrinterInfo info;
  if (!info.read(input)) {
    MWAW_DEBUG_MSG(("SuperPaintParser::readPrintInfo: can not read print info\n"));
    return false;
  }
  f << info;
  MWAWVec2i paperSize = info.paper().size();
  MWAWVec2i pageSize = info.page().size();
  if (pageSize.x() <= 0 || pageSize.y() <= 0 ||
      paperSize.x() <= 0 || paperSize.y() <= 0) {
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    input->seek(endPos, librevenge::RVNG_SEEK_SET);
    return true;
  }

  // define margin from print info
  MWAWVec2i lTopMargin= -1 * info.paper().pos(0);
  MWAWVec2i rBotMargin=info.paper().size() - info.page().size();

  // move margin left | top
  int decalX = lTopMargin.x() > 14 ? lTopMargin.x()-14 : 0;
  int decalY = lTopMargin.y() > 14 ? lTopMargin.y()-14 : 0;
  lTopMargin -= MWAWVec2i(decalX, decalY);
  rBotMargin += MWAWVec2i(decalX, decalY);

  // decrease right | bottom
  int rightMarg = rBotMargin.x() -50;
  if (rightMarg < 0) rightMarg=0;
  int botMarg = rBotMargin.y() -50;
  if (botMarg < 0) botMarg=0;

  getPageSpan().setMarginTop(lTopMargin.y()/72.0);
  getPageSpan().setMarginBottom(botMarg/72.0);
  getPageSpan().setMarginLeft(lTopMargin.x()/72.0);
  getPageSpan().setMarginRight(rightMarg/72.0);
  getPageSpan().setFormLength(paperSize.y()/72.);
  getPageSpan().setFormWidth(paperSize.x()/72.);

  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  return true;
}

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