Сравнение двух шрифтов в HTML5 Canvas

#javascript #html #canvas #fonts #ligature

#javascript #HTML #холст #шрифты #лигатура

Вопрос:

Я пытаюсь собрать инструмент, который проверяет, отображается ли данный символ шрифтом указанного стиля или системным шрифтом по умолчанию. Моя конечная цель — иметь возможность проверять, по крайней мере, в современных (читай: IE8 ) браузерах, поддерживается ли лигатура в данном шрифте.

У меня есть два холста, отображающие одну и ту же лигатуру (в данном случае st). Я превращаю эти холсты в данные и сравниваю их, чтобы увидеть, совпадают ли символы.

Arial (как и большинство шрифтов) не имеет лигатуры st, поэтому он возвращается к шрифту с засечками по умолчанию. Вот где это становится странным: хотя они отображают один и тот же шрифт, два холста не имеют одинаковых данных.

Почему? Потому что их позиции на холсте не совсем одинаковы. Я предполагаю, что это как-то связано с разной относительной высотой шрифтов (один немного выше другого, хотя он варьируется от шрифта к шрифту). Разница, по-видимому, составляет один или два пикселя, и она варьируется от шрифта к шрифту.

Как можно решить эту проблему? Моя единственная текущая идея — найти способ измерить высоту шрифта и соответствующим образом отрегулировать его положение, но, к сожалению, я понятия не имею, как это сделать. Существуют ли другие подходы, которые я мог бы предпринять, чтобы сделать два изображения идентичными?

Вы можете увидеть код ниже. Оба холста успешно инициализированы и добавлены к телу элемента, поэтому я могу видеть, что происходит визуально (хотя в реальном скрипте, над которым я работаю, это необязательно). Я отбросил инициализацию и контекст, так как все они работают просто отлично.

 function checkLig() {

   lig = 'fb06'   // this is the unicode for the st ligature

   canvas0.width = 250;
   canvas0.height = 50;
   context0.fillStyle = 'rgb(0, 0, 0)';
   context0.textBaseline = 'top';
   context0.font = 'normal normal normal 40px Arial';
   context0.fillText(String.fromCharCode(parseInt(lig, 16)), 0, 0);
   var print0 = context0.getImageData(0, 0, 720, 50).data;

   canvas1.width = 250;
   canvas1.height = 50;
   context1.fillStyle = 'rgb(0, 0, 0)';
   context1.textBaseline = 'top';
   context1.font = 'normal normal normal 40px serif';
   context1.fillText(String.fromCharCode(parseInt(lig, 16)), 0, 0);
   var print1 = context1.getImageData(0, 0, 720, 50).data;

   var i = 0, same = true, len = 720 * 50 * 4;
   while (i < len amp;amp; same === true) {
      if (print0[i] !== print1[i]) {
         same = false;
      }
      else {
         i  ;
      }
   }

   return same;
}
 

Комментарии:

1. Если кому-то интересно, все еще (очень незавершенные ) результаты этого можно найти здесь .

Ответ №1:

Итак, я правильно понимаю вопрос, проблема в том, что один холст указывает Arial (но возвращается к Serif), а другой — Serif, и когда вы выполняете сопоставление пикселей, и это не совпадение, потому что один из них имеет небольшое смещение?

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

Например, в этом случае вы могли бы получить свой эталонный пиксель, начав проверять пиксели с верхнего левого угла одного холста и сканировать вниз в этом столбце, а когда вы дойдете до низа, вернитесь к началу следующего столбца и сканируйте вниз.

Как только вы нажмете на пиксель, который не совпадает с цветом вашего фона, запишите позицию и используйте ее в качестве эталонного пикселя. Это должно быть краем вашего шрифта. Проделайте то же самое со следующим холстом, а затем сравните позиции двух опорных пикселей, чтобы получить смещение. Учитывайте это смещение в цикле сравнения.

Надеюсь, у меня правильное представление о проблеме, и я надеюсь, что это поможет!

Комментарии:

1. Вы правильно поняли вопрос, и это хорошая идея… Я поиграю с этим. Я обеспокоен тем, что это может привести к проблемам с общим случаем (когда шрифты не возвращаются), но я не думаю, что это должно быть проблемой в целом. Настоящий трюк будет заключаться в том, чтобы выяснить, как перемещаться по холсту таким образом.

2. Если getImageData возвращает одномерный массив, сканирование по горизонтали приведет к тому же результату. Используя один и тот же метод сканирования на обоих холстах, вы должны нажать «land» в одном и том же месте. В общем случае, когда шрифты разные, я думаю, что предварительное сканирование не повлияет на результат. Единственное соображение — это дополнительный удар по производительности.

3. Правильно. Тогда это должно быть простой математикой, чтобы сделать смещение от этой точки. И это достаточно маленький фрагмент кода, поэтому проблемы с производительностью незначительны. Спасибо!