Fwd: marker-pattern has a weird grammar

[Note: I wrote this reply last week, but accidentally only sent it to Tab
Atkins, not the SVG list, and didn't notice until he replied today.  I no
longer 100% agree with everything below, but we'll post the conversation in
the order it happened for posterity! --ABR]


Why not simplify it further:

<marker-pattern> = [<marker-gap> <marker-ref> ]+  |   [ <marker-ref>
<marker-gap>]+  |  none

<marker-gap> = <length> | <percentage>
<marker-ref> = none | <url> | child | <child-selector>

In other words, you must alternate length measures and marker references in
equal number but you can start with either a marker or a gap.  If you start
with a gap, the initial marker will be offset by that point; repeats will
be the same whether you put the gap at the start or end of the sequence.
An odd number of gaps confuses the repeat pattern.

If an author wants to place multiple markers at the same point, just use a
gap of zero.

I don't really see a use case for `none` within a pattern, since you could
instead use calc() to combine the two gaps into one.  However, I don't see
harm in it.  It could potentially be useful in animating between two
sequences.  Implementers will have to factor in a "none" behaviour anyways,
in case a marker reference is invalid.  And it allows re-use of the
<marker-ref> grammar defined for other properties.
_______________________

After simplifying that much, I couldn't help complicating it up again...

Looking through the previous discussion on this topic from 2012 [1], there
seemed to be agreement that multiple independent marker patterns could be
given as a comma-separated list, so you could create a psuedo-random
pattern by having different repeat lengths for each pattern.  Is there
still interest in this?  It would be useful in creating imperfect repeating
brush mark effects.

There was also discussion of defining patterns per path segment or for the
path as a whole.  I assume this was part of a move to do the same for
stroke-dasharray, although I can't find the equivalent dasharray settings
in the current draft specs.  It would also be useful to add the
stretch/compress options from `stroke-dashadjust`.

*Side note: As currently defined, would stroke-dashadjust *always* apply
per segment, not per path?  That could result in weird effects on closed
shapes (where you want an even number of dashes) that have many smooth
curve segments of differing lengths.  Maybe that's where a [segment|path]
additional keyword option could go.*

There would be no logical equivalent to the dashes or gaps options from
stroke-dashadjust, since markers themselves would never stretch or
compress.  However, there would be a benefit to having a option to include
an extra gap at the start/end of the path or segment, so as to avoid
overlapping the pattern on start/end or vertex markers.  For want of a
better word I'm using "space" to means "add space on either end".

The final grammar therefore would be

<marker-pattern-list> = <marker-pattern># | none

<marker-pattern> =  [ [segment|path]? || [stretch|compress]? || space?  ]
<marker-pattern-sequence>

<marker-pattern-sequence> =  [<marker-gap> <marker-ref> ]+  |   [
<marker-ref> <marker-gap>]+

<marker-gap> and <marker-ref> as above.

"path" would be the default for the first keyword; if stretch/compress
isn't specified, the lengths would be calculated precisely and the space
keyword would have no effect.  I haven't included a "none" keyword for the
stretch/compress option, since that would cause confusion with "none" as
the first value of a marker-pattern-sequence.

None of this would alter the current definition of the shorthand marker
property, since the slash would still identify the start of a pattern
setting.

Examples:

A) Alternate two markers evenly along the path, offset from the start
(Example 15 in the current draft):

marker-pattern: 40 url(#m1) 40 url(#m2);


B) Position a marker at every vertex and evenly spaced between, no closer
than 20px from each other:

marker-pattern: segment stretch url(#m1) 20px;

marker-end: url(#m1);
/* The pattern is a marker followed by a gap, which will be restarted on
every segment, so there will always be a marker at the beginning of a
segment.  The pattern will be stretched to evenly cover the length of each
segment so that other markers won't get too close to the next vertex.
However, since the pattern ends in a gap, the marker-end property is set to
mark the end of the final segment.
 */


C) Create an erratic pattern of the same 10px-wide brush mark repeating
itself, overlapping along the path; ensure that at least some marks always
fall exactly on the vertices:

marker-pattern: segment compress 5px url(#brush),

                       url(#brush) 7px,

                       9px url(#brush);
/* The last two patterns would be evenly repeated along the entire path
since the keywords apply per pattern.  The 7px pattern would include the
very start of the path, the 9px pattern would not.  Neither would be forced
to include the end of the path, but they might if the distance matched
exactly.  The 5px pattern would not include the start of each segment, but
would be rounded off to include the end of every segment since "space" is
not specified.

 */


D) Alternate a major marker with two instances of a minor marker.  The
space between consecutive minor markers is slightly smaller than the space
on either side of the major marker.  Adjust the entire pattern to cover the
complete path an even number of times, but leave a space on either end.

marker-pattern: stretch space path 15 url(#minor) 20 url(#major) 20
url(#minor);

/* The total pattern length is 15+20+20 = 55 units; however, we also need
to repeat the initial gap at the end of the path, so the pattern will be
scaled such that scale*(55n + 15) exactly equals the path length, where n
is Math.floor( (pathLength - 15)/55 ).

 */

Note that the "space" requirement would generate the exact same result if
the pattern was defined in marker>gap order instead of gap>marker order; in
this case, the extra space would be added at the beginning of the path:

marker-pattern: stretch space path url(#minor) 20 url(#major) 20
url(#minor) 15;


E) Draw a path-specific child marker (e.g., a highway number) overtop of a
generic marker (e.g., the highway number sign), making sure to have at
least one sign on every path segment but avoiding markers on vertices:

marker-pattern: segment compress space 40 url(#highway-marker) 0 child;

/* The total pattern length is 40 units; the repeated space is also 40; the
compress requirement is calculated per segment such that scale*(40n + 40)
equals the segment length, where n is Math.ceil( (segmentLength - 40)/40
).  Each pattern starts with an (approx.) 40 unit gap, then draws the first
marker, then a zero-length "gap" and draw the second marker on top, leaving
the final gap without a marker.

 */


Note that this assumes that for a single marker pattern, markers would be
painted in the order they are given (highway sign before the child with the
highway number).  For a list of separate patterns, it would be best to use
the top to bottom painting order used in CSS background images and the new
multiple-fill syntax (i.e., the last pattern in the list would be painted
first, at the bottom of the stack).  In other words, the following pattern
would look the same as the above, assuming that each marker is much smaller
than the 40 unit gaps:

marker-pattern: segment compress space 40 child,
                       segment compress space 40 url(#highway-marker);


_________________________

One final suggestion:
A separate property, markerPatternUnits, equivalent to markerUnits, that
would control whether lengths are proportional to strokeWidth (and
therefore potentially proportional to the size of the markers) instead of
being defined in user space.

An equivalent property for stroke-dasharray could ensure that markers and
dashes are synchronized.  It would also allow dotted line patterns where
the dots are always square (or any chosen aspect ratio) regardless of the
stroke width.  Most importantly, it *could* be defined such as to make it
easy to implement the popular "drawing the shape" stroke-dasharray
animation.

Currently (SVG1.1), percentage values aren't clearly defined for
markerUnits="strokeWidth"; implementations tested (Firefox and Chrome) seem
to ignore the markerUnits setting and just use the userSpace definition of
100%.

The SVG2 draft suggests that the definition of 100% for
markerUnits="strokeWidth" should be inherited from the userSpace, but
scaled proportional to strokeWidth.  I'm not sure if this was an
intentional change from current implementations.

It would be very convenient if, instead, when markerUnits,
markerPatternUnits, or strokeDasharrayUnits were set to strokeWidth,
percentages would be relative to path length.  If that is a backwards
compatibility issue for markerUnits, it could only apply to the patterns
which are measured along path length anyway.

If you added the extra rule that for "segment" marker or dash patterns,
100% is the length of the segment, then you could define vertex markers,
segment markers, corner dashes and so on just in terms of patterns.

E.g., the stroke-dashcorner effect (Figure 9 in the draft specs) would be

strokeDasharrayUnits: strokeWidth;

stroke-width: 2px;

stroke-dashadjust: segment;
  /* assuming a segment/path option added to the dashadjust syntax */

stroke-dasharray: 8 calc(100%-16) 8 0;
  /* 8 unit dash at the start and end of every segment, where
1unit=strokeWidth=2px */


The marker-segment example (Example 14/Figure 16) with circles on the
vertices and x-marks at mid-points would be:

markerPatternUnits: strokeWidth;

marker-pattern: segment url(#Circle) 50% url(#Cross) 50%;


The above proposal assumes that the xxxUnits attributes would be upgraded
from regular XML attributes to CSS properties/presentation attributes.
Which would be useful in many situations for many of these units (e.g., in
filters it would be very nice to be able to specify primitiveUnits on
individual filter elements).  For markers, it would allow you the option to
specify them on the path instead of on the marker, and have the style
inherit.  The camelCase vs hyphenated syntax is unfortunate, though.  Not
sure if it would be worth defining duplicate XML attribute names to make
the CSS more consistent.


[1]: http://lists.w3.org/Archives/Public/www-svg/2012Nov/0023.html (and
subsequent)


On 5 November 2014 16:32, Tab Atkins Jr. <jackalmage@gmail.com> wrote:

> On Wed, Nov 5, 2014 at 3:29 PM, Tab Atkins Jr. <jackalmage@gmail.com>
> wrote:
> > Here's a grammar that expresses what we want better:
> >
> > <marker-gap>? <marker-ref-group> [ <marker-gap> <marker-ref-group> ]*
> > <marker-gap>?
> > <marker-gap> = <length> | <percentage>
> > <marker-ref-group> = none | <marker-ref>+
>
> Line-wrapping made this harder to read - the first two lines are the
> marker-pattern grammar.
>
> ~TJ
>
>

Received on Wednesday, 12 November 2014 00:18:27 UTC