Jak znaleźć wysokość tekstu na płótnie HTML?

148

Specyfikacja ma funkcję context.measureText (tekst), która powie Ci, jakiej szerokości wymagałoby wydrukowanie tego tekstu, ale nie mogę znaleźć sposobu, aby dowiedzieć się, jaka jest wysokość. Wiem, że jest oparty na czcionce, ale nie wiem, jak przekonwertować ciąg czcionki na wysokość tekstu.

swampsjohn
źródło
1
Chciałbym poznać lepszy sposób niż najlepsza odpowiedź. Jeśli istnieje jakiś algorytm, który bierze dowolną czcionkę punktową i znajduje na niej granice maksymalne / minimalne, byłbym bardzo szczęśliwy, gdyby o tym usłyszał. =)
beatgammit
@tjameson - wydaje się, że jest. Zobacz odpowiedź od ellisbben (i moje ulepszenie).
Daniel Earwicker
2
Zastanawiam się, czy znak Unicode „FULL BLOCK” (U + 2588) może być użyty jako przybliżenie poprzez pomnożenie jego szerokości przez dwa.
Daniel F
1
Warto zauważyć, że odpowiedź zależy trochę od Twoich wymagań. Na przykład wysokość wymagana do renderowania znaku „a” różni się od wysokości wymaganej do renderowania znaku „y” ze względu na linię opadającą, która rozciąga się poniżej linii bazowej czcionki. Poniższe odpowiedzi oparte na HTML nie uwzględniają tego i podają ogólną wysokość odpowiednią dla dowolnego tekstu, podczas gdy odpowiedź @ Noitidart podaje dokładniejszą wysokość określonego tekstu.
Dale Anderson
2
Pamiętaj, że możesz mieć postacie, które wyglądają tak M̶̢̹̝͖̦̖̭͕̭̣͆̃̀̅̒̊͌̿ͅ, więc jest to naprawdę trudny problem, więc rozwiąż go dla ogólnego przypadku.
GetFree

Odpowiedzi:

78

UPDATE - jako przykład tej pracy użyłem tej techniki w edytorze Carota .

Kontynuując odpowiedź ellisbben, oto ulepszona wersja, która pozwala uzyskać wznoszenie i opadanie z linii bazowej, tj . Takie same jak tmAscenti tmDescentzwracane przez GetTextMetric API Win32 . Jest to potrzebne, jeśli chcesz utworzyć tekst zawijany w słowo z rozpiętościami w różnych czcionkach / rozmiarach.

Duży tekst na płótnie z liniami metrycznymi

Powyższy obraz został wygenerowany na płótnie w Safari, gdzie czerwony to górna linia, w której płótno ma narysować tekst, zielony to linia bazowa, a niebieski to dół (tak więc czerwony do niebieskiego to pełna wysokość).

Używanie jQuery do zwięzłości:

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;
};

Oprócz elementu tekstowego dodaję element div z pomocą, display: inline-blockaby ustawić jego vertical-alignstyl, a następnie dowiedzieć się, gdzie umieściła go przeglądarka.

Otrzymujesz więc obiekt za pomocą ascent, descenti height(co jest tylko ascent+ descentdla wygody). Aby to przetestować, warto mieć funkcję rysującą poziomą linię:

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();
};

Następnie możesz zobaczyć, jak tekst jest umieszczony na płótnie względem góry, linii bazowej i dołu:

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');
Daniel Earwicker
źródło
3
Dlaczego nie użyć tego tekstu do określenia wysokości? abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 W zależności od czcionki możesz mieć znaki znacznie większe lub mniejsze niż g i M
omatase
1
@ellisbben warto zauważyć, że wyniki tego nieco różnią się od twoich, chociaż nie wiem dlaczego. Na przykład twój mówi Courier New 8pt ==> 12 pikseli wysokości, a to mówi: Courier New 8pt ==> 13 pikseli wysokości. Dodałem „g” do twojej metody, ale to nie była różnica. Można się zastanawiać, która wartość byłaby najbardziej użyteczna (niekoniecznie poprawna technicznie).
Orwellophile
3
Mogłem wszystko działać poprawnie tylko wtedy, gdy zmieniłem pierwszą linię getTextHeight()na var text = $('<span>Hg</span>').css({ 'font-family': fontName, 'font-size' : fontSize });, tj. Osobno dodając rozmiar.
cameron.bracken
1
Jak sprawić, by działało w przypadku tekstu innego niż angielski? patrz jsfiddle.net/siddjain/6vURk
morpheus
1
Dziękuję Ci ! zmodyfikować <div></div>do <div style="white-space : nowrap;"></div>obsługi bardzo długich strun
Mickaël Gauvin
40

Możesz uzyskać bardzo dokładne przybliżenie wysokości pionowej, sprawdzając długość dużej litery M.

ctx.font='bold 10px Arial';

lineHeight=ctx.measureText('M').width;
Vic Fanberg
źródło
8
W jaki sposób szerokość daje nam przybliżoną wysokość linii?
Richard Barker
11
Oznacza to, że szerokość pojedynczej dużej litery „M” przy danym rozmiarze czcionki jest mniej więcej taka sama jak wysokość wiersza. (Nie wiem, czy to prawda, ale to właśnie mówi odpowiedź)
Nathan
2
Jest to całkiem przyzwoite przybliżenie, którego używam do szybkiego prototypowania. Nie jest idealne, ale to 90% rozwiązanie.
Austin D,
37

Specyfikacja płótna nie daje nam metody pomiaru wysokości sznurka. Możesz jednak ustawić rozmiar tekstu w pikselach i zwykle możesz stosunkowo łatwo ustalić, jakie są pionowe granice.

Jeśli potrzebujesz czegoś bardziej precyzyjnego, możesz rzucić tekst na płótno, a następnie pobrać dane pikseli i dowiedzieć się, ile pikseli jest używanych w pionie. Byłoby to stosunkowo proste, ale niezbyt wydajne. Możesz zrobić coś takiego (to działa, ale rysuje na płótnie tekst, który chcesz usunąć):

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);

W przypadku Bespin fałszują wysokość, mierząc szerokość małej litery „m” ... Nie wiem, jak to jest używane i nie polecałbym tej metody. Oto odpowiednia metoda Bespin:

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; }
    }
};
Prestaul
źródło
28
Wątpię, żeby o tym myśleli ludzie, którzy napisali specyfikację HTML5.
Steve Hanov
17
To okropny, okropny hack, który bardzo mi się podoba. +1
Allain Lalonde
1
Nie rozumiem. Gdzie jest związek między podniesieniem czcionki a szerokością litery „m”?
kayahr
6
emjest względną miarą czcionki, w której jeden em jest równy wysokości litery Mw domyślnym rozmiarze czcionki.
jerone
1
Racja, wysokość, a nie szerokość ... Nadal jestem zdezorientowany co do połączenia. Myślę też, że ems są nieistotne, biorąc pod uwagę, że zależy nam tylko na wysokości w pikselach.
Prestaul
35

Przeglądarki zaczynają obsługiwać zaawansowane metryki tekstu , co sprawi, że to zadanie będzie trywialne, gdy będzie szeroko obsługiwane:

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

fontHeightpodaje wysokość obwiedni, która jest stała niezależnie od renderowanego ciągu. actualHeightjest specyficzne dla renderowanego ciągu.

Specyfikacja: https://www.w3.org/TR/2012/CR-2dcontext-20121217/#dom-textmetrics-fontboundingboxascent i sekcje tuż poniżej.

Stan wsparcia (20 sierpnia 2017 r.):

ZachB
źródło
2
Wszystkich prosimy o głosowanie na stronach błędów, aby te funkcje zostały wdrożone wcześniej
Jeremiah Rose,
21

EDYCJA: Czy używasz przekształceń płótna? Jeśli tak, będziesz musiał śledzić macierz transformacji. Poniższa metoda powinna mierzyć wysokość tekstu przy początkowej transformacji.

EDYCJA # 2: Dziwne, poniższy kod nie daje poprawnych odpowiedzi, gdy uruchamiam go na tej stronie StackOverflow; jest całkowicie możliwe, że obecność pewnych reguł stylu może zakłócić tę funkcję.

Płótno używa czcionek zdefiniowanych przez CSS, więc teoretycznie możemy po prostu dodać odpowiednio stylizowany fragment tekstu do dokumentu i zmierzyć jego wysokość. Myślę, że jest to znacznie łatwiejsze niż renderowanie tekstu, a następnie sprawdzanie danych pikseli, a także powinno uwzględniać wznoszenia i opadanie. Sprawdź następujące:

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.");
  }
}

Musisz upewnić się, że styl czcionki jest poprawny w elemencie DOM, którego wysokość mierzysz, ale to całkiem proste; naprawdę powinieneś użyć czegoś takiego jak

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.
*/
ellisbben
źródło
1
+1 Lepsze rozwiązanie IMO. Powinno być również możliwe uzyskanie pozycji linii bazowej.
Daniel Earwicker
Dodałem odpowiedź, która zawiera punkt odniesienia.
Daniel Earwicker
czy to działa? Nawet nie myślałem o umieszczeniu go w div. Prawdopodobnie nie trzeba tego nawet dodawać do DOMU, prawda?
beatgammit
Całkowicie nie wiem, jakie pola rozmiaru i pozycji węzła istnieją, gdy nie są one częścią dokumentu. Byłbym bardzo zainteresowany przeczytaniem odniesienia, które dotyczy tego, jeśli znasz jakiś.
ellisbben
5
+1 za ekran pełen skomplikowanego kodu, który byłby po prostu context.measureText (tekst) .height w równoległym wszechświecie z lepszym interfejsem API Canvas
rsp,
11

Czy wysokość tekstu w pikselach nie jest równa rozmiarowi czcionki (w punktach), jeśli zdefiniujesz czcionkę za pomocą context.font?

Bachalo
źródło
2
Oto, co twierdzi to źródło: html5canvastutorials.com/tutorials/html5-canvas-text-metrics
Rui Marques
w prostych przypadkach: zawsze możesz wydzielić wysokość z nazwy czcionki: parseInt (ctx.font.split ('') [0] .replace ('px', '')); // parsowanie ciągu: „10px Verdana”
Sean,
Możesz użyć px, pt, em i% jako rozmiaru czcionki. Właśnie dlatego ta odpowiedź jest myląca.
Jacksonkr
@Jacksonkr, tak, ale nadal możesz je przeanalizować i odpowiednio dostosować, prawda? Czy może jest gdzieś nieodłączne ograniczenie tego podejścia?
Pacerier
@Pacerier Ograniczeniem jest to, że możesz wprowadzić pewne błędy, które powodują wyrywanie włosów. Pamiętaj tylko, że typy jednostek mieszających mogą prowadzić do kodu błędnego / spaghetti. To powiedziawszy, nie jestem ponad sporadycznym hackowaniem, o ile ryzyko problemów jest niskie.
Jacksonkr
10

Jak sugeruje JJ Stiff, możesz dodać swój tekst do rozpiętości, a następnie zmierzyć przesunięcie Wysokość rozpiętości.

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);

Jak pokazano na HTML5Rocks

Matt Palmerlee
źródło
3
To bardzo fajne rozwiązanie, dziękuję ... ale nie wiem dlaczego, jeśli ten span nie został dodany do strony i widoczny, zanim otrzymam jego offset. zawsze zwraca wysokość jako ZERO w Chrome i Firefox!
Mustafah,
1
Masz rację, myślę, że trzeba to dodać do domeny, żeby zajęło miejsce. Oto JS Fiddle tego działania: jsfiddle.net/mpalmerlee/4NfVR/4 Zaktualizowałem również powyższy kod.
Matt Palmerlee,
Korzystanie z clientHeight jest również możliwe. Chociaż ta odpowiedź jest rozwiązaniem problemu, jest to brzydkie obejście. Mimo to dał +1.
Daniel F
Nie uwzględnia faktycznej wysokości widocznego tekstu i zwykle pojawia się z dodatkowym marginesem na górze tekstu…
Ain Tohvri
8

Aby dodać do odpowiedzi Daniela (co jest świetne! I absolutnie słuszne!), Wersja bez 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];
}

Właśnie przetestowałem powyższy kod i działa świetnie w najnowszym Chrome, FF i Safari na Macu.

EDYCJA: Dodałem również rozmiar czcionki i przetestowałem z czcionką internetową zamiast czcionką systemową - działa świetnie.

Sinisa
źródło
7

Rozwiązałem ten problem w prosty sposób - używając manipulacji pikselami.

Oto graficzna odpowiedź:

Oto kod:

    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;

}
Anton Putov
źródło
2
Uważam, że to rozwiązanie nie bierze pod uwagę kropkowanych liter, takich jak małe litery i i j
wusauarus
3

Piszę emulator terminala, więc musiałem rysować prostokąty wokół znaków.

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

Oczywiście, jeśli chcesz dokładnej ilości miejsca zajmowanego przez postać, to nie pomoże. Ale da ci to dobre przybliżenie dla pewnych zastosowań.


źródło
3

Oto prosta funkcja. Nie potrzeba żadnej biblioteki.

Napisałem tę funkcję, aby uzyskać górne i dolne granice względem linii bazowej. Jeśli textBaselinejest ustawiona na alphabetic. Tworzy kolejne płótno, a następnie rysuje je, a następnie znajduje najwyższy i najniższy niepusty piksel. I to są górne i dolne granice. Zwraca ją jako względną, więc jeśli wysokość wynosi 20 pikseli i nie ma nic poniżej linii bazowej, to górna granica to -20.

Musisz dostarczyć do niego postacie. W przeciwnym razie da ci to oczywiście 0 wysokości i 0 szerokości.

Stosowanie:

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

Oto funkcja:

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, relativeTopi heightsą użytecznymi rzeczami w zwracanym obiekcie.

Oto przykładowe użycie:

<!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>

relativeBotI relativeTopto, co widzisz w tym obrazie tutaj:

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

Noitidart
źródło
3

odpowiedź w jednej linii

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

CSS „line-height: normal” mieści się w zakresie od 1 do 1,2

przeczytaj tutaj, aby uzyskać więcej informacji

Durgpal Singh
źródło
2

Oto, co zrobiłem na podstawie niektórych innych odpowiedzi tutaj:

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));

Raz siłowałem się z niedźwiedziem.
źródło
1

Przede wszystkim należy ustawić wysokość rozmiaru czcionki, a następnie zgodnie z wartością wysokości czcionki, aby określić aktualną wysokość Twojego tekstu, to ile, przecinające się linie tekstu, oczywiście, taka sama wysokość czcionka musi się kumulować, jeśli tekst nie przekracza największej wysokości pola tekstowego, wszystkie pokazują, w przeciwnym razie pokazują tylko tekst w tekście ramki. Wysokie wartości wymagają własnej definicji. Im większa wstępnie ustawiona wysokość, tym większa wysokość tekstu, który należy wyświetlić i przechwycić.

Po przetworzeniu efektu (rozwiązaniu)

Zanim efekt zostanie przetworzony (nierozwiązany)

  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;};
kiss_von
źródło
1

Odkryłem, że JUST FOR ARIAL najprostszym, najszybszym i najdokładniejszym sposobem na określenie wysokości ramki granicznej jest użycie szerokości określonych liter. Jeśli planujesz użyć określonej czcionki, nie pozwalając użytkownikowi na wybranie innej, możesz przeprowadzić trochę badań, aby znaleźć właściwą literę, która będzie działać dla tej czcionki.

<!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>

tmx976
źródło
0

ustawienie rozmiaru czcionki może jednak nie być praktyczne, ponieważ ustawienie

ctx.font = ''

użyje tego zdefiniowanego przez CSS, jak również wszelkich osadzonych tagów czcionek. Jeśli używasz czcionki CSS, nie masz pojęcia, jaka jest wysokość w sposób programistyczny, używając metody MeasureText, która jest bardzo krótkowzroczna. Z drugiej jednak strony IE8 zwraca szerokość i wysokość.


źródło
0

Działa to 1) również w przypadku tekstu wielowierszowego 2), a nawet w 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};
};
Andrey
źródło
0

Wiem, że jest to stare pytanie, na które udzielono odpowiedzi, ale na przyszłość chciałbym dodać krótkie, minimalne, tylko w JS (bez jquery) rozwiązanie. Wierzę, że ludzie mogą skorzystać z:

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;
};
Ronen Ness
źródło
-3

W normalnych sytuacjach powinny działać:

var can = CanvasElement.getContext('2d');          //get context
var lineHeight = /[0-9]+(?=pt|px)/.exec(can.font); //get height from font variable
Lodewijk
źródło
5
Całkowicie i całkowicie błędne. Istnieje OGROMNA różnica między wielkością punktu a rozmiarem piksela. Rozmiar punktu skutkuje tekstem o różnym rozmiarze w zależności od DPI, w którym strona jest renderowana, gdzie - jako rozmiar piksela nie bierze tego pod uwagę.
Orvid King
2
Czy znasz pojęcie pikseli ekranu? Możesz uznać to za pouczające. Niezależnie od tego, pt i px rzeczywiście wskazują różne wysokości. Warto zauważyć, że czcionka może wypełniać mniej niż „wysokość”, a przynajmniej więcej. Nie jestem pewien, czy piksele płótna są skalowane, ale zakładam, że tak. Jest to jednak „zła” odpowiedź. Jest prosta i może być używana w wielu sytuacjach.
Lodewijk
-4

To szalone ... Wysokość tekstu to rozmiar czcionki .. Czy nikt z Was nie czytał dokumentacji?

context.font = "22px arial";

spowoduje to ustawienie wysokości na 22 piksele.

jedynym powodem jest ...

context.measureText(string).width

wynika z tego, że szerokość łańcucha nie może zostać określona, ​​chyba że zna łańcuch, którego szerokość chcesz, ale dla wszystkich ciągów narysowanych czcionką ... wysokość będzie wynosić 22 piksele.

jeśli użyjesz innego pomiaru niż px, wysokość nadal będzie taka sama, ale z tym pomiarem, więc co najwyżej wszystko, co musisz zrobić, to przekonwertować pomiar.

user3283552
źródło
Niektóre litery mogą wykraczać poza lub poniżej limitów, zobacz whatwg.org/specs/web-apps/current-work/images/baselines.png
Octavia Togami
1
Źle sformułowane, ale zwykle prawdziwe.
Lodewijk
przeczytaj wszystkie te odpowiedzi, ale w przypadku mojej prostej aplikacji to była poprawna odpowiedź, pt === px
rob
Po prostu całkowicie niepoprawne. Wypróbuj niektóre z proponowanych rozwiązań z różnymi czcionkami, a zauważysz duże rozbieżności.
Dale Anderson
-4

Przybliżone rozwiązanie:

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;

Będzie to dokładny wynik w czcionce o stałej szerokości.

sonichy
źródło