/* -*- 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.
*/
/*
* Structure to store and construct a table from an unstructured list
* of cell
*
*/
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <librevenge/librevenge.h>
#include "MWAWCell.hxx"
#include "MWAWGraphicShape.hxx"
#include "MWAWGraphicStyle.hxx"
#include "MWAWListener.hxx"
#include "MWAWPosition.hxx"
#include "MWAWTable.hxx"
/** Internal: the structures of a MWAWTable */
namespace MWAWTableInternal
{
//! a comparaison structure used retrieve the rows and the columns
struct Compare {
//! constructor
explicit Compare(int dim)
: m_coord(dim)
{
}
//! small structure to define a cell point
struct Point {
Point(int wh, MWAWCell const *cell, int cellId)
: m_which(wh)
, m_cell(cell)
, m_cellId(cellId)
{
}
float getPos(int coord) const
{
if (m_which)
return m_cell->bdBox().max()[coord];
return m_cell->bdBox().min()[coord];
}
/** returns the cells size */
float getSize(int coord) const
{
return m_cell->bdBox().size()[coord];
}
/** the position of the point in the cell (0: LT, 1: RB) */
int m_which;
/** the cell */
MWAWCell const *m_cell;
//! the cell id ( used by compare)
int m_cellId;
};
//! comparaison function
bool operator()(Point const &c1, Point const &c2) const
{
float diffF = c1.getPos(m_coord)-c2.getPos(m_coord);
if (diffF < 0) return true;
if (diffF > 0) return false;
int diff = c2.m_which - c1.m_which;
if (diff) return (diff < 0);
diffF = c1.m_cell->bdBox().size()[m_coord]
- c2.m_cell->bdBox().size()[m_coord];
if (diffF < 0) return true;
if (diffF > 0) return false;
return c1.m_cellId < c2.m_cellId;
}
//! the coord to compare
int m_coord;
};
}
////////////////////////////////////////////////////////////
// MWAWTable
////////////////////////////////////////////////////////////
// destructor, ...
MWAWTable::~MWAWTable()
{
}
std::shared_ptr<MWAWCell> MWAWTable::get(int id)
{
if (id < 0 || id >= int(m_cellsList.size())) {
MWAW_DEBUG_MSG(("MWAWTable::get: cell %d does not exists\n",id));
return std::shared_ptr<MWAWCell>();
}
return m_cellsList[size_t(id)];
}
void MWAWTable::addTablePropertiesTo(librevenge::RVNGPropertyList &propList) const
{
switch (m_alignment) {
case Paragraph:
break;
case Left:
propList.insert("table:align", "left");
propList.insert("fo:margin-left", double(m_leftMargin), librevenge::RVNG_POINT);
break;
case Center:
propList.insert("table:align", "center");
break;
case Right:
propList.insert("table:align", "right");
propList.insert("fo:margin-right", double(m_rightMargin), librevenge::RVNG_POINT);
break;
#if !defined(__clang__)
default:
break;
#endif
}
if (mergeBorders())
propList.insert("table:border-model","collapsing");
size_t nCols = m_colsSize.size();
float tableWidth = 0;
librevenge::RVNGPropertyListVector columns;
for (size_t c = 0; c < nCols; ++c) {
librevenge::RVNGPropertyList column;
column.insert("style:column-width", double(m_colsSize[c]), librevenge::RVNG_POINT);
columns.append(column);
tableWidth += m_colsSize[c];
}
propList.insert("style:width", double(tableWidth), librevenge::RVNG_POINT);
propList.insert("librevenge:table-columns", columns);
}
////////////////////////////////////////////////////////////
// send extra line
void MWAWTable::sendExtraLines(MWAWListenerPtr listener) const
{
if (!listener) {
MWAW_DEBUG_MSG(("MWAWTable::sendExtraLines: called without listener\n"));
return;
}
std::vector<float> rowsPos, columnsPos;
size_t nRows = m_rowsSize.size();
rowsPos.resize(nRows+1);
rowsPos[0] = 0;
for (size_t r = 0; r < nRows; ++r)
rowsPos[r+1] = rowsPos[r]+
float(m_rowsSize[r]<0?-m_rowsSize[r]:m_rowsSize[r]);
size_t nColumns = m_colsSize.size();
columnsPos.resize(nColumns+1);
columnsPos[0] = 0;
for (size_t c = 0; c < nColumns; ++c)
columnsPos[c+1] = columnsPos[c]+
float(m_colsSize[c]<0?-m_colsSize[c]:m_colsSize[c]);
for (const auto &c : m_cellsList) {
if (!c) continue;
MWAWCell const &cell=*c;
if (!cell.hasExtraLine())
continue;
MWAWVec2i const &pos=c->position();
MWAWVec2i const &span=c->numSpannedCells();
if (span[0] <= 0 || span[1] <= 0 || pos[0]+span[0] > static_cast<int>(nColumns) ||
pos[1]+span[1] > static_cast<int>(nRows))
continue;
MWAWBox2f box;
box.setMin(MWAWVec2f(columnsPos[size_t(pos[0])], rowsPos[size_t(pos[1])]));
box.setMax(MWAWVec2f(columnsPos[size_t(pos[0]+span[0])],
rowsPos[size_t(pos[1]+span[1])]));
MWAWBorder const &border=cell.extraLineType();
MWAWGraphicStyle pStyle;
pStyle.m_lineWidth=float(border.m_width);
pStyle.m_lineColor=border.m_color;
MWAWPosition lPos(box[0], box.size(), librevenge::RVNG_POINT);
lPos.setRelativePosition(MWAWPosition::Frame);
lPos.m_wrapping=MWAWPosition::WForeground;
lPos.setOrder(-1);
if (cell.extraLine()==MWAWCell::E_Cross || cell.extraLine()==MWAWCell::E_Line1)
listener->insertShape(lPos, MWAWGraphicShape::line(MWAWVec2f(0,0), box.size()), pStyle);
if (cell.extraLine()==MWAWCell::E_Cross || cell.extraLine()==MWAWCell::E_Line2)
listener->insertShape(lPos, MWAWGraphicShape::line(MWAWVec2f(0,box.size()[1]), MWAWVec2f(box.size()[0], 0)), pStyle);
}
}
////////////////////////////////////////////////////////////
// build the table structure
bool MWAWTable::buildStructures()
{
if (m_setData&CellPositionBit)
return true;
if ((m_setData&BoxBit)==0) {
MWAW_DEBUG_MSG(("MWAWTable::buildStructures: can not reconstruct cellule position if their boxes are not set\n"));
return false;
}
size_t nCells = m_cellsList.size();
std::vector<float> listPositions[2];
for (int dim = 0; dim < 2; dim++) {
MWAWTableInternal::Compare compareFunction(dim);
std::set<MWAWTableInternal::Compare::Point,
MWAWTableInternal::Compare> set(compareFunction);
for (size_t c = 0; c < nCells; ++c) {
set.insert(MWAWTableInternal::Compare::Point(0, m_cellsList[c].get(), int(c)));
set.insert(MWAWTableInternal::Compare::Point(1, m_cellsList[c].get(), int(c)));
}
std::vector<float> positions;
float maxPosiblePos=0;
int actCell = -1;
for (auto it : set) {
float pos = it.getPos(dim);
if (actCell < 0 || pos > maxPosiblePos) {
actCell++;
positions.push_back(pos);
maxPosiblePos = pos+2.0f; // 2 pixel ok
}
if (it.m_which == 0 && it.getPos(dim)-2.0f < maxPosiblePos)
maxPosiblePos = (it.getPos(dim)+pos)/2.f;
}
listPositions[dim] = positions;
}
for (auto cell : m_cellsList) {
if (!cell) continue;
int cellPos[2], spanCell[2];
for (int dim = 0; dim < 2; dim++) {
float pt[2] = { cell->bdBox().min()[dim],
cell->bdBox().max()[dim]
};
std::vector<float> &pos = listPositions[dim];
size_t numPos = pos.size();
size_t i = 0;
while (i+1 < numPos && pos[i+1] < pt[0])
i++;
if (i+1 < numPos && (pos[i]+pos[i+1])/2 < pt[0])
i++;
if (i+1 > numPos) {
MWAW_DEBUG_MSG(("MWAWTable::buildStructures: impossible to find cell position !!!\n"));
return false;
}
cellPos[dim] = int(i);
while (i+1 < numPos && pos[i+1] < pt[1])
i++;
if (i+1 < numPos && (pos[i]+pos[i+1])/2 < pt[1])
i++;
spanCell[dim] = int(i)-cellPos[dim];
if (spanCell[dim]==0 &&
(cell->bdBox().size()[dim] < 0 || cell->bdBox().size()[dim] > 0)) {
MWAW_DEBUG_MSG(("MWAWTable::buildStructures: impossible to find span number !!!\n"));
return false;
}
if (spanCell[dim] > 1 &&
pos[size_t(cellPos[dim])]+2.0f > pos[size_t(cellPos[dim]+1)]) {
spanCell[dim]--;
cellPos[dim]++;
}
}
cell->setPosition(MWAWVec2i(cellPos[0], cellPos[1]));
cell->setNumSpannedCells(MWAWVec2i(spanCell[0], spanCell[1]));
}
m_setData |= CellPositionBit;
// finally update the row/col size
for (int dim = 0; dim < 2; dim++) {
std::vector<float> const &pos = listPositions[dim];
size_t numPos = pos.size();
if (!numPos) continue;
std::vector<float> &res = (dim==0) ? m_colsSize : m_rowsSize;
res.resize(numPos-1);
for (size_t i = 0; i < numPos-1; i++)
res[i] = pos[i+1]-pos[i];
}
m_setData |= TableDimBit;
return true;
}
bool MWAWTable::buildPosToCellId()
{
if (m_setData&TablePosToCellBit)
return true;
if ((m_setData&CellPositionBit)==0) {
MWAW_DEBUG_MSG(("MWAWTable::buildPosToCellId: can not reconstruct cellule position if their boxes are not set\n"));
return false;
}
m_posToCellId.resize(0);
size_t nCells = m_cellsList.size();
m_numRows=(m_setData&TableDimBit) ? m_rowsSize.size() : 0;
m_numCols=(m_setData&TableDimBit) ? m_colsSize.size() : 0;
if ((m_setData&TableDimBit)==0) {
// m_numCols, m_numRows is not updated, we must compute it
m_numCols = 0;
m_numRows = 0;
for (auto &cell : m_cellsList) {
if (!cell) continue;
MWAWVec2i const &lastPos=cell->position() + cell->numSpannedCells();
if (lastPos[0]>int(m_numCols)) m_numCols=size_t(lastPos[0]);
if (lastPos[1]>int(m_numRows)) m_numRows=size_t(lastPos[1]);
}
}
if (!m_numCols || !m_numRows)
return false;
m_posToCellId.resize(m_numCols*m_numRows, -1);
for (size_t c = 0; c < nCells; ++c) {
if (!m_cellsList[c]) continue;
if (m_cellsList[c]->hasExtraLine())
m_hasExtraLines=true;
MWAWVec2i const &pos=m_cellsList[c]->position();
MWAWVec2i lastPos=pos+m_cellsList[c]->numSpannedCells();
for (int x = pos[0]; x < lastPos[0]; x++) {
for (int y = pos[1]; y < lastPos[1]; y++) {
int tablePos = getCellIdPos(x,y);
if (tablePos<0) {
MWAW_DEBUG_MSG(("MWAWTable::buildPosToCellId: the position is bad!!!\n"));
return false;
}
if (m_posToCellId[size_t(tablePos)] != -1) {
MWAW_DEBUG_MSG(("MWAWTable::buildPosToCellId: cells is used!!!\n"));
return false;
}
if (x == pos[0] && y == pos[1])
m_posToCellId[size_t(tablePos)] = int(c);
else
m_posToCellId[size_t(tablePos)] = -2;
}
}
}
m_setData |= TablePosToCellBit;
return true;
}
bool MWAWTable::buildDims()
{
if (m_setData&TableDimBit)
return true;
if ((m_setData&CellPositionBit)==0)
return false;
if ((m_setData&BoxBit)==0 && (m_setData&SizeBit)==0) {
MWAW_DEBUG_MSG(("MWAWTable::buildDims: not enough information to reconstruct dimension\n"));
return false;
}
if (m_numRows==0 || m_numCols==0) {
MWAW_DEBUG_MSG(("MWAWTable::buildDims: can not compute the number of columns/row\n"));
return false;
}
std::vector<float> colLimit(m_numCols+1,0);
std::vector<bool> isFixed(m_numCols+1, (m_setData&BoxBit));
for (int c = 0; c < int(m_numCols); ++c) {
for (int r = 0; r < int(m_numRows); ++r) {
int cPos = getCellIdPos(c, r);
if (cPos<0 || m_posToCellId[size_t(cPos)]<0) continue;
auto cell=m_cellsList[size_t(m_posToCellId[size_t(cPos)])];
if (!cell) continue;
MWAWVec2i const &pos=cell->position();
MWAWVec2i lastPos=pos+cell->numSpannedCells();
if (m_setData&BoxBit) {
colLimit[size_t(pos[0])] = cell->bdBox()[0][0];
colLimit[size_t(lastPos[0])] = cell->bdBox()[1][0];
}
else if (cell->bdSize()[0]>=0) {
colLimit[size_t(lastPos[0])] = colLimit[size_t(pos[0])]+cell->bdSize()[0];
isFixed[size_t(lastPos[0])]=true;
}
else if (!isFixed[size_t(lastPos[0])])
colLimit[size_t(lastPos[0])] = colLimit[size_t(pos[0])]-cell->bdSize()[0];
}
if (colLimit[size_t(c)+1]<=colLimit[size_t(c)]) {
MWAW_DEBUG_MSG(("MWAWTable::buildDims: oops can not find the size of col %d\n", c));
return false;
}
}
m_colsSize.resize(m_numCols);
for (size_t c = 0; c < m_numCols; ++c) {
if (isFixed[c+1])
m_colsSize[c]=colLimit[c+1]-colLimit[c];
else
m_colsSize[c]=colLimit[c]-colLimit[c+1];
}
std::vector<float> rowLimit(m_numRows+1,0);
isFixed.resize(0);
isFixed.resize(m_numRows+1,(m_setData&BoxBit));
for (int r = 0; r < int(m_numRows); ++r) {
for (int c = 0; c < int(m_numCols); ++c) {
int cPos = getCellIdPos(c, r);
if (cPos<0 || m_posToCellId[size_t(cPos)]<0) continue;
auto cell=m_cellsList[size_t(m_posToCellId[size_t(cPos)])];
if (!cell) continue;
MWAWVec2i const &pos=cell->position();
MWAWVec2i lastPos=pos+cell->numSpannedCells();
if (m_setData&BoxBit) {
rowLimit[size_t(pos[1])] = cell->bdBox()[0][1];
rowLimit[size_t(lastPos[1])] = cell->bdBox()[1][1];
}
else if (cell->bdSize()[1]>=0) {
rowLimit[size_t(lastPos[1])] = rowLimit[size_t(pos[1])]+cell->bdSize()[1];
isFixed[size_t(lastPos[1])]=true;
}
else if (!isFixed[size_t(lastPos[1])])
rowLimit[size_t(lastPos[1])] = rowLimit[size_t(pos[1])]-cell->bdSize()[1];
}
if (rowLimit[size_t(r)+1]<=rowLimit[size_t(r)]) {
MWAW_DEBUG_MSG(("MWAWTable::buildDims: oops can not find the size of row %d\n", r));
return false;
}
}
m_rowsSize.resize(m_numRows);
for (size_t r = 0; r < m_numRows; ++r) {
if (isFixed[r+1])
m_rowsSize[r]=rowLimit[r+1]-rowLimit[r];
else
m_rowsSize[r]=rowLimit[r]-rowLimit[r+1];
}
m_setData |= TableDimBit;
return true;
}
////////////////////////////////////////////////////////////
// try to send the table
bool MWAWTable::updateTable()
{
if ((m_setData&CellPositionBit)==0 && !buildStructures())
return false;
if ((m_setData&TablePosToCellBit)==0 && !buildPosToCellId())
return false;
if (!m_numCols || !m_numRows)
return false;
if ((m_givenData&TableDimBit)==0 && !buildDims())
return false;
return true;
}
bool MWAWTable::sendTable(MWAWListenerPtr listener, bool inFrame)
{
if (!updateTable())
return false;
if (!listener)
return true;
listener->openTable(*this);
for (size_t r = 0; r < m_numRows; ++r) {
listener->openTableRow(m_rowsSize[r], librevenge::RVNG_POINT);
for (size_t c = 0; c < m_numCols; ++c) {
int tablePos = getCellIdPos(int(c), int(r));
if (tablePos<0)
continue;
int id = m_posToCellId[size_t(tablePos)];
if (id == -1)
listener->addEmptyTableCell(MWAWVec2i(int(c), int(r)));
if (id < 0) continue;
m_cellsList[size_t(id)]->send(listener, *this);
}
listener->closeTableRow();
}
listener->closeTable();
if (inFrame && m_hasExtraLines)
sendExtraLines(listener);
return true;
}
////////////////////////////////////////////////////////////
// try to send the table
bool MWAWTable::sendAsText(MWAWListenerPtr listener)
{
if (!listener) return true;
for (auto cell : m_cellsList) {
if (!cell) continue;
cell->sendContent(listener, *this);
listener->insertEOL();
}
return true;
}