From 2d58ab78b718e8b8ea5401f16ef32afa2a3f6c71 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 13 Feb 2025 18:24:21 +0100 Subject: [PATCH 1/5] Limit the scope of CSS style in the SVG through `svg@id` --- include/vrv/svgdevicecontext.h | 4 +++- src/svgdevicecontext.cpp | 21 +++++++++++++-------- src/toolkit.cpp | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index dfb77798ec..70fc44f057 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -42,7 +42,7 @@ class SvgDeviceContext : public DeviceContext { * @name Constructors, destructors, and other standard methods */ ///@{ - SvgDeviceContext(); + SvgDeviceContext(const std::string &docId); virtual ~SvgDeviceContext(); ///@} @@ -384,6 +384,8 @@ class SvgDeviceContext : public DeviceContext { std::string m_glyphPostfixId; // embedding of the smufl text font option_SMUFLTEXTFONT m_smuflTextFont; + // the document id + std::string m_docId; }; } // namespace vrv diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 2291ef7a6a..15ac20dd74 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -33,8 +33,10 @@ namespace vrv { // SvgDeviceContext //---------------------------------------------------------------------------- -SvgDeviceContext::SvgDeviceContext() : DeviceContext(SVG_DEVICE_CONTEXT) +SvgDeviceContext::SvgDeviceContext(const std::string &docId) : DeviceContext(SVG_DEVICE_CONTEXT) { + m_docId = docId; + m_originX = 0; m_originY = 0; @@ -61,6 +63,7 @@ SvgDeviceContext::SvgDeviceContext() : DeviceContext(SVG_DEVICE_CONTEXT) m_svgNode.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink"; m_svgNode.append_attribute("xmlns:mei") = "http://www.music-encoding.org/ns/mei"; m_svgNode.append_attribute("overflow") = "visible"; + m_svgNode.append_attribute("id") = m_docId; // start the stack m_svgNodeStack.push_back(m_svgNode); @@ -472,19 +475,21 @@ void SvgDeviceContext::StartPage() if (this->UseGlobalStyling()) { m_currentNode = m_currentNode.append_child("style"); m_currentNode.append_attribute("type") = "text/css"; - m_currentNode.text().set("g.page-margin{font-family:Times,serif;} " - //"g.page-margin{background: pink;} " - //"g.bounding-box{stroke:red; stroke-width:10} " - //"g.content-bounding-box{stroke:blue; stroke-width:10} " - "g.ending, g.fing, g.reh, g.tempo{font-weight:bold;} g.dir, g.dynam, " - "g.mNum{font-style:italic;} g.label{font-weight:normal;} path{stroke:currentColor}"); + m_currentNode.text().set("#" + m_docId + + " " + "g.page-margin{font-family:Times,serif;} " + //"g.page-margin{background: pink;} " + //"g.bounding-box{stroke:red; stroke-width:10} " + //"g.content-bounding-box{stroke:blue; stroke-width:10} " + "g.ending, g.fing, g.reh, g.tempo{font-weight:bold;} g.dir, g.dynam, " + "g.mNum{font-style:italic;} g.label{font-weight:normal;} path{stroke:currentColor}"); m_currentNode = m_svgNodeStack.back(); } if (!m_css.empty()) { m_currentNode = m_currentNode.append_child("style"); m_currentNode.append_attribute("type") = "text/css"; - m_currentNode.text().set(m_css.c_str()); + m_currentNode.text().set(("#" + m_docId + " " + m_css).c_str()); m_currentNode = m_svgNodeStack.back(); } diff --git a/src/toolkit.cpp b/src/toolkit.cpp index fcdcec817d..25ea70cfd4 100644 --- a/src/toolkit.cpp +++ b/src/toolkit.cpp @@ -1684,7 +1684,7 @@ std::string Toolkit::RenderToSVG(int pageNo, bool xmlDeclaration) int initialPageNo = (m_doc.GetDrawingPage() == NULL) ? -1 : m_doc.GetDrawingPage()->GetIdx(); // Create the SVG object, h & w come from the system // We will need to set the size of the page after having drawn it depending on the options - SvgDeviceContext svg; + SvgDeviceContext svg(m_doc.GetID()); svg.SetResources(&m_doc.GetResources()); int indent = (m_options->m_outputIndentTab.GetValue()) ? -1 : m_options->m_outputIndent.GetValue(); From da5157adbefd609992bbc1dfb61401b145fc42a3 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Thu, 13 Feb 2025 18:24:46 +0100 Subject: [PATCH 2/5] Remove invalid xmlns:mei --- src/svgdevicecontext.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 15ac20dd74..51618d5fb3 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -61,7 +61,6 @@ SvgDeviceContext::SvgDeviceContext(const std::string &docId) : DeviceContext(SVG m_svgNode.append_attribute("version") = "1.1"; m_svgNode.append_attribute("xmlns") = "http://www.w3.org/2000/svg"; m_svgNode.append_attribute("xmlns:xlink") = "http://www.w3.org/1999/xlink"; - m_svgNode.append_attribute("xmlns:mei") = "http://www.music-encoding.org/ns/mei"; m_svgNode.append_attribute("overflow") = "visible"; m_svgNode.append_attribute("id") = m_docId; From d6a0d1aa5b9c10dbe5909d49b9c99499cf44a1f9 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 16 Feb 2025 19:42:04 +0100 Subject: [PATCH 3/5] Add method to prefix the CSS rules in the SVG --- include/vrv/svgdevicecontext.h | 11 +++++++- src/svgdevicecontext.cpp | 49 +++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/include/vrv/svgdevicecontext.h b/include/vrv/svgdevicecontext.h index 70fc44f057..80746b5fba 100644 --- a/include/vrv/svgdevicecontext.h +++ b/include/vrv/svgdevicecontext.h @@ -233,7 +233,11 @@ class SvgDeviceContext : public DeviceContext { /** * Setter for an additional CSS */ - void SetCss(const std::string &css) { m_css = css; } + void SetCss(const std::string &css) + { + m_css = css; + this->PrefixCssRules(m_css); + } /** * Copies additional attributes of defined elements to the SVG, each string in the form "elementName@attribute" @@ -307,6 +311,11 @@ class SvgDeviceContext : public DeviceContext { void AppendStrokeDashArray(pugi::xml_node node, const Pen &pen); ///@} + /** + * Prefix the CSS rules with a #docId for scoping them to the SVG + */ + void PrefixCssRules(std::string &rules); + public: // private: diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 51618d5fb3..639149adf1 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -10,6 +10,7 @@ //---------------------------------------------------------------------------- #include +#include //---------------------------------------------------------------------------- @@ -474,21 +475,21 @@ void SvgDeviceContext::StartPage() if (this->UseGlobalStyling()) { m_currentNode = m_currentNode.append_child("style"); m_currentNode.append_attribute("type") = "text/css"; - m_currentNode.text().set("#" + m_docId - + " " - "g.page-margin{font-family:Times,serif;} " - //"g.page-margin{background: pink;} " - //"g.bounding-box{stroke:red; stroke-width:10} " - //"g.content-bounding-box{stroke:blue; stroke-width:10} " - "g.ending, g.fing, g.reh, g.tempo{font-weight:bold;} g.dir, g.dynam, " - "g.mNum{font-style:italic;} g.label{font-weight:normal;} path{stroke:currentColor}"); + std::string css = "g.page-margin{font-family:Times,serif;} " + //"g.page-margin{background: pink;} " + //"g.bounding-box{stroke:red; stroke-width:10} " + //"g.content-bounding-box{stroke:blue; stroke-width:10} " + "g.ending, g.fing, g.reh, g.tempo{font-weight:bold;} g.dir, g.dynam, " + "g.mNum{font-style:italic;} g.label{font-weight:normal;} path{stroke:currentColor}"; + this->PrefixCssRules(css); + m_currentNode.text().set(css); m_currentNode = m_svgNodeStack.back(); } if (!m_css.empty()) { m_currentNode = m_currentNode.append_child("style"); m_currentNode.append_attribute("type") = "text/css"; - m_currentNode.text().set(("#" + m_docId + " " + m_css).c_str()); + m_currentNode.text().set(m_css); m_currentNode = m_svgNodeStack.back(); } @@ -617,6 +618,36 @@ void SvgDeviceContext::AppendStrokeDashArray(pugi::xml_node node, const Pen &pen } } +void SvgDeviceContext::PrefixCssRules(std::string &rules) +{ + static std::regex selectorRegex(R"(([^{}]+)\s*\{([^}]*)\})"); + static std::regex multiSelectorRegex(R"((\b\w+\.[\w-]+\b))"); + + std::sregex_iterator it(rules.begin(), rules.end(), selectorRegex); + std::sregex_iterator end; + + std::string result; + + while (it != end) { + + std::string selectors = (*it)[1].str(); + std::string properties = (*it)[2].str(); + + // Trim trailing spaces from selectors to prevent extra spaces before `{` + selectors = std::regex_replace(selectors, std::regex(R"(\s+$)"), ""); + + // Prepend `#docId` to each selector + selectors = std::regex_replace(selectors, multiSelectorRegex, "#" + m_docId + " $1"); + + // Keep contents inside `{}` unchanged + result += selectors + " {" + properties + "}"; + + ++it; + } + + rules = result; +} + // Drawing methods void SvgDeviceContext::DrawQuadBezierPath(Point bezier[3]) { From fbc6e3b4de07f1ea258a815821b9928304ff42ba Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Sun, 16 Feb 2025 19:48:46 +0100 Subject: [PATCH 4/5] Make dot optional --- src/svgdevicecontext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 639149adf1..17f1d3091e 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -621,7 +621,7 @@ void SvgDeviceContext::AppendStrokeDashArray(pugi::xml_node node, const Pen &pen void SvgDeviceContext::PrefixCssRules(std::string &rules) { static std::regex selectorRegex(R"(([^{}]+)\s*\{([^}]*)\})"); - static std::regex multiSelectorRegex(R"((\b\w+\.[\w-]+\b))"); + static std::regex multiSelectorRegex(R"((\b\w+\.?[\w-]+\b))"); std::sregex_iterator it(rules.begin(), rules.end(), selectorRegex); std::sregex_iterator end; From f1dbc3021762ebad5a00a164722049efb0872480 Mon Sep 17 00:00:00 2001 From: Laurent Pugin Date: Mon, 17 Feb 2025 12:28:34 +0100 Subject: [PATCH 5/5] Separate commented css rules --- src/svgdevicecontext.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/svgdevicecontext.cpp b/src/svgdevicecontext.cpp index 17f1d3091e..131cb29b29 100644 --- a/src/svgdevicecontext.cpp +++ b/src/svgdevicecontext.cpp @@ -476,11 +476,11 @@ void SvgDeviceContext::StartPage() m_currentNode = m_currentNode.append_child("style"); m_currentNode.append_attribute("type") = "text/css"; std::string css = "g.page-margin{font-family:Times,serif;} " - //"g.page-margin{background: pink;} " - //"g.bounding-box{stroke:red; stroke-width:10} " - //"g.content-bounding-box{stroke:blue; stroke-width:10} " "g.ending, g.fing, g.reh, g.tempo{font-weight:bold;} g.dir, g.dynam, " "g.mNum{font-style:italic;} g.label{font-weight:normal;} path{stroke:currentColor}"; + // bounding box css - for debugging + // css += " g.bounding-box{stroke:red; stroke-width:10} " + // "g.content-bounding-box{stroke:blue; stroke-width:10}"; this->PrefixCssRules(css); m_currentNode.text().set(css); m_currentNode = m_svgNodeStack.back();