/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.react.views.text import android.content.Context import android.graphics.Rect import android.text.Layout import android.text.TextPaint import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableArray public object FontMetricsUtil { private const val CAP_HEIGHT_MEASUREMENT_TEXT = "T" private const val X_HEIGHT_MEASUREMENT_TEXT = "x" private const val AMPLIFICATION_FACTOR = 100f @JvmStatic public fun getFontMetrics( text: CharSequence, layout: Layout, paint: TextPaint, context: Context ): WritableArray { val dm = context.resources.displayMetrics val lines = Arguments.createArray() // To calculate xHeight and capHeight we have to render an "x" and "T" and manually measure // their height. In order to get more precision than Android offers, we blow up the text size by // 100 and // measure it. Luckily, text size affects rendering linearly, so we can do this trick. val paintCopy = TextPaint(paint).apply { textSize *= AMPLIFICATION_FACTOR } val capHeightBounds = Rect() paintCopy.getTextBounds( CAP_HEIGHT_MEASUREMENT_TEXT, 0, CAP_HEIGHT_MEASUREMENT_TEXT.length, capHeightBounds) val capHeight = capHeightBounds.height() / AMPLIFICATION_FACTOR / dm.density val xHeightBounds = Rect() paintCopy.getTextBounds( X_HEIGHT_MEASUREMENT_TEXT, 0, X_HEIGHT_MEASUREMENT_TEXT.length, xHeightBounds) val xHeight = xHeightBounds.height() / AMPLIFICATION_FACTOR / dm.density for (i in 0 until layout.lineCount) { val endsWithNewLine = text.isNotEmpty() && text[layout.getLineEnd(i) - 1] == '\n' val lineWidth = if (endsWithNewLine) layout.getLineMax(i) else layout.getLineWidth(i) val bounds = Rect() layout.getLineBounds(i, bounds) val line = Arguments.createMap().apply { putDouble("x", (layout.getLineLeft(i) / dm.density).toDouble()) putDouble("y", (bounds.top / dm.density).toDouble()) putDouble("width", (lineWidth / dm.density).toDouble()) putDouble("height", (bounds.height() / dm.density).toDouble()) putDouble("descender", (layout.getLineDescent(i) / dm.density).toDouble()) putDouble("ascender", (-layout.getLineAscent(i) / dm.density).toDouble()) putDouble("baseline", (layout.getLineBaseline(i) / dm.density).toDouble()) putDouble("capHeight", capHeight.toDouble()) putDouble("xHeight", xHeight.toDouble()) putString( "text", text.subSequence(layout.getLineStart(i), layout.getLineEnd(i)).toString()) } lines.pushMap(line) } return lines } }