Skip to content

measureText() returns inconsistent widths for monospaced font characters, unlike getTextWidth() #3488

@bj97301

Description

@bj97301

Description

When using measureText() on a monospaced font (RobotoMono in my case), the returned width varies depending on the character being measured, even though monospaced fonts are designed to have uniform character widths. This causes issues when trying to calculate the width of dynamic text by measuring a single character and multiplying by the string length.

Expected Behavior

For monospaced fonts, font.measureText('W').width should equal font.measureText('i').width since all characters should have the same width.

Actual Behavior

font.measureText('W').width returns a significantly larger value than font.measureText('i').width, making it impossible to reliably calculate text width by measuring a single character.

Workaround

Using font.getTextWidth(text) instead of font.measureText(text).width produces correct, consistent results for monospaced fonts.

Code Example

import { useFont } from '@shopify/react-native-skia';

const font = useFont(require('./assets/RobotoMono_700Bold.ttf'), 48);

// Inconsistent results with measureText:
const widthW = font.measureText('W').width;  // Returns larger width
const widthI = font.measureText('i').width;  // Returns smaller width
// widthW !== widthI (but should be equal for monospaced font)

// Correct results with getTextWidth:
const correctWidthW = font.getTextWidth('W');  // Returns correct uniform width
const correctWidthI = font.getTextWidth('i');  // Returns correct uniform width
// correctWidthW === correctWidthI (as expected)

Environment

  • react-native-reanimated: 3.16.0
  • @shopify/react-native-skia: 1.5.8
  • React Native: (Expo dev client)
  • Platform: iOS/Android
  • Font: RobotoMono_700Bold.ttf (monospaced font)

React Native Skia Version

1.5.8

React Native Version

0.79.5

Using New Architecture

  • Enabled

Steps to Reproduce

Steps to Reproduce

  1. Load a monospaced font (e.g., RobotoMono):

import { useFont } from '@shopify/react-native-skia';

const font = useFont(require('./assets/RobotoMono_700Bold.ttf'), 48);

  1. Measure the width of different characters using measureText:

const widthW = font.measureText('W').width;
const widthI = font.measureText('i').width;
const width0 = font.measureText('0').width;

console.log('measureText W:', widthW); // e.g., 32.5
console.log('measureText i:', widthI); // e.g., 24.8
console.log('measureText 0:', width0); // e.g., 28.9
// All should be equal for monospaced font, but they're not

  1. Compare with getTextWidth which returns correct, consistent widths:

const correctWidthW = font.getTextWidth('W');
const correctWidthI = font.getTextWidth('i');
const correctWidth0 = font.getTextWidth('0');

console.log('getTextWidth W:', correctWidthW); // e.g., 28.8
console.log('getTextWidth i:', correctWidthI); // e.g., 28.8
console.log('getTextWidth 0:', correctWidth0); // e.g., 28.8
// All correctly equal for monospaced font

  1. The issue manifests when trying to center dynamic text, as the calculated width using measureText varies depending on which character is displayed, causing the text to shift position horizontally as the value changes.

Snack, Code Example, Screenshot, or Link to Repository

import React from 'react';
import { Canvas, Text, useFont } from '@shopify/react-native-skia';
import { View, StyleSheet } from 'react-native';

export const MonospacedFontBugDemo = () => {
  const font = useFont(require('./assets/RobotoMono_700Bold.ttf'), 48);

  if (!font) return null;

  const widthW = font.measureText('W').width;
  const widthI = font.measureText('i').width;
  const width0 = font.measureText('0').width;

  const correctWidthW = font.getTextWidth('W');
  const correctWidthI = font.getTextWidth('i');
  const correctWidth0 = font.getTextWidth('0');

  console.log('measureText results (should be equal for monospaced font):');
  console.log('  W:', widthW);
  console.log('  i:', widthI);
  console.log('  0:', width0);
  console.log('getTextWidth results (correctly equal):');
  console.log('  W:', correctWidthW);
  console.log('  i:', correctWidthI);
  console.log('  0:', correctWidth0);

  return (
    <View style={styles.container}>
      <Canvas style={styles.canvas}>
        <Text x={100} y={50} text="W" font={font} />
        <Text x={100} y={100} text="i" font={font} />
        <Text x={100} y={150} text="0" font={font} />
      </Canvas>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  canvas: {
    width: 300,
    height: 300,
  },
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions