Skip to content

Commit

Permalink
Font Manager (#1717)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcandillon authored Sep 9, 2023
1 parent ad4e930 commit a3d6e03
Show file tree
Hide file tree
Showing 45 changed files with 1,062 additions and 74 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/build-skia.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ jobs:
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm/libsvg.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm/libskottie.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm/libsksg.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm/libskparagraph.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm/libskunicode.a
- name: Upload artifacts - Android arm64
uses: actions/upload-artifact@v2
Expand All @@ -68,6 +70,8 @@ jobs:
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm64/libsvg.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm64/libskottie.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm64/libsksg.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm64/libskparagraph.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/arm64/libskunicode.a
- name: Upload artifacts - Android x86
uses: actions/upload-artifact@v2
Expand All @@ -79,6 +83,8 @@ jobs:
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x86/libsvg.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x86/libskottie.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x86/libsksg.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x86/libskparagraph.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x86/libskunicode.a
- name: Upload artifacts - Android x64
uses: actions/upload-artifact@v2
Expand All @@ -90,6 +96,8 @@ jobs:
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x64/libsvg.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x64/libskottie.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x64/libsksg.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x64/libskparagraph.a
${{ env.WORKING_DIRECTORY }}/externals/skia/out/android/x64/libskunicode.a
- name: Upload artifacts - iOS xcframeworks
uses: actions/upload-artifact@v2
Expand All @@ -101,3 +109,5 @@ jobs:
${{ env.WORKING_DIRECTORY }}/package/libs/ios/libsvg.xcframework
${{ env.WORKING_DIRECTORY }}/package/libs/ios/libskottie.xcframework
${{ env.WORKING_DIRECTORY }}/package/libs/ios/libsksg.xcframework
${{ env.WORKING_DIRECTORY }}/package/libs/ios/libskparagraph.xcframework
${{ env.WORKING_DIRECTORY }}/package/libs/ios/libskunicode.xcframework
155 changes: 155 additions & 0 deletions docs/docs/text/fonts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
id: fonts
title: Fonts
sidebar_label: Fonts
slug: /text/fonts
---

In Skia, the `FontMgr` object manages a collection of font families.
It allows you to access fonts from the system and manage custom fonts.

## Custom Fonts

The `useFonts` hooks allows you to load custom fonts to be used for your Skia drawing.
The font files should be organized by family names.
For example:

```tsx twoslash
import {useFonts} from "@shopify/react-native-skia";

const fontMgr = useFonts({
Roboto: [
require("./Roboto-Medium.ttf"),
require("./Roboto-Regular.ttf"),
require("./Roboto-Bold.ttf"),
],
UberMove: [require("./UberMove-Medium_mono.ttf")],
});
if (!fontMgr) {
// Returns null until all fonts are loaded
}
// Now the fonts are available
```

Once the fonts are loaded, we provide a `matchFont` function that given a font style will return a font object that you can use directly.

```tsx twoslash
import {useFonts, Text, matchFont} from "@shopify/react-native-skia";

const Demo = () => {
const fontMgr = useFonts({
Roboto: [
require("./Roboto-Medium.ttf"),
require("./Roboto-Regular.ttf"),
require("./Roboto-Bold.ttf"),
],
UberMove: [require("./UberMove-Medium_mono.ttf")],
});
if (!fontMgr) {
return null;
}
const fontStyle = {
fontFamily: "Roboto",
fontWeight: "bold",
fontSize: 16
} as const;
const font = matchFont(fontStyle, fontMgr);
return (
<Text text="Hello World" y={32} x={32} font={font} />
);
};
```

## System Fonts

System fonts are available via `Skia.FontMgr.System()`.
You can list system fonts via `listFontFamilies` function returns the list of available system font families.
By default the function will list system fonts but you can pass an optional `fontMgr` object as parameter.

```jsx twoslash
import {listFontFamilies} from "@shopify/react-native-skia";

console.log(listFontFamilies());
```

Output example on Android:
```
["sans-serif", "arial", "helvetica", "tahoma", "verdana", ...]
```

or on iOS:
```
["Academy Engraved LET", "Al Nile", "American Typewriter", "Apple Color Emoji", ...]
```

By default matchFont, will match fonts from the system font manager:

```jsx twoslash
import {Platform} from "react-native";
import {Canvas, Text, matchFont, Fill, Skia} from "@shopify/react-native-skia";

const fontFamily = Platform.select({ ios: "Helvetica", default: "serif" });
const fontStyle = {
fontFamily,
fontSize: 14,
fontStyle: "italic",
fontWeight: "bold",
};
const font = matchFont(fontStyle);

export const HelloWorld = () => {
return (
<Canvas style={{ flex: 1 }}>
<Fill color="white" />
<Text
x={0}
y={fontStyle.fontSize}
text="Hello World"
font={font}
/>
</Canvas>
);
};
```

The `fontStyle` object can have the following list of optional attributes:

- `fontFamily`: The name of the font family.
- `fontSize`: The size of the font.
- `fontStyle`: The slant of the font. Can be `normal`, `italic`, or `oblique`.
- `fontWeight`: The weight of the font. Can be `normal`, `bold`, or any of `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`.

By default, `matchFont` uses the system font manager to match the font style. However, if you want to use your custom font manager, you can pass it as the second parameter to the `matchFont` function:

```jsx
const fontMgr = useFonts([
require("../../Tests/assets/Roboto-Medium.ttf"),
require("../../Tests/assets/Roboto-Bold.ttf"),
]);

const font = matchFont(fontStyle, fontMgr);
```

## Low-level API

The basic usage of the system font manager is as follows.
These are the APIs used behind the scene by the `matchFont` function.

```tsx twoslash
import {Platform} from "react-native";
import {Skia, FontStyle} from "@shopify/react-native-skia";

const familyName = Platform.select({ ios: "Helvetica", default: "serif" });
const fontSize = 32;
// Get the system font manager
const fontMgr = Skia.FontMgr.System();
// The custom font manager is available via Skia.TypefaceFontProvider.Make()
const customFontMgr = Skia.TypefaceFontProvider.Make();
// typeface needs to be loaded via Skia.Data and instanciated via
// Skia.Typeface.MakeFreeTypeFaceFromData()
// customFontMgr.registerTypeface(customTypeFace, "Roboto");

// Matching a font
const typeface = fontMgr.matchFamilyStyle(familyName, FontStyle.Bold);
const font = Skia.Font(typeface, fontSize);
```
8 changes: 7 additions & 1 deletion docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,13 @@ const sidebars = {
collapsed: true,
type: "category",
label: "Text",
items: ["text/text", "text/glyphs", "text/path", "text/blob"],
items: [
"text/fonts",
"text/text",
"text/glyphs",
"text/path",
"text/blob",
],
},
{
collapsed: true,
Expand Down
2 changes: 1 addition & 1 deletion example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ SPEC CHECKSUMS:
React-jsinspector: 9885f6f94d231b95a739ef7bb50536fb87ce7539
React-logger: 3f8ebad1be1bf3299d1ab6d7f971802d7395c7ef
react-native-safe-area-context: dfe5aa13bee37a0c7e8059d14f72ffc076d120e9
react-native-skia: c677fe0a5d0cc7e447df20d511a7181c47e20071
react-native-skia: 54fc333c477cc04df78b87b03e87b7e112bc3f47
React-perflogger: 2d505bbe298e3b7bacdd9e542b15535be07220f6
React-RCTActionSheet: 0e96e4560bd733c9b37efbf68f5b1a47615892fb
React-RCTAnimation: fd138e26f120371c87e406745a27535e2c8a04ef
Expand Down
90 changes: 90 additions & 0 deletions example/src/Examples/API/FontMgr.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from "react";
import { ScrollView, useWindowDimensions } from "react-native";
import {
Canvas,
Skia,
Text,
matchFont,
useFonts,
} from "@shopify/react-native-skia";

const PADDING = 16;

const titleFontSize = 32;

const titleText = "Fonts from the System";
const titleY = titleFontSize + PADDING;
const subtitleY = titleY + 14 + PADDING;

const fontMgr = Skia.FontMgr.System();
const familyNames = new Array(fontMgr.countFamilies())
.fill(0)
.map((_, i) => fontMgr.getFamilyName(i));

const title2Y = subtitleY + 16 * familyNames.length + PADDING + titleFontSize;

export const FontMgr = () => {
const { width } = useWindowDimensions();
const customFontMgr = useFonts({
Roboto: [
require("../../Tests/assets/Roboto-Medium.ttf"),
require("../../Tests/assets/Roboto-Regular.ttf"),
],
UberMove: [require("../../Tests/assets/UberMove-Medium_mono.ttf")],
});
if (customFontMgr === null) {
return null;
}
const customfamilyNames = new Array(customFontMgr.countFamilies())
.fill(0)
.map((_, i) => customFontMgr.getFamilyName(i));
const titleFont = matchFont({
fontFamily: "Helvetica",
fontSize: titleFontSize,
fontWeight: "bold",
});
const subtitleFont = matchFont();
return (
<ScrollView>
<Canvas style={{ width: width, height: 1800 }}>
<Text font={titleFont} text={titleText} x={PADDING} y={titleY} />
<Text
font={subtitleFont}
x={PADDING}
y={subtitleY}
text="List of fonts available on the system"
/>
{familyNames.map((fontFamily, i) => {
const font = matchFont({ fontFamily });
const resolvedFont =
font.getGlyphIDs(fontFamily)[0] === 0 ? subtitleFont : font;
return (
<Text
key={fontFamily}
font={resolvedFont}
x={PADDING}
y={subtitleY + 16 * (i + 1)}
text={fontFamily}
/>
);
})}
<Text font={titleFont} text="Custom Fonts" x={PADDING} y={title2Y} />
{customfamilyNames.map((fontFamily, i) => {
const font = matchFont({ fontFamily }, customFontMgr);

const resolvedFont =
font.getGlyphIDs(fontFamily)[0] === 0 ? subtitleFont : font;
return (
<Text
key={fontFamily}
font={resolvedFont}
x={PADDING}
y={title2Y + 16 * (i + 1)}
text={fontFamily}
/>
);
})}
</Canvas>
</ScrollView>
);
};
4 changes: 4 additions & 0 deletions example/src/Examples/API/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export const examples = [
screen: "BlendModes",
title: "🎨 Blend Modes",
},
{
screen: "FontMgr",
title: "💬 Font Manager",
},
{
screen: "Data",
title: "📊 Data",
Expand Down
1 change: 1 addition & 0 deletions example/src/Examples/API/Routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type Routes = {
SVG: undefined;
Touch: undefined;
BlendModes: undefined;
FontMgr: undefined;
Data: undefined;
Picture: undefined;
Checker: undefined;
Expand Down
8 changes: 8 additions & 0 deletions example/src/Examples/API/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { PictureViewExample } from "./PictureView";
import { OnLayoutDemo } from "./OnLayout";
import { Snapshot } from "./Snapshot";
import { IconsExample } from "./Icons";
import { FontMgr } from "./FontMgr";

const Stack = createNativeStackNavigator<Routes>();
export const API = () => {
Expand Down Expand Up @@ -134,6 +135,13 @@ export const API = () => {
title: "🎨 Blend Modes",
}}
/>
<Stack.Screen
name="FontMgr"
component={FontMgr}
options={{
title: "💬 Font Manager",
}}
/>
<Stack.Screen
name="Data"
component={Data}
Expand Down
Binary file added example/src/Tests/assets/Roboto-Regular.ttf
Binary file not shown.
2 changes: 1 addition & 1 deletion externals/depot_tools
Submodule depot_tools updated from 6d0c23 to c7aca3
2 changes: 1 addition & 1 deletion externals/skia
Submodule skia updated from f44dbc to ffbc37
Loading

0 comments on commit a3d6e03

Please sign in to comment.