font-measure
Advanced tools
Comparing version
194
index.js
@@ -5,22 +5,46 @@ 'use strict' | ||
var canvas = measure.canvas = document.createElement('canvas') | ||
var ctx = canvas.getContext('2d') | ||
var chars = { | ||
upper: 'H', | ||
lower: 'x', | ||
descent: 'p', | ||
ascent: 'h', | ||
tittle: 'i', | ||
overshoot: 'O' | ||
} | ||
measure.canvas = document.createElement('canvas') | ||
measure.cache = {} | ||
var l = canvas.height = 100 | ||
canvas.width = l * .5 | ||
var fs = 64 | ||
function measure (font, o) { | ||
if (!o) o = {} | ||
if (typeof font === 'string' || Array.isArray(font)) { | ||
o.family = font | ||
} | ||
function measure (family) { | ||
family = Array.isArray(family) ? family.join(', ') : family | ||
ctx.font = fs + 'px ' + family | ||
var family = Array.isArray(o.family) ? o.family.join(', ') : o.family | ||
if (!family) throw Error('`family` must be defined') | ||
var fs = o.size || o.fontSize || o.em || 48 | ||
var font = fs + 'px ' + family | ||
var origin = o.origin || 'top' | ||
if (measure.cache[family]) { | ||
// return more precise values if cache has them | ||
if (fs <= measure.cache[family].em) { | ||
return origin === 'top' ? measure.cache[family] : applyOrigin(measure.cache[family], origin) | ||
} | ||
} | ||
var canvas = o.canvas || measure.canvas | ||
var ctx = canvas.getContext('2d') | ||
var chars = { | ||
upper: o.upper !== undefined ? o.upper : 'H', | ||
lower: o.lower !== undefined ? o.lower : 'x', | ||
descent: o.descent !== undefined ? o.descent : 'p', | ||
ascent: o.ascent !== undefined ? o.ascent : 'h', | ||
tittle: o.tittle !== undefined ? o.tittle : 'i', | ||
overshoot: o.overshoot !== undefined ? o.overshoot : 'O' | ||
} | ||
var l = Math.ceil(fs * 1.5) | ||
canvas.height = l | ||
canvas.width = l * .5 | ||
ctx.font = font | ||
var char = 'H' | ||
var result = { | ||
top: 0 | ||
} | ||
// measure line-height | ||
@@ -30,9 +54,10 @@ ctx.clearRect(0, 0, l, l) | ||
ctx.fillStyle = 'black' | ||
ctx.fillText(chars.upper, 0, 0) | ||
ctx.fillText(char, 0, 0) | ||
var topPx = firstTop(ctx.getImageData(0, 0, l, l)) | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'bottom' | ||
ctx.fillText(chars.upper, 0, l) | ||
ctx.fillText(char, 0, l) | ||
var bottomPx = firstTop(ctx.getImageData(0, 0, l, l)) | ||
var lineHeight = l - bottomPx + topPx | ||
result.lineHeight = | ||
result.bottom = l - bottomPx + topPx | ||
@@ -42,5 +67,7 @@ // measure baseline | ||
ctx.textBaseline = 'alphabetic' | ||
ctx.fillText(chars.upper, 0, l) | ||
ctx.fillText(char, 0, l) | ||
var baselinePx = firstTop(ctx.getImageData(0, 0, l, l)) | ||
var baseline = l - baselinePx + topPx | ||
var baseline = l - baselinePx - 1 + topPx | ||
result.baseline = | ||
result.alphabetic = baseline | ||
@@ -50,5 +77,6 @@ // measure median | ||
ctx.textBaseline = 'middle' | ||
ctx.fillText(chars.upper, 0, l * .5) | ||
ctx.fillText(char, 0, l * .5) | ||
var medianPx = firstTop(ctx.getImageData(0, 0, l, l)) | ||
var median = l - medianPx + topPx - l * .5 | ||
result.median = | ||
result.middle = l - medianPx - 1 + topPx - l * .5 | ||
@@ -58,5 +86,5 @@ // measure hanging | ||
ctx.textBaseline = 'hanging' | ||
ctx.fillText(chars.upper, 0, l * .5) | ||
ctx.fillText(char, 0, l * .5) | ||
var hangingPx = firstTop(ctx.getImageData(0, 0, l, l)) | ||
var hanging = l - hangingPx + topPx - l * .5 | ||
result.hanging = l - hangingPx - 1 + topPx - l * .5 | ||
@@ -66,82 +94,82 @@ // measure ideographic | ||
ctx.textBaseline = 'ideographic' | ||
ctx.fillText(chars.upper, 0, l) | ||
var ideographicH = firstTop(ctx.getImageData(0, 0, l, l)) | ||
var ideographic = l - hangingPx + topPx | ||
ctx.fillText(char, 0, l) | ||
var ideographicPx = firstTop(ctx.getImageData(0, 0, l, l)) | ||
result.ideographic = l - ideographicPx - 1 + topPx | ||
// measure cap | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.upper, 0, 0) | ||
var upper = firstTop(ctx.getImageData(0, 0, l, l)) | ||
if (chars.upper) { | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.upper, 0, 0) | ||
result.upper = firstTop(ctx.getImageData(0, 0, l, l)) | ||
result.capHeight = (result.baseline - result.upper) | ||
} | ||
// measure x | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.lower, 0, 0) | ||
var lower = firstTop(ctx.getImageData(0, 0, l, l)) | ||
if (chars.lower) { | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.lower, 0, 0) | ||
result.lower = firstTop(ctx.getImageData(0, 0, l, l)) | ||
result.xHeight = (result.baseline - result.lower) | ||
} | ||
// measure tittle | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.tittle, 0, 0) | ||
var tittle = firstTop(ctx.getImageData(0, 0, l, l)) | ||
if (chars.tittle) { | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.tittle, 0, 0) | ||
result.tittle = firstTop(ctx.getImageData(0, 0, l, l)) | ||
} | ||
// measure ascent | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.ascent, 0, 0) | ||
var ascent = firstTop(ctx.getImageData(0, 0, l, l)) | ||
if (chars.ascent) { | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.ascent, 0, 0) | ||
result.ascent = firstTop(ctx.getImageData(0, 0, l, l)) | ||
} | ||
// measure descent | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.descent, 0, 0) | ||
var descent = l - firstBottom(ctx.getImageData(0, 0, l, l)) | ||
if (chars.descent) { | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.descent, 0, 0) | ||
result.descent = firstBottom(ctx.getImageData(0, 0, l, l)) | ||
} | ||
// measure overshoot | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.overshoot, 0, 0) | ||
var overshootPx = firstBottom(ctx.getImageData(0, 0, l, l)) | ||
var overshoot = overshootPx - baseline | ||
var result = { | ||
top: 0, | ||
bottom: lineHeight, | ||
lineHeight: lineHeight, | ||
baseline: baseline, | ||
alphabetic: baseline, | ||
xHeight: (baseline - lower), | ||
capHeight: (baseline - upper), | ||
lower: lower, | ||
upper: upper, | ||
ascent: ascent, | ||
descent: descent, | ||
tittle: tittle, | ||
overshoot: overshoot, | ||
ideographic: ideographic, | ||
hanging: hanging, | ||
median: median, | ||
middle: median | ||
if (chars.overshoot) { | ||
ctx.clearRect(0, 0, l, l) | ||
ctx.textBaseline = 'top' | ||
ctx.fillText(chars.overshoot, 0, 0) | ||
var overshootPx = firstBottom(ctx.getImageData(0, 0, l, l)) | ||
result.overshoot = overshootPx - baseline | ||
} | ||
var origin = 'top' | ||
var originValue = result[origin] | ||
// normalize result | ||
for (var name in result) { | ||
result[name] = result[name] - originValue | ||
result[name] /= fs | ||
} | ||
return result | ||
result.em = fs | ||
measure.cache[family] = result | ||
return applyOrigin(result, origin) | ||
} | ||
function applyOrigin(obj, origin) { | ||
var res = {} | ||
if (typeof origin === 'string') origin = obj[origin] | ||
for (var name in obj) { | ||
if (name === 'em') continue | ||
res[name] = obj[name] - origin | ||
} | ||
return res | ||
} | ||
function firstTop(iData) { | ||
var l = iData.height | ||
var data = iData.data | ||
for (var i = 3; i < data.length; i+=4) { | ||
var px = data[i] | ||
if (data[i] !== 0) { | ||
@@ -154,5 +182,5 @@ return Math.floor((i - 3) *.25 / l) | ||
function firstBottom(iData) { | ||
var l = iData.height | ||
var data = iData.data | ||
for (var i = data.length - 1; i > 0; i -= 4) { | ||
var px = data[i] | ||
if (data[i] !== 0) { | ||
@@ -159,0 +187,0 @@ return Math.floor((i - 3) *.25 / l) |
{ | ||
"name": "font-measure", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Calculate metrics of a font", | ||
@@ -38,4 +38,4 @@ "main": "index.js", | ||
"dependencies": { | ||
"css-font": "^1.0.0" | ||
"object-assign": "^4.1.1" | ||
} | ||
} |
@@ -15,18 +15,18 @@ # font-measure [](http://github.com/badges/stability-badges) | ||
top: 0, | ||
median: 0.640625, | ||
middle: 0.640625, | ||
bottom: 1.3125, | ||
alphabetic: 1.03125, | ||
baseline: 1.03125, | ||
upper: 0.328125, | ||
lower: 0.515625, | ||
upper: 0.328125, | ||
xHeight: 0.53125, | ||
alphabetic: 1.046875, | ||
baseline: 1.046875, | ||
capHeight: 0.71875, | ||
median: 0.65625, | ||
middle: 0.65625, | ||
capHeight: 0.703125, | ||
xHeight: 0.515625 | ||
ascent: 0.28125, | ||
descent: 0.328125, | ||
hanging: 0.21875, | ||
ideographic: 1, | ||
descent: 1.234375, | ||
hanging: 0.203125, | ||
ideographic: 1.296875, | ||
lineHeight: 1.3125, | ||
overshoot: 0.015625, | ||
tittle: 0.28125, | ||
overshoot: 0 | ||
} | ||
@@ -39,9 +39,9 @@ */ | ||
### `metrics = measure(font|options)` | ||
### `let metrics = measure(font, options?)` | ||
Get metrics data for a font or custom options. | ||
Get metrics data for a font, possibly with custom options. Font can be a string or an array with fonts. | ||
#### `metrics`: | ||
<img src="./sphinx.svg" width="720"/> | ||
<img src="https://github.com/dy/font-measure/raw/master/sphinx.svg?sanitize=true" width="720"/> | ||
@@ -53,12 +53,11 @@ | ||
---|---|--- | ||
`family` | `null` | Font-family to detect metrics. Can be a string or an array (fontstack). | ||
`origin` | `top` | Origin for metrics. Can be changed to `baseline` or any other metric. | ||
`size` | `64` | Font-size to use for calculations. Larger size gives higher precision with slower performance. | ||
`canvas` | `measure.canvas` | Canvas to use for measurements. | ||
`tittle` | `i` | Character to detect tittle. Null value ignores calculation. | ||
`descent` | `p` | Character to detect descent line. Null value ignores calculation. | ||
`ascent` | `h` | Character to detect ascent line. Null value ignores calculation. | ||
`overshoot` | `O` | Character to detect overshoot. Null value ignores calculation. | ||
`upper` | `H` | Character to detect upper line / cap-height. Null value ignores calculation. | ||
`lower` | `x` | Character to detect lower line / x-height. Null value ignores calculation. | ||
`tittle` | `i` | Character to detect tittle. `null` disables calculation. | ||
`descent` | `p` | Character to detect descent line. `null` disables calculation. | ||
`ascent` | `h` | Character to detect ascent line. `null` disables calculation. | ||
`overshoot` | `O` | Character to detect overshoot. `null` disables calculation. | ||
`upper` | `H` | Character to detect upper line / cap-height. `null` disables calculation. | ||
`lower` | `x` | Character to detect lower line / x-height. `null` disables calculation. | ||
@@ -65,0 +64,0 @@ |
35
test.js
@@ -6,20 +6,23 @@ 'use strict' | ||
a.deepEqual(m('Roboto'), { | ||
top: 0, | ||
let fix = { | ||
alphabetic: 1.03125, | ||
ascent: 0.28125, | ||
baseline: 1.03125, | ||
bottom: 1.3125, | ||
capHeight: 0.703125, | ||
descent: 1.234375, | ||
hanging: 0.203125, | ||
ideographic: 1.296875, | ||
lineHeight: 1.3125, | ||
lower: 0.515625, | ||
median: 0.640625, | ||
middle: 0.640625, | ||
overshoot: 0.015625, | ||
tittle: 0.28125, | ||
top: 0, | ||
upper: 0.328125, | ||
xHeight: 0.53125, | ||
alphabetic: 1.046875, | ||
baseline: 1.046875, | ||
capHeight: 0.71875, | ||
median: 0.65625, | ||
middle: 0.65625, | ||
ascent: 0.28125, | ||
descent: 0.328125, | ||
hanging: 0.21875, | ||
ideographic: 1, | ||
lineHeight: 1.3125, | ||
tittle: 0.28125, | ||
overshoot: 0 | ||
}) | ||
xHeight: 0.515625 | ||
} | ||
a.deepEqual(m('Roboto', {fontSize: 64}), fix) | ||
a.equal(m('Roboto', {fontSize: 64, origin: 'baseline'}).baseline, 0) |
225
15.38%9880
-88.24%5
-16.67%77
-1.28%+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed