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

[hiccup-svg] Add pattern to hiccup-svg #332

Open
dennemark opened this issue Dec 6, 2021 · 2 comments
Open

[hiccup-svg] Add pattern to hiccup-svg #332

dennemark opened this issue Dec 6, 2021 · 2 comments

Comments

@dennemark
Copy link

dennemark commented Dec 6, 2021

Hi,

I haven´t tested it yet, but it should be possible to add patterns to svgs via the attached code.

import { fattribs, ff } from "./format.js";
import type { Vec2Like } from "./api.js";

/**
 * Defines pattern to use within svg.
 * Should be used within <defs> </defs> ["defs", {}, ["pattern", ..., ]]
 * @param id - id of pattern, which can be used by other elements via i.e. fill: "url(#patternid)"
 * @param p - position of pattern representing x,y coordinates of its bounding  box
 * @param width - width of pattern
 * @param height - height of pattern
 * @param attribs - attributes object
 * @param body - shape primitives
 */
export const pattern = (
    id: string,
    p: Vec2Like,
    width: number,
    height:  number,
    pattern: any[],
    attribs?: any): any[] => [
    "pattern",
    fattribs({ 
        ...attribs,
        id: id,
        x: ff(p[0]),
        y: ff(p[1]),
        width: ff(width),
        height: ff(height)

     }),
    ...pattern,
];

I hope this convert function makes sense:

case "pattern":
            return pattern(attribs.id, attribs.p, attribs.width, attribs.height, 
                tree[2],
                {
                    patternContentUnits: attribs.patternContentUnits || "userSpaceOnUse",
                    patternUnits: attribs.patternUnits || "objectBoundingBox",
                    preserveAspectRatio: attribs.preserveAspectRatio || "xMidYMid meet",
                    "xlink:href": attribs["xlink:href"]
                });
@dennemark
Copy link
Author

dennemark commented Dec 7, 2021

I digged a bit more into the tooling of hiccup. Got a hang on vite and building the tools. But I realised it is already possible to use pattern as a generic hiccup that can be transduced:

["defs", {},
                        ["pattern", {id:"pattern", x: 0, y:0, width: 10, height: 10},
                            ["circle", {cx: 3, cy: 3, r: 3, fill: "#ff0000"  }]
                        ]
                    ]

One thing I observed and which confused me, is the api for creating i.e. a circle.

  • Generic hiccup: ["circle", {cx: 3, cy: 3, r: 3, fill: "#ff0000" }]
  • Hiccup Svg function: svg.circle([3, 3], 3, { fill: "#ff0000" })
  • Hiccup Svg: ["circle", { fill: "#ff0000" }, [3, 3], 3]

Hiccup-svg function creates hiccup-svg, which will be converted to hiccup as far as I understood from the code.
I always thought hiccup syntax looked like the generic one an this allows it to be easily parsed as xml. But what are the advantages of hiccup-svg? I understand the functional approach svg.circle(...) since it adds typing and add a bit additional logic to svg parameters, but this is not accessible within hiccup-svg right?

The reason why I went this way, is the missing parsing of hiccup within thi.ng/geom
The svgDoc function only allows the use of IShape. This is now clear to me. Maybe it would be nice to be able to extend this functionality with hiccup above.

The alternative way would be to mix with hiccup-svg, and map each geom IShape via toHiccup() function.

//thi.ng/geom - calcs viewBox from IShape bounds
svgDoc({width: 100, height: 100}, ...shapes)
//thi.ng/hiccup-svg svg function
svg.svg({width: 100, height: 100, viewBox: "0, 0, 100, 100"}, hiccup, ...shapes.map(s => s.toHiccup()))

@postspectacular
Copy link
Member

Hi @dennemark - do you wanna create a PR for the pattern function above? Would be a useful addition indeed (though I've rarely used SVG patterns myself so far). Thank you!

As for the differences between the different hiccup flavors for defining shape elements. The issue with SVG is that all attribute values are being stringified and therefore cause a serious perf hit, esp. for shapes with larger data values, e.g. paths, polygons etc. To avoid that hit and be more flexible in terms of transforming hiccup to other output formats, the geom package is generating hiccup for all supported shapes in the more "raw" (i.e. non-stringified) format also used by the hiccup-canvas package. This allows for much faster & better processing/drawing of shapes using the HTML canvas API. But I also always considered hiccup as a more general intermediate format for encoding any hierarchical data (outside any HTML/SVG specifics) and this is also how you should understand it in this case...

The convertTree() function in the hiccup-svg package is a one-way conversion of the more "raw" version of this hiccup flavor for shapes into the form which is expected for HTML/SVG serialization. If you're using the geom package for shape creation/composition, you often don't even need to use the hiccup-svg package directly. You can just do this:

import { asSVG, svgDoc, circle } from "@thi.ng/geom";

asSvg(svgDoc({}, circle(100, { fill: [1,0,0] })))

This does the following: compute view box (optional), doc dimensions (optional), convert colors to CSS, call convertTree() and serialize everything to an SVG string...

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" viewBox="-100 -100 200 200"><circle fill="#ff0000" cx="0" cy="0" r="100"/></svg> 

This approach sadly won't work if you're going to use SVG specific additions like patterns, <defs>, gradients etc. For that the svgDoc() and convertTree() functions need to be updated to support & check for non-IShape values. I will make a note of it...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants