How to trace one edge of an open path in SVG - 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>

Related

Reference SVG text element in path coordinates

Is it possible to programmatically reference the extents of a text bounding box while creating a path? For example, depending on the chosen font, scale, and glyphs, a specific text might be larger or smaller. I always want, for example, a path drawn exactly under the text. Or over the text (not using the text-decoration: underline/overline attributes, but by using a path). Alternatively, if I need to guess the width of the text, is it possible to at least center a path horizontally relative to a text block without knowing its exact extents?
I played around with the "50%" attributes, but those always seem to be relative to the page, not individual SVG elements.
Example:
<svg height="200" width="300">
<text x="0" y="50" style="fill: red">Blue line on top, green line on right</text>
<path d="m 0 0 h 50" style="stroke-width: 2px; stroke: blue" />
<path d="m 50 0 v 15" style="stroke-width: 2px; stroke: green" />
</svg>
How can the path of the blue line be made to be exactly on the top boundingbox, the green line to be exactly on the righthand side of the text element?
Maybe real SVG gurus can chime in and create exactly what you want.
The JavaScript/Web Component is not required, I just did not want to copy/paste SVG code
customElements.define("svg-text-border", class extends HTMLElement{
connectedCallback(){
setTimeout(()=>{ // wait till innerHTML is parsed
this.innerHTML =
`<svg viewbox="0 0 220 30">
<path d="M10 20h200" stroke="red" pathLength="100" id="R" />
<path d="M210 20v-15" stroke="green"/>
<path d="M210 5h-200" stroke="blue"/>
<text textLength="90%">
<textPath href="#R" startoffset="100" text-anchor="end">
${this.innerHTML.trim()}
</textPath>
</text>
</svg>`;
});
}
})
<style>svg{ max-height:60px }</style>
<svg-text-border>The quick brown fox jumps over the lazy dog</svg-text-border>
<svg-text-border>Hello World!</svg-text-border>
<svg-text-border>Web Components are Cool!</svg-text-border>

Positioning rotated letters inside viewBox in 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>

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.

SVG textpath text layout on complex, changing paths

I am not sure the problem I have is solvable using the current SVG standard but I thought I would ask here anyway if anyone knows an answer
I have a constantly changing svg path (vertices defined by forming a hull around nodes made in d3, force driven so the nodes constantly move and the bounding hull moves to accomodate the nodes)
Because I can't predict the vertices nor do I know what the text will be (as it depends on the grouping of the nodes in that situation, which changes) all i can do is blindly apply text on a textpath to the path. The problemis sometimes the text does not display nicely.
Problem 1: upside down text - I don't mind where on the path the text goes but its annoying that it often ends up upside down
For example (image):
[NB Problem 2 branched into SVG textpath rendering breaks words up badly on a textpath with sharp corners as suggested in answer]
Problem 2: broken up text - when a corner forms, text has a tendency to split. up. I don't think my use of dy to push the text outside the boundary helps (the path is actually tight to the nodes and I apply a 40 stroke-width to give some padding: the dy pushed the text outside that stroke)
For example (image):
Any ideas on what I can do to fix this?
--Chris
svg code for reference:
Problem 1:
<g id="hull_elements">
<path class="boundary" id="Secure" d="M219.31353652066463,309.7274362305448L199.3259715998452,277.60331505353355L54.5215284230899,92.9756148805194L29.418010605669316,64.72387260525474Z" style="fill: #b0c4de; stroke: #b0c4de; stroke-width: 40px; stroke-linejoin: round;"></path>
<path class="boundary" id="DMZ" d="M234.7675515627913,79.25604751762172L122.76947855325542,190.1418483839412L271.90702281166267,76.40758102069142Z" style="fill: #b0c4de; stroke: #b0c4de; stroke-width: 40px; stroke-linejoin: round;"></path>
</g>
<g id="hull_text">
<text dy="30"><textPath startOffset="0" text-anchor="start" method="align" spacing="auto" xlink:href="#Secure">Secure</textPath></text>
<text dy="30"><textPath startOffset="0" text-anchor="start" method="align" spacing="auto" xlink:href="#DMZ">DMZ</textPath></text>
</g>
Problem 2:
<g id="hull_elements"><path class="boundary" id="Secure" d="M30.716331539726912,88.02778447495649L66.8405337274694,100.01086904278971L251.78816229874747,53.214214251587265L277.8704519199028,25.642491075146587Z" style="fill: #b0c4de; stroke: #b0c4de; stroke-width: 40px; stroke-linejoin: round;"></path>
<path class="boundary" id="DMZ" d="M177.8575710153683,149.56053657599713L251.04637461899244,245.55658992744486L277.76418020025847,271.7261370009561L159.53295211932644,118.0340968521715Z" style="fill: #b0c4de; stroke: #b0c4de; stroke-width: 40px; stroke-linejoin: round;"></path>
</g>
<g id="hull_text">
<text dy="30"><textPath startOffset="0" text-anchor="start" method="align" spacing="auto" xlink:href="#Secure">Secure</textPath></text>
<text dy="30"><textPath startOffset="0" text-anchor="start" method="align" spacing="auto" xlink:href="#DMZ">DMZ</textPath></text>
</g>
jsfiddle to play with that shows this (move the nodes to see the issues)
http://jsfiddle.net/zuzzy/GC2C2/
[edited to add the NB of the branch of problem 2 - zuzzy]
For problem 1 I think you need to detect when the x co-ordinates are moving to the left and draw the path back to front in that case.
If you have
M 0,0 L 100, 0
that's OK 100 > 0 so leave it as it is. But
M 100, 0 L 0,0
has 0 < 100 so that would need reversing. In this case reversing would give us the path in the first case.
Here's a complete example.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path id="MyPath"
d="M 300 200
L 100 200" />
<path id="MyPathReversed"
d="M 100 200
L 300 200" />
</defs>
<desc>Example toap01 - simple text on a path</desc>
<g transform="translate(0, 100)">
<text font-family="Verdana" font-size="42.5" fill="blue" >
<textPath xlink:href="#MyPath">
upside down
</textPath>
</text>
</g>
<text font-family="Verdana" font-size="42.5" fill="blue" >
<textPath xlink:href="#MyPathReversed">
right way up
</textPath>
</text>
</svg>
BTW I suggest you ask problem 2 as a separate question.

Scale a svg text to fit an exact width or the parent container width [duplicate]

This is likely a very simple question, but how do I get text in SVG to stretch to fit its container?
I don't care if it looks ugly from being stretched too long or high, but it needs to fits its container and be as big as possible.
Thanks
If you really don't care that the text gets ugly, here's how to fit unknown length text into a known width.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<g>
<text y="50%" textLength="436" lengthAdjust="spacingAndGlyphs">UGLY TEXT</text>
</g>
</svg>
Here is what I have come up with... Its similar to what other people have posted, but I think it resizes and scales nicely. This code will add spacing to any text roughly between 10-25 characters to make it fill the entire width of its parent. If you need longer or shorter text, just adjust the viewBox width and textLength attributes.
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox='0 0 300 24'>
<text textLength='290' lengthAdjust="spacing" x='5' y="14" >
Some Unknown Text that is resizing
</text>
</svg>
Maybe this could work for you. You'd have to tweak the values, but it does resize when the parent div resizes. Here's my dabblet example. It works similarly to fittext.js
I used the ‘viewBox’ & ‘preserveAspectRatio’ attributes.
<svg><text x="50%" y="50%" dy=".3em">Look, I’m centered!</text></svg>
<svg viewBox="-50 -50 100 100" preserveAspectRatio="xMidYMid meet"><text font-size="16" dy=".3em" >I’m also resizeable!</text></svg>
other resources I looked at:
Making Sense of SVG viewBox's Madness
How to Style Scalable Vector Graphics Using CSS
You can use the textPath tag in conjunction with the text tag. If you then set the lengthAdjust attribute of the textPath tag to "spacingAndGlyphs" (you may additionally use the method attribute and set it to "stretch" to adjust the rendering method).
Example:
<div style="width: 100%">
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 200 100"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<g>
<path id="svg-text" d="M 10 50 H 180" fill="transparent" stroke="lightgray" />
<text>
<textPath
xlink:href="#svg-text"
method="stretch"
lengthAdjust="spacingAndGlyphs"
>Beautifully resized!</textPath>
</text>
</g>
</svg>
<div>

Resources