JSFiddle - React, Tailwind, and code Playground

by int32_t

HTML

<p id="r1">before <ruby>A <ruby>base<rt>inner-ruby</ruby><rt>outer-<ruby>ruby<rt>ruby-of-ruby</ruby></rt> base2<rt>inner-2</ruby> <ruby>base3<rt>ruby3</ruby> after</p>
<div id="console"></div>

CSS

p {
  font-size:40px;
}
ruby {
  background-color: rgba(238,255,238,0.9);
}
rt {
  background-color: rgba(255,238,238,0.8);
}
#console {
  white-space: pre;
  font-family: monospace;
  font-size: 14px;
}

JavaScript

function formatLevel(rt_context) {
  return `${rt_context.map(d => d.max - d.current + 1).join(',')}`;
}

function buildChunkList(ifc) {
  let chunk_list = [];
  let chunk_text = '';
  let current = ifc.firstChild;
  let ruby_depth = 0;
  let max_ruby_depth = 0;
  let rt_context = [];
  let level_list = [];
  let parent_chunk = null;
  let parent_chunk_stack = [];
  let base_last_chunk_stack = [];
  const pushBaseChunk = (ct, rd, rc, cl) => {
    if (rc.length > 0) {
      cl.push({t:ct, l:`L${formatLevel(rc)}`});
    } else if (rd == 0) {
      cl.push({t:ct, l:'*'});
    } else {
      cl.push({t:ct, l:'L0'});
    }
    return cl[cl.length - 1];
  };
  const addLink = (from, to) => {
    from[to.l] = to;
  };
  while (current) {
    if (current.nodeType == Node.ELEMENT_NODE) {
      if (current.firstChild) {
        if (current.tagName == 'RT') {
          const new_base_chunk = pushBaseChunk(chunk_text, ruby_depth, rt_context, chunk_list);
          chunk_text = '';
          addLink(parent_chunk, new_base_chunk);
          base_last_chunk_stack.push(new_base_chunk);
          parent_chunk = parent_chunk_stack.pop();
          rt_context.push({max:max_ruby_depth, current:ruby_depth});
          const level = formatLevel(rt_context);
          if (!level_list.includes(level))
            level_list.push(level);
          max_ruby_depth = 0;
          ruby_depth = 0;
        } else if (current.tagName == 'RUBY') {
          if (chunk_text.length > 0) {
            const new_chunk = pushBaseChunk(chunk_text, ruby_depth, rt_context, chunk_list);
            chunk_text = '';
            if (parent_chunk)
              addLink(parent_chunk, new_chunk);
            parent_chunk = new_chunk;
          }
          // Save to use it on <rt>.
          parent_chunk_stack.push(parent_chunk);

          if (ruby_depth == 0) {
            max_ruby_depth = 1;
          }
          ++ruby_depth;
          max_ruby_depth = Math.max(ruby_depth, max_ruby_depth);
        }
      ...