最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - Font Family is not applying to canvas in React Native Expo - Stack Overflow

programmeradmin1浏览0评论

So, I used react-native-canvas library to use canvas on my React Native project, everything went very good but when it comes to selecting of font family's, like for example pacifico or anything like that, it was not applying to my canvas, but on my _layout.tsx, I actually specifically installed a library called as:

import { Pacifico_400Regular } from "@expo-google-fonts/pacifico";

and I loaded fonts as well in Expo, here is my code:

_layout.tsx:

import { SplashScreen, Stack } from "expo-router";
import "./globals.css";
import { useFonts } from "expo-font";
import { useEffect } from "react";
import { ClerkProvider, ClerkLoaded, ClerkLoading } from "@clerk/clerk-expo";
import { tokenCache } from "@/lib/auth";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { Alert, Text } from "react-native";
import CustomLoader from "@/components/CustomLoader";
import { StripeProvider } from "@stripe/stripe-react-native";
import { Pacifico_400Regular } from "@expo-google-fonts/pacifico";

export default function RootLayout() {
  const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!;

  if (!publishableKey) {
    throw new Error("Add EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY to your .env file");
  }
  const [fontsLoaded] = useFonts({
    Pacifico: Pacifico_400Regular,
    "Rubik-Bold": require("../assets/fonts/Rubik-Bold.ttf"),
    "Rubik-ExtraBold": require("../assets/fonts/Rubik-ExtraBold.ttf"),
    "Rubik-Medium": require("../assets/fonts/Rubik-Medium.ttf"),
    "Rubik-Light": require("../assets/fonts/Rubik-Light.ttf"),
    "Rubik-Regular": require("../assets/fonts/Rubik-Regular.ttf"),
    "Rubik-SemiBold": require("../assets/fonts/Rubik-SemiBold.ttf"),
  });
  useEffect(() => {
    if (!process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY) {
      console.error("Missing Clerk Publishable Key!");
      Alert.alert("Configuration Error", "Missing Clerk credentials");
    }
  }, []);
  useEffect(() => {
    if (fontsLoaded) {
      SplashScreen.hideAsync();
    }
  }, [fontsLoaded]);
  if (!fontsLoaded) {
    return null;
  }
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <StripeProvider
        publishableKey={process.env.EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY!}
      >
        <ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
          <ClerkLoaded>
            <Stack screenOptions={{ headerShown: false }} />
          </ClerkLoaded>
          <ClerkLoading>
            {/* Optional: Loading indicator or splash screen alternative */}
            <CustomLoader loading={true} />
          </ClerkLoading>
        </ClerkProvider>
      </StripeProvider>
    </GestureHandlerRootView>
  );
}

Yes, it worked when I used it in my Expo screens, but not in canvas!, why? here is my code:

constants/fonts.ts:

export const fontConfig: any = {
  Arial: { isPremium: false },
  Helvetica: { isPremium: false },
  Pacifico: { isPremium: false },
  Montserrat: { isPremium: true },
  "Playfair Display": { isPremium: true },
  // Add other fonts...
};

export const freeFonts = Object.keys(fontConfig).filter(
  (font) => !fontConfig[font].isPremium
);
export const premiumFonts = Object.keys(fontConfig).filter(
  (font) => fontConfig[font].isPremium
);

Font Picker Modal:

          <Modal
            visible={showFontPicker}
            transparent={true}
            animationType="slide"
          >
            <View className="flex-1 justify-center items-center bg-black/50">
              <View className="bg-white p-4 rounded-lg w-80 max-h-[80%]">
                <Text className="text-lg font-bold mb-4">
                  Select Font
                </Text>

                <ScrollView>
                  {/* Free Fonts Section */}
                  <Text className="text-gray-700 font-semibold mb-2">
                    Free Fonts
                  </Text>
                  {freeFonts.map((font) => (
                    <TouchableOpacity
                      key={font}
                      onPress={() => {
                        updateLayer(selectedLayer.id, {
                          fontFamily: font,
                        });
                        setSelectedFont(font);
                        setShowFontPicker(false);
                      }}
                      className="p-2"
                    >
                      <Text style={{ fontFamily: font }}>{font}</Text>
                    </TouchableOpacity>
                  ))}

                  {/* Premium Fonts Section */}
                  <Text className="text-gray-700 font-semibold mt-4 mb-2">
                    Premium Fonts
                  </Text>
                  {premiumFonts.map((font) => (
                    <TouchableOpacity
                      key={font}
                      onPress={() => {
                        if (plan !== "PRO") {
                          Alert.alert(
                            "Premium Feature",
                            "Upgrade to access premium fonts"
                          );
                          return;
                        }
                        updateLayer(selectedLayer.id, {
                          fontFamily: font,
                        });
                        setSelectedFont(font);
                        setShowFontPicker(false);
                      }}
                      className="p-2"
                      disabled={plan !== "PRO"} // Disable for free users
                    >
                      <Text
                        style={{
                          fontFamily: font,
                          color: plan !== "PRO" ? "#ccc" : "#000", // Gray out for free users
                        }}
                      >
                        {font}
                      </Text>
                    </TouchableOpacity>
                  ))}
                </ScrollView>

                {/* Close Button */}
                <TouchableOpacity
                  onPress={() => setShowFontPicker(false)}
                  className="mt-4 p-2 bg-gray-200 rounded"
                >
                  <Text>Close</Text>
                </TouchableOpacity>
              </View>
            </View>
          </Modal>

Canvas Rendering Logic:

  const drawOnCanvas = useCallback(
    async (layers: TextLayer[]) => {
      if (!canvasRef.current || !imageUrl || !processedImageurl) return;

      try {
        const ctx = canvasRef.current.getContext("2d");
        const img = new CanvasImage(canvasRef.current);
        const img_2 = new CanvasImage(canvasRef.current);

        canvasRef.current.width = screenWidth;
        canvasRef.current.height = canvasHeight;

        const [originalBase64, processedBase64] = await Promise.all([
          FileSystem.readAsStringAsync(imageUrl, {
            encoding: FileSystem.EncodingType.Base64,
          }),
          FileSystem.readAsStringAsync(processedImageurl, {
            encoding: FileSystem.EncodingType.Base64,
          }),
        ]);

        const imgLoaded = new Promise((resolve) =>
          img.addEventListener("load", resolve)
        );
        const img2Loaded = new Promise((resolve) =>
          img_2.addEventListener("load", resolve)
        );

        img.src = `data:image/png;base64,${processedBase64}`;
        img_2.src = `data:image/png;base64,${originalBase64}`;

        await Promise.all([imgLoaded, img2Loaded]);

        const ratio = Math.min(
          screenWidth / img_2.width,
          canvasHeight / img_2.height
        );
        const displayWidth = img_2.width * ratio;
        const displayHeight = img_2.height * ratio;

        ctx.clearRect(0, 0, screenWidth, canvasHeight);

        // Draw original image
        ctx.drawImage(
          img_2,
          (screenWidth - displayWidth) / 2,
          (canvasHeight - displayHeight) / 2,
          displayWidth,
          displayHeight
        );

        // Draw text layers
        layers.forEach((layer) => {
          ctx.save();
          ctx.translate(layer.x, layer.y);
          ctx.rotate((layer.rotation * Math.PI) / 180);
          ctx.font = `${layer.fontWeight} ${layer.fontSize}px "${layer.fontFamily}"`; // Ensure fontFamily is wrapped in quotes
          ctx.fillStyle = layer.color;
          ctx.globalAlpha = layer.opacity;
          ctx.fillText(layer.text, 0, 0);
          ctx.restore();
        });

        // Draw processed image
        ctx.drawImage(
          img,
          (screenWidth - displayWidth) / 2,
          (canvasHeight - displayHeight) / 2,
          displayWidth,
          displayHeight
        );
      } catch (error) {
        console.error("Error drawing image:", error);
      }
    },
    [imageUrl, processedImageurl, screenWidth, canvasHeight]
  );

please help me!

So, I used react-native-canvas library to use canvas on my React Native project, everything went very good but when it comes to selecting of font family's, like for example pacifico or anything like that, it was not applying to my canvas, but on my _layout.tsx, I actually specifically installed a library called as:

import { Pacifico_400Regular } from "@expo-google-fonts/pacifico";

and I loaded fonts as well in Expo, here is my code:

_layout.tsx:

import { SplashScreen, Stack } from "expo-router";
import "./globals.css";
import { useFonts } from "expo-font";
import { useEffect } from "react";
import { ClerkProvider, ClerkLoaded, ClerkLoading } from "@clerk/clerk-expo";
import { tokenCache } from "@/lib/auth";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { Alert, Text } from "react-native";
import CustomLoader from "@/components/CustomLoader";
import { StripeProvider } from "@stripe/stripe-react-native";
import { Pacifico_400Regular } from "@expo-google-fonts/pacifico";

export default function RootLayout() {
  const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!;

  if (!publishableKey) {
    throw new Error("Add EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY to your .env file");
  }
  const [fontsLoaded] = useFonts({
    Pacifico: Pacifico_400Regular,
    "Rubik-Bold": require("../assets/fonts/Rubik-Bold.ttf"),
    "Rubik-ExtraBold": require("../assets/fonts/Rubik-ExtraBold.ttf"),
    "Rubik-Medium": require("../assets/fonts/Rubik-Medium.ttf"),
    "Rubik-Light": require("../assets/fonts/Rubik-Light.ttf"),
    "Rubik-Regular": require("../assets/fonts/Rubik-Regular.ttf"),
    "Rubik-SemiBold": require("../assets/fonts/Rubik-SemiBold.ttf"),
  });
  useEffect(() => {
    if (!process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY) {
      console.error("Missing Clerk Publishable Key!");
      Alert.alert("Configuration Error", "Missing Clerk credentials");
    }
  }, []);
  useEffect(() => {
    if (fontsLoaded) {
      SplashScreen.hideAsync();
    }
  }, [fontsLoaded]);
  if (!fontsLoaded) {
    return null;
  }
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <StripeProvider
        publishableKey={process.env.EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY!}
      >
        <ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
          <ClerkLoaded>
            <Stack screenOptions={{ headerShown: false }} />
          </ClerkLoaded>
          <ClerkLoading>
            {/* Optional: Loading indicator or splash screen alternative */}
            <CustomLoader loading={true} />
          </ClerkLoading>
        </ClerkProvider>
      </StripeProvider>
    </GestureHandlerRootView>
  );
}

Yes, it worked when I used it in my Expo screens, but not in canvas!, why? here is my code:

constants/fonts.ts:

export const fontConfig: any = {
  Arial: { isPremium: false },
  Helvetica: { isPremium: false },
  Pacifico: { isPremium: false },
  Montserrat: { isPremium: true },
  "Playfair Display": { isPremium: true },
  // Add other fonts...
};

export const freeFonts = Object.keys(fontConfig).filter(
  (font) => !fontConfig[font].isPremium
);
export const premiumFonts = Object.keys(fontConfig).filter(
  (font) => fontConfig[font].isPremium
);

Font Picker Modal:

          <Modal
            visible={showFontPicker}
            transparent={true}
            animationType="slide"
          >
            <View className="flex-1 justify-center items-center bg-black/50">
              <View className="bg-white p-4 rounded-lg w-80 max-h-[80%]">
                <Text className="text-lg font-bold mb-4">
                  Select Font
                </Text>

                <ScrollView>
                  {/* Free Fonts Section */}
                  <Text className="text-gray-700 font-semibold mb-2">
                    Free Fonts
                  </Text>
                  {freeFonts.map((font) => (
                    <TouchableOpacity
                      key={font}
                      onPress={() => {
                        updateLayer(selectedLayer.id, {
                          fontFamily: font,
                        });
                        setSelectedFont(font);
                        setShowFontPicker(false);
                      }}
                      className="p-2"
                    >
                      <Text style={{ fontFamily: font }}>{font}</Text>
                    </TouchableOpacity>
                  ))}

                  {/* Premium Fonts Section */}
                  <Text className="text-gray-700 font-semibold mt-4 mb-2">
                    Premium Fonts
                  </Text>
                  {premiumFonts.map((font) => (
                    <TouchableOpacity
                      key={font}
                      onPress={() => {
                        if (plan !== "PRO") {
                          Alert.alert(
                            "Premium Feature",
                            "Upgrade to access premium fonts"
                          );
                          return;
                        }
                        updateLayer(selectedLayer.id, {
                          fontFamily: font,
                        });
                        setSelectedFont(font);
                        setShowFontPicker(false);
                      }}
                      className="p-2"
                      disabled={plan !== "PRO"} // Disable for free users
                    >
                      <Text
                        style={{
                          fontFamily: font,
                          color: plan !== "PRO" ? "#ccc" : "#000", // Gray out for free users
                        }}
                      >
                        {font}
                      </Text>
                    </TouchableOpacity>
                  ))}
                </ScrollView>

                {/* Close Button */}
                <TouchableOpacity
                  onPress={() => setShowFontPicker(false)}
                  className="mt-4 p-2 bg-gray-200 rounded"
                >
                  <Text>Close</Text>
                </TouchableOpacity>
              </View>
            </View>
          </Modal>

Canvas Rendering Logic:

  const drawOnCanvas = useCallback(
    async (layers: TextLayer[]) => {
      if (!canvasRef.current || !imageUrl || !processedImageurl) return;

      try {
        const ctx = canvasRef.current.getContext("2d");
        const img = new CanvasImage(canvasRef.current);
        const img_2 = new CanvasImage(canvasRef.current);

        canvasRef.current.width = screenWidth;
        canvasRef.current.height = canvasHeight;

        const [originalBase64, processedBase64] = await Promise.all([
          FileSystem.readAsStringAsync(imageUrl, {
            encoding: FileSystem.EncodingType.Base64,
          }),
          FileSystem.readAsStringAsync(processedImageurl, {
            encoding: FileSystem.EncodingType.Base64,
          }),
        ]);

        const imgLoaded = new Promise((resolve) =>
          img.addEventListener("load", resolve)
        );
        const img2Loaded = new Promise((resolve) =>
          img_2.addEventListener("load", resolve)
        );

        img.src = `data:image/png;base64,${processedBase64}`;
        img_2.src = `data:image/png;base64,${originalBase64}`;

        await Promise.all([imgLoaded, img2Loaded]);

        const ratio = Math.min(
          screenWidth / img_2.width,
          canvasHeight / img_2.height
        );
        const displayWidth = img_2.width * ratio;
        const displayHeight = img_2.height * ratio;

        ctx.clearRect(0, 0, screenWidth, canvasHeight);

        // Draw original image
        ctx.drawImage(
          img_2,
          (screenWidth - displayWidth) / 2,
          (canvasHeight - displayHeight) / 2,
          displayWidth,
          displayHeight
        );

        // Draw text layers
        layers.forEach((layer) => {
          ctx.save();
          ctx.translate(layer.x, layer.y);
          ctx.rotate((layer.rotation * Math.PI) / 180);
          ctx.font = `${layer.fontWeight} ${layer.fontSize}px "${layer.fontFamily}"`; // Ensure fontFamily is wrapped in quotes
          ctx.fillStyle = layer.color;
          ctx.globalAlpha = layer.opacity;
          ctx.fillText(layer.text, 0, 0);
          ctx.restore();
        });

        // Draw processed image
        ctx.drawImage(
          img,
          (screenWidth - displayWidth) / 2,
          (canvasHeight - displayHeight) / 2,
          displayWidth,
          displayHeight
        );
      } catch (error) {
        console.error("Error drawing image:", error);
      }
    },
    [imageUrl, processedImageurl, screenWidth, canvasHeight]
  );

please help me!

Share Improve this question edited Mar 19 at 11:08 Hamed Jimoh 1,3878 silver badges20 bronze badges asked Mar 19 at 10:37 RaghunadhRaghunadh 155 bronze badges 1
  • I can't say for sure as I don't use react or any of it's plugins, but font files loaded are not necessarily "registered" yet until you use them in the DOM, and this font plugin seems to be made for using in the DOM, not canvas. To see if it this is the case, add some random div with some text to your page with its font set to your desired font, then re-render your canvas. You could also register them using document.fonts and FontFaceSet interfaces in good old, regular JS. Knowing the actual underlying system instead of just the top layer is a boon! – somethinghere Commented Mar 19 at 13:22
Add a comment  | 

1 Answer 1

Reset to default 1

So I tried to reproduce your problem by making a Snack on expo, and I reached the same results, I'm not able to render the proper fontFamily inside the canvas, which I think is a thing of the webview inside the react-native-canvas package. You cannot render the font family because expo-font does not reach the webview context.

Also looking for the pull requests of the canvas repo, I saw a contributor trying to implement a Font API, but it didn't merge: https://github/iddan/react-native-canvas/pull/294.

Just to my conclusion, the plugin of expo-font doesn't have the flexibility to add fonts inside webview contexts, at least doing it this way.

This is the code that I made to reproduce this issue:

import { Text, SafeAreaView, StyleSheet } from 'react-native';
import { useEffect } from 'react';
import { useFonts } from 'expo-font';
import Canvas from 'react-native-canvas';

const WaitFont = (props) => {
  const [loaded, error] = useFonts({
    'eightBit': require('./assets/fonts/eightBit.ttf'),
  });

  useEffect(() => {
    if (loaded || error) {
      console.log('Working?')
    }
  }, [loaded, error]);

  if (!loaded && !error) {
    return null;
  }

  return props.children
}

export default function App() {
  const handleCanvas = (canvas) => {
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    ctx.font = '500 26px "eightBit"';
    ctx.fillText("Hello from <Canvas />", 8, 28) ;
  };

  return (
    <SafeAreaView style={styles.container}>
      <WaitFont>
        <Text style={styles.paragraph}>
          {`Hello from <Text />`}
        </Text>
        <Canvas style={styles.canvas} ref={handleCanvas} />
      </WaitFont>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'center',
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  paragraph: {
    fontSize: 50,
    fontWeight: '500',
    fontFamily: 'eightBit'
  },
  canvas: {
    borderWidth: 1,
    borderColor: 'red',
    width: '100%'
  }
});
发布评论

评论列表(0)

  1. 暂无评论