Inline SVG pattern repeating horizontally and scaling vertically? - svg

I'm looking for a way to use an inline SVG pattern that scales vertically and only repeats horizontally. Does anyone know if this is possible and if so how?
I know I can makes this just using a SVG as a background-image, but I want to be able to use this SVG in a javascript/component-based workflow so inline is the best fit for that.
Here is link with my work-in-progress: https://codepen.io/devotee/pen/GRJJpKL
And some code:
<div class="divider">
<svg width="100%" height="40px">
<defs>
<pattern id="pattern" x="0" y="0" width="60" height="6" patternUnits="userSpaceOnUse">
<path fill="none" stroke="#F5A861" d="M60 5C45 5 45 1 30 1S15 5 0 5"/>
</pattern>
</defs>
<rect x="10" y="6" width="100%" height="12" fill="url(#pattern)" />
</svg>
As you can see in the link, this repeats in both directions, so setting a bigger height value does not accomplish what I want. I would like this pattern to always fill the containers height (or simply be set to a value with CSS) but repeat horizontally.
Here are some images to illustrate what I mean:
Top is wanted behaviour, bottom is unwanted behaviour:
The background scales vertically and does not repeat. It takes as much space vertically as it can (fills parent height or whatever height value it has specified)
Top is wanted behaviour, bottom is unwanted behaviour:
It does not stretch the SVG horizontally but merely repeats it.
Any ideas or input on how to achieve this?

Related

Styling SVG elements with stroke and globally-positioned pattern

Goal
I am trying to replicate this effect algorithmically:
This is for a project I am working on where I need to make stylised visualisations of different kinds of pizza. The image shows a Margherita (tomato base, mozzarella and basil on top).
The rules I need to adhere to:
each ‘layer’ consists of uniformly positioned objects with a stroke and filled with a pattern (that shares coordinates)
the layers need to cover lower layers
Means
To achieve the uniform distribution, I use Poisson-Disc sampling.
I chose SVG because I need the result to be visible in the browser and generate this server-side.
For efficiency and simplicity – as the viewing size will be smaller – I decided to reference one object with <use> elements and vary it only with rotations, as opposed to the sample.
Attempts
Every approach I tried reached a dead end:
Creating a <clipPath> filled with <use> elements for clipping the background.
This does not allow me to add a stroke around the clipped area, which I need. A workaround would be to use a feMorphology filter, but that seems like it’s going to be needlessly costly on the client. A second workaround seemed to be:
Grouping the elements and using that group twice: once in a <clipPath> for clipping the pattern background, once directly on the canvas with an added stroke.
This does not work as <g> elements are unsupported in web browsers due to completely arbitrary reasons (it does work in Inkscape, however, which I used for the proof-of-concept). A workaround would be to use two copies of all the <use> elements, but that would essentially double the file size.
Grouping the elements and applying a fill with SVG patterns.
This does not work as since we create the distribution using <use> elements, the pattern looks identical in every instance. Moreover, I cannot rotate the objects, as the pattern would get rotated too. A workaround would be not to use <use>, but that would create the same problem as in point 2.
Those approaches won't work because patterns are affected by any transforms applied to the same shape.
In the solution below, we create a whole layer of an ingredient (id="basil-layer"). Then use that layer to first draw the ingredient outlines (strokes). Then afterwards we use a mask, created from that same layer, to draw hatching on top of the outlines.
You'll need to duplicate this process for each of the ingredients.
More documentation of what's happening inside the code.
<svg width="600" height="400">
<defs>
<pattern id="diagonalHatch" patternUnits="userSpaceOnUse" viewBox="0 0 4 4" width="16" height="16">
<rect fill="black" width="4" height="4"/>
<path d="M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2"
style="stroke:green; stroke-width:0.5" />
</pattern>
<!-- Definition for a leaf of basil -->
<ellipse id="basil" cx="0" cy="0" rx="60" ry="30"/>
<!-- A layer of N pieces of basil -->
<g id="basil-layer">
<use xlink:href="#basil" transform="translate(300,200)"/>
<use xlink:href="#basil" transform="translate(400,150) rotate(45)"/>
<use xlink:href="#basil" transform="translate(450,200) rotate(110)"/>
</g>
<!-- A mask that consists of all the pieces of basil -->
<!-- The fill is white to keep the *insides* of the basil shape.
And we stroke with black so that this mask doesn't hide any of the
green stroke outline of the leaf, when use this mask to lay down
the hatch pattern on top of the drawn basil leaves. -->
<mask id="basil-layer-mask">
<use xlink:href="#basil-layer" fill="white" stroke="black" stroke-width="2"/>
</mask>
</defs>
<!-- Fill SVG with a black background -->
<rect width="100%" height="100%" fill="black"/>
<!-- Draw all the basil pieces with a black fill and a green outline -->
<use xlink:href="#basil-layer" fill="black" stroke="green" stroke-width="2"/>
<!-- Finally draw the basil layer hatching.
This is a whole-SVG sized rectangle of hatching masked by the basil layer mask -->
<rect width="100%" height="100%" fill="url(#diagonalHatch)" mask="url(#basil-layer-mask)"/>
</svg>

Is there a way to draw shapes so that their fills act as opaque within the group but are transparent towards the background

I'm trying to find tools to render a group of overlapping shapes so that within the group the fill acts as opaque (so the overlapped circle outlines are not shown), but at the same time the fills of group members are transparent to any background layers underneath.
I hope this picture will make it clearer:
What I'm able to accomplish is a bunch of circles with empty fills (d in the image). What I would like to achieve (h in the image) is circle outlines being hidden in the overlaps so that the layering in the outlined circle group is clear, but the group as a whole having a transparent fill so that the background layer (blue-filled bubbles in the picture) is visible.
The perimeter of the circles in the group would be determined by data and possibly dynamic so I guess they need to be identifiable as separate objects, so I cannot turn them into some kind of compound path to get what I need.
From what I found, this cannot be done in SVG or CSS alone, so I wonder if there is some knock-out filter or any kind of solution within Pixi.js or general WebGL that would allow me to set a different transparency behaviour within the layer and different towards other layers.
Something like this perhaps.
You can add more circles as desired. The mask circles overlap each other but as a whole when applied to the rectangle the masked parts are transparent.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%" height="100%" viewBox="0 0 200 200" >
<defs>
<mask id="circle-mask" width="1" height="1" maskContentUnits="objectBoundingBox" maskUnits="objectBoundingBox">
<circle cx="0.2" cy="0.2" r="0.1" stroke="white" stroke-width="0.01"/>
<circle cx="0.3" cy="0.3" r="0.1" stroke="white" stroke-width="0.01"/>
</mask>
</defs>
<circle cx="30" cy="60" r="20" fill="blue" fill-opacity="0.5"/>
<rect width="200" height="200" mask= "url(#circle-mask)" fill="blue"/>
</svg>

Zero stroke-width in SVG

Having used Postscript for years, I am now learning SVG. There is a feature of PS that I have not been able to replicate so far: zero-width lines. In PS, a line with zero width is always visible: PostScript converts zero line width to the smallest printable width. On the screen, when zooming they never get any thinkness, yet are visible no matter the scale. I have used them when I wanted to render very thin lines, without worring about the final resolution I was going to use, and they turned out really useful.
However, in the official SVG docs (https://www.w3.org/TR/svg-strokes/) it says that:
A zero value causes no stroke to be painted. A negative value is invalid.
Is there a way in SVG to build zero-width lines in the sense of PostScript?
As Robert said, the nearest thing to what you want in SVG is vector-effect="non-scaling-stroke". This fixes the stroke width at 1 no matter how the SVG is scaled.
This works on Chrome and Firefox (and probably Opera - haven't checked), but AFAIK not IE/Edge.
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80"
fill="none" stroke="black" stroke-width="1"
vector-effect="non-scaling-stroke"/>
</svg>
Note that antialiasing will come into play depending on the position of the lines. The position will be affected by the scale.
If your lines are rectilinear (horizontal or vertical), you might also want to use shape-rendering="crispEdges". This will turn off antialiasing for the shape on which it is used, resulting in sharp one-pixel lines.
<svg viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80"
fill="none" stroke="black" stroke-width="1"
vector-effect="non-scaling-stroke" shape-rendering="crispEdges"/>
</svg>

Scaling when using a defined symbol

I want to create a SVG graphic that loads an external SVG file, stores it as a symbol into its defs section and uses this symbol in the main section:
<svg width="1000" height="1000">
<defs>
<symbol id="sym1" width="50" height="50" viewBox="0 0 45 45">
<svg width="45" height="45">
<rect x="0" y="0" width="45" height="45" style="stroke:red;stroke-width:1;fill:none"/>
</svg>
</symbol>
</defs>
<rect x="100" y="100" width="50" height="50" style="stroke:black;stroke-width:1;fill:none"/>
<use x="100" y="100" xlink:href="#sym1"></use>
</svg>
http://jsfiddle.net/tx0rpxdf/4/
One frame condition is that I cannot change the inner SVG element, because it will be loaded from an external file.
The symbol should have a size of 50x50, so I define its width and height accordingly. I know that the inner SVG element has a size of 45x45, so I define its viewBox as "0 0 45 45".
To test the usage of the symbol, I create a black rectangle of size 50x50 and place a symbol instance at the same position. The lines should match.
But they don't match using Chrome (Chromium) on Debian Jessie. Even worse, it produces different results in Firefox and Opera.
What's the problem here and how can I produce the correct result in all browsers that support SVG?
There are a couple of issues:
a <rect> with width/height 45 and a stroke-width of 1 is not 45 user units wide, it's 46 user units wide as the stroke pokes out inside by 0.5 of a unit and outside by 0.5 of a unit on each edge. If you can't change the inner <svg> element then you won't be able to fix this issue though.
drawing two shapes one on top of another will not make one hide another as there will be antialiasing in effect. shape-rendering="optimizeSpeed" can prevent this.

How can I make text automatically scale down in an SVG?

I've got an SVG element that I've created with Inkscape. I then take that SVG and put in as part of an XSL-FO stylesheet that is used to transform XML data and then passed through the IBEX renderer to create a pdf (which is usually then printed). As an example, I have elements in the svg/stylesheet that look like this (extra noise due to the Inkscape export):
<text x="114" x="278.36218" id="id1" xml:space="preserve" style="big-long-style-string-from-inkscape">
<tspan x="114" y="278.36218" id="id2" style="style-string">
<xsl:value-of select="Alias1"/>
</tspan>
</text>
My problem lies in the fact that I don't know how big this text area is going to be. For this particular one, I've got an image to the right of the text in the SVG. However, if this string is the maximum allowed number of W's, it's way too long and goes over the image. What I'd like (in a perfect world) is a way to tell it how many pixels wide I want the text block to be and then have it automatically make the text smaller until it fits in that area. If I can't do that, truncating the text would also work. As a last ditch resort, I'm going to use a fixed width font and do the string truncation myself in the XML generation, although that creates something both less usable and less pretty.
I've poked around the SVG documentation and looked into flowRegions a bit as well as paths, but neither seem to be be quite what I want (maybe they are though). If it helps, here's that style string that Inkscape generates:
"font-size:20px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
Thanks in advance for the help.
You have text of arbitrary line length (in terms of characters) and you want to scale it to fit inside a fixed amount of space? The only way I can think of to rescale text to a fixed size is to place it inside an svg element and then scale the SVG to that size:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Resizing Text</title>
<defs>
<svg id="text-1" viewBox="0 0 350 20">
<text id="text-2" x="0" y="0" fill="#000" alignment-baseline="before-edge">It's the end of the world as we know it, and I feel fine!</text>
</svg>
</defs>
<rect x="500" y="100" width="200" height="40" fill="#eee" />
<use x="510" y="110" width="180" height="20" xlink:href="#text-1" />
</svg>
However, as seen above, the viewBox on the encapsulating svg element needs to be set to the width of the text, which you presumably don't know.
If you're referencing this SVG inside a user agent with scripting available (e.g. a web browser) then you could easily write a script to capture the width of the text or tspan element and set the viewBox accordingly.

Resources