/* -*- 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 #include #include #include #include #include #include #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 TableLayout; void createTableLayout(const std::vector &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 > ParagraphToCellMap_t; typedef std::vector SpanTexts_t; typedef std::vector ParagraphTexts_t; void mapTableTextToCells( const std::vector &text, const std::vector &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 &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 &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 &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 MSPUBCollector::getShapeAdjustValues(const ShapeInfo &info) const { std::vector ret; std::shared_ptr 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 > MSPUBCollector::getShapeText(const ShapeInfo &info) const { if (bool(info.m_textId)) { unsigned stringId = info.m_textId.get(); const std::vector *ptr_str = getIfExists_const(m_textStringsById, stringId); if (ptr_str) { return *ptr_str; } } return boost::optional >(); } 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(new ImgFill(index, this, false, rot)); } } elt.setShapeInfo(*ptr_info); std::pair flips = ptr_info->m_flips.get_value_or(std::pair(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 MSPUBCollector::paintShape(const ShapeInfo &info, const Coordinate &/* relativeTo*/, const VectorTransformation2D &foldedTransform, bool isGroup, const VectorTransformation2D &thisTransform) const { std::vector 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 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 > 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(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(obc.r) / 255.0, librevenge::RVNG_PERCENT); graphicsProps.insert("draw:blue", static_cast(obc.b) / 255.0, librevenge::RVNG_PERCENT); graphicsProps.insert("draw:green", static_cast(obc.g) / 255.0, librevenge::RVNG_PERCENT); } if (bool(info.m_pictureBrightness)) graphicsProps.insert("draw:luminance", static_cast(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(s.m_offsetXInEmu) / EMUS_IN_INCH); graphicsProps.insert("draw:shadow-offset-y", static_cast(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(), 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 &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(info.m_lines[0].m_widthInEmu) / EMUS_IN_INCH; auto numImagesHoriz = static_cast(width / borderImgWidth); auto numImagesVert = static_cast(height / borderImgWidth); double borderVertTotalPadding = height - numImagesVert * borderImgWidth; double borderHorizTotalPadding = width - numImagesHoriz * borderImgWidth; if (numImagesHoriz >= 2 && numImagesVert >= 2) { auto numStretchedImagesVert = static_cast(0.5 + (height - 2 * borderImgWidth) / borderImgWidth); auto numStretchedImagesHoriz = static_cast(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 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(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 &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 >::const_iterator it = m_tableCellTextEndsByTextId.find(get(info.m_textId)); const std::vector &tableCellTextEnds = (it != m_tableCellTextEndsByTextId.end()) ? it->second : std::vector(); 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 &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 oneBitColor) const { librevenge::RVNGPropertyList props; if (bool(oneBitColor)) { Color obc = oneBitColor.get(); props.insert("draw:color-mode", "greyscale"); props.insert("draw:red", static_cast(obc.r) / 255.0, librevenge::RVNG_PERCENT); props.insert("draw:blue", static_cast(obc.b) / 255.0, librevenge::RVNG_PERCENT); props.insert("draw:green", static_cast(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 &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 &adjustValues) const { std::shared_ptr 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(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, 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 name) { m_fonts.push_back(name); } librevenge::RVNGPropertyList MSPUBCollector::getParaStyleProps(const ParagraphStyle &style, boost::optional 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 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 MSPUBCollector::getMasterPageSeqNum(unsigned pageSeqNum) const { boost::optional 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 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 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::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::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::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 &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 &str) { for (const auto &i : str) { for (unsigned j = 0; j < i.spans.size(); ++j) { const std::vector &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(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(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: */