Files
ElyOffice/patches/libreoffice/0028-svx-chart-look-helpers.patch
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

281 lines
12 KiB
Diff

svx: chart look helpers a kit client can drive
ChartHelper gains IsChartLook, SetChartLook and WriteChartLook (type,
titles, legend, as JSON), moved here from Calc so Impress can use them.
The JSON keeps its types (boost.json): a field of the wrong type, such
as a legend that is not true or false or a title that is not text, is
refused before anything changes.
A scatter starts as points only when the chart becomes one; a scatter
kept as one keeps its lines.
diff --git a/include/svx/charthelper.hxx b/include/svx/charthelper.hxx
index 3ad1fba..9bdf0a6 100644
--- a/include/svx/charthelper.hxx
+++ b/include/svx/charthelper.hxx
@@ -31,6 +31,8 @@ namespace com::sun::star {
}
namespace basegfx { class B2DRange; }
+namespace tools { class JsonWriter; }
+namespace com::sun::star::uno { class XInterface; }
class SAL_WARN_UNUSED SVXCORE_DLLPUBLIC ChartHelper
{
@@ -54,6 +56,13 @@ public:
// newly created charts using this method
static void AdaptDefaultsForChart(
const css::uno::Reference < css::embed::XEmbeddedObject > & xEmbObj);
+
+ /// A kit chart look: type, titles, legend, as JSON.
+ static bool IsChartLook(std::u16string_view rJson);
+ static bool SetChartLook(const css::uno::Reference<css::uno::XInterface>& xChart,
+ std::u16string_view rJson);
+ static void WriteChartLook(::tools::JsonWriter& rJson,
+ const css::uno::Reference<css::uno::XInterface>& xChart);
};
#endif // INCLUDED_SVX_CHARTHELPER_HXX
diff --git a/svx/Library_svxcore.mk b/svx/Library_svxcore.mk
index e2d178b..1dfa044 100644
--- a/svx/Library_svxcore.mk
+++ b/svx/Library_svxcore.mk
@@ -332,6 +332,7 @@ $(eval $(call gb_Library_add_exception_objects,svxcore,\
svx/source/sdr/properties/e3dcompoundproperties \
svx/source/sdr/properties/oleproperties \
svx/source/svdraw/charthelper \
+ svx/source/svdraw/chartlook \
svx/source/svdraw/constructhelper \
svx/source/svdraw/clonelist \
svx/source/svdraw/gradtrns \
diff --git a/svx/source/svdraw/chartjson.hxx b/svx/source/svdraw/chartjson.hxx
new file mode 100644
index 0000000..d105464
--- /dev/null
+++ b/svx/source/svdraw/chartjson.hxx
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice 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/.
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <boost/json.hpp>
+
+#include <optional>
+#include <string_view>
+
+namespace svx::chartjson
+{
+/// The JSON object, or none if it is not one.
+std::optional<boost::json::object> Object(std::u16string_view rJson);
+/// Member `rKey` as text; absent is empty, another type fails.
+std::optional<OUString> Text(const boost::json::object& rObject, std::string_view rKey);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svx/source/svdraw/chartlook.cxx b/svx/source/svdraw/chartlook.cxx
new file mode 100644
index 0000000..79d4e6c
--- /dev/null
+++ b/svx/source/svdraw/chartlook.cxx
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice 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 <svx/charthelper.hxx>
+#include "chartjson.hxx"
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/chart/XAxisXSupplier.hpp>
+#include <com/sun/star/chart/XAxisYSupplier.hpp>
+#include <com/sun/star/chart/XChartDocument.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <tools/json_writer.hxx>
+// One translation unit builds boost.json.
+#include <boost/json/src.hpp>
+
+#include <algorithm>
+#include <functional>
+#include <optional>
+
+using namespace ::com::sun::star;
+
+namespace svx::chartjson
+{
+/// The JSON object, or none if it is not one.
+std::optional<boost::json::object> Object(std::u16string_view rJson)
+{
+ boost::system::error_code aError;
+ boost::json::value aValue = boost::json::parse(std::string(OUString(rJson).toUtf8()), aError);
+ if (aError || !aValue.is_object())
+ return std::nullopt;
+ return aValue.as_object();
+}
+
+/// Member `rKey` as text; absent is empty, another type fails.
+std::optional<OUString> Text(const boost::json::object& rObject, std::string_view rKey)
+{
+ const boost::json::value* pValue = rObject.if_contains(rKey);
+ if (!pValue)
+ return OUString();
+ if (!pValue->is_string())
+ return std::nullopt;
+ const boost::json::string& rText = pValue->as_string();
+ return OUString::fromUtf8(std::string_view(rText.data(), rText.size()));
+}
+}
+
+namespace
+{
+/// Kit names and the diagram services they map to.
+constexpr std::pair<std::string_view, std::u16string_view> aChartTypes[]
+ = { { "column", u"com.sun.star.chart.BarDiagram" },
+ { "bar", u"com.sun.star.chart.BarDiagram" },
+ { "line", u"com.sun.star.chart.LineDiagram" },
+ { "scatter", u"com.sun.star.chart.XYDiagram" },
+ { "pie", u"com.sun.star.chart.PieDiagram" } };
+
+/// A parsed look; "other" keeps the diagram.
+struct ChartLook
+{
+ std::string_view aType;
+ OUString aTitle, aXTitle, aYTitle;
+ bool bLegend = true;
+};
+
+std::optional<ChartLook> lcl_ParseLook(std::u16string_view rJson)
+{
+ const std::optional<boost::json::object> oObject = svx::chartjson::Object(rJson);
+ if (!oObject)
+ return std::nullopt;
+ const boost::json::value* pType = oObject->if_contains("type");
+ if (!pType || !pType->is_string())
+ return std::nullopt;
+ const std::string_view aType(pType->as_string().data(), pType->as_string().size());
+ ChartLook aLook;
+ if (aType == "other")
+ aLook.aType = "other";
+ for (const auto& [rName, rService] : aChartTypes)
+ if (rName == aType)
+ aLook.aType = rName;
+ const std::optional<OUString> oTitle = svx::chartjson::Text(*oObject, "title");
+ const std::optional<OUString> oXTitle = svx::chartjson::Text(*oObject, "xtitle");
+ const std::optional<OUString> oYTitle = svx::chartjson::Text(*oObject, "ytitle");
+ const boost::json::value* pLegend = oObject->if_contains("legend");
+ if (aLook.aType.empty() || !oTitle || !oXTitle || !oYTitle || (pLegend && !pLegend->is_bool()))
+ return std::nullopt;
+ aLook.aTitle = *oTitle;
+ aLook.aXTitle = *oXTitle;
+ aLook.aYTitle = *oYTitle;
+ aLook.bLegend = !pLegend || pLegend->as_bool();
+ return aLook;
+}
+
+/// Shows a title with this text; empty hides it.
+void lcl_SetTitle(const uno::Reference<beans::XPropertySet>& xOwner, const OUString& rHas,
+ const std::function<uno::Reference<drawing::XShape>()>& rTitle,
+ const OUString& rText)
+{
+ xOwner->setPropertyValue(rHas, uno::Any(!rText.isEmpty()));
+ if (rText.isEmpty())
+ return;
+ uno::Reference<beans::XPropertySet> xTitle(rTitle(), uno::UNO_QUERY_THROW);
+ xTitle->setPropertyValue(u"String"_ustr, uno::Any(rText));
+}
+
+OUString lcl_Title(const uno::Reference<beans::XPropertySet>& xOwner, const OUString& rHas,
+ const std::function<uno::Reference<drawing::XShape>()>& rTitle)
+{
+ OUString aText;
+ if (!xOwner->getPropertyValue(rHas).get<bool>())
+ return aText;
+ uno::Reference<beans::XPropertySet> xTitle(rTitle(), uno::UNO_QUERY_THROW);
+ xTitle->getPropertyValue(u"String"_ustr) >>= aText;
+ return aText;
+}
+}
+
+bool ChartHelper::IsChartLook(std::u16string_view rJson) { return lcl_ParseLook(rJson).has_value(); }
+
+bool ChartHelper::SetChartLook(const uno::Reference<uno::XInterface>& xChart,
+ std::u16string_view rJson)
+{
+ const std::optional<ChartLook> oLook = lcl_ParseLook(rJson);
+ uno::Reference<chart::XChartDocument> xDoc(xChart, uno::UNO_QUERY);
+ if (!oLook || !xDoc.is())
+ return false;
+ uno::Reference<chart::XDiagram> xDiagram = xDoc->getDiagram();
+ const auto pType = std::find_if(std::begin(aChartTypes), std::end(aChartTypes),
+ [&oLook](const auto& rPair) { return rPair.first == oLook->aType; });
+ if (pType != std::end(aChartTypes))
+ {
+ const bool bNewType = xDiagram->getDiagramType() != pType->second;
+ if (bNewType)
+ {
+ uno::Reference<lang::XMultiServiceFactory> xFactory(xDoc, uno::UNO_QUERY_THROW);
+ xDiagram.set(xFactory->createInstance(OUString(pType->second)), uno::UNO_QUERY_THROW);
+ xDoc->setDiagram(xDiagram);
+ }
+ uno::Reference<beans::XPropertySet> xProps(xDiagram, uno::UNO_QUERY_THROW);
+ if (pType->second == u"com.sun.star.chart.BarDiagram")
+ xProps->setPropertyValue(u"Vertical"_ustr, uno::Any(oLook->aType == "bar"));
+ // A new scatter starts as points; kept ones keep lines.
+ if (bNewType && oLook->aType == "scatter")
+ xProps->setPropertyValue(u"Lines"_ustr, uno::Any(false));
+ }
+ uno::Reference<beans::XPropertySet> xDocProps(xDoc, uno::UNO_QUERY_THROW);
+ lcl_SetTitle(xDocProps, u"HasMainTitle"_ustr, [&xDoc] { return xDoc->getTitle(); },
+ oLook->aTitle);
+ xDocProps->setPropertyValue(u"HasLegend"_ustr, uno::Any(oLook->bLegend));
+ // A pie has no axes.
+ if (xDiagram->getDiagramType() == u"com.sun.star.chart.PieDiagram")
+ return true;
+ uno::Reference<chart::XAxisXSupplier> xX(xDiagram, uno::UNO_QUERY_THROW);
+ uno::Reference<chart::XAxisYSupplier> xY(xDiagram, uno::UNO_QUERY_THROW);
+ uno::Reference<beans::XPropertySet> xProps(xDiagram, uno::UNO_QUERY_THROW);
+ lcl_SetTitle(xProps, u"HasXAxisTitle"_ustr, [&xX] { return xX->getXAxisTitle(); },
+ oLook->aXTitle);
+ lcl_SetTitle(xProps, u"HasYAxisTitle"_ustr, [&xY] { return xY->getYAxisTitle(); },
+ oLook->aYTitle);
+ return true;
+}
+
+void ChartHelper::WriteChartLook(::tools::JsonWriter& rJson,
+ const uno::Reference<uno::XInterface>& xChart)
+{
+ uno::Reference<chart::XChartDocument> xDoc(xChart, uno::UNO_QUERY_THROW);
+ uno::Reference<chart::XDiagram> xDiagram = xDoc->getDiagram();
+ uno::Reference<beans::XPropertySet> xProps(xDiagram, uno::UNO_QUERY_THROW);
+ const OUString aService = xDiagram->getDiagramType();
+ std::string_view aType = "other";
+ for (const auto& [rName, rService] : aChartTypes)
+ if (rService == aService && aType == "other")
+ aType = rName;
+ if (aType == "column" && xProps->getPropertyValue(u"Vertical"_ustr).get<bool>())
+ aType = "bar";
+ rJson.put("type", aType);
+ uno::Reference<beans::XPropertySet> xDocProps(xDoc, uno::UNO_QUERY_THROW);
+ rJson.put("title", lcl_Title(xDocProps, u"HasMainTitle"_ustr, [&xDoc] { return xDoc->getTitle(); }));
+ rJson.put("legend", xDocProps->getPropertyValue(u"HasLegend"_ustr).get<bool>());
+ uno::Reference<chart::XAxisXSupplier> xX(xDiagram, uno::UNO_QUERY_THROW);
+ uno::Reference<chart::XAxisYSupplier> xY(xDiagram, uno::UNO_QUERY_THROW);
+ const bool bAxes = aType != "pie";
+ rJson.put("xtitle", bAxes ? lcl_Title(xProps, u"HasXAxisTitle"_ustr, [&xX] { return xX->getXAxisTitle(); }) : OUString());
+ rJson.put("ytitle", bAxes ? lcl_Title(xProps, u"HasYAxisTitle"_ustr, [&xY] { return xY->getYAxisTitle(); }) : OUString());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */