Saturday 18 December 2021

Universe 4: User Interface

The Universe character browser has a very basic user interface. In addition to the HTML5 presentation, there is a URL scheme that allows you to jump to various subpages.

Landing Page

universe.html#

The landing page gives a brief introduction and a collapsible table of statistics. As with all pages, a header contains breadcrumbs and a footer lists some useful jumping-off points.

Codepoints

To examine a specific codepoint, expressed in hexadecimal between "0000" and "10FFFF" inclusive, use:

universe.html#U+<hex>

In this case, "#U+0040" examines "@". The subpage lists the block, column and codepoint details, as well as font support, UCD data fields and external links, where appropriate.

Planes

To list all 17 Unicode planes, use:

universe.html#P

To examine details of the plane containing codepoint "U+<hex>", use:

universe.html#P+<hex>

Sheets

In this context, a "sheet" is a 32-by-32 grid of 1024 contiguous codepoints; it is how the Notoverse glyphs are organised.

To list all 161 sheets which contain allocated codepoints, use:

universe.html#S

To examine the sheet containing codepoint "U+<hex>", use:

universe.html#S+<hex>

Blocks

To list all 320 named Unicode blocks, use:

universe.html#B

To examine the block containing codepoint "U+<hex>", use:

universe.html#B+<hex>

This will list constituent codepoints, organised by column.

Columns

To examine just the column containing codepoint "U+<hex>", use:

universe.html#C+<hex>

This will list constituent codepoints in more detail.

Queries

To query the UCD for matching codepoints, use:

universe.html#<key>=<value>

The "<key>" can be:

  • One of the abbreviated UCD field names, such as "gc" or "na".
  • "id" to search the codepoint id (e.g. "U+0040").
  • "basic" to search the computed codepoint basic category.
  • "kind" to search the computed codepoint kind.
  • "script" to search the codepoint script list (see below).
  • "extra" to search NamesList.txt annotations.
  • "text" to search all the above.

The "<value>" can be simple text or a JavaScript regular expression in the form "/<expr>/<flags>". For example:

universe.html#na=/\bANT\b/i

This searches for codepoints whose name field contains the whole word "ANT" case-insensitively.

Scripts

Queries with the special key "script" search the fields "sc" and "scx". To query for the 29 codepoints of the Ogham script, use:

universe.html#script=Ogam

List all the 210 scripts of the ISO-15924 standard, use:

universe.html#script

Search

Queries with the special key "search" perform full-text searches. To bring up a search dialog, use:

universe.html#search

Gears

As mentioned earlier, loading the full UCD database and glyph sheets for the first time can take quite a few minutes. Searches can also take a few seconds. For long-running JavaScript functions, we display animated gears:

To keep the page responsive, we wrap the long-running functionality inside a call to the "Gears()" function in universe.js:

function Gears(parent, asynchronous, complete) {
  var gears = document.createElement("img");
  gears.className = "gears";
  gears.title = "Please wait...";
  gears.src = "gears.svg";
  parent.appendChild(gears);
  gears.onload = async () => {
    var result = await asynchronous();
    if (gears.parentNode === parent) {
      parent.removeChild(gears);
    }
    if (complete) {
      complete(result);
    }
  };
}

Inside the asynchronous, long-running function we have to make sure we periodically call "YieldAsync()":

function YieldAsync(milliseconds) {
  // Yield every few milliseconds 
  // (or every time this function is called if argument is missing)
  var now = performance.now();
  if (!YieldAsync.already) {
    // First call
    YieldAsync.already = Promise.resolve(undefined);
    YieldAsync.channel = new MessageChannel();
  } else if (milliseconds && ((now - YieldAsync.previous) < milliseconds)) {
    // Resolve immediately
    return YieldAsync.already;
  }
  YieldAsync.previous = now;
  return new Promise(resolve => {
    YieldAsync.channel.port1.addEventListener("message",
      () => resolve(), { once: true });
    YieldAsync.channel.port1.start();
    YieldAsync.channel.port2.postMessage(null);
  });
}

This was inspired by a much-underrated StackOverflow answer.

No comments:

Post a Comment