Optimize tspan’s - JSFiddle

by denvdmj

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.11.0/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.11.0/styles/androidstudio.min.css">
<svg-example>
<svg viewBox="0 0 530 300"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink">

  <text>
    <tspan x="145.2" y="61.6">b</tspan>
    <tspan x="78.1"  y="103.8">a</tspan>
    <tspan x="78.8"  y="146">s</tspan>
    <tspan x="113.8" y="188.2">t</tspan>
    <tspan x="145.7" y="103.8">e</tspan>
    <tspan x="179"   y="146">y</tspan>
    <tspan x="212.7" y="103.8">h</tspan>
    <tspan x="282.6" y="19.4">i</tspan>
    <tspan x="213.2" y="146">e</tspan>
    <tspan x="281.5" y="61.6">s</tspan>
    <tspan x="246.5" y="103.8">n</tspan>
    <tspan x="316.4" y="103.8">t</tspan>
    <tspan x="381.6" y="61.6">o</tspan>
    <tspan x="381.6" y="103.8">n</tspan>
    <tspan x="349.5" y="146">f</tspan>
    <tspan x="417"   y="146">r</tspan>
    <tspan x="451.5" y="103.8">t</tspan>
    <tspan x="449.2" y="146">o</tspan>
  </text>

  <text>
    <tspan x="74.1 83.5 91.7 109.1 118.4 124.3" y="230.5">as at </tspan>
    <tspan x="140.5 151.1 160.4" y="230.5">be </tspan>
    <tspan x="173.6 184.1 194.7" y="230.5">by </tspan>
    <tspan x="208.1 218.6 228" y="230.5">he </tspan>
    <tspan x="243.5 249.3 259.9" y="230.5">in </tspan>
    <tspan x="278.4 284.3 292.5" y="230.5">is </tspan>
    <tspan x="313.3 319.2 325.1" y="230.5">it </tspan>
    <tspan x="344.3 354.9 361.9" y="230.5">of </tspan>
    <tspan x="376.2 386.8 397.3" y="230.5">on </tspan>
    <tspan x="411.9 422.4 429.4" y="230.5">or </tspan>
    <tspan x="446.1 452" y="230.5">to</tspan>
  </text>

  <text>
    <tspan x="4 18.3 24.8 36.6 48.3 57.4 67.8 73.7 85.4 91.3" y="282.5">Figure 2. </tspan>
    <tspan x="103 117.3 123.1 129.7 140.1 149.2 160.9 172.7 181.8 192.2 198 207.2 217.6 229.3 238.4 248.8 260.6 266.4 272.9 282.1 292.5 302.9 308.7 315.2 327 336.1 341.9 353.7...

CSS

@import url(https://typeof.net/Iosevka/fonts.css);

svg text { font-size: 21px }
svg path { stroke: black }

:root,
:root > body {
  background: #464b51;
}
:root > body {
  margin: auto;
  padding: 1rem;
}
svg-example {
  display: block;
  text-align: center;
  width: 100%;
}
svg {
  display: inline-block;
  background: white;
  margin: 0 0 1em 0;
  max-width: 800px;
}
button {
  position: relative;
  display: inline-block;
  padding: 0 1.6em;
  margin: 0 3px 6px 0;
  height: 3em;
  line-height: 3em;
  border: none;
  border-radius: 2px;
  background: #3d4247;
  color: #ccc;
  text-shadow: #000 0 0 2px, #000 0 0 1px;
  box-shadow: 0 2px 4px 0 rgba(0,0,0,.5);
  outline: none;
  user-select: none;
  transition: all .2s ease-out;
}
button:hover {
  background: hsla(0,100%,100%,.1);
  box-shadow: 0px 4px 10px 0 rgba(0,0,0,1);
  transition: all .5s ease-out;
}
button:active {
  transition: all .08s ease-out;
  background: hsla(0,100%,100%,.12);
  color: #fff;
  box-shadow: none;
  box-shadow: 0 0 3px -1px #000;
  left: 1px;
  top: 1px;
}

pre, code {
  font: 14px/1.5 "Iosevka WEB", "Condensed-Coding", "Consolas", monospace;
  background: #1d1f21 !important;
  color: #fff;
}

@font-face {
  font-family: 'Condensed-Coding';
  font-style: normal;
  font-weight: 400;
  src: url(https://cdn.rawgit.com/frantisek-sodomka-gomorrka/fonts/3063e30b/condensed-coding.r-webfont.woff2?raw=true) format('woff2'),
  url(https://cdn.rawgit.com/frantisek-sodomka-gomorrka/fonts/3063e30b/condensed-coding.r-webfont.woff?raw=true) format('woff');
}

JavaScript

'use strict';

clickme.onclick = () => {
  tspans_merge_y_equal_groups(
    document.querySelectorAll('svg > text > tspan')
  );
  show();
};

// the number of digits after the decimal point
const accuracy = 1;

const round = (num, acc = accuracy) =>
  Math.round(num * Math.pow(10, acc)) / Math.pow(10, acc);

const qsa = sel =>
  [].slice.call(document.querySelectorAll(sel));


function tspans_merge_y_equal_groups (tspans) {

  tspans_foreach_y_equal_groups(tspans, group => {

    group.sort((a, b) =>
      round(a.getAttribute('x')) -
      round(b.getAttribute('x'))
    );

    tspans_merge(group);
    tspan_remove_spaces(group[0]);

  });
}

function tspans_foreach_y_equal_groups (tspans, func) {

  let groups = {};

  tspans.forEach(tspan => {
    let y = round(tspan.getAttribute('y'));
    if (!groups[y])
      groups[y] = [];
    groups[y].push(tspan);
  });

  Object.values(groups).forEach(v => func(v));
}

function tspans_merge (tspans) {
  let first = tspans[0];
  let tlist = [];
  let xlist = [];

  tspans.forEach(tspan => {
    tlist.push(tspan.textContent);
    xlist.push(tspan.getAttribute('x'));
    if (tspan !== first)
      tspan.parentNode.removeChild(tspan);
  });

  first.setAttribute('x', xlist.join(' '));
  first.textContent = tlist.join('');
}

function tspan_remove_spaces (tspan) {

  let chars = tspan.textContent.split('');
  let xvals = tspan.getAttribute('x').split(/\s*,\s*|\s+|(?=\-)|(?=\+)/);

  if (chars.length !== xvals.length)
    return;

  let new_chars = [];
  let new_xvals = [];

  for (let i = 0, len = chars.length; i < len; i++) {
    if (/\S/.test(chars[i])) {
      new_chars.push(chars[i]);
      new_xvals.push(xvals[i]);
    }
  }

  tspan.textContent = new_chars.join('');
  tspan.setAttribute('x', new_xvals.join(' '));
}

//-------------------------------------------------
// show svg source code with syntax hightlighting

function show () {
  const code = document.querySelector('pre > code');
  code.textContent =...