-
Notifications
You must be signed in to change notification settings - Fork 552
Description
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
- Load a monospaced font (e.g., RobotoMono):
import { useFont } from '@shopify/react-native-skia';
const font = useFont(require('./assets/RobotoMono_700Bold.ttf'), 48);
- 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
- 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
- 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,
},
});