PerfectVector
लेखक: Irene Kim

SVG टेक्स्ट रैपिंग: tspan से मापी हुई पंक्तियाँ बनाएँ

वास्तविक फ़ॉन्ट से शब्द मापकर SVG लेबल की पंक्तियाँ बनाएँ और tspans दें। संकरे लेबल जाँचें, अधिक चौड़े शब्द या पंक्तियाँ अस्वीकार करें और परिणाम सहेजें।

10 मिनट का पठन
इस पेज पर

बदलते SVG लेबल को स्पष्ट पंक्तियों में बाँटने के लिए getComputedTextLength() से संभावित शब्द समूह मापें, फिर हर स्वीकृत पंक्ति का एक <tspan> लिखें। पहले दस्तावेज़ के फ़ॉन्ट की प्रतीक्षा करें और तय करें कि अकेला शब्द लेबल से चौड़ा हो या परिणाम पंक्ति सीमा से बढ़े तो क्या हो।

नीचे का उदाहरण खाली जगह से अलग शब्द लेता है और दोनों तरह का अतिप्रवाह बिना अधूरा लेबल दिए अस्वीकार करता है। हमारा मूल खोखला हीरा चित्रण शब्दों से अलग रखता है, इसलिए लेबल चौड़ाई बदलने से कलाकृति दोबारा नहीं बनती।

पंक्तियाँ रखने से पहले रैपिंग नीति चुनें

tspan एलिमेंट SVG टेक्स्ट के हिस्सों की जगह तय करने देता है। हमारी रूटीन हर पंक्ति पर वही x और बाद की पंक्तियों पर dy ऑफ़सेट रखती है। पंक्ति कहाँ टूटेगी यह रूटीन तय करती है; एलिमेंट परिणाम रखता है।

यह एक स्पष्ट लेआउट तरीका है। SVG 2 टेक्स्ट विनिर्देश content area और CSS text layout से automatic wrapping भी परिभाषित करता है। यह लेख उन सुविधाओं को नहीं जाँचता और न कहता है कि हर ब्राउज़र में वे अनुपस्थित हैं।

चित्रण के बाहर HTML पैराग्राफ के लिए CSS से तैरती SVG बाहरी आकृति के आसपास टेक्स्ट रखें। वह लेआउट चित्र रूपरेखा का अनुसरण करता है और शब्द साधारण HTML रहते हैं, स्थित SVG लेबल पंक्तियाँ नहीं बनते।

इस छोटे अंग्रेज़ी-लेबल उदाहरण की नीति है:

  • खाली जगह समेटें और मूल क्रम में एक स्पेस से अलग शब्द आज़माएँ।
  • संभावित पंक्ति तभी रखें जब मापी लंबाई उपलब्ध चौड़ाई में हो।
  • अकेले न समाने वाला शब्द अस्वीकार करें; नया hyphenation बिंदु न गढ़ें।
  • maxLines से अधिक चाहिए तो पूरा परिणाम अस्वीकार करें; आख़िरी शब्द चुपचाप न छोड़ें।

पंक्ति सीमा लेआउट निर्णय है। उसे शुरुआती baseline, पंक्ति दूरी और उपलब्ध ऊँचाई के साथ चुनें। रैपर आपके लिए लंबवत content box गणना नहीं करता।

लेबल चौड़ाई बदलें, कलाकृति रखें
खोखले हीरे के चित्रण के साथ चौड़े लेबल में लंबी और संकरे लेबल में छोटी पंक्तियों की वैचारिक तुलना
चित्रण: संकरे लेबल को अधिक पंक्तियाँ चाहिए हो सकती हैं। अमूर्त पट्टियाँ और बिंदीदार फ़्रेम वैचारिक संकेत हैं; वास्तविक शब्द, माप और अस्वीकृति अवस्थाएँ नीचे स्वतंत्र ब्राउज़र उदाहरण से हैं।

उसी फ़ॉन्ट से मापें जो लेबल बनाएगा

getComputedTextLength() SVG टेक्स्ट की computed लंबाई मापता है। उदाहरण लेबल के text एलिमेंट का clone उसी जुड़े SVG में छिपी probe की तरह रखता है, फिर पंक्तियाँ तय होने पर हटाता है। दोनों 22 इकाई पर स्पष्ट Arial, sans-serif लेते हैं।

मापने से पहले कोड document.fonts.ready की प्रतीक्षा करता है। वह दस्तावेज़ फ़ॉन्ट लोडिंग और संबंधित लेआउट तक रुकता है; निर्यात SVG में फ़ॉन्ट एम्बेड नहीं करता। अलग उपलब्ध फ़ॉन्ट से लंबाइयाँ बदल सकती हैं। शब्द, फ़ॉन्ट सेटिंग या उपलब्ध चौड़ाई बदलें तो लेआउट फिर चलाएँ।

पूरा निर्भरता-मुक्त उदाहरण चलाएँ

इसे starter.html सहेजकर ब्राउज़र में खोलें। छह मामले बनाता है, माप छापता है और हर परिणामी SVG serialize करता है। पहले दो केवल लेबल चौड़ाई बदलते हैं, तीसरा शब्द बदलता है। बाकी अस्वीकृति और literal text जाँचते हैं।

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Measured SVG label lines</title>
<style>
  body { margin: 24px; font: 16px system-ui; background: #f6f2e8; }
  #cases { display: grid; grid-template-columns: repeat(2, 360px); gap: 20px; }
  figure { margin: 0; }
  svg { display: block; background: white; }
  figcaption { margin: 6px 0; }
  pre { white-space: pre-wrap; font-size: 12px; }
</style>
<h1>Keep artwork and label layout separate</h1>
<div id="cases"></div>
<pre id="report"></pre>
<script>
  const ns = 'http://www.w3.org/2000/svg';
  const emblem = 'M60 32 L100 72 L60 112 L20 72 Z M60 54 L78 72 L60 90 L42 72 Z';
  function element(name, attributes) {
    const node = document.createElementNS(ns, name);
    for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, value);
    return node;
  }
  function wrapWords(text, source, { width, maxLines, lineHeight }) {
    text.replaceChildren();
    if (!(width > 0 && lineHeight > 0 && Number.isInteger(maxLines) && maxLines > 0)) {
      return { status: 'invalid-options', lines: [] };
    }
    const words = source.trim().split(/\s+/u).filter(Boolean);
    const probe = text.cloneNode(false);
    probe.setAttribute('visibility', 'hidden');
    text.parentNode.append(probe);
    const measure = value => { probe.textContent = value; return probe.getComputedTextLength(); };
    const lines = [];
    let line = '';
    try {
      for (const word of words) {
        const wordWidth = measure(word);
        if (wordWidth > width) {
          return { status: 'overlong-word', word, measuredWidth: wordWidth, lines: [] };
        }
        const candidate = line ? line + ' ' + word : word;
        if (line && measure(candidate) > width) {
          lines.push(line);
          line = word;
        } else line = candidate;
      }
      if (line) lines.push(line);
      if (lines.length > maxLines) {
        return { status: 'too-many-lines', requiredLines: lines.length, lines: [] };
      }
      const x = text.getAttribute('x');
      lines.forEach((value, index) => {
        const span = element('tspan', { x, dy: index ? lineHeight : 0 });
        span.textContent = value;
        text.append(span);
      });
      return { status: 'ok', lines,
        lengths: [...text.children].map(span => span.getComputedTextLength()) };
    } finally { probe.remove(); }
  }
  const cases = [
    { id: 'normal', source: 'Keep artwork and labels separate', width: 200, maxLines: 4 },
    { id: 'narrow', source: 'Keep artwork and labels separate', width: 110, maxLines: 5 },
    { id: 'changed', source: 'Keep the original artwork beside every exported label', width: 200, maxLines: 4 },
    { id: 'long-word', source: 'Artwork supercalifragilisticexpialidocious', width: 110, maxLines: 5 },
    { id: 'line-limit', source: 'Keep artwork and labels separate', width: 110, maxLines: 2 },
    { id: 'literal', source: 'Cove <g> & field', width: 200, maxLines: 4 },
  ];
  const entries = cases.map(test => {
    const figure = document.createElement('figure');
    const svg = element('svg', { width: 360, height: 240, viewBox: '0 0 360 240' });
    svg.append(element('path', { d: emblem, fill: '#176b5b', 'fill-rule': 'evenodd' }));
    svg.append(element('rect', { x: 140, y: 34, width: test.width, height: 170,
      fill: 'none', stroke: '#ce7b55', 'stroke-dasharray': '4 4' }));
    const text = element('text', { x: 140, y: 60, 'font-family': 'Arial, sans-serif',
      'font-size': 22, fill: '#172b34' });
    svg.append(text);
    const caption = document.createElement('figcaption');
    figure.append(svg, caption);
    document.getElementById('cases').append(figure);
    return { test, svg, text, caption };
  });
  async function run() {
    await document.fonts.ready;
    const results = entries.map(({ test, svg, text, caption }) => {
      const result = wrapWords(text, test.source, { ...test, lineHeight: 30 });
      caption.textContent = test.id + ': ' + result.status;
      return { ...test, ...result, fontFamily: getComputedStyle(text).fontFamily,
        fontSize: getComputedStyle(text).fontSize, tspanCount: text.children.length,
        pathD: svg.querySelector('path').getAttribute('d'),
        pathCount: svg.querySelectorAll('path').length,
        imageCount: svg.querySelectorAll('image').length,
        foreignObjectCount: svg.querySelectorAll('foreignObject').length,
        serialized: new XMLSerializer().serializeToString(svg) };
    });
    document.getElementById('report').textContent = JSON.stringify({
      fontStatus: document.fonts.status, policy: 'Whitespace words; reject a long word or too many lines without partial output', results,
    }, null, 2);
  }
  run();
</script>
</html>

रूटीन शब्दों को markup डालने के बजाय textContent से हर पंक्ति बनाती है। Literal मामले में Cove <g> & field साधारण टेक्स्ट रहता है: serialized SVG में एस्केप किए angle brackets और ampersand हैं, अतिरिक्त group एलिमेंट नहीं।

माप वाला clone जुड़ा लेकिन छिपा है और दिखते लेबल जैसे फ़ॉन्ट गुण रखता है। अस्वीकृति समेत उसे finally में हटाया जाता है। शब्द चौड़ाई और अधिकतम पंक्ति जाँच पास होने के बाद ही पंक्तियाँ जुड़ती हैं। पहले पिछला लेबल साफ़ होता है, इसलिए अस्वीकृत अपडेट पुराना या अधूरा टेक्स्ट नहीं छोड़ता; ऐप इंटरफ़ेस में लौटाई स्थिति सँभालें।

छह मूल मामलों ने क्या दिया

हमने ठीक यही उदाहरण Chrome में चलाया जहाँ दस्तावेज़ फ़ॉन्ट ने loaded बताया। नीचे की माप उस परिवेश की SVG टेक्स्ट लंबाई हैं, पेंट glyph सीमा या फ़ॉन्ट पोर्टेबिलिटी की गारंटी नहीं।

मामलाचौड़ाई / अधिकतम पंक्तियाँपरिणामदेखी सबसे लंबी पंक्ति
मूल वाक्यांश200 / 4दो पंक्तियाँ: Keep artwork and; labels separate172.44
वही वाक्यांश, संकरा बॉक्स110 / 5चार पंक्तियाँ: Keep; artwork; and labels; separate100.30
लंबे शब्द200 / 4तीन पंक्तियाँ198.13
बहुत लंबा शब्द110 / 5overlong-word; शून्य tspansशब्द की माप 311.82
संकरा वाक्यांश, दो-पंक्ति सीमा110 / 2too-many-lines; चार चाहिए; शून्य tspansलेबल नहीं दिया
Literal angle brackets और ampersand200 / 4एक literal पंक्ति162.66

मूल वाक्यांश Keep artwork and labels separate है। लंबा संस्करण Keep the original artwork beside every exported label है। हर स्वीकृत पंक्ति चुनी चौड़ाई में मापी गई। अस्वीकृति मामलों में चित्रण दिखता रहा, लेबल नहीं, जिससे विफलता जाँचना आसान था।

छहों serialized फ़ाइलों में वही मूल हीरा पाथ, एक native text एलिमेंट और कोई image या foreignObject नहीं था। स्वीकृत मामलों में क्रमशः दो, चार, तीन और एक tspans थे; दोनों अस्वीकृत में शून्य। हमने छहों SVG उसी ब्राउज़र में चित्र की तरह फिर खोले, सभी 360×240 पर लोड हुए। यह इन फ़ाइलों और ब्राउज़र की जाँच है, हर एडिटर या रेंडरर की नहीं।

लेआउट किया SVG सहेजें, फिर डिलीवरी जाँचें

XMLSerializer.serializeToString() रिपोर्ट में serialized markup बनाता है। सफल मामले की serialized स्ट्रिंग .svg फ़ाइल सहेजें। उसमें स्पष्ट पंक्तियाँ पहले से हैं; उन्हें फिर बनाने के लिए JavaScript रूटीन नहीं चाहिए।

शब्द अभी लाइव टेक्स्ट हैं। फ़ाइल font-family अनुरोध रखती है, फ़ॉन्ट फ़ाइल या outline glyph नहीं। दूसरे फ़ॉन्ट वाले गंतव्य में अक्षर बदल सकते हैं, भले पंक्ति स्ट्रिंग और जगह वही हों। मूल शब्द और लेआउट इनपुट निर्यात प्रति के साथ रखें और देने से पहले वास्तविक गंतव्य जाँचें। निश्चित अक्षर आकृति चाहिए तो उचित स्रोत एडिटर में outline डिलीवरी प्रति बनाएँ और मास्टर में लाइव टेक्स्ट रखें।

foreignObject टेक्स्ट गाइड दूसरे रेंडरर में गायब HTML लेबल बताती है। यह उदाहरण शुरू से native text बनाता है। textLength गाइड लंबाई बदलकर एक पंक्ति फ़िट करना बताती है; वह मापी पंक्तियाँ जोड़ने से अलग निर्णय है। पूरे लेबल को आसपास की कलाकृति के सापेक्ष रखना हो तो लंबवत संरेखण गाइड लें।

रैपर की सीमाएँ स्पष्ट रखें

खाली जगह से बाँटना जानबूझकर छोटी नीति है। वह बार-बार स्पेस, टैब और स्पष्ट newline समेटती है और भाषा-विशिष्ट लाइन ब्रेक, hyphenation, bidirectional पैराग्राफ या मिश्रित शैली वाले rich spans नहीं सँभालती। ऐसे फ़ीचर चाहिए तो दूसरा लेआउट तंत्र चुनें या नीति बढ़ाकर जाँचें।

चौड़ाई जाँच सजावटी glyph overhang को खींचे फ़्रेम में रहने या पाँच baselines को चुनी ऊँचाई में समाने की गारंटी भी नहीं देती। डिलीवरी आकार पर दिखते अक्षर जाँचें। हमारा परीक्षण क्षैतिज लेबल, निश्चित फ़ॉन्ट गुण और 30-इकाई line step लेता है; दोबारा मापे बिना संख्याएँ दूसरे typeface पर न लें।

चित्रण केवल PNG या JPG हो और संपादन योग्य कलाकृति चाहिए तो PerfectVector से clipart SVG तैयार करें, किनारे व भीतरी खुले भाग जाँचें और कलाकृति लेबल से अलग रखें। शब्द native text में फिर टाइप करें, रैपिंग नीति चलाएँ और संयुक्त निर्यात जाँचें। वेक्टराइज़ेशन चित्रण पाथ बनाता है; लाइन ब्रेक नहीं चुनता और ट्रेस अक्षरों से संपादन योग्य शब्द नहीं लौटाता। अच्छे मौजूदा पाथ वैसे रह सकते हैं। वेक्टराइज़ेशन गाइड स्रोत पुनर्प्राप्ति चरण समझाती है।

अक्सर पूछे जाने वाले प्रश्न

क्या tspan हर पंक्ति की टूटने की जगह अपने-आप तय करता है? यह उदाहरण शब्द समूह मापकर निर्णय लेता है, फिर हर स्वीकृत पंक्ति का positioned tspan बनाता है। SVG 2 की CSS-आधारित automatic wrapping नहीं जाँचता।

लेबल से चौड़े शब्द का क्या होता है? रूटीन overlong-word लौटाती है और लेबल नहीं देती। चुपचाप काटने के बजाय क्षेत्र बढ़ाएँ, शब्द छोटा करें या अलग जाँची breaking नीति दें।

क्या maxLines बाकी शब्द हटा देता है? नहीं। अधिक पंक्तियाँ चाहिए तो नीति परिणाम अस्वीकार करती है, ज़रूरी संख्या बताती है और शून्य tspans रखती है। ऐप को स्थिति सँभालनी है।

दूसरे फ़ॉन्ट में निर्यात पंक्तियाँ समान दिखेंगी? स्पष्ट शब्द और जगह रहते हैं, लेकिन फ़ाइल फ़ॉन्ट एम्बेड या glyph outline नहीं करती। वास्तविक गंतव्य में उपलब्ध फ़ॉन्ट और अक्षर जाँचें।

स्रोत

  1. MDN tspan एलिमेंट — स्थित उपटेक्स्ट और line-offset गुण।
  2. W3C SVG 2 टेक्स्ट — स्पष्ट स्थिति और अलग automatic-wrapping मॉडल।
  3. MDN getComputedTextLength — वास्तविक SVG टेक्स्ट मापना।
  4. MDN FontFaceSet.ready — फ़ॉन्ट लोडिंग और लेआउट की प्रतीक्षा।
  5. MDN textContent — markup के बजाय सादा टेक्स्ट डालना।
  6. MDN XMLSerializer — लेआउट किया SVG markup सहेजना।

अपने रास्टर चित्रण के लिए संपादन योग्य clipart कलाकृति तैयार करें, आकृतियाँ जाँचें और मास्टर रखें। शब्द अलग जोड़ें, फिर देने से पहले सहेजे SVG में स्वीकृत पंक्तियाँ और दिखते अक्षर सत्यापित करें।

Blog से और

PerfectVector

ऐसे साफ़ SVG से शुरुआत करें
जिसे एडिट करना आसान हो

क्रेडिट कार्ड की ज़रूरत नहीं