/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */
/* libmwaw
* Version: MPL 2.0 / LGPLv2+
*
* The contents of this file are subject to the Mozilla Public License Version
* 2.0 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2002 William Lachance (wrlach@gmail.com)
* Copyright (C) 2002,2004 Marc Maurer (uwog@uwog.net)
* Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch)
* Copyright (C) 2006, 2007 Andrew Ziem
* Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr)
*
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
* in which case the provisions of the LGPLv2+ are applicable
* instead of those above.
*/
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include "libmwaw_internal.hxx"
#include "MWAWCell.hxx"
#include "MWAWListener.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWGraphicStyle.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPictData.hxx"
#include "MWAWSubDocument.hxx"
#include "MWAWTable.hxx"
#include "MsWksGraph.hxx"
#include "MsWksDocument.hxx"
#include "MsWksTable.hxx"
/** Internal: the structures of a MsWksTable */
namespace MsWksTableInternal
{
////////////////////////////////////////
//! Internal: the chart of a MsWksTable
struct Chart {
//! constructor
explicit Chart(MsWksGraph::Style const &style)
: m_style(style)
, m_backgroundEntry()
, m_zoneId(-1)
{
for (auto &id : m_textZonesId) id=-1;
}
//! empty constructor
Chart()
: m_style()
, m_backgroundEntry()
, m_zoneId(-1)
{
for (auto &id : m_textZonesId) id=-1;
}
//! the graphic style
MWAWGraphicStyle m_style;
//! the three text pictures
int m_textZonesId[3];
//! the background entry
MWAWEntry m_backgroundEntry;
//! the chart zone id (in the graph parser )
int m_zoneId;
};
////////////////////////////////////////
//! Internal: the table of a MsWksTable
struct Table {
//! the cell content
struct Cell {
Cell()
: m_pos(-1,-1)
, m_font()
, m_text("")
{
}
//! the cell position
MWAWVec2i m_pos;
//! the font
MWAWFont m_font;
//! the text
std::string m_text;
};
//! constructor
explicit Table(MsWksGraph::Style const &style)
: m_style(style)
, m_numRows(0)
, m_numCols(0)
, m_rowsDim()
, m_colsDim()
, m_font()
, m_cellsList()
{
m_style.m_surfaceColor = style.m_baseSurfaceColor;
}
//! empty constructor
Table()
: m_style()
, m_numRows(0)
, m_numCols(0)
, m_rowsDim()
, m_colsDim()
, m_font()
, m_cellsList()
{
}
//! try to find a cell
Cell const *getCell(MWAWVec2i const &pos) const
{
for (auto &cell : m_cellsList) {
if (cell.m_pos == pos)
return &cell;
}
return nullptr;
}
//! the graphic style
MWAWGraphicStyle m_style;
int m_numRows /** the number of rows*/, m_numCols/** the number of columns*/;
std::vector<int> m_rowsDim/**the rows dimensions*/, m_colsDim/*the columns dimensions*/;
//! the default font
MWAWFont m_font;
//! the list of cell
std::vector<Cell> m_cellsList;
};
////////////////////////////////////////
//! Internal: the state of a MsWksTable
struct State {
//! constructor
State()
: m_version(-1)
, m_idChartMap()
, m_idTableMap()
{
}
//! the version
int m_version;
//! the map id->chart
std::map<int, Chart> m_idChartMap;
//! the map id->table
std::map<int, Table> m_idTableMap;
};
}
////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
MsWksTable::MsWksTable(MWAWParser &parser, MsWksDocument(&zone), MsWksGraph &graph)
: m_parserState(parser.getParserState())
, m_state(new MsWksTableInternal::State)
, m_mainParser(&parser)
, m_graphParser(&graph)
, m_zone(zone)
{
}
MsWksTable::~MsWksTable()
{ }
int MsWksTable::version() const
{
if (m_state->m_version < 0)
m_state->m_version = m_parserState->m_version;
return m_state->m_version;
}
////////////////////////////////////////////////////////////
// table
////////////////////////////////////////////////////////////
bool MsWksTable::sendTable(int zoneId)
{
MWAWListenerPtr listener=m_parserState->getMainListener();
if (!listener) return false;
if (m_state->m_idTableMap.find(zoneId)==m_state->m_idTableMap.end()) {
MWAW_DEBUG_MSG(("MsWksTable::sendTable: can not find textbox %d\n", zoneId));
return false;
}
if (m_parserState->m_type==MWAWParserState::Spreadsheet) {
/* inserting a table may cause problem when there happens in some
ods file, so... */
MWAW_DEBUG_MSG(("MsWksTable::sendTable: inserting a table in a spreadsheet is not implemented\n"));
return false;
}
auto &table = m_state->m_idTableMap.find(zoneId)->second;
// open the table
size_t nCols = table.m_colsDim.size();
size_t nRows = table.m_rowsDim.size();
if (!nCols || !nRows) {
MWAW_DEBUG_MSG(("MsWksTable::sendTable: problem with dimensions\n"));
return false;
}
std::vector<float> colsDims(nCols);
for (size_t c = 0; c < nCols; c++) colsDims[c] = float(table.m_colsDim[c]);
MWAWTable theTable(MWAWTable::TableDimBit);
theTable.setColsSize(colsDims);
listener->openTable(theTable);
int const borderPos = libmwaw::TopBit | libmwaw::RightBit |
libmwaw::BottomBit | libmwaw::LeftBit;
MWAWBorder border, internBorder;
internBorder.m_width=0.5;
internBorder.m_color=MWAWColor(0xC0,0xC0,0xC0);
MWAWParagraph para;
para.m_justify=MWAWParagraph::JustificationCenter;
for (size_t row = 0; row < nRows; row++) {
listener->openTableRow(float(table.m_rowsDim[row]), librevenge::RVNG_POINT);
for (size_t col = 0; col < nCols; col++) {
MWAWCell cell;
MWAWVec2i cellPosition(MWAWVec2i(static_cast<int>(col),static_cast<int>(row)));
cell.setPosition(cellPosition);
cell.setBorders(borderPos, border);
int internWhat=0;
if (col!=0) internWhat|=libmwaw::LeftBit;
if (col+1!=nCols) internWhat|=libmwaw::RightBit;
if (row!=0) internWhat|=libmwaw::TopBit;
if (row+1!=nRows) internWhat|=libmwaw::BottomBit;
cell.setBorders(internWhat, internBorder);
if (!table.m_style.m_surfaceColor.isWhite())
cell.setBackgroundColor(table.m_style.m_surfaceColor);
listener->openTableCell(cell);
listener->setParagraph(para);
auto const *tCell=table.getCell(cellPosition);
if (tCell) {
listener->setFont(tCell->m_font);
size_t nChar = tCell->m_text.size();
for (size_t ch = 0; ch < nChar; ch++) {
auto c = static_cast<unsigned char>(tCell->m_text[ch]);
switch (c) {
case 0x9:
MWAW_DEBUG_MSG(("MsWksTable::sendTable: find a tab\n"));
listener->insertChar(' ');
break;
case 0xd:
listener->insertEOL();
break;
default:
listener->insertCharacter(c);
break;
}
}
}
listener->closeTableCell();
}
listener->closeTableRow();
}
// close the table
listener->closeTable();
return true;
}
bool MsWksTable::readTable(int numCol, int numRow, int zoneId, MsWksGraph::Style const &style)
{
int vers=version();
MWAWInputStreamPtr input=m_zone.getInput();
long actPos = input->tell();
libmwaw::DebugFile &ascFile = m_zone.ascii();
libmwaw::DebugStream f, f2;
f << "Entries(Table): ";
MsWksTableInternal::Table table(style);
table.m_numRows=numRow;
table.m_numCols=numCol;
// first we read the dim
for (int i = 0; i < 2; i++) {
auto &dim = i==0 ? table.m_rowsDim : table.m_colsDim;
dim.resize(0);
auto sz = static_cast<int>(input->readLong(4));
if (i == 0 && sz != 2*table.m_numRows) return false;
if (i == 1 && sz != 2*table.m_numCols) return false;
if (i == 0) f << "rowS=(";
else f << "colS=(";
for (int j = 0; j < sz/2; j++) {
auto val = static_cast<int>(input->readLong(2));
if (val < -10) return false;
dim.push_back(val);
f << val << ",";
}
f << "), ";
}
long sz = input->readLong(4);
f << "szOfCells=" << sz;
ascFile.addPos(actPos);
ascFile.addNote(f.str().c_str());
actPos = input->tell();
long endPos = actPos+sz;
// now we read the data for each size
while (input->tell() != endPos) {
f.str("");
actPos = input->tell();
MsWksTableInternal::Table::Cell cell;
auto y = static_cast<int>(input->readLong(2));
auto x = static_cast<int>(input->readLong(2));
cell.m_pos = MWAWVec2i(x,y);
if (x < 0 || y < 0 ||
x >= table.m_numCols || y >= table.m_numRows) return false;
f << "Table:("<< cell.m_pos << "):";
auto nbChar = static_cast<int>(input->readLong(1));
if (nbChar < 0 || actPos+5+nbChar > endPos) return false;
std::string fName("");
for (int c = 0; c < nbChar; c++)
fName +=char(input->readLong(1));
input->seek(actPos+34, librevenge::RVNG_SEEK_SET);
f << std::hex << "unk=" << input->readLong(2) << ", "; // 0|827
auto v = static_cast<int>(input->readLong(2));
if (v) f << "f0=" << v << ", ";
auto fSize = static_cast<int>(input->readLong(2));
v = static_cast<int>(input->readLong(2));
if (v) f2 << "unkn0=" << v << ", ";
auto fFlags = static_cast<int>(input->readLong(2));
nbChar = static_cast<int>(input->readLong(4));
if (nbChar <= 0 || input->tell()+nbChar > endPos) return false;
v = static_cast<int>(input->readLong(2));
if (v) f << "f1=" << v << ", ";
auto fColors = static_cast<int>(input->readLong(2));
v = static_cast<int>(input->readLong(2));
if (v) f << "f2=" << v << ", ";
auto bgColors = static_cast<int>(input->readLong(2));
if (bgColors)
f2 << std::dec << "bgColorId(?)=" << bgColors << ", "; // indexed
cell.m_font=MWAWFont(m_parserState->m_fontConverter->getId(fName), float(fSize));
uint32_t flags = 0;
if (fFlags & 0x1) flags |= MWAWFont::boldBit;
if (fFlags & 0x2) flags |= MWAWFont::italicBit;
if (fFlags & 0x4) cell.m_font.setUnderlineStyle(MWAWFont::Line::Simple);
if (fFlags & 0x8) flags |= MWAWFont::embossBit;
if (fFlags & 0x10) flags |= MWAWFont::shadowBit;
if (fFlags & 0x20) {
if (vers==1)
cell.m_font.set(MWAWFont::Script(20,librevenge::RVNG_PERCENT,80));
else
cell.m_font.set(MWAWFont::Script::super100());
}
if (fFlags & 0x40) {
if (vers==1)
cell.m_font.set(MWAWFont::Script(-20,librevenge::RVNG_PERCENT,80));
else
cell.m_font.set(MWAWFont::Script::sub100());
}
cell.m_font.setFlags(flags);
if (fColors != 0xFF) {
MWAWColor col;
if (m_zone.getColor(fColors,col,3))
cell.m_font.setColor(col);
else
f << "#colId=" << fColors << ",";
}
f << "[" << cell.m_font.getDebugString(m_parserState->m_fontConverter) << "," << f2.str()<< "],";
// check what happens, if the size of text is greater than 4
for (int c = 0; c < nbChar; c++)
cell.m_text+=char(input->readLong(1));
f << cell.m_text;
table.m_cellsList.push_back(cell);
ascFile.addPos(actPos);
ascFile.addNote(f.str().c_str());
}
if (m_state->m_idTableMap.find(zoneId)!=m_state->m_idTableMap.end()) {
MWAW_DEBUG_MSG(("MsWksTable::readTable: oops a table with id=%d already exists\n", zoneId));
}
else
m_state->m_idTableMap[zoneId]=table;
return true;
}
////////////////////////////////////////////////////////////
// chart
////////////////////////////////////////////////////////////
void MsWksTable::setChartZoneId(int chartId, int zoneId)
{
if (m_state->m_idChartMap.find(chartId)==m_state->m_idChartMap.end()) {
MWAW_DEBUG_MSG(("MsWksTable::setChartZoneId: can not find chart %d\n", chartId));
return;
}
auto &chart = m_state->m_idChartMap.find(chartId)->second;
chart.m_zoneId = zoneId;
}
bool MsWksTable::sendChart(int chartId)
{
MWAWListenerPtr listener=m_parserState->getMainListener();
if (!listener) {
MWAW_DEBUG_MSG(("MsWksTable::sendChart: can not find a listener\n"));
return false;
}
if (m_state->m_idChartMap.find(chartId)==m_state->m_idChartMap.end()) {
MWAW_DEBUG_MSG(("MsWksTable::sendChart: can not find chart %d\n", chartId));
return false;
}
auto &chart = m_state->m_idChartMap.find(chartId)->second;
MWAWInputStreamPtr input=m_zone.getInput();
MWAWPosition chartPos;
if (chart.m_zoneId < 0 || !m_graphParser->getZonePosition(chart.m_zoneId, MWAWPosition::Frame, chartPos)) {
MWAW_DEBUG_MSG(("MsWksTable::sendChart: oops can not find chart bdbox %d[%d]\n", chartId, chart.m_zoneId));
return false;
}
MWAWPosition pictPos(MWAWVec2f(0,0), chartPos.size(),librevenge::RVNG_POINT);
pictPos.setRelativePosition(MWAWPosition::Frame, MWAWPosition::XLeft, MWAWPosition::YTop);
if (chart.m_backgroundEntry.valid()) {
long actPos = input->tell();
#ifdef DEBUG_WITH_FILES
if (1) {
librevenge::RVNGBinaryData file;
input->seek(chart.m_backgroundEntry.begin(), librevenge::RVNG_SEEK_SET);
input->readDataBlock(chart.m_backgroundEntry.length(), file);
static int volatile pictName = 0;
libmwaw::DebugStream f;
f << "Pict-" << ++pictName << ".pct";
libmwaw::Debug::dumpFile(file, f.str().c_str());
}
#endif
input->seek(chart.m_backgroundEntry.begin(), librevenge::RVNG_SEEK_SET);
MWAWBox2f naturalBox;
auto res = MWAWPictData::check(input, static_cast<int>(chart.m_backgroundEntry.length()), naturalBox);
if (res == MWAWPict::MWAW_R_BAD) {
MWAW_DEBUG_MSG(("MsWksTable::sendChart: can not find the picture\n"));
}
else {
input->seek(chart.m_backgroundEntry.begin(), librevenge::RVNG_SEEK_SET);
std::shared_ptr<MWAWPict> pict(MWAWPictData::get(input, static_cast<int>(chart.m_backgroundEntry.length())));
MWAWEmbeddedObject picture;
if (pict && pict->getBinary(picture))
listener->insertPicture(pictPos, picture);
}
input->seek(actPos, librevenge::RVNG_SEEK_SET);
}
for (int i=0; i < 3; i++) {
int cId=chart.m_textZonesId[i];
MWAWPosition childPos;
if (!m_graphParser->getZonePosition(cId, MWAWPosition::Frame, childPos)) {
MWAW_DEBUG_MSG(("MsWksTable::sendChart: oops can not find chart bdbox for child %d[%d]\n", i, cId));
continue;
}
MWAWPosition textPos(pictPos);
textPos.setOrigin(childPos.origin()-chartPos.origin());
textPos.setSize(childPos.size());
m_graphParser->send(cId, textPos);
}
return true;
}
bool MsWksTable::readChart(int chartId, MsWksGraph::Style const &style)
{
// checkme: works for some chart, but not sure that it can work for all chart...
MWAWInputStreamPtr input=m_zone.getInput();
long pos = input->tell();
int const vers=version();
if (vers<=2 || (vers==3&&m_parserState->m_type!=MWAWParserState::Spreadsheet) ||
!input->checkPosition(pos+306))
return false;
libmwaw::DebugFile &ascFile = m_zone.ascii();
libmwaw::DebugStream f;
f << "Entries(Chart):";
MsWksTableInternal::Chart chart(style);
auto val = static_cast<int>(input->readLong(2));
switch (val) {
case 1:
f << "bar,";
break;
case 2:
f << "stacked,";
break;
case 3:
f << "line,";
break; // checkme
case 4:
f << "combo,";
break; // checkme
case 5:
f << "pie,";
break; // checkme
case 6:
f << "hi-lo-choose,";
break; // checkme
default:
f << "#type=val";
break;
}
for (int i = 0; i < 4; i++) {
val = static_cast<int>(input->readLong(2));
if (val) f << "col" << i << "=" << val << ",";
}
f << "rows=";
for (int i = 0; i < 2; i++) {
val = static_cast<int>(input->readLong(2));
f << val;
if (i==0) f << "-";
else f << ",";
}
val = static_cast<int>(input->readLong(2));
if (val) f << "colLabels=" << val << ",";
val = static_cast<int>(input->readLong(2));
if (val) f << "rowLabels=" << val << ",";
std::string name("");
auto sz = static_cast<int>(input->readULong(1));
if (sz > 31) {
MWAW_DEBUG_MSG(("MsWksTable::readChart: string size is too long\n"));
return false;
}
for (int i = 0; i < sz; i++) {
auto c = char(input->readLong(1));
if (!c) break;
name+=c;
}
f << name << ",";
input->seek(pos+50, librevenge::RVNG_SEEK_SET);
for (int i = 0; i < 128; i++) { // always 0 ?
val = static_cast<int>(input->readLong(2));
if (val) f << "g" << i << "=" << val << std::dec << ",";
}
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
pos = input->tell();
ascFile.addPos(pos);
ascFile.addNote("Chart(II)");
input->seek(vers==3 ? 1992 : 2428, librevenge::RVNG_SEEK_CUR);
// three textbox
for (int i = 0; i < 3; i++) {
pos = input->tell();
MWAWEntry childZone;
chart.m_textZonesId[i] = m_graphParser->getEntryPicture(-9999, childZone, false, i+2);
if (chart.m_textZonesId[i]<0) {
MWAW_DEBUG_MSG(("MsWksTable::readChart: can not find textbox\n"));
input->seek(pos, librevenge::RVNG_SEEK_SET);
return false;
}
}
// the background picture
pos = input->tell();
auto dataSz = long(input->readULong(4));
auto smDataSz = long(input->readULong(2));
if (!dataSz || (dataSz&0xFFFF) != smDataSz || !input->checkPosition(pos+4+dataSz))
// background picture not always present ( at least in v3)
input->seek(pos, librevenge::RVNG_SEEK_SET);
else {
MWAWEntry &background=chart.m_backgroundEntry;
background.setBegin(pos+4);
background.setLength(dataSz);
ascFile.skipZone(background.begin(), background.end()-1);
ascFile.addPos(pos);
ascFile.addNote("Chart(picture)");
input->seek(background.end(), librevenge::RVNG_SEEK_SET);
}
// last the value ( by columns ? )
for (int i = 0; i < 4; i++) {
pos = input->tell();
dataSz = long(input->readULong(4));
if (dataSz > input->size() - pos - 4)
dataSz = input->size() - pos - 4;
if (dataSz%0x10) {
MWAW_DEBUG_MSG(("MsWksTable::readChart: can not read end last zone\n"));
input->seek(pos, librevenge::RVNG_SEEK_SET);
return false;
}
f.str("");
f << "Chart(A" << i << ")";
ascFile.addPos(pos);
ascFile.addNote(f.str().c_str());
auto numLine = int(dataSz/0x10);
for (int l = 0; l < numLine; l++) {
f.str("");
f << "Chart(A" << i << "-" << l << ")";
ascFile.addPos(pos+4+0x10*l);
ascFile.addNote(f.str().c_str());
}
if (input->seek(pos+4+dataSz, librevenge::RVNG_SEEK_SET) != 0) {
MWAW_DEBUG_MSG(("MsWksTable::readChart: reading past the end\n"));
return false;
}
}
if (m_state->m_idChartMap.find(chartId)!=m_state->m_idChartMap.end()) {
MWAW_DEBUG_MSG(("MsWksTable::readChart: oops a chart with id=%d already exists\n", chartId));
}
else
m_state->m_idChartMap[chartId]=chart;
return true;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: