diff --git a/src/font.ts b/src/font.ts index cf03a395..abb25da0 100644 --- a/src/font.ts +++ b/src/font.ts @@ -183,7 +183,7 @@ export default class FontLoader { public getEngine( fontSize = 16, - lineHeight = 1.2, + lineHeight: number | string = 'normal', { fontFamily = 'sans-serif', fontWeight = 400, @@ -314,6 +314,7 @@ export default class FontLoader { resolvedFont.ascender return (_ascender / resolvedFont.unitsPerEm) * fontSize } + const descender = (resolvedFont: opentype.Font, useOS2Table = false) => { const _descender = (useOS2Table ? resolvedFont.tables?.os2?.sTypoDescender : 0) || @@ -321,6 +322,20 @@ export default class FontLoader { return (_descender / resolvedFont.unitsPerEm) * fontSize } + const height = (resolvedFont: opentype.Font, useOS2Table = false) => { + if ('string' === typeof lineHeight && 'normal' === lineHeight) { + const _lineGap = + (useOS2Table ? resolvedFont.tables?.os2?.sTypoLineGap : 0) || 0 + return ( + ascender(resolvedFont, useOS2Table) - + descender(resolvedFont, useOS2Table) + + (_lineGap / resolvedFont.unitsPerEm) * fontSize + ) + } else if ('number' === typeof lineHeight) { + return fontSize * lineHeight + } + } + const resolve = (s: string) => { return resolveFont(s, false) } @@ -340,31 +355,17 @@ export default class FontLoader { s?: string, resolvedFont = typeof s === 'undefined' ? fonts[0] : resolveFont(s) ) => { - // https://www.w3.org/TR/css-inline-3/#css-metrics - // https://www.w3.org/TR/CSS2/visudet.html#leading - // Note. It is recommended that implementations that use OpenType or - // TrueType fonts use the metrics "sTypoAscender" and "sTypoDescender" - // from the font's OS/2 table for A and D (after scaling to the current - // element's font size). In the absence of these metrics, the "Ascent" - // and "Descent" metrics from the HHEA table should be used. - const A = ascender(resolvedFont, true) - const D = descender(resolvedFont, true) - const glyphHeight = engine.height(s, resolvedFont) - const { yMax, yMin } = resolvedFont.tables.head - - const sGlyphHeight = A - D - const baselineOffset = (yMax / (yMax - yMin) - 1) * sGlyphHeight - - return glyphHeight * ((1.2 / lineHeight + 1) / 2) + baselineOffset + const asc = ascender(resolvedFont) + const desc = descender(resolvedFont) + const contentHeight = asc - desc + + return asc + (height(resolvedFont) - contentHeight) / 2 }, height: ( s?: string, resolvedFont = typeof s === 'undefined' ? fonts[0] : resolveFont(s) ) => { - return ( - (ascender(resolvedFont) - descender(resolvedFont)) * - (lineHeight / 1.2) - ) + return height(resolvedFont) }, measure: ( s: string, diff --git a/src/handler/expand.ts b/src/handler/expand.ts index 92ba9d9f..380fb334 100644 --- a/src/handler/expand.ts +++ b/src/handler/expand.ts @@ -233,7 +233,7 @@ type MainStyle = { whiteSpace: string wordBreak: string textAlign: string - lineHeight: number + lineHeight: number | string letterSpacing: number fontFamily: string | string[] @@ -352,7 +352,7 @@ export default function expand( // Line height needs to be relative. if (prop === 'lineHeight') { - if (typeof value === 'string') { + if (typeof value === 'string' && value !== 'normal') { value = serializedStyle[prop] = lengthToNumber( value, diff --git a/src/satori.ts b/src/satori.ts index a6a9b9cd..61f2292a 100644 --- a/src/satori.ts +++ b/src/satori.ts @@ -100,7 +100,7 @@ export default async function satori( fontWeight: 'normal', fontFamily: 'serif', fontStyle: 'normal', - lineHeight: 1.2, + lineHeight: 'normal', color: 'black', opacity: 1, whiteSpace: 'normal',