OpenHarmonyでReact Nativeのカスタムフォントを正しく読み込む実践ガイド

OpenHarmony上でReact Nativeアプリを構築する際、カスタムフォントの読み込みは一見シンプルに見えますが、プラットフォーム固有の仕様により意外と落とし穴があります。本記事では、OpenHarmony 6.0.0(API Level 20)とReact Native 0.72.5/0.77.1を対象に、安定したフォント管理を実現するための完备な実装手法を解説します。

なぜOpenHarmonyではフォント読み込みが特殊なのか

Androidではassets/fonts/配下に配置すれば自動で認識され、iOSではInfo.plistに登録すればOKですが、OpenHarmonyは異なります:

  • フォントファイルは /resources/base/media/ に配置
  • 明示的なJSON定義ファイル font.json が必要
  • ArkTS側でAPI経由で手動登録が必須
  • React Nativeの初期化より前に完了している必要あり

この仕様は、セキュリティとパッケージサイズの制御を重視した設計ですが、従来のモバイル開発経験だけでは見落としがちなポイントです。

実装ステップ:6つのKEY POINT

  1. フォントファイルの用意
    .ttfまたは.otf形式を推奨。日本語や中国語を含む商用フォントは、可能であればHarmonyOS Sansなどのシステムフォント優先利用が filesizeと互換性の観点から望ましい。
  2. プロジェクト構造へ配置
    project/
    └── harmony/
        └── entry/
            └── src/
                └── main/
                    └── resources/
                        └── base/
                            ├── media/
                            │   ├── HarmonyOS_Sans-Regular.ttf
                            │   ├── BrandFont-Light.otf
                            │   └── FontAwesome.otf
                            └── profile/
                                └── font.json
    注意: assets/ 配下はスキャンされません。必ず media/ 配下へ。
  3. font.jsonで家族名を宣言
    { "fonts": [ { "familyName": "HarmonyOS_Sans", "fontFile": "$media:HarmonyOS_Sans-Regular.ttf" }, { "familyName": "BrandFont", "fontFile": "$media:BrandFont-Light.otf" }, { "familyName": "FontAwesome", "fontFile": "$media:FontAwesome.otf" } ] }
    familyName がReact Native側で使うfontFamilyのキーとなるため、ケース敏感・完全一致が必須。
  4. ArkTSスクリプトで明示ロード
    // entry/src/main/ets/entryability/EntryAbility.ets import fontManager from '@ohos.multimodalInput.font'; export function onForeground(): void { const ctx = getContext() as common.UIAbilityContext; const rm = ctx.resourceManager; try { // 同期ロード:UI描画より先に完了を保証 fontManager.loadFontSync( 'HarmonyOS_Sans', rm.getRawFileContent('media/HarmonyOS_Sans-Regular.ttf') ); fontManager.loadFontSync( 'BrandFont', rm.getRawFileContent('media/BrandFont-Light.otf') ); // 非同期ロード:アイコン等の補助フォント fontManager.loadFont( 'FontAwesome', rm.getRawFileContent('media/FontAwesome.otf') ).then(() => console.log('FontAwesome loaded')); } catch (e) { console.error('Failed to load custom fonts:', e); } }
    onForeground は起動直後のライフサイクルイベントであり、React Native Bridgeの初期化前に実行されるクリティカルなポイントです。
  5. React Native側でスタイル適用
    // App.tsx import React from 'react'; import { Text, StyleSheet } from 'react-native'; const App = () => { return ( <> これは HarmonyOS Sans です BrandFont 文字 ); };
    ここでのfontFamily値は、font.jsonfamilyNameと文字列一致(match case=True)である必要があります。
  6. アイコンフォント向けユーティリティコンポーネント
    // components/Icon.tsx import React from 'react'; import { Text, TextStyle, ViewProps } from 'react-native'; interface IconProps { name: string; size?: number; color?: string; style?: TextStyle; } export const Icon = ({ name, size = 24, color = 'currentColor', style }: IconProps) => { const codes: Record<string, string> = { 'home': '\ue900', 'user': '\ue901', 'settings': '\ue902', 'bell': '\ue903', }; const char = codes[name]; if (!char) { console.warn(`Icon '${name}' not found`); return null; } return ( <Text accessible={true} accessibilityLabel={name} style={[ { fontSize: size, color, fontFamily: 'FontAwesome', textAlignVertical: 'center', includeFontPadding: false, }, style, ]} > {char} </Text> ); }; TypeScriptによる型付と.NULLアセス対策により、実行時エラーを防ぎます。

さらに安定させるための実践ヒント

  • ロード失敗時のフォールバック
    フォント読込失敗時にloadFontSyncが例外を出さない環境も存在するため、UI側でfontFamily指定後にonLayoutなどで読み込み完了を検知する代替ヒューリスティックが有効です。
  • サブセット化で容量削減
    日本語・中国語を含む完全フォントは2〜5MBになることも珍しくありません。
    pyftsubset NotoSansCJK-Jp-Regular.otf --text="あいうえおABC123" --output-file=NotoCJK-Jp-subset.ttf
    のように必要な文字コードだけを抽出して Griffonビルドに含めることで、アプリサイズを大幅に削減できます。
  • 動的ロードのための機構
    フォントをページ単位・言語別に切り替える必要がある場合、fontManager APIは単一プロセス空間内で複数回ロード可能です。ただし、一度に肩合わせて読み込めばUIブロックは解消されます。:
async loadLanguageFont(lang: 'ja' | 'en' | 'zh') {
  const fileName = lang === 'en' ? 'OpenSans' : lang === 'zh' ? 'PingFangSC' : 'HarmonyOS_Sans';
  await fontManager.loadFont(fileName, rm.getRawFileContent(`media/${fileName}.ttf`));
}

よくあるトラブルと対処

症状原因対応
「□□□」が表示されるフォントが該当コードポイント(Unicode)を含んでいない文字セット対応範囲を確認し、Noto系やHarmonyOS Sans等の多言語対応フォントへ差し替え
ロード後も日常的に文字が崩れる/反映されないfamilyNameのスペルミスや Case階差パス文字列やfont.jsonfamilyNameをコンソール出力して照合
開発時だけ正常、ビルド配布後だけ表示されないProGuard/R8によるリソース圧縮・明示除外漏れresources/element/shape/placeholder.json に信頼するファイルを明記し、除外設定を加える

開発時のデバッグ支援ツール

  • React DevTools ReplacementでfontFamily_applyされいるか可視化
  • hdc std -t xxxで端末ログにfontManagerのエラーメッセージをリアルタイム確認
  • AbilityStage.onPrepareでフォントロード状況をlocalStorageへ書き出すカスタムダッシュボード実装

タグ: OpenHarmony ReactNative font fontLoader ArkTS

6月3日 21:51 投稿