Подход к отображению данных глифов шрифта в пользовательском интерфейсе?

Как воссоздать/получить эти данные, отображаемые в этом пользовательском интерфейсе?

В основном это восходящий элемент, высота заглавной буквы, высота по оси x и т. д. Материал справа можно получить из глифа Юникода или получить из данных Юникода, но левый материал выглядит как данные глифа, специфичные для шрифта.

Вопросы:

  1. Какой формат шрифта мне следует использовать для получения этих данных глифа (ttf, otf, woff, woff2 или svg)?
  2. Я знаю JS API opentype, https://github.com/opentypejs/opentype.js . В частности, объект Glyph кажется тем, что мне нужно, но единственные, казалось бы, релевантные реквизиты - это advanceWidth, leftSideBearing, xMin, xMax, yMin и yMax, но это не похоже на это изображение.... Есть ли там есть все, что есть на этом изображении?
  3. Существуют ли лучшие библиотеки JS для работы с определенным форматом шрифта для получения этих данных?
  4. Если JS-библиотек не существует, какие низкоуровневые свойства шрифта из определенного формата шрифта мне нужно изучить для чтения? Например, в какой-то момент я проанализировал cmap и соответствующие таблицы шрифтов, так что, может быть, это где-то там?

Любая помощь будет оценена по достоинству.

🤔 А знаете ли вы, что...
С JavaScript можно создавать интерактивные формы и проверять введенные пользователем данные.


105
1

Ответ:

Решено

Свойства, которые вы ищете, хранятся глобально, а не в данных глифов (например, в таблице os2).

Разбор и рендеринг через opentype.js:

let fontSrc =
  "https://fonts.gstatic.com/s/robotoflex/v9/NaPccZLOBv5T3oB7Cb4i0zu6RME.woff2";
getVerticalMetrics(fontSrc);

async function getVerticalMetrics(fontSrc) {
  let font = await loadFont(fontSrc);
  let fontFamily = font.tables.name.fontFamily.en;
  
  // collect data records
  let {unitsPerEm, ascender, descender} = font

  //get x-height from os2 table
  let xHeight = font.tables.os2.sxHeight
  let capHeight = font.tables.os2.sCapHeight
  
  //optional:  get x height from glyph data
  let xHeight_x = font.charToGlyph('x').yMax
  let capHeight_H = font.charToGlyph('H').yMax
  
  let data = {
    fontFamily: fontFamily,
    xHeight: xHeight,
    capHeight: capHeight,
    ascender: ascender,
    descender: descender,
    unitsPerEm: unitsPerEm
  }
  
  pre.textContent= JSON.stringify(data, null, ' ');
  
  
  // render example
  let fontSize = 100;
  let scale = fontSize / unitsPerEm;
  let lineHeight = (ascender + Math.abs(descender) ) * scale;
  let ratAsc = ascender / unitsPerEm;
  let yOffset = fontSize * ratAsc;
  let path = font.getPath('Hxg', 0, yOffset, fontSize)
  
  let pathData = path.toPathData(1)
  preview.setAttribute('d', pathData)
  let yBaseline = ascender*scale
  let yXHeight = yBaseline-xHeight*scale
  let ypathCapHeight = yBaseline - capHeight*scale
  pathBaseline.setAttribute('y1',yBaseline );
  pathBaseline.setAttribute('y2',yBaseline );
  
  pathXheight.setAttribute('y1',yXHeight );
  pathXheight.setAttribute('y2',yXHeight );
  
  pathCapHeight.setAttribute('y1',ypathCapHeight );
  pathCapHeight.setAttribute('y2',ypathCapHeight );
  
  svg.setAttribute('viewBox', [0, 0, 200, lineHeight])


}


/**
* opentype.js helper
* Based on @yne's comment
* https://github.com/opentypejs/opentype.js/issues/183#issuecomment-1147228025
* will decompress woff2 files
*/
async function loadFont(src, options = {}) {
  let buffer = {};
  let font = {};
  let ext = 'woff2';
  let url;

  // 1. is file
  if (src instanceof Object) {
    // get file extension to skip woff2 decompression
    let filename = src.name.split(".");
    ext = filename[filename.length - 1];
    buffer = await src.arrayBuffer();
  }

  // 2. is base64 data URI
  else if (/^data/.test(src)) {
    // is base64
    let data = src.split(";");
    ext = data[0].split("/")[1];

    // create buffer from blob
    let srcBlob = await (await fetch(src)).blob();
    buffer = await srcBlob.arrayBuffer();
  }

  // 3. is url
  else {


    // if google font css - retrieve font src
    if (/googleapis.com/.test(src)) {
      ext = 'woff2';
      src = await getGoogleFontUrl(src, options);
    }


    // might be subset - no extension
    let hasExt = (src.includes('.woff2') || src.includes('.woff') || src.includes('.ttf') || src.includes('.otf')) ? true : false;
    url = src.split(".");
    ext = hasExt ? url[url.length - 1] : 'woff2';

    let fetchedSrc = await fetch(src);
    buffer = await fetchedSrc.arrayBuffer();
  }

  // decompress woff2
  if (ext === "woff2") {
    buffer = Uint8Array.from(Module.decompress(buffer)).buffer;
  }

  // parse font
  font = opentype.parse(buffer);
  return font;
}
svg{
  border: 1px solid #ccc;
}
<!-- neeeded for woff2 fonts/brotli decompression -->
<script src = "https://unpkg.com/[email protected]/build/decompress_binding.js"></script>
<script src='https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js'></script>

<div id = "sample">
  <svg id = "svg" viewBox = "0 0 200 100">
    <path id = "preview" />
    <line x1 = "0" y1 = "0" x2 = "100%"  y2 = "" id = "pathBaseline" stroke = "red" fill = "none" />
    <line x1 = "0" y1 = "0" x2 = "100%"  y2 = "" id = "pathXheight" stroke = "green" fill = "none" />
    <line x1 = "0" y1 = "0" x2 = "100%"  y2 = "" id = "pathCapHeight" stroke = "#ccc" fill = "none" />
    
  </svg>
</div>

<code>
  <pre id = "pre"></pre>
</code>

Координаты в файлах шрифтов

Шрифты используют классическое декартово координатное пространство (ось Y идет снизу вверх), тогда как svg имеет вертикально перевернутую ось Y (сверху вниз).
Поэтому вам необходимо преобразовать значения для рендеринга SVG или Canvas.

Единицы измерения в файлах шрифтов

Имейте в виду, что все единицы измерения относятся к UPM шрифта (значения единиц на em).

Поэтому вам может потребоваться вычислить относительное значение желаемого размера шрифта для рендеринга. Например, открытый тип .otf будет возвращать значения на основе 1000 UPM, тогда как версия truetype .ttf возвращает значения (обычно) на основе соотношения 2048 UPM.

Идеальный формат шрифта

Формат шрифта имеет значение только для распаковки перед анализом:

Для woff2 требуется сложный скрипт декомпрессии Brotli, который значительно увеличивает общее время загрузки и обработки.

При загрузке woff или truetype вы можете использовать уже включенные функции deflate.

Шрифты .svg устарели. Однако, если у вас также есть SVG-версия вашего текущего шрифта, вы также можете загрузить ее через fetch() и написать свой собственный анализатор шрифтов SVG.

let font = new DOMParser().parseFromString(svgFont, 'text/html').querySelector('font')
let fontface = font.querySelector('font-face')
let data = [...fontface.attributes].map( item=>{let obj = {}; obj[item.name]=item.value; return obj})
console.info(data)
<script>

let svgFont = `<svg xmlns = "http://www.w3.org/2000/svg" xmlns:xlink = "http://www.w3.org/1999/xlink" version = "1.1">
<metadata>
Created by FontForge 20201107 at Mon Jun 22 12:22:04 2020
 By Jimmy Wärting
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
</metadata>
<defs>
<font id = "Poppins-Medium" horiz-adv-x = "678" >
  <font-face 
    font-family = "Poppins Medium"
    font-weight = "500"
    font-stretch = "normal"
    units-per-em = "1000"
    panose-1 = "0 0 6 0 0 0 0 0 0 0"
    ascent = "800"
    descent = "-200"
    x-height = "551"
    cap-height = "695"
    bbox = "-28 -272 1048 969"
    underline-thickness = "50"
    underline-position = "-100"
    unicode-range = "U+000D-2215"
  />
<missing-glyph horiz-adv-x = "500" 
d = "M0 700h500v-700h-500v700zM420 650h-340l170 -255zM50 95l170 255l-170 255v-510zM450 95v510l-170 -255zM420 50l-170 255l-170 -255h340z" />
    <glyph glyph-name = ".notdef" horiz-adv-x = "500" 
d = "M0 700h500v-700h-500v700zM420 650h-340l170 -255zM50 95l170 255l-170 255v-510zM450 95v510l-170 -255zM420 50l-170 255l-170 -255h340z" />
    <glyph glyph-name = "NULL" horiz-adv-x = "0" 
 />
    <glyph glyph-name = "NULL" horiz-adv-x = "0" 
 />
    <glyph glyph-name = "CR" unicode = "&#xd;" horiz-adv-x = "260" 
 />
    <glyph glyph-name = "space" unicode = " " horiz-adv-x = "260" 
 />
    <glyph glyph-name = "exclam" unicode = "!" horiz-adv-x = "321" 
d = "M218 695l-13 -485h-95l-13 485h121zM109 14q-21 21 -21 52t21 52t52 21q30 0 51 -21t21 -52t-21 -52t-51 -21q-31 0 -52 21z" />
    <glyph glyph-name = "quotedbl" unicode = "&#x22;" horiz-adv-x = "323" 
d = "M137 797l-12 -205h-78l-13 205h103zM288 797l-12 -205h-78l-13 205h103z" />
    <glyph glyph-name = "numbersign" unicode = "#" horiz-adv-x = "872" 
d = "M658 458l-37 -173h132v-100h-153l-40 -185h-109l40 185h-196l-40 -185h-109l40 185h-155v100h176l37 173h-154v100h175l39 182h109l-39 -182h196l39 182h109l-39 -182h133v-100h-154zM549 458h-196l-37 -173h196z" />
  </font>
</defs></svg>`
</script>

Информация о шрифте без функций рендеринга через lib-font.js

lib-font.js может быть альтернативой, если вам не нужны функции рендеринга, например, для преобразования контуров глифов в svg.
lib-font.js обычно предоставляет наиболее полную информацию о таблице данных — особенно данные переменных шрифтов (например, информация об осях) часто не полностью реализуются другими анализаторами.

// retrieve font data after all required assets are loaded (e.g for decompression)
let fontSrc =
  "https://fonts.gstatic.com/s/robotoflex/v9/NaPccZLOBv5T3oB7Cb4i0zu6RME.woff2";

window.addEventListener("DOMContentLoaded", (e) => {
  getVerticalMetrics(fontSrc);
});

async function getVerticalMetrics(fontSrc) {
  let font = new Font("fontname", {
    skipStyleSheet: true
  });
  font.src = fontSrc;
  font.onload = (evt) => {
    let font = evt.detail.font;
    let tables = font.opentype.tables;
    let os2 = tables["OS/2"];
    let ascender = os2.sTypoAscender;
    let descender = os2.sTypoDescender;
    let xHeight = os2.sxHeight;
    let capHeight = os2.sCapHeight;
    //console.info(font);
    //console.info(os2);

    let data = {
      xHeight: xHeight,
      capHeight: capHeight,
      ascender: ascender,
      descender: descender,
      unitsPerEm: tables.head.unitsPerEm
    };

    pre.textContent = JSON.stringify(data, null, " ");
  };
  
}
<!-- add brotli decompression needed for woff2 -->
<script src = "https://cdn.jsdelivr.net/npm/[email protected]/lib/unbrotli.js"></script>
<script src = "https://cdn.jsdelivr.net/npm/[email protected]/lib-font.browser.js" type = "module"></script>

<code>
  <pre id = "pre"></pre>
</code>