Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
magicsunday committed Aug 31, 2024
1 parent 36366ee commit 9be7eda
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 18 deletions.
6 changes: 3 additions & 3 deletions resources/css/svg.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,16 @@
font-size: 13px;
}

.webtrees-pedigree-chart-container svg .wt-chart-box-name {
.webtrees-pedigree-chart-container svg text.wt-chart-box-name {
fill: var(--link-color, currentColor);
}

.webtrees-pedigree-chart-container svg .wt-chart-box-name-alt {
.webtrees-pedigree-chart-container svg text.wt-chart-box-name-alt {
fill: currentColor;
font-weight: 500;
font-size: 0.85em;
}

.webtrees-pedigree-chart-container svg .wt-chart-box-name:hover:not(.wt-chart-box-name-alt) {
.webtrees-pedigree-chart-container svg text.wt-chart-box-name:hover:not(.wt-chart-box-name-alt) {
fill: var(--link-color-hover);
}
136 changes: 125 additions & 11 deletions resources/js/modules/lib/chart/svg/export/svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,89 @@ import Export from "../export";
*/
export default class SvgExport extends Export
{
/**
* Replaces all CSS variables in the given CSS string with its computed style equivalent.
*
* @param {String} css
*
* @returns {String}
*/
replaceCssVariables(css)
{
// Match all CSS selectors and their content
// const regexSelector = new RegExp("\\s*([^,}\\\/\\s].*)(?<!\\s).*{([\\s\\S]*?)}", "g");
const regexSelector = new RegExp("([^{]+)\\s*\\{\\s*([^}]+)\\s*}", "g");

// Match all properties containing an CSS variable
const regexVariables = new RegExp("\\s*([a-zA-Z0-9-_]+)??[\\s:=]*\\s*(\\bvar[(]-{2}[^)].+[)]+);", "g");

let matchesSelector;
let replacedCss = css;

// Match all CSS selectors and their content
while ((matchesSelector = regexSelector.exec(css)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (matchesSelector.index === regexSelector.lastIndex) {
regexSelector.lastIndex++;
}

// Use the selector to look up the element in the DOM
const element = document.querySelector(matchesSelector[1].trim());

let matchesVariables;

// Match all properties of the previous matched selector and check if it contains an CSS variable
while ((matchesVariables = regexVariables.exec(matchesSelector[2])) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (matchesVariables.index === regexVariables.lastIndex) {
regexVariables.lastIndex++;
}

// If the element was not found, remove the CSS variable and its property
if (element === null) {
replacedCss = replacedCss.replace(matchesVariables[0], "");
continue;
}

// Get the computed style of the property
const computedFillProperty = window
.getComputedStyle(element)
.getPropertyValue(matchesVariables[1]);

// Replace the variable property with the computed style
if (computedFillProperty !== "") {
replacedCss = replacedCss.replace(matchesVariables[2], computedFillProperty);
}
}
}

return replacedCss;
}

/**
* Returns an unique sorted list of class names from all SVG elements.
*
* @param {NodeListOf<Element>} elements
*
* @returns {String[]}
*/
extractClassNames(elements)
{
let classes = {};

return Array.prototype
.concat
.apply(
[],
[...elements].map(function (element) {
return [...element.classList];
})
)
// Reduce the list of classNames to a unique list
.filter(name => !classes[name] && (classes[name] = true))
.sort();
}

/**
* Copies recursively all the styles from the list of container elements from the source
* to the destination node.
Expand All @@ -29,22 +112,53 @@ export default class SvgExport extends Export
*/
copyStylesInline(cssFiles, destinationNode, containerClassName)
{
// Assign class wt-global so theme related styles are correctly set in export
destinationNode.classList.add("wt-global");

const elementsWithClass = destinationNode.querySelectorAll("[class]");
const usedClasses = this.extractClassNames(elementsWithClass);
usedClasses.push("wt-global", containerClassName);

const style = document.createElementNS(
"http://www.w3.org/2000/svg",
"style"
);

let cssMap = new Map();

return new Promise(resolve => {
Promise
.all(cssFiles.map(url => d3.text(url)))
.then((filesData) => {
filesData.forEach(data => {
// Remove parent container selector as the CSS is included directly in the SVG element
data = data.replace(new RegExp("." + containerClassName + " ", "g"), "");
const classList = "\\." + usedClasses.join("|\\.");
const regex = new RegExp("(([^,}]*)(" + classList + "))\\b(?!-)[^}]*}", 'g')

let matches;

let style = document.createElementNS("http://www.w3.org/2000/svg", "style");
style.appendChild(document.createTextNode(data));
while ((matches = regex.exec(data)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (matches.index === regex.lastIndex) {
regex.lastIndex++;
}

destinationNode.prepend(style);
// Store all matches CSS rules (merge duplicates into same entry)
cssMap.set(
JSON.stringify(matches[0]),
matches[0]
);
}
});

// Assign class wt-global so theme related styles are correctly set in export
destinationNode.classList.add("wt-global");
// Convert the CSS map to the final CSS string
let finalCss = [...cssMap.values()].flat().join("\n");

// Remove parent container selector as the CSS is included directly in the SVG element
finalCss = this.replaceCssVariables(finalCss);
finalCss = finalCss.replaceAll("." + containerClassName + " ", "");

style.appendChild(document.createTextNode(finalCss));
destinationNode.prepend(style);

resolve(destinationNode);
});
Expand All @@ -61,11 +175,11 @@ export default class SvgExport extends Export
convertToObjectUrl(svg)
{
return new Promise(resolve => {
let data = (new XMLSerializer()).serializeToString(svg);
let DOMURL = window.URL || window.webkitURL || window;
let data = (new XMLSerializer()).serializeToString(svg);
let DOMURL = window.URL || window.webkitURL || window;
let svgBlob = new Blob([ data ], { type: "image/svg+xml;charset=utf-8" });
let url = DOMURL.createObjectURL(svgBlob);
let img = new Image();
let url = DOMURL.createObjectURL(svgBlob);
let img = new Image();

img.onload = () => {
resolve(url);
Expand Down
3 changes: 1 addition & 2 deletions resources/js/modules/lib/tree/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ export default class Date
appendDate(parent)
{
const table = parent
.append("g")
.attr("class", "table");
.append("g");

// Top/Bottom and Bottom/Top
if ((this._orientation instanceof OrientationTopBottom)
Expand Down
4 changes: 2 additions & 2 deletions resources/js/pedigree-chart-1.7.2-dev.min.js

Large diffs are not rendered by default.

0 comments on commit 9be7eda

Please sign in to comment.