SVG 텍스트에서 자동 줄 바꿈
에 을 .<text> SVG에 ?<rect>가 HTML을 <div>요소들. 방법이 있습니까?저는다를 .<tspan>s.
텍스트 래핑은 현재 구현된 사양인 SVG1.1의 일부가 아닙니다.
에서 SVG , SVG 에 HTML 를 을 내장할 수 .<foreignObject/>요소. 예:
<svg ...>
<switch>
<foreignObject x="20" y="90" width="150" height="200">
<p xmlns="http://www.w3.org/1999/xhtml">Text goes here</p>
</foreignObject>
<text x="20" y="20">Your SVG viewer cannot display html.</text>
</switch>
</svg>
HTML 지원이 없는 순수 SVG 렌더러를 대상으로 하거나 전문 벡터 그래픽 조작 소프트웨어(Adobe Illustrator, Inkscape, ...)를 사용하여 그래픽을 편집할 수 있도록 하려면 이 솔루션이 적합하지 않을 수 있습니다.
대안은 다음과 같습니다.
<svg ...>
<switch>
<g requiredFeatures="http://www.w3.org/Graphics/SVG/feature/1.2/#TextFlow">
<textArea width="200" height="auto">
Text goes here
</textArea>
</g>
<foreignObject width="200" height="200"
requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<p xmlns="http://www.w3.org/1999/xhtml">Text goes here</p>
</foreignObject>
<text x="20" y="20">No automatic linewrapping.</text>
</switch>
</svg>
foreignObject가 해당 featurestring을 지원하는 것으로 보고될 수 있지만 SVG 1.1 규격에서 HTML을 표시할 수 있다는 보장은 없습니다.현재 html-in-foreign 개체 지원을 위한 피쳐 문자열이 없습니다.그러나 아직도 많은 브라우저에서 지원되므로 향후에는 이에 상응하는 기능 문자열이 필요할 가능성이 높습니다.
SVG Tiny 1.2의 'textArea' 요소는 모든 표준 svg 기능(예: 고급 채우기 등)을 지원하며, 너비 또는 높이 중 하나를 자동으로 지정할 수 있으므로 텍스트가 해당 방향으로 자유롭게 흐를 수 있습니다.외부 개체는 클리핑 뷰포트 역할을 합니다.
참고: 위의 예는 유효한 SVG 1.1 콘텐츠이지만, SVG 2에서 'required Features' 특성이 제거되었습니다. 즉, 'switch' 요소는 SVG 1.2 'textArea' 요소 지원 여부에 관계없이 첫 번째 'g' 요소를 렌더링하려고 시도합니다.SVG2 스위치 요소 사양 참조.
경우에 따라서는 텍스트 경로가 좋을 수도 있습니다.
<svg width="200" height="200"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<!-- define lines for text lies on -->
<path id="path1" d="M10,30 H190 M10,60 H190 M10,90 H190 M10,120 H190"></path>
</defs>
<use xlink:href="#path1" x="0" y="35" stroke="blue" stroke-width="1" />
<text transform="translate(0,35)" fill="red" font-size="20">
<textPath xlink:href="#path1">This is a long long long text ......</textPath>
</text>
</svg>
@Mike Gledhill의 코드를 기반으로, 저는 한 단계 더 나아가 더 많은 파라미터를 추가했습니다.SVG RECT가 있고 텍스트 안에 래핑할 텍스트를 원하는 경우 유용합니다.
function wraptorect(textnode, boxObject, padding, linePadding) {
var x_pos = parseInt(boxObject.getAttribute('x')),
y_pos = parseInt(boxObject.getAttribute('y')),
boxwidth = parseInt(boxObject.getAttribute('width')),
fz = parseInt(window.getComputedStyle(textnode)['font-size']); // We use this to calculate dy for each TSPAN.
var line_height = fz + linePadding;
// Clone the original text node to store and display the final wrapping text.
var wrapping = textnode.cloneNode(false); // False means any TSPANs in the textnode will be discarded
wrapping.setAttributeNS(null, 'x', x_pos + padding);
wrapping.setAttributeNS(null, 'y', y_pos + padding);
// Make a copy of this node and hide it to progressively draw, measure and calculate line breaks.
var testing = wrapping.cloneNode(false);
testing.setAttributeNS(null, 'visibility', 'hidden'); // Comment this out to debug
var testingTSPAN = document.createElementNS(null, 'tspan');
var testingTEXTNODE = document.createTextNode(textnode.textContent);
testingTSPAN.appendChild(testingTEXTNODE);
testing.appendChild(testingTSPAN);
var tester = document.getElementsByTagName('svg')[0].appendChild(testing);
var words = textnode.textContent.split(" ");
var line = line2 = "";
var linecounter = 0;
var testwidth;
for (var n = 0; n < words.length; n++) {
line2 = line + words[n] + " ";
testing.textContent = line2;
testwidth = testing.getBBox().width;
if ((testwidth + 2*padding) > boxwidth) {
testingTSPAN = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
testingTSPAN.setAttributeNS(null, 'x', x_pos + padding);
testingTSPAN.setAttributeNS(null, 'dy', line_height);
testingTEXTNODE = document.createTextNode(line);
testingTSPAN.appendChild(testingTEXTNODE);
wrapping.appendChild(testingTSPAN);
line = words[n] + " ";
linecounter++;
}
else {
line = line2;
}
}
var testingTSPAN = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
testingTSPAN.setAttributeNS(null, 'x', x_pos + padding);
testingTSPAN.setAttributeNS(null, 'dy', line_height);
var testingTEXTNODE = document.createTextNode(line);
testingTSPAN.appendChild(testingTEXTNODE);
wrapping.appendChild(testingTSPAN);
testing.parentNode.removeChild(testing);
textnode.parentNode.replaceChild(wrapping,textnode);
return linecounter;
}
document.getElementById('original').onmouseover = function () {
var container = document.getElementById('destination');
var numberoflines = wraptorect(this,container,20,1);
console.log(numberoflines); // In case you need it
};
다음 코드는 정상적으로 작동합니다.코드 스니펫을 실행합니다.
아마 SVG의 모든 텍스트 태그에서 자동으로 작동하거나 정리할 수 있을 것입니다.
function svg_textMultiline() {
var x = 0;
var y = 20;
var width = 360;
var lineHeight = 10;
/* get the text */
var element = document.getElementById('test');
var text = element.innerHTML;
/* split the words into array */
var words = text.split(' ');
var line = '';
/* Make a tspan for testing */
element.innerHTML = '<tspan id="PROCESSING">busy</tspan >';
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var testElem = document.getElementById('PROCESSING');
/* Add line in testElement */
testElem.innerHTML = testLine;
/* Messure textElement */
var metrics = testElem.getBoundingClientRect();
testWidth = metrics.width;
if (testWidth > width && n > 0) {
element.innerHTML += '<tspan x="0" dy="' + y + '">' + line + '</tspan>';
line = words[n] + ' ';
} else {
line = testLine;
}
}
element.innerHTML += '<tspan x="0" dy="' + y + '">' + line + '</tspan>';
document.getElementById("PROCESSING").remove();
}
svg_textMultiline();
body {
font-family: arial;
font-size: 20px;
}
svg {
background: #dfdfdf;
border:1px solid #aaa;
}
svg text {
fill: blue;
stroke: red;
stroke-width: 0.3;
stroke-linejoin: round;
stroke-linecap: round;
}
<svg height="300" width="500" xmlns="http://www.w3.org/2000/svg" version="1.1">
<text id="test" y="0">GIETEN - Het college van Aa en Hunze is in de fout gegaan met het weigeren van een zorgproject in het failliete hotel Braams in Gieten. Dat stelt de PvdA-fractie in een brief aan het college. De partij wil opheldering over de kwestie en heeft schriftelijke
vragen ingediend. Verkeerde route De PvdA vindt dat de gemeenteraad eerst gepolst had moeten worden, voordat het college het plan afwees. "Volgens ons is de verkeerde route gekozen", zegt PvdA-raadslid Henk Santes.</text>
</svg>
이 기능은 자바스크립트를 이용하여 추가할 수도 있습니다.Carto.net 의 예는 다음과 같습니다.
http://old.carto.net/papers/svg/textFlow/
편집 가능한 텍스트 영역을 사용하는 것도 유용할 수 있습니다.
http://old.carto.net/papers/svg/gui/textbox/
SVG "text" 요소에 가짜 단어 래핑을 추가하기 위해 다음과 같은 작업 과정을 게시했습니다.
문자열을 더 짧은 "tspan" 요소로 분할하는 간단한 자바스크립트 함수만 추가하면 됩니다.다음은 다음과 같은 보기의 예입니다.

도움이 되길 바랍니다!
저는 모든 답변을 시도했습니다. 그 중 어떤 것도 저와 함께 작동하지 않습니다. 저는 noob 솔루션만 만들었지만 알 수 없는 코드 라인 없이 해결할 것입니다. 내용 없이 텍스트 태그를 추가하고 텍스트 길이를 최대 첫 번째 텍스트 길이 > 나머지를 다른 텍스트 태그에 추가하는 경우 텍스트 길이를 확인합니다.텍스트 내용을 진술하고 변경하면 단순 자바스크립트만 있으면 됩니다.
if (data['clinic']['cicovidcliniccity'].length > 35 && data['clinic']['cicovidcliniccity'].length < 75) {
const cname = data['clinic']['cicovidcliniccity'];
const ctext2_shodow = document.querySelector("#c_text2_shdow");
ctext2.textContent = cname.substring(1, 35)
ctext2_shodow.textContent = cname.substring(35, cname.length);
}
if (data['clinic']['cicovidcliniccity'].length > 75 && data['clinic']['cicovidcliniccity'].length < 110) {
const cname1 = data['clinic']['cicovidcliniccity'];
const ctext2_shodow = document.querySelector("#c_text2_shdow");
const ctext3_shodow = document.querySelector("#c_text3_shdow");
ctext2.textContent = cname1.substring(1, 35)
ctext2_shodow.textContent = cname1.substring(35, 75);
ctext3_shodow.textContent = cname1.substring(75, cname1.length);
}
동적 JS를 사용한 또 다른 예
const myTextContent = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's s";
const lineLength = 20;
const maxLineHeight = 15;
const mySVG = document.querySelector("svg");
let loopMax = Math.round(myTextContent.length/lineLength);
const txts = [];
for (let i=1; i<=loopMax; i++){
txts.push(myTextContent.substring((lineLength*i)-lineLength,lineLength*i));
}
txts.forEach( (txt,i)=>{
const newTxt = document.createElementNS("http://www.w3.org/2000/svg", "text");
newTxt.setAttribute("x", "0");
newTxt.setAttribute("y", `${maxLineHeight * (i+1)}`);
newTxt.setAttribute("fill", "red");
newTxt.textContent = txt;
mySVG.appendChild(newTxt);
});
<svg height="90" width="200">
</svg>
에릭 달스트롬 등은 다음과 같이 지적했습니다.
<foreignObject>현재 SVG에서 다중 줄 텍스트를 달성하는 가장 쉬운 방법일 것입니다.
안타깝게도 잉크스케이프, 어도비 일러스트레이터 또는 미리보기 앱과 같은 대부분의 데스크톱 그래픽 편집기는 콘텐츠를 렌더링하거나 변환할 수 없습니다.
대안적 해결책: 자바스크립트를 통해 foreignObject를 네이티브 텍스트로 변환
HTML 텍스트 요소를 네이티브 svg로 재구축하는 투박한 도우미 기능을 생각해 냈습니다.<text>.
면책 사항: 다음 기능은 대부분의 블록 및 인라인 텍스트 요소를 지원하지만 목록이나 테이블은 지원하지 않습니다.
function foreignObjectToNativeSvgtext(foreignObject) {
let body = foreignObject.children[0];
let children = [...body.children];
// children - will become svg <text> elements
children.forEach((child, i) => {
// clone text el
let type = child.nodeName.toLowerCase();
let clone;
clone = document.createElement(type);
body.insertBefore(clone, body.children[i]);
let textNodes = textNodesInEl(child);
let textL = textNodes.length;
for (let n = 0; n < textL; n++) {
let textNode = textNodes[n];
// get computed styles for parent
let style = window.getComputedStyle(textNode.parentNode);
copyStyleProps(child, clone)
// split to words
let words = textNode.textContent.split(' ');
words.forEach((word, w) => {
let newtextNode = document.createTextNode(word);
let span = document.createElement('span');
span.classList.add('span2tspan', 'span2tspan' + type);
// get computed styles for child
copyStyleProps(textNode.parentNode, span)
span.textContent = word + ' ';
clone.appendChild(span);
})
}
// convert to svg tspan
let nativeText = htmlTextEl2Svg(clone);
//insert before foreignObject
foreignObject.parentNode.insertBefore(nativeText, foreignObject);
// delete original foreign object elements
child.remove();
})
//preserve whitespace
let svg = foreignObject.closest('svg');
svg.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve')
foreignObject.remove();
output.value = new XMLSerializer().serializeToString(svg)
}
function htmlTextEl2Svg(el) {
const ns = "http://www.w3.org/2000/svg";
let parentSVG = el.closest('svg');
let newText = document.createElementNS(ns, 'text');
let parentProps = copyStyleProps(el, newText);
let bb = el.getBoundingClientRect();
let {
x,
y
} = bb;
let point = parentSVG.createSVGPoint();
point.x = x;
point.y = y;
let ctm = parentSVG.getScreenCTM().inverse();
point = point.matrixTransform(ctm);
// round
[x, y] = [x, y].map(val => {
return +(val).toFixed(3)
})
newText.setAttribute('x', x);
newText.setAttribute('y', y);
// children
let children = el.querySelectorAll('.span2tspan');
/**
* add x and y only for vertical shifts
* (new line breaks)
*/
let xPrev = 0;
let yPrev = 0;
let prevStyle = '';
children.forEach(child => {
let bb = child.getBoundingClientRect();
let {
x,
y,
width,
height
} = bb;
let tspan = document.createElementNS(ns, 'tspan');
let style = window.getComputedStyle(child);
let currentProps = copyStyleProps(child, tspan);
// convert coordinates to svg
let point = parentSVG.createSVGPoint();
point.x = x;
point.y = y;
let ctm = parentSVG.getScreenCTM().inverse();
point = point.matrixTransform(ctm);
x = point.x;
// add fontsize to baseline shift
y = point.y + (parseFloat(style.fontSize) * 1);
// round
[x, y] = [x, y].map(val => {
return +(val).toFixed(3)
});
if (x !== xPrev && y !== yPrev) {
tspan.setAttribute('x', x)
}
if (y !== yPrev) {
tspan.setAttribute('y', y)
}
// text color to fill
if (currentProps.color !== 'rgb(0, 0, 0)') {
tspan.style.fill = currentProps.color;
}
// remove superfluous inherited props
tspan.style.removeProperty('margin');
tspan.style.removeProperty('padding');
if (parentProps.fontFamily == currentProps.fontFamily) {
tspan.style.removeProperty('font-family')
}
if (parentProps.fontSize == currentProps.fontSize) {
tspan.style.removeProperty('font-size')
}
if (parentProps.fontWeight == currentProps.fontWeight) {
tspan.style.removeProperty('font-weight')
}
if (parentProps.lineHeight == currentProps.lineHeight) {
tspan.style.removeProperty('line-height')
}
// copy content
tspan.textContent = child.textContent;
// stringify current style
let currentStyle = JSON.stringify(currentProps);
// add to svg
if (child.textContent.trim()) {
let prevTspans = newText.querySelectorAll('tspan');
let prev = prevTspans[prevTspans.length - 1];
if (prev && !tspan.getAttribute('x') &&
!tspan.getAttribute('y') &&
currentStyle == prevStyle
) {
prev.textContent += tspan.textContent;
} else {
newText.appendChild(tspan)
prevStyle = currentStyle;
}
}
xPrev = x;
yPrev = y;
})
return newText;
}
/**
* helper copy computed styles
*/
function copyStyleProps(el, target, styleProps = []) {
let defaultvaluesToExclude = {
'color': 'rgb(0, 0, 0)',
'fontStyle': 'normal',
'letterSpacing': 'normal',
'verticalAlign': 'baseline',
}
let currentProps = {};
// defaults
if (!styleProps.length) {
styleProps = [
'fontFamily',
'fontSize',
'fontStyle',
'fontWeight',
'lineHeight',
'letterSpacing',
'verticalAlign',
'margin',
'padding',
'color'
];
}
let style = window.getComputedStyle(el);
for (prop in style) {
let val = style[prop];
if (styleProps.includes(prop) && val !== defaultvaluesToExclude[prop]) {
target.style[prop] = val;
currentProps[prop] = val;
}
}
return currentProps;
}
/**
* Get text nodes in element
* based on:
* https://stackoverflow.com/questions/10730309/find-all-text-nodes-in-html-page#10730777
*/
function textNodesInEl(el) {
let textNodes = [];
for (el = el.firstChild; el; el = el.nextSibling) {
if (el.nodeType == 3) {
textNodes.push(el);
} else {
textNodes = textNodes.concat(textNodesInEl(el));
}
}
// filter empty text nodes
textNodes = textNodes.filter((node) => node.textContent.trim());
return textNodes;
}
textarea {
width: 100%;
min-height: 30em;
white-space: pre;
font-family: monospace;
font-size: 0.75em;
}
<p><button onclick="foreignObjectToNativeSvgtext(foreignObject)">Convert foreignObject</button></p>
<h3>SVG with foreignObject – text is editable</h3>
<svg id="svg" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
.foreignBody {
font-family: Georgia, serif;
font-size: 1em;
line-height: 1.5em;
}
h1 {
font-family: sans-serif;
font-size: 2em;
line-height: 1.2em;
margin: 0 0 1rem 0;
}
.author {
line-height: 1.2em;
font-style: italic;
margin-bottom: 0em;
}
p {
margin: 0 0 1rem 0;
}
sup {
line-height: 0px;
font-size: 0.5em;
}
ul li:marker{
content:'•';
color:red;
}
</style>
<foreignObject id="foreignObject" x="5" y="5" width="90%" height="90%">
<div class="foreignBody" xmlns="http://www.w3.org/1999/xhtml" contenteditable>
<p class="author">Franz Kafka</p>
<h1>The Metamorphosis</h1>
<p>One morning, when <strong>Gregor Samsa</strong> woke from troubled dreams, he found himself
transformed in
his bed into <em style="color:red; letter-spacing:0.1em">a horrible</em> vermin.<sup>1</sup></em>
</p>
<p>He lay on his armour-like back, and if he lifted his head a little he could see his brown belly,
slightly
domed and divided by arches <strong><em> into stiff sections.</em></strong> The bedding was hardly
able to
cover it and seemed ready to slide off any moment.</p>
</div>
</foreignObject>
</svg>
<h3>Output</h3>
<textarea id="output"></textarea>
작동 원리
다음과 같은 모든 블록 요소를 순환합니다.
<p>,<h1>텍스트 내용을 별도의 텍스트 노드로 분할
각 텍스트 노드에 대한 스타일 정보를 검색합니다.
getComputedStyle()을 통해 자리를 얻다
getBoundingClientRect()HTML 좌표를 svg 사용자 단위로 변환:
let point = parentSVG.createSVGPoint(); point.x = x; point.y = y; let ctm = parentSVG.getScreenCTM().inverse(); point = point.matrixTransform(ctm);텍스트 노드를 다음으로 바꿉니다.
<text>그리고.<span>요소들
향후 SVG2 개념(아직 지원되지 않음!)
일관된 렌더링 공백 구분 렌더링.Firefox가 라인 랩을 렌더링하는 경우
whitespace:pre에 적용된다<text>– 반면에 최적화/최소화된 svgs에서는 작동하지 않습니다(과다한 화이트 스페이스를 제거한 경우).
text{
white-space: pre;
word-break: break-word;
}
svg{
width:20em;
border: 1px solid #ccc;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" xml:space="preserve">
<text x="0" y="20" font-size="10">The Metamorphosis
One morning, when
Gregor Samsa woke
from troubled dreams,
he found himself transformed
in his bed into a
horrible vermin.1
</text>
</svg>
2023 Edition for Web (모든 주요 브라우저에서 테스트됨):
<switch>
<foreignObject requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<p id="ModernText">My really long text</p>
</foreignObject>
<text id="FallbackText">Fallback Description</text>
</switch>
언급URL : https://stackoverflow.com/questions/4991171/auto-line-wrapping-in-svg-text
'programing' 카테고리의 다른 글
| Excel 명명된 범위를 팬더 데이터 프레임으로 읽기 (0) | 2023.09.21 |
|---|---|
| colors.xml 파일에서 새 색상 항목을 만들 수 없습니다. (0) | 2023.09.21 |
| 요청한 리소스에 Access-Control-Allow-Origin 헤더가 없습니다. (0) | 2023.09.21 |
| Angular UI Bootstrap에서 modal과 non-modal 형태에 동일한 컨트롤러를 사용하는 방법? (0) | 2023.09.21 |
| XML에서 &을(를) 탈출하려면 어떻게 해야 합니까? (0) | 2023.09.21 |
