Positioning rotated letters inside viewBox in svg - svg

I have code which generates svg (by means of producing the XML DOM). It takes input text and randomly scatters it's letters on page as shown below.
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<!-- ... styles omitted -->
  <text x="20" y="150" rotate="45">M</text>
<text x="100" y="80" rotate="45">W</text>
<text x="90" y="50" rotate="270">X</text>
<!-- etc ... -->
</svg>
I have problem how to fill the page efficiently without having letters extend outside of the view box. Either I limit the random values for the x and y, but then there is lot of space left empty around borders. Or I use wider ranges for x,y but then the letters bleed outside. As show the red letters in the jsfiddle example (https://jsfiddle.net/5zqrugx1/1/).
I tried to adjust the x, y ranges based on rotation, but still it does not help much because of different letter shapes.
I am looking for way to style/position these letters in svg in a way which would force them to be completely inside the view port while being able to fill the space border-to-border (this second condition added later to clarify). Something like giving 0-100% where 0% would mean "touching left border" and 100% would be "touching right border". Is there any way to do it?
Below is example which I hand-edited to achieve more-less desired result.

What this probably amounts to is controling the center of rotation in such a way that it is in the center of the glyph. This way, all you need is a 0.5em padding at each edge.
You can start out with positioning the text control point at the middle both horizontally and vertically:
text {
text-anchor:middle;
dominant-baseline:middle;
}
Unfortunately, using the rotate attribute of the text element does not work as expected (at least in Firefox). But you can get around that by adding a post-rotation via a transform attribute. The best way to formulate it would be to also position the glyph with a translation:
<text transform="translate(40 100) rotate(60)">A</text>
Order is important - translate must come before rotate.
The following example rotates all glyphs around the center of the circles they are sitting in. It turns out the font-defined middle is a bit off, so you have to tweak with a dy attribute. If it shows still a bit wrong on your screen, this is because the font used by your system might define or compute a different middle line. For a system-independent experience, you would need to use a web font for you to have complete control.
circle {
fill: none;
stroke: blue;
}
text {
text-anchor: middle;
dominant-baseline: middle;
font-family: sans-serif;
font-size: 50px;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 100">
<circle r="25" cx="50" cy="50" />
<circle r="25" cx="100" cy="50" />
<circle r="25" cx="150" cy="50" />
<circle r="25" cx="200" cy="50" />
<circle r="25" cx="250" cy="50" />
<circle r="25" cx="300" cy="50" />
<text dy="4" transform="translate(50 50) rotate(60)">A</text>
<text dy="4" transform="translate(100 50) rotate(120)">B</text>
<text dy="4" transform="translate(150 50) rotate(180)">C</text>
<text dy="4" transform="translate(200 50) rotate(240)">D</text>
<text dy="4" transform="translate(250 50) rotate(300)">E</text>
<text dy="4" transform="translate(300 50) rotate(360)">F</text>
</svg>

Related

SVG | Erase part of another path

I'm working on an SVG image and I can't figure out how to erase a certain part of a path.
This is the current situation: https://gyazo.com/db59fcaf9f122e7e2c0bba5833db9ec5
There are two green letters which overlap and a red bar which does basically represent the area I want to erase so the letters don't stick directly on each other. It works fine when I have a set background colour since I can then easily overwrite lower paths, but with transparent background, this method no longer works, since it appears to make the path transparent, not the entire pixel itself.
TL;DR: How do I make a path actually render the pixel transparent, not just the path element?
You can mask the J with a white rect and a black N with an extra stroke. Next you use again the N. Please play with the stroke width of the mask <use>
svg{border:1px solid; width:90vh}
text{font-family:arial;dominant-baseline:middle}
<svg viewBox="0 0 24 24">
<defs>
<text id="n" x="7" y="14" >N</text>
<mask id="mascara">
<rect width="24" height="24" fill="white" />
<use xlink:href="#n" fill="black" stroke="black" />
</mask>
</defs>
<text x="5" y="10" style="mask: url(#mascara)">J</text>
<use xlink:href="#n" fill="black" />
</svg>

How to trace one edge of an open path in SVG

I am trying to make a web page that allows the user to draw lines in an SVG image. The drawing part is fine, but each line needs to carry a label that fills the width of the line (the lines are 15px wide).
I have tried to use a <textpath> referencing the line they drew, but the baseline of the label ends up running down the middle of the line. Here is a screenshot to show what I mean.
I have tried various ways to nudge the text over slightly using CSS and properties, but the only success I have had is to use a transform, which will often result in the text 'spilling out' if the direction of the line takes a sudden turn.
The other solution I have tried is to generate a second path that runs down one edge of the user-drawn path and using that for the <textpath>, but I'm struggling to find a way to translate the user-drawn path points into points that correspond to the rendered edge of the line.
Does anybody know a way to make either one of these methods work?
I understand that the lines need to carry a label that fills the width of the line (the lines are 15px wide).
In order to move the text I use dy="4"
text{fill:white;stroke:none;font-family:consolas;}
path{stroke-width:15px;fill:none;}
<svg viewBox="50 150 350 150">
<defs>
<path id="path" d="M70,180Q100,330 195,225Q290,120 380,250"></path>
</defs>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#path" stroke="#000000"></use>
<text stroke="#000000" font-size="12" dy="4">
<textPath id="tp" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#path" startOffset="30%">
just some words I wrote
</textPath>
</text>
</svg>
An other option is using dominant-baseline="middle"
text{fill:white;stroke:none;font-family:consolas;}
path{stroke-width:15px;fill:none;}
<svg viewBox="50 150 350 150">
<defs>
<path id="path" d="M70,180Q100,330 195,225Q290,120 380,250" ></path>
</defs>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#path" stroke="#000000"></use>
<text stroke="#000000" font-size="12" dominant-baseline="middle">
<textPath id="tp" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#path" startOffset="30%">
just some words I wrote
</textPath>
</text>
</svg>
I hope this is what you were asking.
You can use the dy attribute to move glyphs in a string - either individually or together - in a vertical direction relative to their orientation.
The spec chapter on <tspan> elements has a lot of practical examples on how to use the various positioning attributes (dx, dy, rotate); I'd recomend to read it.
path {
fill:none;
stroke: red;
stroke-width: 15px;
}
text {
font-family: sans-serif;
font-size: 20px;
}
<svg>
<path id="p1" d="M 25,60 60,30 H 80 V 120" />
<text dy="-7.5px">
<textPath href="#p1">abcdefghijklmn</textPath>
</text>
</svg>

SVG or HTML text that scales to fully fit a container so it stretches, bot vertically and horizontally, disregarding aspect ratio

I need to make text automatically stretch in both dimensions, to fill a container. It will distort.
This shows the the container space in red
This shows what a long name would normally resize to put in that space and maintaining aspect ratio
.
This shows what my client wants to happen
.
I would prefer to use SVG but I will work with what works.
I have searched for a solution to the best of my abilities but all seem to either refer to maintaining aspect ratio or stretching text when the page or viewbox changes dimensions.
That's quite a broad question, but yes you can do it with svg, I'll let you implement it though since you didn't provided anything to chew on.
The key point is to set your svg's preserveAspectRatio to "none":
svg{
height: 100vh;
width: 50vw;
}
body{
margin:0;
}
<div>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 40" preserveAspectRatio="none">
<text x="0" y="35" font-family="Verdana" font-size="35">
Hello, out there
</text>
</svg>
</div>
If your text is already part of an SVG (as it appears in your example), you will probably need to use a nested <svg> element.
<svg width="400" height="400">
<rect width="400" height="400" fill="rebeccapurple"/>
<!-- rect representing area that our text has to squeeze into -->
<rect x="20" y="50" width="200" height="50" fill="white"/>
<!-- x y width height match above rect -->
<!-- viewBox values need to match text bounds -->
<svg x="20" y="50" width="200" height="50"
viewBox="0 8 244 28" preserveAspectRatio="none">
<text x="0" y="35" font-family="Verdana" font-size="35">
HELLO THERE
</text>
</svg>
</svg>
The hardest part is workoing out the correct values for viewBox. It needs to match the bounds of the (normal unsqueezed) text.

Multiple preserveAspectRatio in svg?

I'm looking to have an svg fill a particular space in my layout, so preserveAspectRatio="none" seems like a good first approach.
However, within the svg, there is a mask that I do not want to stretch / warp. Rather, it should occupy 100% the width, with the height scaling according to its ratio. The two images illustrate the mask's behaviour when the parent svg is in either landscape or portrait. (Note: the grey in the image is the rest of the <svg>, which should stretch to fit)
Can the mask have its own aspectRatio settings? Is there a better way to achieve this? Or, is it even possible?
```
<!-- this should scale according to its bounding parent -->
<svg class="fix" viewbox="0 0 2880 1620" preserveAspectRatio="none..?">
<!-- this should scale according to some intrinsic ratio -->
<mask id="mask"
maskUnits="userSpaceOnUse"
maskContentUnits="userSpaceOnUse">
<rect fill="white" x="0" y="0" width="2880" height="1620" />
<path fill="black" d="M57.59,60h409c344.17,.... ...."/>
</mask>
<rect mask="url(#mask)" x="0" y="0" width="100%" height="100%" />
</svg>
```
edit: using mask-image instead of just mask seems possible (as it has additional positioning options), but this does not seem to work with svg elements.
You don't need to use preserveAspectRatio="none" to have the rectangle and mask fill the page. Just extend the <rect> and <mask> past the boundaries of the SVG in all directions. Root <svg> elements have overflow: visible by default, so the extended rect will fill SVGs parent container - as long as you extend far enough of course.
<rect mask="url(#mask)" x="-1000%" y="-1000%" width="3000%" height="3000%" />
I've used 1000% here, but you can adjust that if you need more or less (than 10x).
And note that we are just using the standard default SVG preserveAspectRatio. So we still get the automatic centerinng and scaling of the SVG.
body {
margin: 0;
padding: 0;
}
svg {
width: 100vw;
height: 100vh;
}
<svg viewbox="0 0 2880 1620">
<!-- this should scale according to some intrinsic ratio -->
<mask id="mask"
maskUnits="userSpaceOnUse"
maskContentUnits="userSpaceOnUse"
x="-1000%" y="-1000%" width="3000%" height="3000%">
<rect fill="white" x="-1000%" y="-1000%" width="3000%" height="3000%" />
<circle cx="1440" cy="810" r="400" fill="black"/>
</mask>
<rect mask="url(#mask)" x="-1000%" y="-1000%" width="3000%" height="3000%" />
</svg>
Demo in fiddle format

Why does adding a positive viewbox min-x, min-y value have a negative translate effect?

I was just going through THIS fiddle and the code looks like below:
<svg width=200 height=200 viewbox="0 0 225 225" >
<path d="M220, 220
A200, 200, 0, 0, 0, 20, 20
L 20, 220
Z"
fill = "lightskyblue">
</path>
</svg>
Now when i play around with the viewbox and change the value to viewbox="100 100 225 225" it has the effect of doing something like:
transform:translate(-100px, -100px);
Well i believe when i specify 100 as the min-x, min-y the values of viewbox the effect should have been something like
transform:translate(100px, 100px);
But instead the effect is something similar to:
transform:translate(-100px, -100px);
Why so ? can somebody explain ?
By setting minX and minY to 100, what you are doing is telling the SVG renderer that the top left of your SVG starts at (100,100). And that point should be at the top left of the SVG viewport.
It is the same as if you decided your ruler started at the 10cm mark. The 12cm mark would appear to be at 2cm instead of 12cm. In other words 10cm further left (lower).
Have a look at the following sample SVG. I've marked out an area which we will make set the viewport and viewBox to in a later example.
<svg width="600" height="600">
<!-- mark the area that will become the viewport -->
<rect x="100" y="100" width="300" height="200" fill="linen"/>
<!-- add some other content -->
<circle cx="120" cy="120" r="20" fill="red"/>
<circle cx="200" cy="200" r="50" fill="red"/>
<circle cx="380" cy="270" r="50" fill="red" fill-opacity="0.3"/>
</svg>
If we now set the viewBox to the cream coloured area and set the viewport (SVG width and height) correspondingly, you will see what happens.
<svg width="300" height="200" viewBox="100 100 300 200">
<!-- mark the area that will become the viewport -->
<rect x="100" y="100" width="300" height="200" fill="linen"/>
<!-- add some other content -->
<circle cx="120" cy="120" r="20" fill="red"/>
<circle cx="200" cy="200" r="50" fill="red"/>
<circle cx="380" cy="270" r="50" fill="red" fill-opacity="0.3"/>
</svg>
You can see that the small red circle which is roughly at 100,100, is now at the top left of the viewport.
Hope this makes it clearer for you.
Imagine you have a sheet of paper with your drawing on it and you overlay a piece of cellulite (or anything transparent) on top.
Draw a box on the cellulite and colour in everything outside the box.
Move the cellulite to the right.
Your drawing (the part you can still see within the cellulite box) appears to have moved to the left.
the viewBox is the cellulite box in this example.

Resources