Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scope the CSS included in the SVG #3953

Merged
merged 5 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions include/vrv/svgdevicecontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class SvgDeviceContext : public DeviceContext {
* @name Constructors, destructors, and other standard methods
*/
///@{
SvgDeviceContext();
SvgDeviceContext(const std::string &docId);
virtual ~SvgDeviceContext();
///@}

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -384,6 +393,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
Expand Down
53 changes: 44 additions & 9 deletions src/svgdevicecontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//----------------------------------------------------------------------------

#include <cassert>
#include <regex>

//----------------------------------------------------------------------------

Expand All @@ -33,8 +34,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;

Expand All @@ -59,8 +62,8 @@ SvgDeviceContext::SvgDeviceContext() : DeviceContext(SVG_DEVICE_CONTEXT)
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;

// start the stack
m_svgNodeStack.push_back(m_svgNode);
Expand Down Expand Up @@ -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}");
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_css.c_str());
m_currentNode.text().set(m_css);
m_currentNode = m_svgNodeStack.back();
}

Expand Down Expand Up @@ -613,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])
{
Expand Down
2 changes: 1 addition & 1 deletion src/toolkit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down