Как вы можете найти высоту текста на холсте HTML?


148

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


1
Я хотел бы знать лучший способ, чем лучший ответ. Если есть какой-то алгоритм, позволяющий взять произвольный точечный шрифт и найти на нем максимальные / минимальные границы, то я был бы очень рад услышать об этом. =)
beatgammit

@tjameson - кажется, есть. Смотрите ответ от ellisbben (и мое улучшение к нему).
Даниэль Эрвикер

2
Мне интересно, можно ли использовать символ Юникода 'FULL BLOCK' (U + 2588) в качестве приближения, умножив его ширину на два.
Даниэль Ф

1
Стоит отметить, что ответ немного зависит от ваших требований. Например, высота, необходимая для отрисовки символа «а», отличается от высоты, необходимой для отрисовки символа «у», из-за спускового устройства, которое простирается ниже базовой линии шрифта. Приведенные ниже ответы на основе HTML не учитывают это и дают вам общую высоту, подходящую для любого текста, тогда как ответ @ Noitidart дает более точную высоту для конкретного текста.
Дейл Андерсон

2
Помните, что у вас могут быть персонажи, которые выглядят так M̶̢̹̝͖̦̖̭͕̭̣͆̃̀̅̒̊͌̿ͅ, так что это действительно сложная задача, поэтому решите ее для общего случая.
GetFree

Ответы:


78

ОБНОВИТЬ - для примера этой работы я использовал эту технику в редакторе Carota .

Следуя ответу ellisbben, вот улучшенная версия для получения подъема и спуска от базовой линии, то есть такая же, как tmAscentи tmDescentвозвращенная Win32 GetTextMetric API. Это необходимо, если вы хотите выполнить обтекание текстом с разным шрифтом / размером.

Большой текст на холсте с метрическими линиями

Приведенное выше изображение было сгенерировано на холсте в Safari, красная линия - это верхняя строка, где холсту было приказано нарисовать текст, зеленый - базовая линия, а синий - нижняя (таким образом, красный или синий - полная высота).

Использование jQuery для краткости:

var getTextHeight = function(font) {

  var text = $('<span>Hg</span>').css({ fontFamily: font });
  var block = $('<div style="display: inline-block; width: 1px; height: 0px;"></div>');

  var div = $('<div></div>');
  div.append(text, block);

  var body = $('body');
  body.append(div);

  try {

    var result = {};

    block.css({ verticalAlign: 'baseline' });
    result.ascent = block.offset().top - text.offset().top;

    block.css({ verticalAlign: 'bottom' });
    result.height = block.offset().top - text.offset().top;

    result.descent = result.height - result.ascent;

  } finally {
    div.remove();
  }

  return result;
};

В дополнение к текстовому элементу я добавляю div с помощью, display: inline-blockчтобы я мог установить его vertical-alignстиль, а затем выяснить, куда его поместил браузер.

Таким образом, вы получаете объект с ascent, descentи height(что просто ascent+ descentдля удобства). Чтобы проверить это, стоит иметь функцию, которая рисует горизонтальную линию:

var testLine = function(ctx, x, y, len, style) {
  ctx.strokeStyle = style; 
  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.lineTo(x + len, y);
  ctx.closePath();
  ctx.stroke();
};

Затем вы можете увидеть, как текст располагается на холсте относительно верха, базовой линии и низа:

var font = '36pt Times';
var message = 'Big Text';

ctx.fillStyle = 'black';
ctx.textAlign = 'left';
ctx.textBaseline = 'top'; // important!
ctx.font = font;
ctx.fillText(message, x, y);

// Canvas can tell us the width
var w = ctx.measureText(message).width;

// New function gets the other info we need
var h = getTextHeight(font);

testLine(ctx, x, y, w, 'red');
testLine(ctx, x, y + h.ascent, w, 'green');
testLine(ctx, x, y + h.height, w, 'blue');

3
Почему бы не использовать этот текст для определения высоты? abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 В зависимости от шрифта вы можете иметь символы, которые намного выше или ниже , чем г и М
omatase

1
@ellisbben Стоит отметить, что результаты немного отличаются от ваших, хотя я не знаю почему. Например, ваш говорит, что Courier New 8pt ==> 12 пикселей в высоту, а это говорит: Courier New 8pt ==> 13 пикселей в высоту. Я добавил «g» к вашему методу, но это не было разницей. Интересно, какое значение будет наиболее полезным (необязательно технически правильным).
Орвеллофил

3
Я мог только заставить вещи работать правильно, когда я изменил первую строку getTextHeight()на var text = $('<span>Hg</span>').css({ 'font-family': fontName, 'font-size' : fontSize });, т.е. добавив размер отдельно.
cameron.bracken

1
Как заставить это работать для неанглийского текста? см jsfiddle.net/siddjain/6vURk
Морфеус

1
Спасибо ! изменить, <div></div>чтобы <div style="white-space : nowrap;"></div>обрабатывать очень длинную строку
Mickaël Gauvin

40

Вы можете получить очень близкое приближение вертикальной высоты, проверив длину заглавной буквы М.

ctx.font='bold 10px Arial';

lineHeight=ctx.measureText('M').width;

8
Как ширина дает нам приблизительную высоту линии?
Ричард Баркер

11
Они означают, что ширина одной заглавной буквы «M» при данном размере шрифта примерно равна высоте строки. (Не знаю, правда ли это, но вот что говорит ответ)
Натан

2
На самом деле это довольно приличное приближение, которое я использую для быстрого прототипирования. Это не идеально, но это 90% решение.
Остин Д

37

Спецификация canvas не дает нам метод измерения высоты строки. Тем не менее, вы можете установить размер вашего текста в пикселях, и вы обычно можете определить, какие вертикальные границы относительно легко.

Если вам нужно что-то более точное, вы можете выбросить текст на холст, а затем получить данные о пикселях и выяснить, сколько пикселей используется по вертикали. Это было бы относительно просто, но не очень эффективно. Вы можете сделать что-то вроде этого (это работает, но рисует на холсте текст, который вы хотели бы удалить):

function measureTextHeight(ctx, left, top, width, height) {

    // Draw the text in the specified area
    ctx.save();
    ctx.translate(left, top + Math.round(height * 0.8));
    ctx.mozDrawText('gM'); // This seems like tall text...  Doesn't it?
    ctx.restore();

    // Get the pixel data from the canvas
    var data = ctx.getImageData(left, top, width, height).data,
        first = false, 
        last = false,
        r = height,
        c = 0;

    // Find the last line with a non-white pixel
    while(!last && r) {
        r--;
        for(c = 0; c < width; c++) {
            if(data[r * width * 4 + c * 4 + 3]) {
                last = r;
                break;
            }
        }
    }

    // Find the first line with a non-white pixel
    while(r) {
        r--;
        for(c = 0; c < width; c++) {
            if(data[r * width * 4 + c * 4 + 3]) {
                first = r;
                break;
            }
        }

        // If we've got it then return the height
        if(first != r) return last - first;
    }

    // We screwed something up...  What do you expect from free code?
    return 0;
}

// Set the font
context.mozTextStyle = '32px Arial';

// Specify a context and a rect that is safe to draw in when calling measureTextHeight
var height = measureTextHeight(context, 0, 0, 50, 50);
console.log(height);

Для Беспина они подделывают высоту, измеряя ширину строчной буквы «м» ... Я не знаю, как это используется, и я бы не рекомендовал этот метод. Вот соответствующий метод Беспина:

var fixCanvas = function(ctx) {
    // upgrade Firefox 3.0.x text rendering to HTML 5 standard
    if (!ctx.fillText && ctx.mozDrawText) {
        ctx.fillText = function(textToDraw, x, y, maxWidth) {
            ctx.translate(x, y);
            ctx.mozTextStyle = ctx.font;
            ctx.mozDrawText(textToDraw);
            ctx.translate(-x, -y);
        }
    }

    if (!ctx.measureText && ctx.mozMeasureText) {
        ctx.measureText = function(text) {
            ctx.mozTextStyle = ctx.font;
            var width = ctx.mozMeasureText(text);
            return { width: width };
        }
    }

    if (ctx.measureText && !ctx.html5MeasureText) {
        ctx.html5MeasureText = ctx.measureText;
        ctx.measureText = function(text) {
            var textMetrics = ctx.html5MeasureText(text);

            // fake it 'til you make it
            textMetrics.ascent = ctx.html5MeasureText("m").width;

            return textMetrics;
        }
    }

    // for other browsers
    if (!ctx.fillText) {
        ctx.fillText = function() {}
    }

    if (!ctx.measureText) {
        ctx.measureText = function() { return 10; }
    }
};

28
Я сомневаюсь, что именно это имели в виду люди, написавшие спецификацию HTML5.
Стив Ханов

17
Это ужасный ужасный взлом, который я очень люблю. +1
Аллен Лалонд

1
Я не понимаю Где связь между подъемом шрифта и шириной буквы «м»?
каяр

6
emявляется относительным измерением шрифта, где один em равен высоте буквы Mв размере шрифта по умолчанию.
Джерон

1
Да, высота, а не ширина ... Я все еще не понимаю связь. Кроме того, я думаю, что EMS не имеет значения, учитывая, что мы заботимся только о высоте в пикселях.
Prestaul

35

Браузеры начинают поддерживать расширенные текстовые метрики , которые сделают эту задачу тривиальной, когда она широко поддерживается:

let metrics = ctx.measureText(text);
let fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
let actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;

fontHeightвозвращает вам ограничивающую высоту, которая является постоянной независимо от отображаемой строки. actualHeightспецифично для отображаемой строки.

Spec: https://www.w3.org/TR/2012/CR-2dcontext-20121217/#dom-textmetrics-fontboundingboxascent и разделы чуть ниже него.

Статус поддержки (20 августа 2017 года):


2
Все, пожалуйста, проголосуйте на страницах ошибок, чтобы эти функции были реализованы раньше
Джеремия Роуз

21

РЕДАКТИРОВАТЬ: Вы используете преобразования холста? Если это так, вам придется отслеживать матрицу преобразования. Следующий метод должен измерять высоту текста с начальным преобразованием.

РЕДАКТИРОВАТЬ # 2: странно код ниже не дает правильных ответов, когда я запускаю его на этой странице StackOverflow; Вполне возможно, что наличие некоторых правил стиля может нарушить эту функцию.

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

var determineFontHeight = function(fontStyle) {
  var body = document.getElementsByTagName("body")[0];
  var dummy = document.createElement("div");
  var dummyText = document.createTextNode("M");
  dummy.appendChild(dummyText);
  dummy.setAttribute("style", fontStyle);
  body.appendChild(dummy);
  var result = dummy.offsetHeight;
  body.removeChild(dummy);
  return result;
};

//A little test...
var exampleFamilies = ["Helvetica", "Verdana", "Times New Roman", "Courier New"];
var exampleSizes = [8, 10, 12, 16, 24, 36, 48, 96];
for(var i = 0; i < exampleFamilies.length; i++) {
  var family = exampleFamilies[i];
  for(var j = 0; j < exampleSizes.length; j++) {
    var size = exampleSizes[j] + "pt";
    var style = "font-family: " + family + "; font-size: " + size + ";";
    var pixelHeight = determineFontHeight(style);
    console.log(family + " " + size + " ==> " + pixelHeight + " pixels high.");
  }
}

Вам нужно убедиться, что вы правильно выбрали стиль шрифта для элемента DOM, для которого вы измеряете высоту, но это довольно просто; на самом деле вы должны использовать что-то вроде

var canvas = /* ... */
var context = canvas.getContext("2d");
var canvasFont = " ... ";
var fontHeight = determineFontHeight("font: " + canvasFont + ";");
context.font = canvasFont;
/*
  do your stuff with your font and its height here.
*/

1
+1 Путь лучше решения ИМО. Должна быть возможность определить положение базовой линии.
Даниэль Эрвикер

Добавили ответ, который получает базовый уровень.
Даниэль Уорвикер

Это работает? Я даже не думал о том, чтобы вставить это в div. Это, вероятно, даже не нужно добавлять в DOM, нет?
beatgammit

Я совершенно не знаю, какие поля размера и положения узла существуют, когда он не является частью документа. Я был бы очень заинтересован, чтобы прочитать ссылку на это, если вы знаете об этом.
ellisbben

5
+1 за скриншот сложного кода, который был бы просто context.measureText (text). Высота в параллельной вселенной с лучшим API Canvas
rsp

11

Разве высота текста в пикселях не равна размеру шрифта (в пунктах), если вы определяете шрифт с помощью context.font?


2
Вот что утверждает этот источник: html5canvastutorials.com/tutorials/html5-canvas-text-metrics
Руи Маркиз

для простых случаев: вы всегда можете разобрать высоту по имени шрифта: parseInt (ctx.font.split ('') [0] .replace ('px', '')); // парсинг строки: "10px Verdana"
Шон

Вы можете использовать px, pt, em и% для размера шрифта. Именно поэтому этот ответ вводит в заблуждение.
Джексонкр,

@Jacksonkr, да, но все же вы могли бы их разобрать и соответственно откорректировать, верно? Или где-то есть ограничение этого подхода?
Пейсер

@Pacerier Ограничение заключается в том, что вы можете вносить некоторые ошибки, которые заставляют вас вырывать волосы. Просто помните, что смешивание типов блоков может привести к ошибочному / спагетти-коду. Тем не менее, я не выше случайного взлома, пока риск для проблем низок.
Джексонкр

10

Как предлагает JJ Stiff, вы можете добавить свой текст в диапазон, а затем измерить offsetHeight диапазона.

var d = document.createElement("span");
d.font = "20px arial";
d.textContent = "Hello world!";
document.body.appendChild(d);
var emHeight = d.offsetHeight;
document.body.removeChild(d);

Как показано на HTML5Rocks


3
Это очень хорошее решение, спасибо ... но я не знаю почему, если этот промежуток не был добавлен на страницу и виден до того, как я получу его offsetHeight! он всегда возвращает высоту как ноль в Chrome и Firefox!
Мустафа

1
Вы правы, я думаю, это должно быть добавлено в дом, чтобы занять место. Вот JS Fiddle этой работы: jsfiddle.net/mpalmerlee/4NfVR/4 Я также обновил код выше.
Мэтт Палмерли

Использование clientHeight также возможно. Хотя этот ответ является решением проблемы, это уродливый обходной путь. +1 все равно.
Даниэль Ф

Это не учитывает фактическую высоту видимого текста и обычно сталкивается с дополнительным полем в верхней части текста ...
Айн Тохври

8

Просто добавьте к ответу Даниэля (что здорово! И абсолютно верно!) Версию без JQuery:

function objOff(obj)
{
    var currleft = currtop = 0;
    if( obj.offsetParent )
    { do { currleft += obj.offsetLeft; currtop += obj.offsetTop; }
      while( obj = obj.offsetParent ); }
    else { currleft += obj.offsetLeft; currtop += obj.offsetTop; }
    return [currleft,currtop];
}
function FontMetric(fontName,fontSize) 
{
    var text = document.createElement("span");
    text.style.fontFamily = fontName;
    text.style.fontSize = fontSize + "px";
    text.innerHTML = "ABCjgq|"; 
    // if you will use some weird fonts, like handwriting or symbols, then you need to edit this test string for chars that will have most extreme accend/descend values

    var block = document.createElement("div");
    block.style.display = "inline-block";
    block.style.width = "1px";
    block.style.height = "0px";

    var div = document.createElement("div");
    div.appendChild(text);
    div.appendChild(block);

    // this test div must be visible otherwise offsetLeft/offsetTop will return 0
    // but still let's try to avoid any potential glitches in various browsers
    // by making it's height 0px, and overflow hidden
    div.style.height = "0px";
    div.style.overflow = "hidden";

    // I tried without adding it to body - won't work. So we gotta do this one.
    document.body.appendChild(div);

    block.style.verticalAlign = "baseline";
    var bp = objOff(block);
    var tp = objOff(text);
    var taccent = bp[1] - tp[1];
    block.style.verticalAlign = "bottom";
    bp = objOff(block);
    tp = objOff(text);
    var theight = bp[1] - tp[1];
    var tdescent = theight - taccent;

    // now take it off :-)
    document.body.removeChild(div);

    // return text accent, descent and total height
    return [taccent,theight,tdescent];
}

Я только что протестировал приведенный выше код и отлично работает на последних Chrome, FF и Safari на Mac.

РЕДАКТИРОВАТЬ: я также добавил размер шрифта и протестировал с веб-шрифтом вместо системного шрифта - работает потрясающе.


7

Я решил эту проблему с первого взгляда - используя пиксельные манипуляции.

Вот графический ответ:

Вот код:

    function textHeight (text, font) {

    var fontDraw = document.createElement("canvas");

    var height = 100;
    var width = 100;

    // here we expect that font size will be less canvas geometry
    fontDraw.setAttribute("height", height);
    fontDraw.setAttribute("width", width);

    var ctx = fontDraw.getContext('2d');
    // black is default
    ctx.fillRect(0, 0, width, height);
    ctx.textBaseline = 'top';
    ctx.fillStyle = 'white';
    ctx.font = font;
    ctx.fillText(text/*'Eg'*/, 0, 0);

    var pixels = ctx.getImageData(0, 0, width, height).data;

    // row numbers where we first find letter end where it ends 
    var start = -1;
    var end = -1;

    for (var row = 0; row < height; row++) {
        for (var column = 0; column < width; column++) {

            var index = (row * width + column) * 4;

            // if pixel is not white (background color)
            if (pixels[index] == 0) {
                // we havent met white (font color) pixel
                // on the row and the letters was detected
                if (column == width - 1 && start != -1) {
                    end = row;
                    row = height;
                    break;
                }
                continue;
            }
            else {
                // we find top of letter
                if (start == -1) {
                    start = row;
                }
                // ..letters body
                break;
            }

        }

    }
   /*
    document.body.appendChild(fontDraw);
    fontDraw.style.pixelLeft = 400;
    fontDraw.style.pixelTop = 400;
    fontDraw.style.position = "absolute";
   */

    return end - start;

}

2
Я считаю, что это решение не учитывает пунктирные буквы, такие как строчные буквы i и j
wusauarus

3

Я пишу эмулятор терминала, поэтому мне нужно было рисовать прямоугольники вокруг символов.

var size = 10
var lineHeight = 1.2 // CSS "line-height: normal" is between 1 and 1.2
context.font = size+'px/'+lineHeight+'em monospace'
width = context.measureText('m').width
height = size * lineHeight

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


3

Я реализовал хорошую библиотеку для измерения точной высоты и ширины текста с помощью HTML canvas. Это должно делать то, что вы хотите.

https://github.com/ChrisBellew/text-measurer.js


Вы, вероятно, должны прочитать, как предложить личные библиотеки с открытым исходным кодом? перед публикацией вашей библиотеки, чтобы получить больше ответов.
Мартин Питерс

3

Вот простая функция. Библиотека не нужна.

Я написал эту функцию, чтобы получить верхнюю и нижнюю границы относительно базовой линии. Если textBaselineустановлено на alphabetic. Что он делает, так это создает другой холст, а затем рисует там, а затем находит самый верхний и самый нижний непустой пиксель. И это верхняя и нижняя границы. Он возвращает его как относительный, поэтому, если высота равна 20px, а под базовой линией ничего нет, тогда верхняя граница-20 .

Вы должны предоставить символы для этого. В противном случае это даст вам 0 высоту и 0 ширину, очевидно.

Использование:

alert(measureHeight('40px serif', 40, 'rg').height)

Вот функция:

function measureHeight(aFont, aSize, aChars, aOptions={}) {
    // if you do pass aOptions.ctx, keep in mind that the ctx properties will be changed and not set back. so you should have a devoted canvas for this
    // if you dont pass in a width to aOptions, it will return it to you in the return object
    // the returned width is Math.ceil'ed
    console.error('aChars: "' + aChars + '"');
    var defaultOptions = {
        width: undefined, // if you specify a width then i wont have to use measureText to get the width
        canAndCtx: undefined, // set it to object {can:,ctx:} // if not provided, i will make one
        range: 3
    };

    aOptions.range = aOptions.range || 3; // multiples the aSize by this much

    if (aChars === '') {
        // no characters, so obviously everything is 0
        return {
            relativeBot: 0,
            relativeTop: 0,
            height: 0,
            width: 0
        };
        // otherwise i will get IndexSizeError: Index or size is negative or greater than the allowed amount error somewhere below
    }

    // validateOptionsObj(aOptions, defaultOptions); // not needed because all defaults are undefined

    var can;
    var ctx; 
    if (!aOptions.canAndCtx) {
        can = document.createElement('canvas');;
        can.mozOpaque = 'true'; // improved performanceo on firefox i guess
        ctx = can.getContext('2d');

        // can.style.position = 'absolute';
        // can.style.zIndex = 10000;
        // can.style.left = 0;
        // can.style.top = 0;
        // document.body.appendChild(can);
    } else {
        can = aOptions.canAndCtx.can;
        ctx = aOptions.canAndCtx.ctx;
    }

    var w = aOptions.width;
    if (!w) {
        ctx.textBaseline = 'alphabetic';
        ctx.textAlign = 'left'; 
        ctx.font = aFont;
        w = ctx.measureText(aChars).width;
    }

    w = Math.ceil(w); // needed as i use w in the calc for the loop, it needs to be a whole number

    // must set width/height, as it wont paint outside of the bounds
    can.width = w;
    can.height = aSize * aOptions.range;

    ctx.font = aFont; // need to set the .font again, because after changing width/height it makes it forget for some reason
    ctx.textBaseline = 'alphabetic';
    ctx.textAlign = 'left'; 

    ctx.fillStyle = 'white';

    console.log('w:', w);

    var avgOfRange = (aOptions.range + 1) / 2;
    var yBaseline = Math.ceil(aSize * avgOfRange);
    console.log('yBaseline:', yBaseline);

    ctx.fillText(aChars, 0, yBaseline);

    var yEnd = aSize * aOptions.range;

    var data = ctx.getImageData(0, 0, w, yEnd).data;
    // console.log('data:', data)

    var botBound = -1;
    var topBound = -1;

    // measureHeightY:
    for (y=0; y<=yEnd; y++) {
        for (var x = 0; x < w; x += 1) {
            var n = 4 * (w * y + x);
            var r = data[n];
            var g = data[n + 1];
            var b = data[n + 2];
            // var a = data[n + 3];

            if (r+g+b > 0) { // non black px found
                if (topBound == -1) { 
                    topBound = y;
                }
                botBound = y; // break measureHeightY; // dont break measureHeightY ever, keep going, we till yEnd. so we get proper height for strings like "`." or ":" or "!"
                break;
            }
        }
    }

    return {
        relativeBot: botBound - yBaseline, // relative to baseline of 0 // bottom most row having non-black
        relativeTop: topBound - yBaseline, // relative to baseline of 0 // top most row having non-black
        height: (botBound - topBound) + 1,
        width: w// EDIT: comma has been added to fix old broken code.
    };
}

relativeBot, relativeTopИ heightявляются полезными вещами в возвратном объекте.

Вот пример использования:

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script>
function measureHeight(aFont, aSize, aChars, aOptions={}) {
	// if you do pass aOptions.ctx, keep in mind that the ctx properties will be changed and not set back. so you should have a devoted canvas for this
	// if you dont pass in a width to aOptions, it will return it to you in the return object
	// the returned width is Math.ceil'ed
	console.error('aChars: "' + aChars + '"');
	var defaultOptions = {
		width: undefined, // if you specify a width then i wont have to use measureText to get the width
		canAndCtx: undefined, // set it to object {can:,ctx:} // if not provided, i will make one
		range: 3
	};
	
	aOptions.range = aOptions.range || 3; // multiples the aSize by this much
	
	if (aChars === '') {
		// no characters, so obviously everything is 0
		return {
			relativeBot: 0,
			relativeTop: 0,
			height: 0,
			width: 0
		};
		// otherwise i will get IndexSizeError: Index or size is negative or greater than the allowed amount error somewhere below
	}
	
	// validateOptionsObj(aOptions, defaultOptions); // not needed because all defaults are undefined
	
	var can;
	var ctx; 
	if (!aOptions.canAndCtx) {
		can = document.createElement('canvas');;
		can.mozOpaque = 'true'; // improved performanceo on firefox i guess
		ctx = can.getContext('2d');
		
		// can.style.position = 'absolute';
		// can.style.zIndex = 10000;
		// can.style.left = 0;
		// can.style.top = 0;
		// document.body.appendChild(can);
	} else {
		can = aOptions.canAndCtx.can;
		ctx = aOptions.canAndCtx.ctx;
	}
	
	var w = aOptions.width;
	if (!w) {
		ctx.textBaseline = 'alphabetic';
		ctx.textAlign = 'left';	
		ctx.font = aFont;
		w = ctx.measureText(aChars).width;
	}
	
	w = Math.ceil(w); // needed as i use w in the calc for the loop, it needs to be a whole number
	
	// must set width/height, as it wont paint outside of the bounds
	can.width = w;
	can.height = aSize * aOptions.range;
	
	ctx.font = aFont; // need to set the .font again, because after changing width/height it makes it forget for some reason
	ctx.textBaseline = 'alphabetic';
	ctx.textAlign = 'left';	
	
	ctx.fillStyle = 'white';
	
	console.log('w:', w);
	
	var avgOfRange = (aOptions.range + 1) / 2;
	var yBaseline = Math.ceil(aSize * avgOfRange);
	console.log('yBaseline:', yBaseline);
	
	ctx.fillText(aChars, 0, yBaseline);
	
	var yEnd = aSize * aOptions.range;
	
	var data = ctx.getImageData(0, 0, w, yEnd).data;
	// console.log('data:', data)
	
	var botBound = -1;
	var topBound = -1;
	
	// measureHeightY:
	for (y=0; y<=yEnd; y++) {
		for (var x = 0; x < w; x += 1) {
			var n = 4 * (w * y + x);
			var r = data[n];
			var g = data[n + 1];
			var b = data[n + 2];
			// var a = data[n + 3];
			
			if (r+g+b > 0) { // non black px found
				if (topBound == -1) { 
					topBound = y;
				}
				botBound = y; // break measureHeightY; // dont break measureHeightY ever, keep going, we till yEnd. so we get proper height for strings like "`." or ":" or "!"
				break;
			}
		}
	}
	
	return {
		relativeBot: botBound - yBaseline, // relative to baseline of 0 // bottom most row having non-black
		relativeTop: topBound - yBaseline, // relative to baseline of 0 // top most row having non-black
		height: (botBound - topBound) + 1,
		width: w
	};
}

</script>
</head>
<body style="background-color:steelblue;">
<input type="button" value="reuse can" onClick="alert(measureHeight('40px serif', 40, 'rg', {canAndCtx:{can:document.getElementById('can'), ctx:document.getElementById('can').getContext('2d')}}).height)">
<input type="button" value="dont reuse can" onClick="alert(measureHeight('40px serif', 40, 'rg').height)">
<canvas id="can"></canvas>
<h1>This is a Heading</h1>
<p>This is a paragraph.</p>
</body>
</html>

relativeBotИ relativeTopто , что вы видите здесь это изображение:

https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text


3

однострочный ответ

var height = parseInt(ctx.font) * 1.2; 

CSS "line-height: normal" находится между 1 и 1.2

читай здесь для получения дополнительной информации


2

Забавно, что TextMetrics имеет только ширину и высоту:

http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#textmetrics

Можете ли вы использовать Span, как в этом примере?

http://mudcu.be/journal/2011/01/html5-typographic-metrics/#alignFix


1
Причина, по которой указана только ширина, заключается в следующем: stackoverflow.com/a/12112978/1085483
Руи Маркиз

2

Это то, что я сделал, основываясь на некоторых других ответах здесь:

function measureText(text, font) {
	const span = document.createElement('span');
	span.appendChild(document.createTextNode(text));
	Object.assign(span.style, {
		font: font,
		margin: '0',
		padding: '0',
		border: '0',
		whiteSpace: 'nowrap'
	});
	document.body.appendChild(span);
	const {width, height} = span.getBoundingClientRect();
	span.remove();
	return {width, height};
}

var font = "italic 100px Georgia";
var text = "abc this is a test";
console.log(measureText(text, font));


1

Прежде всего, вам нужно установить высоту размера шрифта, а затем в соответствии со значением высоты шрифта определить текущую высоту вашего текста, сколько, кросс-текстовых строк, конечно же, такая же высота Шрифт нужно накапливать, если текст не превышает наибольшее текстовое поле Высота, все показывать, в противном случае показывать текст только внутри текста блока. Высокие значения требуют вашего собственного определения. Чем больше заданная высота, тем больше высота текста, который необходимо отобразить и перехватить.

После того, как эффект обработан (решить)

До того, как эффект обработан (не решен)

  AutoWrappedText.auto_wrap = function(ctx, text, maxWidth, maxHeight) {
var words = text.split("");
var lines = [];
var currentLine = words[0];

var total_height = 0;
for (var i = 1; i < words.length; i++) {
    var word = words[i];
    var width = ctx.measureText(currentLine + word).width;
    if (width < maxWidth) {
        currentLine += word;
    } else {
        lines.push(currentLine);
        currentLine = word;
        // TODO dynamically get font size
        total_height += 25;

        if (total_height >= maxHeight) {
          break
        }
    }
}
if (total_height + 25 < maxHeight) {
  lines.push(currentLine);
} else {
  lines[lines.length - 1] += "…";
}
return lines;};

1

Я обнаружил, что JUST FOR ARIAL самый простой, быстрый и точный способ определить высоту ограничительной рамки - это использовать ширину определенных букв. Если вы планируете использовать определенный шрифт, не позволяя пользователю выбирать другой, вы можете провести небольшое исследование, чтобы найти правильную букву, которая выполняет работу для этого шрифта.

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="700" height="200" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = "100px Arial";
var txt = "Hello guys!"
var Hsup=ctx.measureText("H").width;
var Hbox=ctx.measureText("W").width;
var W=ctx.measureText(txt).width;
var W2=ctx.measureText(txt.substr(0, 9)).width;

ctx.fillText(txt, 10, 100);
ctx.rect(10,100, W, -Hsup);
ctx.rect(10,100+Hbox-Hsup, W2, -Hbox);
ctx.stroke();
</script>

<p><strong>Note:</strong> The canvas tag is not supported in Internet 
Explorer 8 and earlier versions.</p>

</body>
</html>


0

установка размера шрифта может быть непрактичной, поскольку настройка

ctx.font = ''

будет использовать тот, который определен CSS, а также любые встроенные теги шрифта. Если вы используете шрифт CSS, вы не знаете, какая высота от программного способа, используя метод measureText, который очень недальновиден. С другой стороны, IE8 возвращает ширину и высоту.


0

Это работает 1) и для многострочного текста 2) и даже в IE9!

<div class="measureText" id="measureText">
</div>


.measureText {
  margin: 0;
  padding: 0;
  border: 0;
  font-family: Arial;
  position: fixed;
  visibility: hidden;
  height: auto;
  width: auto;
  white-space: pre-wrap;
  line-height: 100%;
}

function getTextFieldMeasure(fontSize, value) {
    const div = document.getElementById("measureText");

    // returns wrong result for multiline text with last line empty
    let arr = value.split('\n');
    if (arr[arr.length-1].length == 0) {
        value += '.';
    }

    div.innerText = value;
    div.style['font-size']= fontSize + "px";
    let rect = div.getBoundingClientRect();

    return {width: rect.width, height: rect.height};
};

0

Я знаю, что это старый ответ на вопрос, но для дальнейшего использования я хотел бы добавить короткое, минимальное решение только для JS (без jquery), которое, как я полагаю, может принести пользу людям:

var measureTextHeight = function(fontFamily, fontSize) 
{
    var text = document.createElement('span');
    text.style.fontFamily = fontFamily;
    text.style.fontSize = fontSize + "px";
    text.textContent = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
    document.body.appendChild(text);
    var result = text.getBoundingClientRect().height;
    document.body.removeChild(text);
    return result;
};

-3

В обычных ситуациях должно работать следующее:

var can = CanvasElement.getContext('2d');          //get context
var lineHeight = /[0-9]+(?=pt|px)/.exec(can.font); //get height from font variable

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

2
Вы знакомы с концепцией пикселей экрана? Вы можете найти это поучительным. Независимо от того, pt и px действительно указывают на разные высоты. Стоит отметить, что шрифт может заполнять меньше, чем его «высота» или больше, в любом случае. Я не уверен, масштабируются ли пиксели холста, но я предполагаю, что так. Это «неправильный» ответ. Это просто и может быть использовано во многих ситуациях.
Lodewijk

-4

Это безумие ... Высота текста - это размер шрифта. Кто-нибудь из вас читал документацию?

context.font = "22px arial";

это установит высоту 22px.

единственная причина есть ..

context.measureText(string).width

это потому, что ширина строки не может быть определена, если она не знает строку, ширину которой вы хотите, но для всех строк, нарисованных шрифтом ... высота будет 22px.

если вы используете другое измерение, чем px, то высота будет такой же, но с этим измерением, поэтому самое большее, что вам нужно будет сделать, - это преобразовать измерение.


Некоторые буквы могут превышать или превышать пределы, см. Whatwg.org/specs/web-apps/current-work/images/baselines.png
Октавия Тогами

1
Плохо сформулировано, но все еще обычно верно.
Lodewijk

прочитайте все эти ответы, но для моего простого приложения это был правильный ответ, pt === px
rob

Просто совершенно неверно. Попробуйте некоторые из предложенных решений с различными шрифтами, и вы найдете большие расхождения.
Дейл Андерсон

-4

Примерное решение:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = "100px Arial";
var txt = "Hello guys!"
var wt = ctx.measureText(txt).width;
var height = wt / txt.length;

Это будет точный результат в моноширинном шрифте.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.