include/docmodel/styles/ChartStyle.hxx | 118 +++++++++++++++++++-- include/oox/export/chartexport.hxx | 9 + oox/inc/drawingml/chart/stylefragment.hxx | 24 +++- oox/inc/drawingml/chart/stylemodel.hxx | 4 oox/source/drawingml/chart/stylefragment.cxx | 147 ++++++++++++--------------- oox/source/export/chartexport.cxx | 35 ++++-- 6 files changed, 228 insertions(+), 109 deletions(-)
New commits: commit 5effa8d0977ac9ce13a8e66cbd2302e585c41c6d Author: Kurt Nordback <kurt.nordb...@collabora.com> AuthorDate: Fri Sep 19 04:39:14 2025 -0600 Commit: Tomaž Vajngerl <qui...@gmail.com> CommitDate: Mon Sep 22 06:04:55 2025 +0200 tdf#168472 - Chart round-trip MSO -> LO -> MSO broken Chart style import/export was not handling a:ST_FontCollectionIndex correctly. Despite the name, its value is not an integer index, but one of three strings (major, minor, none). Separate the FontReference and StyleReference code better in order to handle this OOXML value correctly. Change-Id: I1638f6484ecfe5a9bd8c1adab8d854cff47ce14f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191195 Reviewed-by: Tomaž Vajngerl <qui...@gmail.com> Tested-by: Jenkins diff --git a/include/docmodel/styles/ChartStyle.hxx b/include/docmodel/styles/ChartStyle.hxx index b05060a6f5d2..a5a7155421ba 100644 --- a/include/docmodel/styles/ChartStyle.hxx +++ b/include/docmodel/styles/ChartStyle.hxx @@ -21,8 +21,6 @@ namespace model { struct FontOrStyleRef { - sal_Int32 mnIdx; // required - enum class StyleColorEnum { AUTO @@ -55,6 +53,56 @@ struct FontOrStyleRef return sV; } + // Convert an input string to ColorVal + void setColorValStr(const std::optional<OUString>& str) + { + // The attribute here can be an integer, a string, or "auto" (which + // of course is also a string, but is considered special). So we + // need to try to convert the input string to an integer, and handle + // it as an int if we can. OUString provides toInt(), but it returns + // 0 in the case of failure, which is eminently unhelpful, sinze 0 + // is a perfectly acceptable value. So convert it to a + // std::basic_string, which has stoi(), which throws if it can't do + // the conversion. + // + // Unfortunately OUString characters are sal_Unicode which can be + // uint16_t, and while there's string::stoi() and wstring::stoi(), + // there's no basic_string<uint16_t>::stoi(). So we use wstring and + // construct character by character. + if (str) + { + StyleColorVal v; + + const sal_Unicode* pRawStr = str->getStr(); + std::wstring sBStr; + sBStr.reserve(str->getLength()); + for (const sal_Unicode* pS = pRawStr; pS < pRawStr + str->getLength(); ++pS) + { + sBStr.push_back(*pS); + } + + sal_uInt32 nIntVal = 0; + try + { + nIntVal = stoi(sBStr); + v = nIntVal; + } + catch (std::invalid_argument&) + { + // Not an integer, so see if it's the fixed enum + if (*str == "auto") + { + v = StyleColorEnum::AUTO; + } + else + { + v = *str; + } + } + maStyleClr = std::make_unique<StyleColorVal>(v); + } + } + // A child element of cs:CT_FontReference or cs:CT_StyleReference is // a:EG_ColorChoice. The latter is handled by ColorValueContext. oox::drawingml::Color maColor; // needed for ColorValueContext @@ -63,23 +111,73 @@ struct FontOrStyleRef // FontOrStyleRef. Ignore those for now. TODO }; +struct StyleRef : public FontOrStyleRef +{ + sal_Int32 mnIdx = 0; // StyleMatrixColumnIndex +}; + +struct FontRef : public FontOrStyleRef +{ + enum FontCollectionIndex + { + MAJOR, + MINOR, + NONE + }; + + FontCollectionIndex meIdx = MINOR; // required + + // Get the string value of the font collection index + const char* getFontCollectionStr() const + { + switch (meIdx) + { + case MINOR: + return "minor"; + case MAJOR: + return "major"; + case NONE: + return "none"; + default: + assert(false); + } + return nullptr; + } + + // Set the font collection index from a string + void setFontCollectionIndex(std::u16string_view sIdx) + { + if (sIdx == std::u16string_view(u"major")) + { + meIdx = MAJOR; + } + else if (sIdx == std::u16string_view(u"minor")) + { + meIdx = MINOR; + } + else if (sIdx == std::u16string_view(u"none")) + { + meIdx = NONE; + } + } +}; + struct StyleEntry { - std::shared_ptr<FontOrStyleRef> mxLnClr; + std::shared_ptr<StyleRef> mxLnClr; double mfLineWidthScale = 1.0; - std::shared_ptr<FontOrStyleRef> mxFillClr; - std::shared_ptr<FontOrStyleRef> mxEffectClr; - std::shared_ptr<FontOrStyleRef> mxFontClr; + std::shared_ptr<StyleRef> mxFillClr; + std::shared_ptr<StyleRef> mxEffectClr; + std::shared_ptr<FontRef> mxFontClr; std::shared_ptr<oox::drawingml::Shape> mxShapePr; // The following is derived from a TextCharacterProperties std::shared_ptr<oox::PropertyMap> mrTextCharacterPr; // The following is derived from a TextBodyProperties std::shared_ptr<oox::PropertyMap> mxTextBodyPr; - StyleEntry(std::shared_ptr<FontOrStyleRef> aLnClr, double fLineScale, - std::shared_ptr<FontOrStyleRef> aFillClr, std::shared_ptr<FontOrStyleRef> aEffectClr, - std::shared_ptr<FontOrStyleRef> aFontClr, - std::shared_ptr<oox::drawingml::Shape> aShape, + StyleEntry(std::shared_ptr<StyleRef> aLnClr, double fLineScale, + std::shared_ptr<StyleRef> aFillClr, std::shared_ptr<StyleRef> aEffectClr, + std::shared_ptr<FontRef> aFontClr, std::shared_ptr<oox::drawingml::Shape> aShape, std::shared_ptr<oox::PropertyMap> aCharProps, std::shared_ptr<oox::PropertyMap> aBodyProps) : mxLnClr(std::move(aLnClr)) diff --git a/include/oox/export/chartexport.hxx b/include/oox/export/chartexport.hxx index 66c1dd5e3dda..e52abebb9b46 100644 --- a/include/oox/export/chartexport.hxx +++ b/include/oox/export/chartexport.hxx @@ -70,7 +70,8 @@ namespace core { }} namespace model { - struct FontOrStyleRef; + struct StyleRef; + struct FontRef; struct StyleEntry; } @@ -291,8 +292,10 @@ private: // Functions for style file output void outputStyleEntry(::sax_fastparser::FSHelperPtr pFS, sal_Int32 nElTokenId, model::StyleEntry& aEntry); - void outputFontOrStyleRef(::sax_fastparser::FSHelperPtr pFS, - sal_Int32 nElTokenId, const model::FontOrStyleRef& aColor); + void outputStyleRef(::sax_fastparser::FSHelperPtr pFS, + sal_Int32 nElTokenId, const model::StyleRef& aColor); + void outputFontRef(::sax_fastparser::FSHelperPtr pFS, + sal_Int32 nElTokenId, const model::FontRef& aColor); public: diff --git a/oox/inc/drawingml/chart/stylefragment.hxx b/oox/inc/drawingml/chart/stylefragment.hxx index a7bb698a43e2..9adbea526aba 100644 --- a/oox/inc/drawingml/chart/stylefragment.hxx +++ b/oox/inc/drawingml/chart/stylefragment.hxx @@ -23,24 +23,38 @@ namespace model { -struct FontOrStyleRef; +struct StyleRef; +struct FontRef; } namespace oox::drawingml::chart { -/** Handler for a cs:CT_StyleReference or cs:CT_FontReference element. +/** Handler for a cs:CT_StyleReference element. */ -class StyleReferenceContext final : public ContextBase<model::FontOrStyleRef> +class StyleReferenceContext final : public ContextBase<model::StyleRef> { public: - StyleReferenceContext(ContextHandler2Helper& rParent, sal_Int32 nIdx, - model::FontOrStyleRef& rModel); + StyleReferenceContext(ContextHandler2Helper& rParent, const sal_Int32 nIdx, + model::StyleRef& rModel); virtual ~StyleReferenceContext() override; virtual ::oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement, const AttributeList& rAttribs) override; }; +/** Handler for a cs:CT_FontReference element. + */ +class FontReferenceContext final : public ContextBase<model::FontRef> +{ +public: + FontReferenceContext(ContextHandler2Helper& rParent, std::u16string_view sIdx, + model::FontRef& rModel); + virtual ~FontReferenceContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement, + const AttributeList& rAttribs) override; +}; + struct StyleEntryModel; /** Handler for a cs:CT_StyleEntry element. diff --git a/oox/inc/drawingml/chart/stylemodel.hxx b/oox/inc/drawingml/chart/stylemodel.hxx index ce8c2c8e64d6..55427a2b1c0c 100644 --- a/oox/inc/drawingml/chart/stylemodel.hxx +++ b/oox/inc/drawingml/chart/stylemodel.hxx @@ -28,8 +28,8 @@ namespace oox::drawingml::chart // model for both. struct StyleEntryModel { - typedef ModelRef<model::FontOrStyleRef> StyleRef; // "idx" is ST_StyleMatrixColumnIndex - typedef ModelRef<model::FontOrStyleRef> FontRef; // "idx" is ST_FontCollectionIndex + typedef ModelRef<model::StyleRef> StyleRef; // "idx" is ST_StyleMatrixColumnIndex + typedef ModelRef<model::FontRef> FontRef; // "idx" is ST_FontCollectionIndex typedef ModelRef<TextCharacterProperties> TextCharacterPropsRef; typedef ModelRef<TextBodyProperties> TextBodyPropsRef; typedef ModelRef<Shape> ShapeRef; diff --git a/oox/source/drawingml/chart/stylefragment.cxx b/oox/source/drawingml/chart/stylefragment.cxx index a353ce445d91..b93d983ca6f9 100644 --- a/oox/source/drawingml/chart/stylefragment.cxx +++ b/oox/source/drawingml/chart/stylefragment.cxx @@ -39,85 +39,6 @@ namespace oox::drawingml::chart using namespace ::oox::core; using namespace model; -//======= -// StyleReferenceContext -//======= -StyleReferenceContext::StyleReferenceContext(ContextHandler2Helper& rParent, sal_Int32 nIdx, - model::FontOrStyleRef& rModel) - : ContextBase<FontOrStyleRef>(rParent, rModel) -{ - mrModel.mnIdx = nIdx; -} - -StyleReferenceContext::~StyleReferenceContext() {} - -ContextHandlerRef StyleReferenceContext::onCreateContext(sal_Int32 nElement, - const AttributeList& rAttribs) -{ - if (isRootElement()) - switch (nElement) - { - case CS_TOKEN(styleClr): - { - // The attribute here can be an integer, a string, or "auto" (which - // of course is also a string, but is considered special). So we - // need to try to convert the input string to an integer, and handle - // it as an int if we can. OUString provides toInt(), but it returns - // 0 in the case of failure, which is eminently unhelpful, sinze 0 - // is a perfectly acceptable value. So convert it to a - // std::basic_string, which has stoi(), which throws if it can't do - // the conversion. - // - // Unfortunately OUString characters are sal_Unicode which can be - // uint16_t, and while there's string::stoi() and wstring::stoi(), - // there's no basic_string<uint16_t>::stoi(). So we use wstring and - // construct character by character. - std::optional<OUString> str = rAttribs.getString(XML_val); - if (str) - { - FontOrStyleRef::StyleColorVal v; - - const sal_Unicode* pRawStr = str->getStr(); - std::wstring sBStr; - sBStr.reserve(str->getLength()); - for (const sal_Unicode* pS = pRawStr; pS < pRawStr + str->getLength(); ++pS) - { - sBStr.push_back(*pS); - } - - sal_uInt32 nIntVal = 0; - try - { - nIntVal = stoi(sBStr); - v = nIntVal; - } - catch (std::invalid_argument&) - { - // Not an integer, so see if it's the fixed enum - if (*str == "auto") - { - v = FontOrStyleRef::StyleColorEnum::AUTO; - } - else - { - v = *str; - } - } - mrModel.maStyleClr = std::make_unique<FontOrStyleRef::StyleColorVal>(v); - } - return nullptr; - } - case A_TOKEN(scrgbClr): - case A_TOKEN(srgbClr): - case A_TOKEN(hslClr): - case A_TOKEN(sysClr): - case A_TOKEN(schemeClr): - case A_TOKEN(prstClr): - return new ColorValueContext(*this, mrModel.maColor, &mrModel.maComplexColor); - } - return nullptr; -} - //======= // StyleEntryContext //======= @@ -146,8 +67,8 @@ ContextHandlerRef StyleEntryContext::onCreateContext(sal_Int32 nElement, return new StyleReferenceContext(*this, rAttribs.getInteger(XML_idx, -1), mrModel.mxEffectRef.create()); case CS_TOKEN(fontRef): // CT_FontReference - return new StyleReferenceContext(*this, rAttribs.getInteger(XML_idx, -1), - mrModel.mxFontRef.create()); + return new FontReferenceContext(*this, rAttribs.getString(XML_idx, ""), + mrModel.mxFontRef.create()); case CS_TOKEN(spPr): // a:CT_ShapeProperties return new ShapePropertiesContext(*this, mrModel.mxShapeProp.create()); case CS_TOKEN(defRPr): // a:CT_TextCharacterProperties @@ -173,6 +94,70 @@ void StyleEntryContext::onCharacters(const OUString& rChars) } } +//======= +// StyleReferenceContext +//======= +StyleReferenceContext::StyleReferenceContext(ContextHandler2Helper& rParent, const sal_Int32 nIdx, + model::StyleRef& rModel) + : ContextBase<StyleRef>(rParent, rModel) +{ + mrModel.mnIdx = nIdx; +} + +StyleReferenceContext::~StyleReferenceContext() {} + +ContextHandlerRef StyleReferenceContext::onCreateContext(sal_Int32 nElement, + const AttributeList& rAttribs) +{ + if (isRootElement()) + switch (nElement) + { + case CS_TOKEN(styleClr): + mrModel.setColorValStr(rAttribs.getString(XML_val)); + return nullptr; + case A_TOKEN(scrgbClr): + case A_TOKEN(srgbClr): + case A_TOKEN(hslClr): + case A_TOKEN(sysClr): + case A_TOKEN(schemeClr): + case A_TOKEN(prstClr): + return new ColorValueContext(*this, mrModel.maColor, &mrModel.maComplexColor); + } + return nullptr; +} + +//======= +// FontReferenceContext +//======= +FontReferenceContext::FontReferenceContext(ContextHandler2Helper& rParent, std::u16string_view sIdx, + model::FontRef& rModel) + : ContextBase<FontRef>(rParent, rModel) +{ + mrModel.setFontCollectionIndex(sIdx); +} + +FontReferenceContext::~FontReferenceContext() {} + +ContextHandlerRef FontReferenceContext::onCreateContext(sal_Int32 nElement, + const AttributeList& rAttribs) +{ + if (isRootElement()) + switch (nElement) + { + case CS_TOKEN(styleClr): + mrModel.setColorValStr(rAttribs.getString(XML_val)); + return nullptr; + case A_TOKEN(scrgbClr): + case A_TOKEN(srgbClr): + case A_TOKEN(hslClr): + case A_TOKEN(sysClr): + case A_TOKEN(schemeClr): + case A_TOKEN(prstClr): + return new ColorValueContext(*this, mrModel.maColor, &mrModel.maComplexColor); + } + return nullptr; +} + //======= // StyleFragment //======= diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx index 390f83412626..58b8e44d1442 100644 --- a/oox/source/export/chartexport.cxx +++ b/oox/source/export/chartexport.cxx @@ -5971,11 +5971,30 @@ OUString ChartExport::getNumberFormatCode(sal_Int32 nKey) const return aCode; } -// Create cs:CT_FontReference or cs:CT_StyleReference -void ChartExport::outputFontOrStyleRef(FSHelperPtr pFS, sal_Int32 nElTokenId, const - model::FontOrStyleRef& aColor) +// Create cs:CT_StyleReference +void ChartExport::outputStyleRef(FSHelperPtr pFS, sal_Int32 nElTokenId, + const model::StyleRef& aColor) { - pFS->startElement(FSNS(XML_cs, nElTokenId), XML_idx, OUString::number(aColor.mnIdx)); + pFS->startElement(FSNS(XML_cs, nElTokenId), XML_idx, + OUString::number(aColor.mnIdx)); + + ThemeExport aTE(mpFB, GetDocumentType(), pFS); + aTE.writeComplexColor(aColor.maComplexColor); + + // Get the string for the StyleColorVal + OUString sV = aColor.getColorValStr(); + if (!sV.isEmpty()) { + pFS->singleElement(FSNS(XML_cs, XML_styleClr), XML_val, sV); + } + + pFS->endElement(FSNS(XML_cs, nElTokenId)); +} + +// Create cs:CT_FontReference +void ChartExport::outputFontRef(FSHelperPtr pFS, sal_Int32 nElTokenId, + const model::FontRef& aColor) +{ + pFS->startElement(FSNS(XML_cs, nElTokenId), XML_idx, aColor.getFontCollectionStr()); ThemeExport aTE(mpFB, GetDocumentType(), pFS); aTE.writeComplexColor(aColor.maComplexColor); @@ -5995,15 +6014,15 @@ void ChartExport::outputStyleEntry(FSHelperPtr pFS, sal_Int32 nElTokenId, model: // Just default values for now pFS->startElement(FSNS(XML_cs, nElTokenId)); - outputFontOrStyleRef(pFS, FSNS(XML_cs, XML_lnRef), *(aEntry.mxLnClr)); + outputStyleRef(pFS, FSNS(XML_cs, XML_lnRef), *(aEntry.mxLnClr)); pFS->startElement(FSNS(XML_cs, XML_lineWidthScale)); pFS->write(aEntry.mfLineWidthScale); pFS->endElement(FSNS(XML_cs, XML_lineWidthScale)); - outputFontOrStyleRef(pFS, FSNS(XML_cs, XML_fillRef), *(aEntry.mxFillClr)); - outputFontOrStyleRef(pFS, FSNS(XML_cs, XML_effectRef), *(aEntry.mxEffectClr)); - outputFontOrStyleRef(pFS, FSNS(XML_cs, XML_fontRef), *(aEntry.mxFontClr)); + outputStyleRef(pFS, FSNS(XML_cs, XML_fillRef), *(aEntry.mxFillClr)); + outputStyleRef(pFS, FSNS(XML_cs, XML_effectRef), *(aEntry.mxEffectClr)); + outputFontRef(pFS, FSNS(XML_cs, XML_fontRef), *(aEntry.mxFontClr)); if (aEntry.mxShapePr) { exportShapeProps(aEntry.mxShapePr->getShapeProperties().makePropertySet(), XML_cs);