diff --git a/lib/svg-symbol.tsx b/lib/svg-symbol.tsx index aa101c7..f9a7e62 100644 --- a/lib/svg-symbol.tsx +++ b/lib/svg-symbol.tsx @@ -27,14 +27,24 @@ export type SvgSymbolGradientStop = { * This represents a definition that will be referenced * from elsewhere in an SVG, such as a radial gradient. */ -export type SvgSymbolDef = { - type: "radialGradient"; - id: string; - cx: string; - cy: string; - r: string; - stops: SvgSymbolGradientStop[]; -}; +export type SvgSymbolDef = + | { + type: "radialGradient"; + id: string; + cx: string; + cy: string; + r: string; + stops: SvgSymbolGradientStop[]; + } + | { + type: "linearGradient"; + id: string; + x1: string; + y1: string; + x2: string; + y2: string; + stops: SvgSymbolGradientStop[]; + }; export const EMPTY_SVG_SYMBOL_DATA: SvgSymbolData = { name: "", @@ -167,24 +177,23 @@ function reactifySvgSymbolElement( const SvgSymbolDef: React.FC< { def: SvgSymbolDef; uidMap: UniqueIdMap } & SvgSymbolContext > = ({ def, uidMap, ...ctx }) => { + const id = uidMap.getStrict(def.id); + const stops = def.stops.map((stop, i) => ( + + )); switch (def.type) { case "radialGradient": return ( - - {def.stops.map((stop, i) => ( - - ))} + + {stops} ); + case "linearGradient": + return ( + + {stops} + + ); } }; diff --git a/lib/vocabulary-builder.ts b/lib/vocabulary-builder.ts index 5a3ac6a..830d839 100644 --- a/lib/vocabulary-builder.ts +++ b/lib/vocabulary-builder.ts @@ -89,16 +89,18 @@ function getNonEmptyAttrib(el: cheerio.TagElement, attr: string): string { return result; } -function parseRadialGradient(el: cheerio.TagElement): SvgSymbolDef { +function parseGradientStops( + els: cheerio.TagElement["children"] +): SvgSymbolGradientStop[] { const stops: SvgSymbolGradientStop[] = []; - for (let child of el.children) { - if (child.type === "tag") { - if (child.tagName !== "stop") { + for (let el of els) { + if (el.type === "tag") { + if (el.tagName !== "stop") { throw new Error( `Expected an SVG gradient to only contain elements!` ); } - const style = getNonEmptyAttrib(child, "style"); + const style = getNonEmptyAttrib(el, "style"); const color = style.match(/stop-color\:rgb\((\d+),(\d+),(\d+)\)/); if (!color) { throw new Error(`Expected "${style}" to contain a stop-color!`); @@ -107,18 +109,34 @@ function parseRadialGradient(el: cheerio.TagElement): SvgSymbolDef { .slice(1) .map((value) => parseInt(value)); stops.push({ - offset: getNonEmptyAttrib(child, "offset"), + offset: getNonEmptyAttrib(el, "offset"), color: clampedBytesToRGBColor(rgb), }); } } + return stops; +} + +function parseLinearGradient(el: cheerio.TagElement): SvgSymbolDef { + return { + type: "linearGradient", + id: getNonEmptyAttrib(el, "id"), + x1: getNonEmptyAttrib(el, "x1"), + y1: getNonEmptyAttrib(el, "y1"), + x2: getNonEmptyAttrib(el, "x2"), + y2: getNonEmptyAttrib(el, "y2"), + stops: parseGradientStops(el.children), + }; +} + +function parseRadialGradient(el: cheerio.TagElement): SvgSymbolDef { return { type: "radialGradient", id: getNonEmptyAttrib(el, "id"), cx: getNonEmptyAttrib(el, "cx"), cy: getNonEmptyAttrib(el, "cy"), r: getNonEmptyAttrib(el, "r"), - stops, + stops: parseGradientStops(el.children), }; } @@ -138,20 +156,22 @@ function serializeSvgSymbolElement( if (tagName === "radialGradient") { defsOutput.push(parseRadialGradient(el)); return null; + } else if (tagName === "linearGradient") { + defsOutput.push(parseLinearGradient(el)); + return null; + } else if (!isSupportedSvgTag(tagName)) { + throw new Error(`Unsupported SVG element: <${tagName}>`); } let children = withoutNulls( onlyTags(el.children).map((child) => serializeSvgSymbolElement($, child, defsOutput) ) ); - if (isSupportedSvgTag(tagName)) { - return { - tagName, - props: attribsToProps(el) as any, - children, - }; - } - throw new Error(`Unsupported SVG element: <${tagName}>`); + return { + tagName, + props: attribsToProps(el) as any, + children, + }; } function removeEmptyGroups(s: SvgSymbolElement[]): SvgSymbolElement[] {