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
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,
},
});
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').widthshould equalfont.measureText('i').widthsince all characters should have the same width.Actual Behavior
font.measureText('W').widthreturns a significantly larger value thanfont.measureText('i').width, making it impossible to reliably calculate text width by measuring a single character.Workaround
Using
font.getTextWidth(text)instead offont.measureText(text).widthproduces correct, consistent results for monospaced fonts.Code Example
Environment
React Native Skia Version
1.5.8
React Native Version
0.79.5
Using New Architecture
Steps to Reproduce
Steps to Reproduce
import { useFont } from '@shopify/react-native-skia';
const font = useFont(require('./assets/RobotoMono_700Bold.ttf'), 48);
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
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
Snack, Code Example, Screenshot, or Link to Repository