Experimenting a new syntax to write SVG
yuanchuan.dev
yuanchuan.dev
For example, this piece of code is very short and packs a lot of functionality:
path*3 {
fill: none;
d: @pn(M 5 10 Q 5 5 8 1,
M 5 10 Q 4 5 2 4,
M 5 10 Q 6 6 8 4);
}
circle*3 {
fill: @pn(yellow, blueviolet, deeppink);
cx, cy: @pn(8 1, 2 4, 8 4);
r: .5;
}
But unless you know it by hand. The constructs @pn(...), *3, the codes 8 1, 2 4 ... or M 5 10 Q 5 5 8 1. Are not remotely obvious or intuitive. And it is not obvious what M and Q means? And what can I as programmer write instead?I realize that some of these problems comes from SVG however they are problems none the less.
I’d agree that pn seems a little obtuse, but I guess I can see what it’s doing.
Having worked with svg a lot (we build something sort of in the cad space) I can definitely see the desire for something less terse when you’re defining svgs. We end up generating svg fragments using react, for the most part, which works fairly well.
But it is really good way to program? Or is something that is done as a compromise between being able to program by hand, and have a format for drawing programs to save their files in without them being silly big.
For comparison here's a sample of a graphics stream from a PDF:
Q
9469 1441 m
9549 1433 l
9549 1433 9549 1483 9469 1441 c
S
q
In this context Q means push/save the graphics state and q is pop, but again, it's all very similar.An alternative is to have two representations; a binary format to be used by computers and a textual format for humans (programmers) designed for readability, maintainabilty etc.
is there a point in saving one or two letters on each command? Shld w ll g bck t th gd ld dys f C nd drp rndm lttrs?
You might think you want that, what you actually want is to take an index card and write the SVG path abbreviations next to their long names, and put it by your keyboard while you work.
Won't need it for long.
const color = ["yellow", "blueviolet", "deeppink"];
const path = ["M 5 10 Q 5 5 8 1", "M 5 10 Q 4 5 2 4", "M 5 10 Q 6 6 8 4"];
const endPoint = [[8, 1], [2, 4], [8, 4]];
const BallPath = ({ n }) => (
<>
<path fill="none" d={path[n]} />
<circle fill={color[n]} cx={endPoint[n][0]} cy={endPoint[n][1]} r={0.5} />
</>
);
export default () => (
<svg viewBox="0 0 10 10" stroke="#000" strokeWidth=".1">
{[0, 1, 2].map((n) => (<BallPath n={n} />))}
</svg>
);
React yay. Combine with TypeScript for best results.https://hnhiring.com/search?q=hiccup "0 jobs found"
https://hnhiring.com/search?q=Clojure give seven hits, and I doubt HN is in the top five ways Clojurians find employment.
My whole point was trying to prove that you don't have to have something new and obscure to enable something but it's already there in something that almost everyone is using already. And then you replied that you can do it cleaner in something objectively vastly more obscure. Yes, yes you can. I'm sure you can.
const M = [
{ c:"yellow", d:"M 5 10 Q 5 5 8 1", cx:8, cy:1 },
{ c:"blueviolet", d:"M 5 10 Q 4 5 2 4", cx:2, cy:4 },
{ c:"deeppink", d:"M 5 10 Q 6 6 8 4", cx:8, cy:4 }
];
export default () => <svg viewBox="0 0 10 10" stroke="#000" strokeWidth=".1">
{M.map(({c,d,cx,cy}) => <><path fill="none" d={d} /><circle fill={c} cx={cx} cy={cy} r={0.5} /></>)}
</svg>
seems like as M.map's closure gets bigger, it's already in a good shape for lifting into one or more helpers.Because 1. in the original, they were separate as well, 2. the path is derivative of the endpoint 3. you might want to cycle the colours or they're a pure derivative calculation based of the number of points or whatnot. So combining them like that is definitely prettier and makes sense if the requirements don't change, but also seems like a premature optimisation. (also, forgot about `key`)
Oh I didn't notice that from the example. Is it worth splitting off the last two parts for that?
> you might want to cycle the colours or they're a pure derivative calculation based of the number of points or whatnot
Simply lift color out and use the second argument to map's closure to index it modulo the length of color.
If you really anticipate this, you should have %X.length everywhere, otherwise it just looks like a bug the next guy is going to hit and blame you for it. The problem (as I see it) stems from the hardcoded [0,1,2] iota -- if there's a better (domain-specific) way to eliminate it, then great, but it'll have to consider the rank of M's constituents and I just don't think the juice is worth the squeeze.
> combining them like that is definitely prettier and makes sense if the requirements don't change, but also seems like a premature optimisation
I would urge you to reconsider.
Trying to guess what the next person is going to want to do tends to create more code, not less, and changing a little code is better than changing a lot.
But a more verbose version of e.g. the arc-a? Not interested at all: those seven parameters (+2 from the implicit cursor) would not be particularly palatable even if they were all explicitly named.
I'm working on a fork of it which allows classes to be added, and turns labels into <g> elements with an id attribute.
Pretty great as-is, does more-or-less exactly what you want.
But thanks for sharing anyways, never heard of this and I should have. Even the name alone is awesome!
(speaking of SVG experience, I've been fantasizing about an SVG editor that would, in a mouseover on path numbers, preview what would change if that value would wiggle a little up and down. Does anything like this exist?)
It's by the author of SQLite, and is used for all the syntax diagrams in the SQLite documentation, and a bunch of graphs in the Fossil docs.
And as others have said, I knew the M .. Q stuff was SVG anyhow (not that I know it off the top of my head) ... generally the syntax seems nice TBH
I believe you mean affordance.
I think this depends a lot on who the syntax is tailored towards. This compact stuff reminds me of APL and aspects of Perl. There's nothing wrong with highly specialised brevity. I personally love this syntax, coming from a place of moderate SVG knowledge and CSS fluency. Looks really cool and quick for prototypes. And even the example you posted feels mostly parseable on the first few scans. Nothing insurmountable, and in tune with most other news syntaxes and abstractions in this space tbh.
I too have a pet DSL project for graphs. Think JSON, QML, etc.
A central design problem is divining a single syntax for both "groves" (nested objects decorated with key/value pairs) and tabular data.
Asked another way: How would you inline CSV snippets within JSON?
Parenthesis are used for function parameters.
Curly brackets are used for composition: parent { child { key value, key value } }
Square brackets are used for arrays: parent { array [1 2 3] }}
How to simply encode tables (matrices)? Nested arrays? Like: table [ [1 2 3] [4 5 6] ]
Is there another character or lexeme to use?
Maybe delimit rows with pipe char? Like [ 1 2 3 | 4 5 6 ]
Lua got this right, most other languages didn't.
It's an anti-feature, especially from a data modeling perspective. Treating indices as implicit is good, pretending integer indices are special, and must be separated from all other sorts of mapping, is not.
Consider that Lua can express XML without making up a fake key for children:
{tag = xml, attr = {...}, child1, child2, child3}In my hobby DSL, those inlined childX are syntactic sugar for parent{ children=[child1, child2, child3]}.
More generally, I remain uncomfortably undecided about named params (pairs) and indexed params. Esp wrt constructors.
IIRC, I spotted a (hobby?) language that permitted both indexed params (matched in order per method signature) and named params, plus default values. So given "method( int a=10, int b=20)" you could invoke as "method( 1, 2 )", out of order like "method( b: 2, a: 1)" or partially like "method( b: 2 )".
Riffing a bit, this could then allow passing a map of params to a method. Code construction wise, that looks like passing around a context object.
I'll look at Lua again. (It's been a minute.) Thanks.
It's the primitive, everything else is based on it.
It could be made longer, but then a week later you would want it to be shorter, which it already is.
Once my SVG file is full of 6.829347908723890's, who cares if it is an M or a Move in terms of being human-writable? And being a vector-based format, there is indeed a real difference between 6.83 and 6.829347908723890 so there isn't a great escape from numbers that a human will find excessively precise. Honestly even the two digits is already getting up there when you see it in a big table with a thousand other numbers.
XML is verbose enough to add significant verbosity to the SVG file even in the presence of floating point numbers rendered as strings, but these sort of cute little "M 5 10 Q 5 5 8 1," go too far the other way. SVG files don't look like that.
SVGs aren't human-writable at any sort of scale anyhow. (Here, "at scale" means in the general case. Sure, you can construct an SVG by hand if you like, but that will never be the way they are primarily generated. They will be primarily generated in tools.) If you conclude from that that using a serialization format optimized for human readability was sort of silly... I agree! But it isn't XML's verbosity making SVGs human unwritable at scale, it's the problem space itself. Nobody wants to be typing in all these numbers. Even if you reduce the cost of the non-number stuff to literally zero and stipulate literal mind reading from the rest of the spec, it would still be impractical to hand-write SVGs in the general case.
Eric Meyer was very active in the html standards community and blogged and promoted html css and firefox extensively, wrote books, etc, and was very well known in the community, perhaps the most well known.
At some point his five year old daughter was found to have a terminal illness. He wrote some wrenching blog posts about his struggle knowing she was soon to die, trying to help maintain her innocence of that fact as she continued to get worse, to enjoy the remaining days without getting overwhelmed with grief. Then she died.
In honor of Eric's loss and in recognition of his efforts to promote modern web design, they added a name to the css spec of his daughter's favorite color.
Also: am a dad, and there seems to be something wrong with my eyes.
(I’m not suggesting this was a conscious factor in any way, but at the same time it wouldn’t surprise me if in an alternate history where Becca’s favourite colour was green or blue that the entire thing wouldn’t have come to fruition.)
I wonder how often do people write SVG by hand?
If often then it's definietly a good idea to simplify SVG/XML for writing.
I once experimented with some extremely minimal formats[1] built on Jevko[2] (I am its father) particularly aimed at compacting SVG. For example:
svg width[391] height[391] viewBox[-70.5 -70.5 391 391] xmlns[http://www.w3.org/2000/svg] xmlns:xlink[http://www.w3.org/1999/xlink][
rect fill[#fff] stroke[#000] x[-70] y[-70] width[390] height[390]
g opacity[0.8][
rect x[25] y[25] width[200] height[200] fill[lime] stroke-width[4] stroke[pink]
circle cx[125] cy[125] r[75] fill[orange]
polyline points[50,150 50,200 200,200 200,100] stroke[red] stroke-width[4] fill[none]
line x1[50] y1[50] x2[200] y2[200] stroke[blue] stroke-width[4]
]
]
Compared to CSS-like syntax like in the post, the advantage of this is that it is extremely simple and generic, as it works for XML in general (especially the more minimal flavors, e.g. [3]).
There are no specialized features here but also not many parsing challenges or edge cases.Clearly, there are ways to simplify these things, but not many people seem to be really interested in that.
A data point: The New York Public Library just did their fourth SVG study group earlier this month. The description was "This group is studying Scalable Vector Graphics, images which can be composed in text editors like Notepad, as a way for Blind and low-vision people to create our own drawings or work with existing images. Feel free to join us if you're interested in drawing with code."
Perpetually blows my mind that there's no great way to describe other lovely uses of its concepts.
* I couldn't find one after hours of reading old specs, searching, cold-calling a small number of people involved with the specs, working groups, CSS parsers, etc. Doesn't mean it doesn't exist--but I gave the tree the best shake I could.
prop: a b c / d e f;
can be treated in some properties as list: prop: a b (c / d) e f;
and some others as prop: (a b c) / (d e f);
Same thing for ',' prop: a b c , d;
Can be as prop: (a b c) , (d);
as: prop: a b (c , d); // e.g. in font property definition.
In that sense CSS syntax is a Christmas tree: each member of the family (feature author) adds its own stuff as she sees it. No moderation on that historically.I admit that this section threw me a little bit for a loop. I mean, I can't assume the author doesn't know this, but this article and the repo give no indication that these are being used properly as rebindable prefixes mapping to namespace declarations. They're not just names-with-colons-in.
In fact, CSS already has syntax for namespaces[0]:
@namespace prefix url(XML-namespace-URL);
/* or */
@namespace prefix "XML-namespace-URL";
/* which is then referenced as */
prefix|element { }
…so I imagine it would have been a smaller innovation to continue to use the pipe as the delimiter. @namespace url(http://www.w3.org/2000/svg);
@namespace xlink url(http://www.w3.org/1999/xlink);
use {
xlink|href: #circle;
}
(But rebindable prefixes have turned out to be bad on the open web[1], so maybe not.)[0]: https://developer.mozilla.org/en-US/docs/Web/CSS/@namespace
[1]: https://wiki.whatwg.org/wiki/Change_Proposal_for_ISSUE-120#W...
Something like this:
component Square
variants
color bg: red
elements
shape square
style square
width: 100px
height: 100px
fill: $bgFwiw I don’t think a new language is the right level of abstraction that will gain popularity.
With a few tweaks, and adding a define & use (simple class prototyping), it's basically VRML-97. Before the X3D goons ganked it with all their XML and XSchema nonsense.
Going that way, there are so many (existing) possibilities. Want something that looks like CSS but that has more structure? Use YAML. Preferring braces over spaces to delimit blocks, but don't fancy quotes everywhere? Use Groovy -- you will even be able to write a DSL to generate SVG in an afternoon, with the entire power of the JVM. Don't want anything executable? Forbid it. Refrain from picking a syntax that are not fitting your needs and for a language whose intent is different, you will just call for confusion
As an example, I love it that json is like js, and that js is a bit like C. Even when there are inevitable "false friends" in any new language, it's so nice to have that starting advantage.
> What I love about CSS is its readability. There are no unnecessary quotes around property values even when separated by spaces. And the CSS code always expands from top to bottom if following best practices.
HAML structure is really easy to understand. It attempts to strip out as much of the annoying HTML repetition as possible. For example to write paragraph tag you could use:
%p This is HTML paragraph. HAML will convert that to:
<p>This is HTML paragraph</p>
You can also pass more attributes to the tag you want:
.banners(id="top-banner", title="Your ad here") Your text here Will be rendered like this:
<div id="top-banner" class="banners" title="Your ad here">Your text here</div>
Also supporting something as simple as </> (close current tag) would significantly reduce HTML boilerplate without inventing new language. I agree that shortcuts for id and class are nice too.
My proposal:
<div.content>
<form#login>
<input name=username>
<input name=password>
</>
</> <div content>
<form id=login>
<input name=username>
<input name=password>
</>
</>
Note though that including markup declarations for enumerating custom class names and for the input element having declared content EMPTY is required (the latter declaration actually part of HTML DTDs [1]): <!attlist div class (content) #implied>
<!element input - - empty>
Moreover, in SGML you can have tag omission/inference, like on the p element or the html element itself.Your "</>" Close current tag syntax is actually part of SGML (as is "<>" which is treated as start element tag for the most recently closed element), known as FEATURES MINIMIZE SHORTTAG ENDTAG EMPTY and FEATURES MINIMIZE SHORTTAG STARTTAG EMPTY features, resp. ([2]).
.banners(id="top-banner", title="Your ad here")
Minor correction: the comma there isn't valid Haml syntax, when you use parenthesis for attributes it expects the same format as inside a regular HTML tag (i.e. attributes separated by whitespace).But you can also rewrite that as:
#top-banner.banners(title="Your ad here")CSS as a language is supposed to apply to an existing DOM tree, not synthesize an entirely new one. The selector syntax alone is too ambiguous to actually do that, because it is a search lookup, not a tree structure (even though SASS/LESS can make it look like one). If you specify, say, three `svg path` selectors with rules in them, you aren't asking for three separate paths in an SVG node. You're asking for all paths in all SVG elements, including grandchildren and great-grandchildren, to be handed all of the rules in all matching selectors.
If we wanted to make this idea work while not redefining CSS selectors, we'd have to ban the descendent combinator (because it would create infinite node hierarchies), and use the child combinator and nth-child everywhere exclusively. So instead of `svg path` you'd have `svg > path:nth-child(1)`. And probably a `::synthesize()` selector for saying, "yes I want this CSS rule to create a DOM node".
https://www.youtube.com/watch?v=9qen5CKjUe8
For example
m78.463 298.38c-17.4 8.1-31.8 21.7-40.8 38.6l75.2 34.9z
would become m 78.463 298.38
c -17.4 8.1-31.8 21.7
-40.8 38.6l75.2 34.9
z rect {
x: 8;
y: 8;
width: 1;
height: 1;
fill: yellow;
style transform-box: fill-box;
transform-origin: center;
transform: rotate(45)
}
For me it's confusing since I read it as "all the rects have that x and y" while in this experiment it's actually defining a single instance of a rect. I'd rather prefer the styled components way (in fact I've done few experiments around it) like this: const Diamond = styled.rect`
width: 1;
height: 1;
fill: yellow;
style transform-box: fill-box;
transform-origin: center;
transform: rotate(45)
`;
<svg>
<Diamond x={8} y={8} />
</svg>It's really heartening to read this.
As someone who does a lot of troubleshooting by reading, I thank you.
I haven't done it, but this system would probably also be useful for using CSS to animate SVG
React render-to-string also helps a lot if you need to genereate an svg string
renderToString(<svg>${circles.map(circle=><circle key={circle.id} cx={circle.x} cy={circle.y} r={5} />)}</svg>)> something like this
It's so infuriating when people say this against Tailwind. Nothing is forcing you to leave giant lists of class names directly on the HTML tags, the documentation very clearly states you can use the provided @apply function to move things into CSS. Throwing giant lists of class names down to prototype then refactoring is incredibly efficient, but it's not like you just shit out your first idea to production in any other context.