/* -*- 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 "MSPUBCollector.h"
#include <algorithm>
#include <functional>
#include <math.h>
#include <memory>
#include <boost/multi_array.hpp>
#include <unicode/ucsdet.h>
#include <unicode/uloc.h>
#include "Arrow.h"
#include "Coordinate.h"
#include "Dash.h"
#include "Fill.h"
#include "Line.h"
#include "Margins.h"
#include "MSPUBConstants.h"
#include "MSPUBTypes.h"
#include "PolygonUtils.h"
#include "Shadow.h"
#include "ShapeGroupElement.h"
#include "TableInfo.h"
#include "VectorTransformation2D.h"
#include "libmspub_utils.h"
namespace libmspub
{
using namespace std::placeholders;
namespace
{
static void separateTabsAndInsertText(librevenge::RVNGDrawingInterface *iface, const librevenge::RVNGString &text)
{
if (!iface || text.empty())
return;
librevenge::RVNGString tmpText;
librevenge::RVNGString::Iter i(text);
for (i.rewind(); i.next();)
{
if (*(i()) == '\t')
{
if (!tmpText.empty())
{
if (iface)
iface->insertText(tmpText);
tmpText.clear();
}
if (iface)
iface->insertTab();
}
else if (*(i()) == '\n')
{
if (!tmpText.empty())
{
if (iface)
iface->insertText(tmpText);
tmpText.clear();
}
if (iface)
iface->insertLineBreak();
}
else
{
tmpText.append(i());
}
}
if (iface && !tmpText.empty())
iface->insertText(tmpText);
}
static void separateSpacesAndInsertText(librevenge::RVNGDrawingInterface *iface, const librevenge::RVNGString &text)
{
if (!iface)
return;
if (text.empty())
{
iface->insertText(text);
return;
}
librevenge::RVNGString tmpText;
int numConsecutiveSpaces = 0;
librevenge::RVNGString::Iter i(text);
for (i.rewind(); i.next();)
{
if (*(i()) == ' ')
numConsecutiveSpaces++;
else
numConsecutiveSpaces = 0;
if (numConsecutiveSpaces > 1)
{
if (!tmpText.empty())
{
separateTabsAndInsertText(iface, tmpText);
tmpText.clear();
}
if (iface)
iface->insertSpace();
}
else
{
tmpText.append(i());
}
}
separateTabsAndInsertText(iface, tmpText);
}
struct TableLayoutCell
{
TableLayoutCell()
: m_cell(0)
, m_rowSpan(0)
, m_colSpan(0)
{
}
unsigned m_cell;
unsigned m_rowSpan;
unsigned m_colSpan;
};
bool isCovered(const TableLayoutCell &cell)
{
assert((cell.m_rowSpan == 0) == (cell.m_colSpan == 0));
return (cell.m_rowSpan == 0) && (cell.m_colSpan == 0);
}
typedef boost::multi_array<TableLayoutCell, 2> TableLayout;
void createTableLayout(const std::vector<CellInfo> &cells, TableLayout &tableLayout)
{
for (auto it = cells.begin(); it != cells.end(); ++it)
{
if ((it->m_endRow >= tableLayout.shape()[0]) || (it->m_endColumn >= tableLayout.shape()[1]))
{
MSPUB_DEBUG_MSG((
"cell %u (rows %u to %u, columns %u to %u) overflows the table, ignoring\n",
unsigned(int(it - cells.begin())),
it->m_startRow, it->m_endRow,
it->m_startColumn, it->m_endColumn
));
continue;
}
if (it->m_startRow > it->m_endRow)
{
MSPUB_DEBUG_MSG((
"cell %u (rows %u to %u) has got negative row span, ignoring\n",
unsigned(int(it - cells.begin())),
it->m_startRow, it->m_endRow
));
continue;
}
if (it->m_startColumn > it->m_endColumn)
{
MSPUB_DEBUG_MSG((
"cell %u (columns %u to %u) has got negative column span, ignoring\n",
unsigned(int(it - cells.begin())),
it->m_startColumn, it->m_endColumn
));
continue;
}
const unsigned rowSpan = it->m_endRow - it->m_startRow + 1;
const unsigned colSpan = it->m_endColumn - it->m_startColumn + 1;
if ((rowSpan == 0) != (colSpan == 0))
{
MSPUB_DEBUG_MSG((
"cell %u (rows %u to %u, columns %u to %u) has got 0 span in one dimension, ignoring\n",
unsigned(int(it - cells.begin())),
it->m_startRow, it->m_endRow,
it->m_startColumn, it->m_endColumn
));
continue;
}
TableLayoutCell &layoutCell = tableLayout[it->m_startRow][it->m_startColumn];
layoutCell.m_cell = unsigned(int(it - cells.begin()));
layoutCell.m_rowSpan = rowSpan;
layoutCell.m_colSpan = colSpan;
}
}
typedef std::vector<std::pair<unsigned, unsigned> > ParagraphToCellMap_t;
typedef std::vector<librevenge::RVNGString> SpanTexts_t;
typedef std::vector<SpanTexts_t> ParagraphTexts_t;
void mapTableTextToCells(
const std::vector<TextParagraph> &text,
const std::vector<unsigned> &tableCellTextEnds,
const char *const encoding,
ParagraphToCellMap_t ¶ToCellMap,
ParagraphTexts_t ¶Texts
)
{
assert(paraToCellMap.empty());
assert(paraTexts.empty());
paraToCellMap.reserve(tableCellTextEnds.size());
paraTexts.reserve(tableCellTextEnds.size());
unsigned firstPara = 0;
unsigned offset = 1;
for (unsigned para = 0; para != text.size() && paraToCellMap.size() < tableCellTextEnds.size(); ++para)
{
paraTexts.push_back(SpanTexts_t());
paraTexts.back().reserve(text[para].spans.size());
for (unsigned i_spans = 0; i_spans != text[para].spans.size(); ++i_spans)
{
librevenge::RVNGString textString;
appendCharacters(textString, text[para].spans[i_spans].chars, encoding);
offset += textString.len();
// TODO: why do we not drop these during parse already?
if ((i_spans == text[para].spans.size() - 1) && (textString == "\r"))
continue;
paraTexts.back().push_back(textString);
}
assert(paraTexts.back().size() <= text[para].spans.size());
if (offset >= tableCellTextEnds[paraToCellMap.size()])
{
if (offset > tableCellTextEnds[paraToCellMap.size()])
{
MSPUB_DEBUG_MSG(("text of cell %u ends in the middle of a paragraph!\n", unsigned(paraToCellMap.size())));
}
paraToCellMap.push_back(std::make_pair(firstPara, para));
firstPara = para + 1;
}
}
assert(paraTexts.size() == text.size());
assert(paraToCellMap.size() <= tableCellTextEnds.size());
}
void fillUnderline(librevenge::RVNGPropertyList &props, const Underline underline)
{
switch (underline)
{
case Underline::None:
return;
case Underline::Single:
case Underline::WordsOnly:
case Underline::Double:
case Underline::Thick:
props.insert("style:text-underline-style", "solid");
break;
case Underline::Dotted:
case Underline::ThickDot:
props.insert("style:text-underline-style", "dotted");
break;
case Underline::Dash:
case Underline::ThickDash:
props.insert("style:text-underline-style", "dash");
break;
case Underline::DotDash:
case Underline::ThickDotDash:
props.insert("style:text-underline-style", "dot-dash");
break;
case Underline::DotDotDash:
case Underline::ThickDotDotDash:
props.insert("style:text-underline-style", "dot-dot-dash");
break;
case Underline::Wave:
case Underline::ThickWave:
case Underline::DoubleWave:
props.insert("style:text-underline-style", "wave");
break;
case Underline::LongDash:
case Underline::ThickLongDash:
props.insert("style:text-underline-style", "long-dash");
break;
}
switch (underline)
{
case Underline::Double:
case Underline::DoubleWave:
props.insert("style:text-underline-type", "double");
break;
default:
props.insert("style:text-underline-type", "single");
break;
}
switch (underline)
{
case Underline::Thick:
case Underline::ThickWave:
case Underline::ThickDot:
case Underline::ThickDash:
case Underline::ThickDotDash:
case Underline::ThickDotDotDash:
props.insert("style:text-underline-width", "bold");
break;
default:
props.insert("style:text-underline-width", "auto");
break;
}
switch (underline)
{
case Underline::WordsOnly:
props.insert("style:text-underline-mode", "skip-white-space");
break;
default:
props.insert("style:text-underline-mode", "continuous");
break;
}
}
void fillLocale(librevenge::RVNGPropertyList &props, const unsigned lcid)
{
char locale[ULOC_FULLNAME_CAPACITY];
UErrorCode status = U_ZERO_ERROR;
uloc_getLocaleForLCID(lcid, locale, ULOC_FULLNAME_CAPACITY, &status);
if (!U_SUCCESS(status))
return;
char component[ULOC_FULLNAME_CAPACITY];
int32_t len = uloc_getLanguage(locale, component, ULOC_FULLNAME_CAPACITY, &status);
if (U_SUCCESS(status) && len > 0)
props.insert("fo:language", component);
len = uloc_getCountry(locale, component, ULOC_FULLNAME_CAPACITY, &status);
if (U_SUCCESS(status) && len > 0)
props.insert("fo:country", component);
len = uloc_getScript(locale, component, ULOC_FULLNAME_CAPACITY, &status);
if (U_SUCCESS(status) && len > 0)
props.insert("fo:script", component);
}
} // anonymous namespace
void MSPUBCollector::collectMetaData(const librevenge::RVNGPropertyList &metaData)
{
m_metaData = metaData;
}
void MSPUBCollector::addEOTFont(const librevenge::RVNGString &name, const librevenge::RVNGBinaryData &data)
{
m_embeddedFonts.push_back(EmbeddedFontInfo(name, data));
}
void MSPUBCollector::setShapePictureRecolor(unsigned seqNum,
const ColorReference &recolor)
{
m_shapeInfosBySeqNum[seqNum].m_pictureRecolor = recolor;
}
void MSPUBCollector::setShapePictureBrightness(unsigned seqNum,
int brightness)
{
m_shapeInfosBySeqNum[seqNum].m_pictureBrightness = brightness;
}
void MSPUBCollector::setShapePictureContrast(unsigned seqNum,
int contrast)
{
m_shapeInfosBySeqNum[seqNum].m_pictureContrast = contrast;
}
void MSPUBCollector::setShapeBeginArrow(unsigned seqNum,
const Arrow &arrow)
{
m_shapeInfosBySeqNum[seqNum].m_beginArrow = arrow;
}
void MSPUBCollector::setShapeVerticalTextAlign(unsigned seqNum,
VerticalAlign va)
{
m_shapeInfosBySeqNum[seqNum].m_verticalAlign = va;
}
void MSPUBCollector::setShapeEndArrow(unsigned seqNum,
const Arrow &arrow)
{
m_shapeInfosBySeqNum[seqNum].m_endArrow = arrow;
}
void MSPUBCollector::setShapeTableInfo(unsigned seqNum,
const TableInfo &ti)
{
m_shapeInfosBySeqNum[seqNum].m_tableInfo = ti;
}
void MSPUBCollector::setShapeNumColumns(unsigned seqNum,
unsigned numColumns)
{
m_shapeInfosBySeqNum[seqNum].m_numColumns = numColumns;
}
void MSPUBCollector::setShapeColumnSpacing(unsigned seqNum,
unsigned spacing)
{
m_shapeInfosBySeqNum[seqNum].m_columnSpacing = spacing;
}
void MSPUBCollector::setShapeStretchBorderArt(unsigned seqNum)
{
m_shapeInfosBySeqNum[seqNum].m_stretchBorderArt = true;
}
void MSPUBCollector::setRectCoordProps(Coordinate coord, librevenge::RVNGPropertyList *props) const
{
props->insert("svg:x", coord.getXIn(m_width));
props->insert("svg:y", coord.getYIn(m_height));
props->insert("svg:width", coord.getWidthIn());
props->insert("svg:height", coord.getHeightIn());
}
Coordinate getFudgedCoordinates(Coordinate coord, const std::vector<Line> &lines, bool makeBigger, BorderPosition borderPosition)
{
Coordinate fudged = coord;
unsigned topFudge = 0;
unsigned rightFudge = 0;
unsigned bottomFudge = 0;
unsigned leftFudge = 0;
switch (borderPosition)
{
case HALF_INSIDE_SHAPE:
topFudge = (!lines.empty()) ? lines[0].m_widthInEmu / 2 : 0;
rightFudge = (lines.size() > 1) ? lines[1].m_widthInEmu / 2 : 0;
bottomFudge = (lines.size() > 2) ? lines[2].m_widthInEmu / 2 : 0;
leftFudge = (lines.size() > 3) ? lines[3].m_widthInEmu / 2 : 0;
break;
case OUTSIDE_SHAPE:
topFudge = (!lines.empty()) ? lines[0].m_widthInEmu : 0;
rightFudge = (lines.size() > 1) ? lines[1].m_widthInEmu : 0;
bottomFudge = (lines.size() > 2) ? lines[2].m_widthInEmu : 0;
leftFudge = (lines.size() > 3) ? lines[3].m_widthInEmu : 0;
break;
case INSIDE_SHAPE:
default:
break;
}
if (makeBigger)
{
fudged.m_xs -= leftFudge;
fudged.m_xe += rightFudge;
fudged.m_ys -= topFudge;
fudged.m_ye += bottomFudge;
}
else
{
fudged.m_xs += leftFudge;
fudged.m_xe -= rightFudge;
fudged.m_ys += topFudge;
fudged.m_ye -= bottomFudge;
}
return fudged;
}
void MSPUBCollector::setNextPage(unsigned pageSeqNum)
{
m_pageSeqNumsOrdered.push_back(pageSeqNum);
}
MSPUBCollector::MSPUBCollector(librevenge::RVNGDrawingInterface *painter) :
m_painter(painter), m_contentChunkReferences(), m_width(0), m_height(0),
m_widthSet(false), m_heightSet(false),
m_numPages(0), m_textStringsById(), m_pagesBySeqNum(),
m_images(), m_borderImages(),
m_textColors(), m_fonts(),
m_defaultCharStyles(), m_defaultParaStyles(), m_shapeTypesBySeqNum(),
m_paletteColors(), m_shapeSeqNumsOrdered(),
m_pageSeqNumsByShapeSeqNum(), m_bgShapeSeqNumsByPageSeqNum(),
m_skipIfNotBgSeqNums(),
m_currentShapeGroup(), m_topLevelShapes(),
m_groupsBySeqNum(), m_embeddedFonts(),
m_shapeInfosBySeqNum(), m_masterPages(),
m_shapesWithCoordinatesRotated90(),
m_masterPagesByPageSeqNum(),
m_tableCellTextEndsByTextId(), m_stringOffsetsByTextId(),
m_calculationValuesSeen(), m_pageSeqNumsOrdered(),
m_encodingHeuristic(false), m_allText(),
m_calculatedEncoding(),
m_metaData()
{
}
void MSPUBCollector::setTextStringOffset(
unsigned textId, unsigned offset)
{
m_stringOffsetsByTextId[textId] = offset;
}
void MSPUBCollector::setTableCellTextEnds(
const unsigned textId, const std::vector<unsigned> &ends)
{
m_tableCellTextEndsByTextId[textId] = ends;
}
void MSPUBCollector::useEncodingHeuristic()
{
m_encodingHeuristic = true;
}
void MSPUBCollector::setShapeShadow(unsigned seqNum, const Shadow &shadow)
{
m_shapeInfosBySeqNum[seqNum].m_shadow = shadow;
}
void noop(const CustomShape *)
{
}
void MSPUBCollector::setShapeCoordinatesRotated90(unsigned seqNum)
{
m_shapesWithCoordinatesRotated90.insert(seqNum);
}
void MSPUBCollector::setShapeBorderImageId(unsigned seqNum, unsigned id)
{
m_shapeInfosBySeqNum[seqNum].m_borderImgIndex = id;
}
void MSPUBCollector::setShapeCustomPath(unsigned seqNum,
const DynamicCustomShape &shape)
{
m_shapeInfosBySeqNum[seqNum].m_customShape = shape;
}
void MSPUBCollector::setShapeClipPath(unsigned seqNum, const std::vector<Vertex> &clip)
{
m_shapeInfosBySeqNum[seqNum].m_clipPath = clip;
}
void MSPUBCollector::beginGroup()
{
auto tmp = ShapeGroupElement::create(m_currentShapeGroup);
if (!m_currentShapeGroup)
{
m_topLevelShapes.push_back(tmp);
}
m_currentShapeGroup = tmp;
}
bool MSPUBCollector::endGroup()
{
if (!m_currentShapeGroup)
{
return false;
}
m_currentShapeGroup = m_currentShapeGroup->getParent();
return true;
}
void MSPUBCollector::addShapeLine(unsigned seqNum, Line line)
{
m_shapeInfosBySeqNum[seqNum].m_lines.push_back(line);
}
void MSPUBCollector::setShapeBorderPosition(unsigned seqNum, BorderPosition pos)
{
m_shapeInfosBySeqNum[seqNum].m_borderPosition = pos;
}
bool MSPUBCollector::hasPage(unsigned seqNum) const
{
return m_pagesBySeqNum.find(seqNum) != m_pagesBySeqNum.end();
}
void MSPUBCollector::setShapeMargins(unsigned seqNum, unsigned left, unsigned top, unsigned right, unsigned bottom)
{
m_shapeInfosBySeqNum[seqNum].m_margins = Margins(left, top, right, bottom);
}
void MSPUBCollector::setPageBgShape(unsigned pageSeqNum, unsigned seqNum)
{
m_bgShapeSeqNumsByPageSeqNum[pageSeqNum] = seqNum;
}
bool MSPUBCollector::setCurrentGroupSeqNum(unsigned seqNum)
{
if (!m_currentShapeGroup)
{
return false;
}
m_currentShapeGroup->setSeqNum(seqNum);
m_groupsBySeqNum.insert(std::make_pair(seqNum, m_currentShapeGroup));
return true;
}
void MSPUBCollector::setShapeOrder(unsigned seqNum)
{
auto tmp = ShapeGroupElement::create(m_currentShapeGroup, seqNum);
if (!m_currentShapeGroup)
{
m_topLevelShapes.push_back(tmp);
}
}
void MSPUBCollector::addPaletteColor(Color c)
{
m_paletteColors.push_back(c);
}
void no_op()
{
}
void endShapeGroup(librevenge::RVNGDrawingInterface *painter)
{
painter->endLayer();
}
std::vector<int> MSPUBCollector::getShapeAdjustValues(const ShapeInfo &info) const
{
std::vector<int> ret;
std::shared_ptr<const CustomShape> ptr_shape = info.getCustomShape();
if (ptr_shape)
{
for (unsigned i = 0; i < ptr_shape->m_numDefaultAdjustValues; ++i)
{
ret.push_back(ptr_shape->mp_defaultAdjustValues[i]);
}
}
for (auto i = info.m_adjustValuesByIndex.begin(); i != info.m_adjustValuesByIndex.end(); ++i)
{
unsigned index = i->first;
int adjustVal = i->second;
for (unsigned j = info.m_adjustValues.size(); j <= index; ++j)
{
ret.push_back(0);
}
ret[index] = adjustVal;
}
return ret;
}
boost::optional<std::vector<TextParagraph> > MSPUBCollector::getShapeText(const ShapeInfo &info) const
{
if (bool(info.m_textId))
{
unsigned stringId = info.m_textId.get();
const std::vector<TextParagraph> *ptr_str = getIfExists_const(m_textStringsById, stringId);
if (ptr_str)
{
return *ptr_str;
}
}
return boost::optional<std::vector<TextParagraph> >();
}
void MSPUBCollector::setupShapeStructures(ShapeGroupElement &elt)
{
ShapeInfo *ptr_info = getIfExists(m_shapeInfosBySeqNum, elt.getSeqNum());
if (ptr_info)
{
if (bool(ptr_info->m_imgIndex))
{
unsigned index = ptr_info->m_imgIndex.get();
int rot = 0;
if (bool(ptr_info->m_innerRotation))
rot = ptr_info->m_innerRotation.get();
if (index - 1 < m_images.size())
{
ptr_info->m_fill = std::shared_ptr<const Fill>(new ImgFill(index, this, false, rot));
}
}
elt.setShapeInfo(*ptr_info);
std::pair<bool, bool> flips = ptr_info->m_flips.get_value_or(std::pair<bool, bool>(false, false));
VectorTransformation2D flipsTransform = VectorTransformation2D::fromFlips(flips.second, flips.first);
double rotation = ptr_info->m_rotation.get_value_or(0);
rotation = doubleModulo(rotation, 360);
bool rotBackwards = flips.first ^ flips.second;
VectorTransformation2D rot = VectorTransformation2D::fromCounterRadians((rotBackwards ? -rotation : rotation) * M_PI / 180);
elt.setTransform(rot * flipsTransform);
}
}
std::function<void(void)> MSPUBCollector::paintShape(const ShapeInfo &info, const Coordinate &/* relativeTo*/, const VectorTransformation2D &foldedTransform, bool isGroup, const VectorTransformation2D &thisTransform) const
{
std::vector<int> adjustValues = getShapeAdjustValues(info);
if (isGroup)
{
m_painter->startLayer(librevenge::RVNGPropertyList());
return std::bind(&endShapeGroup, m_painter);
}
librevenge::RVNGPropertyList graphicsProps;
if (info.m_fill)
{
info.m_fill->getProperties(&graphicsProps);
}
bool hasStroke = false;
bool hasBorderArt = false;
boost::optional<unsigned> maybeBorderImg = info.m_borderImgIndex;
if (bool(maybeBorderImg) && !info.m_lines.empty())
{
hasStroke = true;
hasBorderArt = true;
}
else
{
for (const auto &line : info.m_lines)
{
hasStroke = hasStroke || line.m_lineExists;
if (hasStroke)
{
break;
}
}
}
librevenge::RVNGString fill = graphicsProps["draw:fill"] ? graphicsProps["draw:fill"]->getStr() : "none";
bool hasFill = fill != "none";
boost::optional<std::vector<TextParagraph> > maybeText = getShapeText(info);
auto hasText = bool(maybeText);
const auto isTable = bool(info.m_tableInfo);
bool makeLayer = hasBorderArt ||
(hasStroke && hasFill) || (hasStroke && hasText) || (hasFill && hasText);
if (makeLayer)
{
if (info.m_clipPath.size() > 0)
{
const Coordinate coord = info.m_coordinates.get_value_or(Coordinate());
double x, y, height, width;
x = coord.getXIn(m_width);
y = coord.getYIn(m_height);
height = coord.getHeightIn();
width = coord.getWidthIn();
m_painter->startLayer(calcClipPath(info.m_clipPath, x, y, height, width, foldedTransform, info.getCustomShape()));
}
else
m_painter->startLayer(librevenge::RVNGPropertyList());
}
graphicsProps.insert("draw:stroke", "none");
const Coordinate coord = info.m_coordinates.get_value_or(Coordinate());
BorderPosition borderPosition =
hasBorderArt ? INSIDE_SHAPE : info.m_borderPosition.get_value_or(HALF_INSIDE_SHAPE);
ShapeType type;
if (bool(info.m_cropType))
{
type = info.m_cropType.get();
}
else
{
type = info.m_type.get_value_or(RECTANGLE);
}
if (hasFill)
{
double x, y, height, width;
x = coord.getXIn(m_width);
y = coord.getYIn(m_height);
height = coord.getHeightIn();
width = coord.getWidthIn();
if (hasBorderArt)
{
double borderImgWidth =
static_cast<double>(info.m_lines[0].m_widthInEmu) / EMUS_IN_INCH;
if (height > 2 * borderImgWidth && width >= 2 * borderImgWidth)
{
x += borderImgWidth;
y += borderImgWidth;
height -= 2 * borderImgWidth;
width -= 2 * borderImgWidth;
}
}
if (bool(info.m_pictureRecolor))
{
Color obc = info.m_pictureRecolor.get().getFinalColor(m_paletteColors);
graphicsProps.insert("draw:color-mode", "greyscale");
graphicsProps.insert("draw:red",
static_cast<double>(obc.r) / 255.0, librevenge::RVNG_PERCENT);
graphicsProps.insert("draw:blue",
static_cast<double>(obc.b) / 255.0, librevenge::RVNG_PERCENT);
graphicsProps.insert("draw:green",
static_cast<double>(obc.g) / 255.0, librevenge::RVNG_PERCENT);
}
if (bool(info.m_pictureBrightness))
graphicsProps.insert("draw:luminance", static_cast<double>(info.m_pictureBrightness.get() + 32768.0) / 65536.0, librevenge::RVNG_PERCENT);
bool shadowPropsInserted = false;
if (bool(info.m_shadow))
{
const Shadow &s = info.m_shadow.get();
if (!needsEmulation(s))
{
shadowPropsInserted = true;
graphicsProps.insert("draw:shadow", "visible");
graphicsProps.insert("draw:shadow-offset-x", static_cast<double>(s.m_offsetXInEmu) / EMUS_IN_INCH);
graphicsProps.insert("draw:shadow-offset-y", static_cast<double>(s.m_offsetYInEmu) / EMUS_IN_INCH);
graphicsProps.insert("draw:shadow-color", getColorString(s.m_color.getFinalColor(m_paletteColors)));
graphicsProps.insert("draw:shadow-opacity", s.m_opacity, librevenge::RVNG_PERCENT);
}
// TODO: Emulate shadows that don't conform
// to LibreOffice's range of possible shadows.
}
m_painter->setStyle(graphicsProps);
writeCustomShape(type, graphicsProps, m_painter, x, y, height, width,
true, foldedTransform,
std::vector<Line>(), std::bind(&MSPUBCollector::getCalculationValue, this, info, _1, false, adjustValues), m_paletteColors, info.getCustomShape());
if (bool(info.m_pictureRecolor))
{
graphicsProps.remove("draw:color-mode");
graphicsProps.remove("draw:red");
graphicsProps.remove("draw:blue");
graphicsProps.remove("draw:green");
}
if (bool(info.m_pictureBrightness))
graphicsProps.remove("draw:luminance");
if (shadowPropsInserted)
{
graphicsProps.remove("draw:shadow");
graphicsProps.remove("draw:shadow-offset-x");
graphicsProps.remove("draw:shadow-offset-y");
graphicsProps.remove("draw:shadow-color");
graphicsProps.remove("draw:shadow-opacity");
}
}
const std::vector<Line> &lines = info.m_lines;
if (hasStroke)
{
if (hasBorderArt && lines[0].m_widthInEmu > 0)
{
bool stretch = info.m_stretchBorderArt;
double x = coord.getXIn(m_width);
double y = coord.getYIn(m_height);
double height = coord.getHeightIn();
double width = coord.getWidthIn();
double borderImgWidth =
static_cast<double>(info.m_lines[0].m_widthInEmu) / EMUS_IN_INCH;
auto numImagesHoriz = static_cast<unsigned>(width / borderImgWidth);
auto numImagesVert = static_cast<unsigned>(height / borderImgWidth);
double borderVertTotalPadding = height - numImagesVert * borderImgWidth;
double borderHorizTotalPadding = width - numImagesHoriz * borderImgWidth;
if (numImagesHoriz >= 2 && numImagesVert >= 2)
{
auto numStretchedImagesVert = static_cast<unsigned>(0.5 + (height - 2 * borderImgWidth) / borderImgWidth);
auto numStretchedImagesHoriz = static_cast<unsigned>(0.5 + (width - 2 * borderImgWidth) / borderImgWidth);
double stretchedImgHeight = stretch ?
(height - 2 * borderImgWidth) / numStretchedImagesVert :
borderImgWidth;
double stretchedImgWidth = stretch ?
(width - 2 * borderImgWidth) / numStretchedImagesHoriz :
borderImgWidth;
if (stretch)
{
numImagesVert = 2 + numStretchedImagesVert;
numImagesHoriz = 2 + numStretchedImagesHoriz;
}
double borderVertPadding = borderVertTotalPadding / (numImagesVert - 1);
double borderHorizPadding = borderHorizTotalPadding / (numImagesHoriz - 1);
if (maybeBorderImg.get() < m_borderImages.size())
{
const BorderArtInfo &ba = m_borderImages[maybeBorderImg.get()];
if (!ba.m_offsets.empty())
{
librevenge::RVNGPropertyList baProps;
baProps.insert("draw:stroke", "none");
baProps.insert("draw:fill", "solid");
baProps.insert("draw:fill-color", "#ffffff");
m_painter->setStyle(baProps);
librevenge::RVNGPropertyList topRectProps;
topRectProps.insert("svg:x", x);
topRectProps.insert("svg:y", y);
topRectProps.insert("svg:height", borderImgWidth);
topRectProps.insert("svg:width", width);
m_painter->drawRectangle(topRectProps);
librevenge::RVNGPropertyList rightRectProps;
rightRectProps.insert("svg:x", x + width - borderImgWidth);
rightRectProps.insert("svg:y", y);
rightRectProps.insert("svg:height", height);
rightRectProps.insert("svg:width", borderImgWidth);
m_painter->drawRectangle(rightRectProps);
librevenge::RVNGPropertyList botRectProps;
botRectProps.insert("svg:x", x);
botRectProps.insert("svg:y", y + height - borderImgWidth);
botRectProps.insert("svg:height", borderImgWidth);
botRectProps.insert("svg:width", width);
m_painter->drawRectangle(botRectProps);
librevenge::RVNGPropertyList leftRectProps;
leftRectProps.insert("svg:x", x);
leftRectProps.insert("svg:y", y);
leftRectProps.insert("svg:height", height);
leftRectProps.insert("svg:width", borderImgWidth);
m_painter->drawRectangle(leftRectProps);
auto iOffset = ba.m_offsets.begin();
boost::optional<Color> oneBitColor;
if (bool(info.m_lineBackColor))
{
oneBitColor = info.m_lineBackColor.get().getFinalColor(m_paletteColors);
}
// top left
unsigned iOrdOff = find(ba.m_offsetsOrdered.begin(),
ba.m_offsetsOrdered.end(), *iOffset) - ba.m_offsetsOrdered.begin();
if (iOrdOff < ba.m_images.size())
{
const BorderImgInfo &bi = ba.m_images[iOrdOff];
writeImage(x, y, borderImgWidth, borderImgWidth,
bi.m_type, bi.m_imgBlob, oneBitColor);
}
if (iOffset + 1 != ba.m_offsets.end())
{
++iOffset;
}
// top
iOrdOff = find(ba.m_offsetsOrdered.begin(),
ba.m_offsetsOrdered.end(), *iOffset) - ba.m_offsetsOrdered.begin();
if (iOrdOff < ba.m_images.size())
{
const BorderImgInfo &bi = ba.m_images[iOrdOff];
for (unsigned iTop = 1; iTop < numImagesHoriz - 1; ++iTop)
{
double imgX = stretch ?
x + borderImgWidth + (iTop - 1) * stretchedImgWidth :
x + iTop * (borderImgWidth + borderHorizPadding);
writeImage(imgX, y,
borderImgWidth, stretchedImgWidth,
bi.m_type, bi.m_imgBlob, oneBitColor);
}
}
if (iOffset + 1 != ba.m_offsets.end())
{
++iOffset;
}
// top right
iOrdOff = find(ba.m_offsetsOrdered.begin(),
ba.m_offsetsOrdered.end(), *iOffset) - ba.m_offsetsOrdered.begin();
if (iOrdOff < ba.m_images.size())
{
const BorderImgInfo &bi = ba.m_images[iOrdOff];
writeImage(x + width - borderImgWidth, y,
borderImgWidth, borderImgWidth,
bi.m_type, bi.m_imgBlob, oneBitColor);
}
if (iOffset + 1 != ba.m_offsets.end())
{
++iOffset;
}
// right
iOrdOff = find(ba.m_offsetsOrdered.begin(),
ba.m_offsetsOrdered.end(), *iOffset) - ba.m_offsetsOrdered.begin();
if (iOrdOff < ba.m_images.size())
{
const BorderImgInfo &bi = ba.m_images[iOrdOff];
for (unsigned iRight = 1; iRight < numImagesVert - 1; ++iRight)
{
double imgY = stretch ?
y + borderImgWidth + (iRight - 1) * stretchedImgHeight :
y + iRight * (borderImgWidth + borderVertPadding);
writeImage(x + width - borderImgWidth,
imgY,
stretchedImgHeight, borderImgWidth,
bi.m_type, bi.m_imgBlob, oneBitColor);
}
}
if (iOffset + 1 != ba.m_offsets.end())
{
++iOffset;
}
// bottom right
iOrdOff = find(ba.m_offsetsOrdered.begin(),
ba.m_offsetsOrdered.end(), *iOffset) - ba.m_offsetsOrdered.begin();
if (iOrdOff < ba.m_images.size())
{
const BorderImgInfo &bi = ba.m_images[iOrdOff];
writeImage(x + width - borderImgWidth,
y + height - borderImgWidth,
borderImgWidth, borderImgWidth,
bi.m_type, bi.m_imgBlob, oneBitColor);
}
if (iOffset + 1 != ba.m_offsets.end())
{
++iOffset;
}
// bottom
iOrdOff = find(ba.m_offsetsOrdered.begin(),
ba.m_offsetsOrdered.end(), *iOffset) - ba.m_offsetsOrdered.begin();
if (iOrdOff < ba.m_images.size())
{
const BorderImgInfo &bi = ba.m_images[iOrdOff];
for (unsigned iBot = 1; iBot < numImagesHoriz - 1; ++iBot)
{
double imgX = stretch ?
x + width - borderImgWidth - iBot * stretchedImgWidth :
x + width - borderImgWidth - iBot * (borderImgWidth + borderHorizPadding);
writeImage(
imgX, y + height - borderImgWidth,
borderImgWidth, stretchedImgWidth,
bi.m_type, bi.m_imgBlob, oneBitColor);
}
}
if (iOffset + 1 != ba.m_offsets.end())
{
++iOffset;
}
// bottom left
iOrdOff = find(ba.m_offsetsOrdered.begin(),
ba.m_offsetsOrdered.end(), *iOffset) - ba.m_offsetsOrdered.begin();
if (iOrdOff < ba.m_images.size())
{
const BorderImgInfo &bi = ba.m_images[iOrdOff];
writeImage(x, y + height - borderImgWidth,
borderImgWidth, borderImgWidth,
bi.m_type, bi.m_imgBlob, oneBitColor);
}
if (iOffset + 1 != ba.m_offsets.end())
{
++iOffset;
}
// left
iOrdOff = find(ba.m_offsetsOrdered.begin(),
ba.m_offsetsOrdered.end(), *iOffset) - ba.m_offsetsOrdered.begin();
if (iOrdOff < ba.m_images.size())
{
const BorderImgInfo &bi = ba.m_images[iOrdOff];
for (unsigned iLeft = 1; iLeft < numImagesVert - 1; ++iLeft)
{
double imgY = stretch ?
y + height - borderImgWidth - iLeft * stretchedImgHeight :
y + height - borderImgWidth -
iLeft * (borderImgWidth + borderVertPadding);
writeImage(x, imgY, stretchedImgHeight, borderImgWidth,
bi.m_type, bi.m_imgBlob, oneBitColor);
}
}
}
}
}
}
else
{
Coordinate strokeCoord = isShapeTypeRectangle(type) ?
getFudgedCoordinates(coord, lines, true, borderPosition) : coord;
double x, y, height, width;
x = strokeCoord.getXIn(m_width);
y = strokeCoord.getYIn(m_height);
height = strokeCoord.getHeightIn();
width = strokeCoord.getWidthIn();
graphicsProps.insert("draw:fill", "none");
if (bool(info.m_dash) && !info.m_dash.get().m_dots.empty())
{
const Dash &dash = info.m_dash.get();
graphicsProps.insert("draw:stroke", "dash");
graphicsProps.insert("draw:distance", dash.m_distance, librevenge::RVNG_INCH);
switch (dash.m_dotStyle)
{
case ROUND_DOT:
graphicsProps.insert("svg:stroke-linecap", "round");
break;
case RECT_DOT:
graphicsProps.insert("svg:stroke-linecap", "butt");
break;
default:
break;
}
for (unsigned i = 0; i < dash.m_dots.size(); ++i)
{
librevenge::RVNGString dots;
dots.sprintf("draw:dots%d", i + 1);
graphicsProps.insert(dots.cstr(), static_cast<int>(dash.m_dots[i].m_count));
if (bool(dash.m_dots[i].m_length))
{
librevenge::RVNGString length;
length.sprintf("draw:dots%d-length", i + 1);
graphicsProps.insert(length.cstr(), dash.m_dots[i].m_length.get(), librevenge::RVNG_INCH);
}
}
}
else
{
graphicsProps.insert("draw:stroke", "solid");
}
m_painter->setStyle(graphicsProps);
writeCustomShape(type, graphicsProps, m_painter, x, y, height, width,
false, foldedTransform, lines,
std::bind(
&MSPUBCollector::getCalculationValue, this, info, _1, false, adjustValues
),
m_paletteColors, info.getCustomShape());
}
}
if (hasText)
{
const std::vector<TextParagraph> &text = maybeText.get();
graphicsProps.insert("draw:fill", "none");
Coordinate textCoord = isShapeTypeRectangle(type) ?
getFudgedCoordinates(coord, lines, false, borderPosition) : coord;
m_painter->setStyle(graphicsProps);
librevenge::RVNGPropertyList props;
setRectCoordProps(textCoord, &props);
double textRotation = thisTransform.getRotation();
if (textRotation != 0)
{
props.insert("librevenge:rotate", textRotation * 180 / M_PI);
}
if (isTable)
{
librevenge::RVNGPropertyListVector columnWidths;
for (unsigned int col : get(info.m_tableInfo).m_columnWidthsInEmu)
{
librevenge::RVNGPropertyList columnWidth;
columnWidth.insert("style:column-width", double(col) / EMUS_IN_INCH);
columnWidths.append(columnWidth);
}
props.insert("librevenge:table-columns", columnWidths);
m_painter->startTableObject(props);
const std::map<unsigned, std::vector<unsigned> >::const_iterator it = m_tableCellTextEndsByTextId.find(get(info.m_textId));
const std::vector<unsigned> &tableCellTextEnds = (it != m_tableCellTextEndsByTextId.end()) ? it->second : std::vector<unsigned>();
TableLayout tableLayout(boost::extents[get(info.m_tableInfo).m_numRows][get(info.m_tableInfo).m_numColumns]);
createTableLayout(get(info.m_tableInfo).m_cells, tableLayout);
ParagraphToCellMap_t paraToCellMap;
ParagraphTexts_t paraTexts;
mapTableTextToCells(text, tableCellTextEnds, getCalculatedEncoding(), paraToCellMap, paraTexts);
for (unsigned row = 0; row != tableLayout.shape()[0]; ++row)
{
librevenge::RVNGPropertyList rowProps;
if (row < (get(info.m_tableInfo).m_rowHeightsInEmu.size()))
rowProps.insert("librevenge:row-height", double(get(info.m_tableInfo).m_rowHeightsInEmu[row]) / EMUS_IN_INCH);
m_painter->openTableRow(rowProps);
for (unsigned col = 0; col != tableLayout.shape()[1]; ++col)
{
librevenge::RVNGPropertyList cellProps;
cellProps.insert("librevenge:column", int(col));
cellProps.insert("librevenge:row", int(row));
if (isCovered(tableLayout[row][col]))
{
m_painter->insertCoveredTableCell(cellProps);
}
else
{
if (tableLayout[row][col].m_colSpan > 1)
cellProps.insert("table:number-columns-spanned", int(tableLayout[row][col].m_colSpan));
if (tableLayout[row][col].m_rowSpan > 1)
cellProps.insert("table:number-rows-spanned", int(tableLayout[row][col].m_rowSpan));
m_painter->openTableCell(cellProps);
if (tableLayout[row][col].m_cell < paraToCellMap.size())
{
const std::pair<unsigned, unsigned> &cellParas = paraToCellMap[tableLayout[row][col].m_cell];
for (unsigned para = cellParas.first; para <= cellParas.second; ++para)
{
librevenge::RVNGPropertyList paraProps = getParaStyleProps(text[para].style, text[para].style.m_defaultCharStyleIndex);
m_painter->openParagraph(paraProps);
for (unsigned i_spans = 0; i_spans < paraTexts[para].size(); ++i_spans)
{
librevenge::RVNGPropertyList charProps = getCharStyleProps(text[para].spans[i_spans].style, text[para].style.m_defaultCharStyleIndex);
m_painter->openSpan(charProps);
separateSpacesAndInsertText(m_painter, paraTexts[para][i_spans]);
m_painter->closeSpan();
}
m_painter->closeParagraph();
}
}
m_painter->closeTableCell();
}
}
m_painter->closeTableRow();
}
m_painter->endTableObject();
}
else // a text object
{
Margins margins = info.m_margins.get_value_or(Margins());
props.insert("fo:padding-left", (double)margins.m_left / EMUS_IN_INCH);
props.insert("fo:padding-top", (double)margins.m_top / EMUS_IN_INCH);
props.insert("fo:padding-right", (double)margins.m_right / EMUS_IN_INCH);
props.insert("fo:padding-bottom", (double)margins.m_bottom / EMUS_IN_INCH);
if (bool(info.m_verticalAlign))
{
switch (info.m_verticalAlign.get())
{
default:
case TOP:
props.insert("draw:textarea-vertical-align", "top");
break;
case MIDDLE:
props.insert("draw:textarea-vertical-align", "middle");
break;
case BOTTOM:
props.insert("draw:textarea-vertical-align", "bottom");
break;
}
}
if (info.m_numColumns)
{
unsigned ncols = info.m_numColumns.get_value_or(0);
if (ncols > 0)
props.insert("fo:column-count", (int)ncols);
}
if (info.m_columnSpacing)
{
unsigned ngap = info.m_columnSpacing;
if (ngap > 0)
props.insert("fo:column-gap", (double)ngap / EMUS_IN_INCH);
}
m_painter->startTextObject(props);
for (const auto &line : text)
{
librevenge::RVNGPropertyList paraProps = getParaStyleProps(line.style, line.style.m_defaultCharStyleIndex);
m_painter->openParagraph(paraProps);
for (unsigned i_spans = 0; i_spans < line.spans.size(); ++i_spans)
{
librevenge::RVNGString textString;
appendCharacters(textString, line.spans[i_spans].chars,
getCalculatedEncoding());
librevenge::RVNGPropertyList charProps = getCharStyleProps(line.spans[i_spans].style, line.style.m_defaultCharStyleIndex);
m_painter->openSpan(charProps);
separateSpacesAndInsertText(m_painter, textString);
m_painter->closeSpan();
}
m_painter->closeParagraph();
}
m_painter->endTextObject();
}
}
if (makeLayer)
{
m_painter->endLayer();
}
return &no_op;
}
const char *MSPUBCollector::getCalculatedEncoding() const
{
if (bool(m_calculatedEncoding))
{
return m_calculatedEncoding.get();
}
// modern versions are somewhat sane and use Unicode
if (! m_encodingHeuristic)
{
m_calculatedEncoding = "UTF-16LE";
return m_calculatedEncoding.get();
}
// for older versions of PUB, see if we can get ICU to tell us the encoding.
UErrorCode status = U_ZERO_ERROR;
UCharsetDetector *ucd = nullptr;
const UCharsetMatch **matches = nullptr;
const UCharsetMatch *ucm = nullptr;
ucd = ucsdet_open(&status);
int matchesFound = -1;
const char *name = nullptr;
const char *windowsName = nullptr;
if (m_allText.empty())
{
goto csd_fail;
}
if (U_FAILURE(status))
{
goto csd_fail;
}
// don't worry, the below call doesn't require a null-terminated string.
ucsdet_setText(ucd, (const char *)m_allText.data(), m_allText.size(), &status);
if (U_FAILURE(status))
{
goto csd_fail;
}
matches = ucsdet_detectAll(ucd, &matchesFound, &status);
if (U_FAILURE(status))
{
goto csd_fail;
}
//find best fit that is an actual Windows encoding
for (int i = 0; i < matchesFound; ++i)
{
ucm = matches[i];
name = ucsdet_getName(ucm, &status);
if (U_FAILURE(status))
{
goto csd_fail;
}
windowsName = windowsCharsetNameByOriginalCharset(name);
if (windowsName)
{
m_calculatedEncoding = windowsName;
ucsdet_close(ucd);
return windowsName;
}
}
csd_fail:
ucsdet_close(ucd);
return "windows-1252"; // Pretty likely to give garbage text, but it's the best we can do.
}
void MSPUBCollector::setShapeLineBackColor(unsigned shapeSeqNum,
ColorReference backColor)
{
m_shapeInfosBySeqNum[shapeSeqNum].m_lineBackColor = backColor;
}
void MSPUBCollector::writeImage(double x, double y,
double height, double width, ImgType type, const librevenge::RVNGBinaryData &blob,
boost::optional<Color> oneBitColor) const
{
librevenge::RVNGPropertyList props;
if (bool(oneBitColor))
{
Color obc = oneBitColor.get();
props.insert("draw:color-mode", "greyscale");
props.insert("draw:red", static_cast<double>(obc.r) / 255.0, librevenge::RVNG_PERCENT);
props.insert("draw:blue", static_cast<double>(obc.b) / 255.0, librevenge::RVNG_PERCENT);
props.insert("draw:green", static_cast<double>(obc.g) / 255.0, librevenge::RVNG_PERCENT);
}
props.insert("svg:x", x);
props.insert("svg:y", y);
props.insert("svg:width", width);
props.insert("svg:height", height);
props.insert("librevenge:mime-type", mimeByImgType(type));
props.insert("office:binary-data", blob);
m_painter->drawGraphicObject(props);
}
double MSPUBCollector::getSpecialValue(const ShapeInfo &info, const CustomShape &shape, int arg, const std::vector<int> &adjustValues) const
{
if (PROP_ADJUST_VAL_FIRST <= arg && PROP_ADJUST_VAL_LAST >= arg)
{
unsigned adjustIndex = arg - PROP_ADJUST_VAL_FIRST;
if (adjustIndex < adjustValues.size())
{
if ((shape.m_adjustShiftMask >> adjustIndex) & 0x1)
{
return adjustValues[adjustIndex] >> 16;
}
return adjustValues[adjustIndex];
}
return 0;
}
if (arg == ASPECT_RATIO)
{
const Coordinate coord = info.m_coordinates.get_value_or(Coordinate());
return (double)coord.getWidthIn() / coord.getHeightIn();
}
if (arg & OTHER_CALC_VAL)
{
return getCalculationValue(info, arg & 0xff, true, adjustValues);
}
switch (arg)
{
case PROP_GEO_LEFT:
return 0;
case PROP_GEO_TOP:
return 0;
case PROP_GEO_RIGHT:
return shape.m_coordWidth;
case PROP_GEO_BOTTOM:
return shape.m_coordHeight;
default:
break;
}
return 0;
}
double MSPUBCollector::getCalculationValue(const ShapeInfo &info, unsigned index, bool recursiveEntry, const std::vector<int> &adjustValues) const
{
std::shared_ptr<const CustomShape> p_shape = info.getCustomShape();
if (! p_shape)
{
return 0;
}
const CustomShape &shape = *p_shape;
if (index >= shape.m_numCalculations)
{
return 0;
}
if (! recursiveEntry)
{
m_calculationValuesSeen.clear();
m_calculationValuesSeen.resize(shape.m_numCalculations);
}
if (m_calculationValuesSeen[index])
{
//recursion detected. The simplest way to avoid infinite recursion, at the "cost"
// of making custom shape parsing not Turing-complete ;), is to ban recursion entirely.
return 0;
}
m_calculationValuesSeen[index] = true;
const Calculation &c = shape.mp_calculations[index];
bool oneSpecial = (c.m_flags & 0x2000) != 0;
bool twoSpecial = (c.m_flags & 0x4000) != 0;
bool threeSpecial = (c.m_flags & 0x8000) != 0;
double valOne = oneSpecial ? getSpecialValue(info, shape, c.m_argOne, adjustValues) : c.m_argOne;
double valTwo = twoSpecial ? getSpecialValue(info, shape, c.m_argTwo, adjustValues) : c.m_argTwo;
double valThree = threeSpecial ? getSpecialValue(info, shape, c.m_argThree, adjustValues) : c.m_argThree;
m_calculationValuesSeen[index] = false;
switch (c.m_flags & 0xFF)
{
case 0:
case 14:
return valOne + valTwo - valThree;
case 1:
return valOne * valTwo / (valThree == 0 ? 1 : valThree);
case 2:
return (valOne + valTwo) / 2;
case 3:
return fabs(valOne);
case 4:
return std::min(valOne, valTwo);
case 5:
return std::max(valOne, valTwo);
case 6:
return valOne ? valTwo : valThree;
case 7:
return sqrt(valOne * valTwo * valThree);
case 8:
return atan2(valTwo, valOne) / (M_PI / 180);
case 9:
return valOne * sin(valTwo * (M_PI / 180));
case 10:
return valOne * cos(valTwo * (M_PI / 180));
case 11:
return valOne * cos(atan2(valThree, valTwo));
case 12:
return valOne * sin(atan2(valThree, valTwo));
case 13:
return sqrt(valOne);
case 15:
return valThree * sqrt(1 - (valOne / valTwo) * (valOne / valTwo));
case 16:
return valOne * tan(valTwo);
case 0x80:
return sqrt(valThree * valThree - valOne * valOne);
case 0x81:
return (cos(valThree * (M_PI / 180)) * (valOne - 10800) + sin(valThree * (M_PI / 180)) * (valTwo - 10800)) + 10800;
case 0x82:
return -(sin(valThree * (M_PI / 180)) * (valOne - 10800) - cos(valThree * (M_PI / 180)) * (valTwo - 10800)) + 10800;
default:
return 0;
}
}
MSPUBCollector::~MSPUBCollector()
{
}
void MSPUBCollector::setShapeRotation(unsigned seqNum, double rotation)
{
m_shapeInfosBySeqNum[seqNum].m_rotation = rotation;
m_shapeInfosBySeqNum[seqNum].m_innerRotation = (int)rotation;
}
void MSPUBCollector::setShapeFlip(unsigned seqNum, bool flipVertical, bool flipHorizontal)
{
m_shapeInfosBySeqNum[seqNum].m_flips = std::pair<bool, bool>(flipVertical, flipHorizontal);
}
void MSPUBCollector::setShapeType(unsigned seqNum, ShapeType type)
{
m_shapeInfosBySeqNum[seqNum].m_type = type;
}
void MSPUBCollector::setAdjustValue(unsigned seqNum, unsigned index, int adjust)
{
m_shapeInfosBySeqNum[seqNum].m_adjustValuesByIndex[index] = adjust;
}
void MSPUBCollector::addDefaultCharacterStyle(const CharacterStyle &st)
{
m_defaultCharStyles.push_back(st);
}
void MSPUBCollector::addDefaultParagraphStyle(const ParagraphStyle &st)
{
m_defaultParaStyles.push_back(st);
}
bool MSPUBCollector::addPage(unsigned seqNum)
{
if (!(m_widthSet && m_heightSet))
{
return false;
}
MSPUB_DEBUG_MSG(("Adding page of seqnum 0x%x\n", seqNum));
m_pagesBySeqNum[seqNum] = PageInfo();
return true;
}
void MSPUBCollector::addTextShape(unsigned stringId, unsigned seqNum)
{
m_shapeInfosBySeqNum[seqNum].m_textId = stringId;
}
void MSPUBCollector::setShapeImgIndex(unsigned seqNum, unsigned index)
{
MSPUB_DEBUG_MSG(("Setting image index of shape with seqnum 0x%x to 0x%x\n", seqNum, index));
m_shapeInfosBySeqNum[seqNum].m_imgIndex = index;
}
void MSPUBCollector::setShapeDash(unsigned seqNum, const Dash &dash)
{
m_shapeInfosBySeqNum[seqNum].m_dash = dash;
}
void MSPUBCollector::setShapeFill(unsigned seqNum, std::shared_ptr<Fill> fill, bool skipIfNotBg)
{
m_shapeInfosBySeqNum[seqNum].m_fill = fill;
if (skipIfNotBg)
{
m_skipIfNotBgSeqNums.insert(seqNum);
}
}
void MSPUBCollector::setShapeCoordinatesInEmu(unsigned seqNum, int xs, int ys, int xe, int ye)
{
m_shapeInfosBySeqNum[seqNum].m_coordinates = Coordinate(xs, ys, xe, ye);
}
void MSPUBCollector::addFont(std::vector<unsigned char> name)
{
m_fonts.push_back(name);
}
librevenge::RVNGPropertyList MSPUBCollector::getParaStyleProps(const ParagraphStyle &style, boost::optional<unsigned> defaultParaStyleIndex) const
{
ParagraphStyle _nothing;
const ParagraphStyle &defaultStyle = bool(defaultParaStyleIndex) && defaultParaStyleIndex.get() < m_defaultParaStyles.size() ? m_defaultParaStyles[defaultParaStyleIndex.get()] : _nothing;
librevenge::RVNGPropertyList ret;
Alignment align = style.m_align.get_value_or(
defaultStyle.m_align.get_value_or(LEFT));
switch (align)
{
case RIGHT:
ret.insert("fo:text-align", "right");
break;
case CENTER:
ret.insert("fo:text-align", "center");
break;
case JUSTIFY:
ret.insert("fo:text-align", "justify");
break;
case LEFT:
default:
ret.insert("fo:text-align", "left");
break;
}
LineSpacingInfo info = style.m_lineSpacing.get_value_or(
defaultStyle.m_lineSpacing.get_value_or(LineSpacingInfo()));
LineSpacingType lineSpacingType = info.m_type;
double lineSpacing = info.m_amount;
if (!(lineSpacingType == LINE_SPACING_SP && lineSpacing == 1))
{
if (lineSpacingType == LINE_SPACING_SP)
{
ret.insert("fo:line-height", lineSpacing, librevenge::RVNG_PERCENT);
}
else if (lineSpacingType == LINE_SPACING_PT)
{
ret.insert("fo:line-height", lineSpacing, librevenge::RVNG_POINT);
}
}
unsigned spaceAfterEmu = style.m_spaceAfterEmu.get_value_or(
defaultStyle.m_spaceAfterEmu.get_value_or(0));
unsigned spaceBeforeEmu = style.m_spaceBeforeEmu.get_value_or(
defaultStyle.m_spaceBeforeEmu.get_value_or(0));
int firstLineIndentEmu = style.m_firstLineIndentEmu.get_value_or(
defaultStyle.m_firstLineIndentEmu.get_value_or(0));
unsigned leftIndentEmu = style.m_leftIndentEmu.get_value_or(
defaultStyle.m_leftIndentEmu.get_value_or(0));
unsigned rightIndentEmu = style.m_rightIndentEmu.get_value_or(
defaultStyle.m_rightIndentEmu.get_value_or(0));
if (spaceAfterEmu != 0)
{
ret.insert("fo:margin-bottom", (double)spaceAfterEmu / EMUS_IN_INCH);
}
if (spaceBeforeEmu != 0)
{
ret.insert("fo:margin-top", (double)spaceBeforeEmu / EMUS_IN_INCH);
}
if (firstLineIndentEmu != 0)
{
ret.insert("fo:text-indent", (double)firstLineIndentEmu / EMUS_IN_INCH);
}
if (leftIndentEmu != 0)
{
ret.insert("fo:margin-left", (double)leftIndentEmu / EMUS_IN_INCH);
}
if (rightIndentEmu != 0)
{
ret.insert("fo:margin-right", (double)rightIndentEmu / EMUS_IN_INCH);
}
unsigned dropCapLines = style.m_dropCapLines.get_value_or(
defaultStyle.m_dropCapLines.get_value_or(0));
if (dropCapLines != 0)
{
ret.insert("style:drop-cap", (int)dropCapLines);
}
unsigned dropCapLetters = style.m_dropCapLetters.get_value_or(
defaultStyle.m_dropCapLetters.get_value_or(0));
if (dropCapLetters != 0)
{
ret.insert("style:length", (int)dropCapLetters);
}
return ret;
}
librevenge::RVNGPropertyList MSPUBCollector::getCharStyleProps(const CharacterStyle &style, boost::optional<unsigned> defaultCharStyleIndex) const
{
CharacterStyle _nothing;
if (!defaultCharStyleIndex)
{
defaultCharStyleIndex = 0;
}
const CharacterStyle &defaultCharStyle = defaultCharStyleIndex.get() < m_defaultCharStyles.size() ? m_defaultCharStyles[defaultCharStyleIndex.get()] : _nothing;
librevenge::RVNGPropertyList ret;
if (style.italic ^ defaultCharStyle.italic)
{
ret.insert("fo:font-style", "italic");
}
if (style.bold ^ defaultCharStyle.bold)
{
ret.insert("fo:font-weight", "bold");
}
if (style.outline ^ defaultCharStyle.outline)
ret.insert("style:text-outline", "true");
if (style.shadow ^ defaultCharStyle.shadow)
ret.insert("fo:text-shadow", "1pt 1pt");
if (style.smallCaps ^ defaultCharStyle.smallCaps)
ret.insert("fo:font-variant", "small-caps");
else if (style.allCaps ^ defaultCharStyle.allCaps)
ret.insert("fo:text-transform", "uppercase");
if (style.emboss ^ defaultCharStyle.emboss)
ret.insert("style:font-relief", "embossed");
else if (style.engrave ^ defaultCharStyle.engrave)
ret.insert("style:font-relief", "engraved");
if (style.underline)
fillUnderline(ret, get(style.underline));
else if (defaultCharStyle.underline)
fillUnderline(ret, get(defaultCharStyle.underline));
if (style.textScale)
ret.insert("fo:text-scale", get(style.textScale), librevenge::RVNG_PERCENT);
else if (defaultCharStyle.textScale)
ret.insert("fo:text-scale", get(defaultCharStyle.textScale), librevenge::RVNG_PERCENT);
if (bool(style.textSizeInPt))
{
ret.insert("fo:font-size", style.textSizeInPt.get() / POINTS_IN_INCH);
}
else if (bool(defaultCharStyle.textSizeInPt))
{
ret.insert("fo:font-size", defaultCharStyle.textSizeInPt.get()
/ POINTS_IN_INCH);
}
if (style.colorIndex >= 0 && (size_t)style.colorIndex < m_textColors.size())
{
ret.insert("fo:color", getColorString(m_textColors[style.colorIndex].getFinalColor(m_paletteColors)));
}
else if (defaultCharStyle.colorIndex >= 0 && (size_t)defaultCharStyle.colorIndex < m_textColors.size())
{
ret.insert("fo:color", getColorString(m_textColors[defaultCharStyle.colorIndex].getFinalColor(m_paletteColors)));
}
else
{
ret.insert("fo:color", getColorString(Color(0, 0, 0))); // default color is black
}
if (bool(style.fontIndex) &&
style.fontIndex.get() < m_fonts.size())
{
librevenge::RVNGString str;
appendCharacters(str, m_fonts[style.fontIndex.get()],
getCalculatedEncoding());
ret.insert("style:font-name", str);
}
else if (bool(defaultCharStyle.fontIndex) &&
defaultCharStyle.fontIndex.get() < m_fonts.size())
{
librevenge::RVNGString str;
appendCharacters(str, m_fonts[defaultCharStyle.fontIndex.get()],
getCalculatedEncoding());
ret.insert("style:font-name", str);
}
else if (!m_fonts.empty())
{
librevenge::RVNGString str;
appendCharacters(str, m_fonts[0],
getCalculatedEncoding());
ret.insert("style:font-name", str);
}
switch (style.superSubType)
{
case SUPERSCRIPT:
ret.insert("style:text-position", "50% 67%");
break;
case SUBSCRIPT:
ret.insert("style:text-position", "-50% 67%");
break;
default:
break;
}
if (style.lcid)
fillLocale(ret, get(style.lcid));
else if (defaultCharStyle.lcid)
fillLocale(ret, get(defaultCharStyle.lcid));
return ret;
}
librevenge::RVNGString MSPUBCollector::getColorString(const Color &color)
{
librevenge::RVNGString ret;
ret.sprintf("#%.2x%.2x%.2x",(unsigned char)color.r, (unsigned char)color.g, (unsigned char)color.b);
MSPUB_DEBUG_MSG(("String for r: 0x%x, g: 0x%x, b: 0x%x is %s\n", color.r, color.g, color.b, ret.cstr()));
return ret;
}
void MSPUBCollector::addBlackToPaletteIfNecessary()
{
if (m_paletteColors.size() < 8)
{
m_paletteColors.insert(m_paletteColors.begin(), Color());
}
}
void MSPUBCollector::assignShapesToPages()
{
for (auto &topLevelShape : m_topLevelShapes)
{
unsigned *ptr_pageSeqNum = getIfExists(m_pageSeqNumsByShapeSeqNum, topLevelShape->getSeqNum());
topLevelShape->setup(std::bind(&MSPUBCollector::setupShapeStructures, this, _1));
if (ptr_pageSeqNum)
{
PageInfo *ptr_page = getIfExists(m_pagesBySeqNum, *ptr_pageSeqNum);
if (ptr_page)
{
ptr_page->m_shapeGroupsOrdered.push_back(topLevelShape);
}
}
}
}
boost::optional<unsigned> MSPUBCollector::getMasterPageSeqNum(unsigned pageSeqNum) const
{
boost::optional<unsigned> toReturn;
const unsigned *ptr_masterSeqNum = getIfExists_const(m_masterPagesByPageSeqNum, pageSeqNum);
if (ptr_masterSeqNum && m_masterPages.find(*ptr_masterSeqNum) != m_masterPages.end())
{
return *ptr_masterSeqNum;
}
return toReturn;
}
void MSPUBCollector::writePage(unsigned pageSeqNum) const
{
const PageInfo &pageInfo = m_pagesBySeqNum.find(pageSeqNum)->second;
librevenge::RVNGPropertyList pageProps;
if (m_widthSet)
{
pageProps.insert("svg:width", m_width);
}
if (m_heightSet)
{
pageProps.insert("svg:height", m_height);
}
const auto &shapeGroupsOrdered = pageInfo.m_shapeGroupsOrdered;
if (!shapeGroupsOrdered.empty())
{
m_painter->startPage(pageProps);
boost::optional<unsigned> masterSeqNum = getMasterPageSeqNum(pageSeqNum);
auto hasMaster = bool(masterSeqNum);
if (hasMaster)
{
writePageBackground(masterSeqNum.get());
}
writePageBackground(pageSeqNum);
if (hasMaster)
{
writePageShapes(masterSeqNum.get());
}
writePageShapes(pageSeqNum);
m_painter->endPage();
}
}
void MSPUBCollector::writePageShapes(unsigned pageSeqNum) const
{
const PageInfo &pageInfo = m_pagesBySeqNum.find(pageSeqNum)->second;
for (const auto &shapeGroup : pageInfo.m_shapeGroupsOrdered)
shapeGroup->visit(std::bind(&MSPUBCollector::paintShape, this, _1, _2, _3, _4, _5));
}
void MSPUBCollector::writePageBackground(unsigned pageSeqNum) const
{
const unsigned *ptr_fillSeqNum = getIfExists_const(m_bgShapeSeqNumsByPageSeqNum, pageSeqNum);
if (ptr_fillSeqNum)
{
std::shared_ptr<const Fill> ptr_fill;
const ShapeInfo *ptr_info = getIfExists_const(m_shapeInfosBySeqNum, *ptr_fillSeqNum);
if (ptr_info)
{
ptr_fill = ptr_info->m_fill;
}
if (ptr_fill)
{
ShapeInfo bg;
bg.m_type = RECTANGLE;
Coordinate wholePage(-m_width/2 * EMUS_IN_INCH, -m_height/2 * EMUS_IN_INCH, m_width/2 * EMUS_IN_INCH, m_height/2 * EMUS_IN_INCH);
bg.m_coordinates = wholePage;
bg.m_pageSeqNum = pageSeqNum;
bg.m_fill = ptr_fill;
paintShape(bg, Coordinate(), VectorTransformation2D(), false, VectorTransformation2D());
}
}
}
bool MSPUBCollector::pageIsMaster(unsigned pageSeqNum) const
{
return m_masterPages.find(pageSeqNum) != m_masterPages.end();
}
bool MSPUBCollector::go()
{
addBlackToPaletteIfNecessary();
assignShapesToPages();
m_painter->startDocument(librevenge::RVNGPropertyList());
m_painter->setDocumentMetaData(m_metaData);
for (std::list<EmbeddedFontInfo>::const_iterator i = m_embeddedFonts.begin(); i != m_embeddedFonts.end(); ++i)
{
librevenge::RVNGPropertyList props;
props.insert("librevenge:name", i->m_name);
props.insert("librevenge:mime-type", "application/vnd.ms-fontobject");
props.insert("office:binary-data",i->m_blob);
m_painter->defineEmbeddedFont(props);
}
if (m_pageSeqNumsOrdered.empty())
{
for (std::map<unsigned, PageInfo>::const_iterator i = m_pagesBySeqNum.begin();
i != m_pagesBySeqNum.end(); ++i)
{
if (!pageIsMaster(i->first))
{
writePage(i->first);
}
}
}
else
{
for (unsigned int i : m_pageSeqNumsOrdered)
{
std::map<unsigned, PageInfo>::const_iterator iter =
m_pagesBySeqNum.find(i);
if (iter != m_pagesBySeqNum.end() && !pageIsMaster(iter->first))
{
writePage(iter->first);
}
}
}
m_painter->endDocument();
return true;
}
bool MSPUBCollector::addTextString(const std::vector<TextParagraph> &str, unsigned id)
{
MSPUB_DEBUG_MSG(("addTextString, id: 0x%x\n", id));
m_textStringsById[id] = str;
if (m_encodingHeuristic)
{
ponderStringEncoding(str);
}
return true; //FIXME: Warn if the string already existed in the map.
}
void MSPUBCollector::ponderStringEncoding(
const std::vector<TextParagraph> &str)
{
for (const auto &i : str)
{
for (unsigned j = 0; j < i.spans.size(); ++j)
{
const std::vector<unsigned char> &chars = i.spans[j].chars;
m_allText.insert(m_allText.end(), chars.begin(), chars.end());
}
}
}
void MSPUBCollector::setWidthInEmu(unsigned long widthInEmu)
{
//FIXME: Warn if this is called twice
m_width = ((double)widthInEmu) / EMUS_IN_INCH;
m_widthSet = true;
}
void MSPUBCollector::setHeightInEmu(unsigned long heightInEmu)
{
//FIXME: Warn if this is called twice
m_height = ((double)heightInEmu) / EMUS_IN_INCH;
m_heightSet = true;
}
bool MSPUBCollector::addImage(unsigned index, ImgType type, librevenge::RVNGBinaryData img)
{
while (m_images.size() < index)
{
m_images.push_back(std::pair<ImgType, librevenge::RVNGBinaryData>(UNKNOWN, librevenge::RVNGBinaryData()));
}
if (index > 0)
{
MSPUB_DEBUG_MSG(("Image at index %u and of type 0x%x added.\n", index, type));
m_images[index - 1] = std::pair<ImgType, librevenge::RVNGBinaryData>(type, img);
}
else
{
MSPUB_DEBUG_MSG(("0 is not a valid index for image, ignoring.\n"));
}
return index > 0;
}
librevenge::RVNGBinaryData *MSPUBCollector::addBorderImage(ImgType type,
unsigned borderArtIndex)
{
while (borderArtIndex >= m_borderImages.size())
{
m_borderImages.push_back(BorderArtInfo());
}
m_borderImages[borderArtIndex].m_images.push_back(BorderImgInfo(type));
return &(m_borderImages[borderArtIndex].m_images.back().m_imgBlob);
}
void MSPUBCollector::setBorderImageOffset(unsigned index, unsigned offset)
{
while (index >= m_borderImages.size())
{
m_borderImages.push_back(BorderArtInfo());
}
BorderArtInfo &bai = m_borderImages[index];
bai.m_offsets.push_back(offset);
bool added = false;
for (auto i = bai.m_offsetsOrdered.begin();
i != bai.m_offsetsOrdered.end(); ++i)
{
if (*i >= offset)
{
bai.m_offsetsOrdered.insert(i, offset);
added = true;
break;
}
}
if (!added)
{
bai.m_offsetsOrdered.push_back(offset);
}
}
void MSPUBCollector::setShapePage(unsigned seqNum, unsigned pageSeqNum)
{
m_shapeInfosBySeqNum[seqNum].m_pageSeqNum = pageSeqNum;
m_pageSeqNumsByShapeSeqNum[seqNum] = pageSeqNum;
}
void MSPUBCollector::addTextColor(ColorReference c)
{
m_textColors.push_back(c);
}
void MSPUBCollector::designateMasterPage(unsigned seqNum)
{
m_masterPages.insert(seqNum);
}
void MSPUBCollector::setMasterPage(unsigned seqNum, unsigned masterPageSeqNum)
{
m_masterPagesByPageSeqNum[seqNum] = masterPageSeqNum;
}
void MSPUBCollector::setShapeCropType(unsigned seqNum, ShapeType cropType)
{
m_shapeInfosBySeqNum[seqNum].m_cropType = cropType;
}
}
/* vim:set shiftwidth=2 softtabstop=2 expandtab: */