自定义字体的注册和使用
===========

#### 问题现象

为了提升应用的用户体验和视觉吸引力，应用设计常常需要支持自定义字体即非系统字体，如何注册和使用自定义的字体？

#### 背景知识

自定义字体是指开发者根据应用需求创建或选择的字体，通常用于实现特定的文字风格或满足独特的设计要求。当应用需要使用特定的文本样式和字符集时，可以注册并使用自定义字体进行文本渲染。

*   [registerFont](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-font#registerfont)：在字体管理中注册自定义字体。该接口为异步接口，不支持并发调用。
*   [fontFamily](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-securitycomponent-attributes#fontfamily)：设置安全控件文字的字体。

实现流程：

1.  注册自定义字体：将字体文件（如ttf、otf文件等）从应用资源注册到系统中，使得应用能够使用这些字体进行文本渲染。注册过程通常指将字体文件通过字体管理接口注册到系统字体库中，以便在应用中进行调用。
2.  使用自定义字体：在应用中显式指定使用已注册的自定义字体进行文本渲染。开发者可以根据需要选择特定的文本样式（如常规、粗体、斜体等），并将其应用到UI元素、文本控件或其他文本展示区域，以确保符合设计要求并提供一致的视觉效果。

#### 解决方案

1.  注册自定义字体。
    
    | 
    注册方法
    
     | 
    
    资源文件加载方式
    
     |
    | :-- | :-- |
    | 
    
    registerFont
    
     | 
    
    string
    
     |
    | 
    
    registerFont
    
     | 
    
    Resource
    
     |
    | 
    
    registerFont
    
     | 
    
    RawFile
    
     |
    | 
    
    registerFont
    
     | 
    
    沙箱路径前加上file://
    
     |
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/70/v3/gb6kUrDkRbKNnRYzxzeVpA/caution_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134106Z&HW-CC-Expire=86400&HW-CC-Sign=9D6495B4A7D214B0D19453C69BD11CB28B2EA301A0E73E5A8AF47F5FE71DE787)
    
    若项目entry模块下build-profile.json5文件有copyCodeResource字段，需改成true，否则familySrc不支持string和Resource。
    
    在工程中存放开发者自定义字体资源文件，通过registerFont接口在生命周期aboutToAppear中进行自定义字体注册。关键代码如下：
    
    aboutToAppear() {
      _// 注册青鸟华光简美黑字体_
      this.font.registerFont({
        familyName: 'QingNiaoHuaGuangJianMeiHei', _// 注册的字体名称_
        familySrc: '/font/QingNiaoHuaGuangJianMeiHei-2.ttf' _// font文件夹与pages目录同级_
      });
    
     _ // 注册字魂龙腾楷书字体_
      this.font.registerFont({
        familyName: 'ZiHunLongTengKaiShu', _// 注册的字体名称_
        familySrc: '/font/ZiHunLongTengKaiShu(ShangYongXuShouQuan)-2.ttf' _// font文件夹与pages目录同级_
      });
    
      _// 注册手写字体_
      this.font.registerFont({
        familyName: $r('app.string.font\_name'), _// 注册的字体名称_
        familySrc: $r('app.string.font\_src')_// font文件夹与pages目录同级_
      });
    
      _// 注册ExtraLight字体_
      this.font.registerFont({
        familyName: 'SourceHanSerif-ExtraLight', _// 注册的字体名称_
        familySrc: $rawfile('font/SourceHanSerif-ExtraLight.otf') _// resources/rawfile目录下_
      });
    }
    
    其中方案二需要在resources/base/element/string.json中定义font\_name、font\_src。代码如下：
    
    {
      "string": \[
        {
          "name": "font\_name",
          "value": "HandwrittenFont"
        },
        {
          "name": "font\_src",
          "value": "/font/AiNiZaiHuangHunRiLuoShouXieTi-2.ttf"
        }
      \]
    }
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/98/v3/N4UxJrCMSf2EaRx5q7RYEA/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134106Z&HW-CC-Expire=86400&HW-CC-Sign=3B94D142A24ED3E5C8D4DFBAE86F46BEBFD87D6A9E25DF02443656CA1E6E0DD7)
    
    应用若需全局使用自定义字体，请在EntryAbility.ets文件的onWindowStageCreate生命周期中，通过windowStage.loadContent回调注册。参考：[自定义字体无法全局使用如何处理](https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-arkui-571)。
    
2.  在文本组件中通过fontFamily属性使用自定义字体，关键代码如下：
    
    Text('青鸟华光简美黑')
      .align(Alignment.Center)
      .fontSize(50)
      .fontColor(Color.Blue)
      .fontFamily('QingNiaoHuaGuangJianMeiHei') _// 使用青鸟华光简美黑字体_
    Text('字魂龙腾楷书')
      .align(Alignment.Center)
      .fontSize(50)
      .fontColor(Color.Blue)
      .fontFamily('ZiHunLongTengKaiShu') _// 使用字魂龙腾楷书字体_
    Text('手写字体')
      .align(Alignment.Center)
      .fontSize(30)
      .fontColor(Color.Blue)
      .fontFamily('HandwrittenFont') _// 使用手写字体_
    Text(this.message)
      .align(Alignment.Center)
      .fontSize(50)
      .fontColor(Color.Blue)
      .fontFamily('SourceHanSerif-ExtraLight') _// 使用注册字体SourceHanSerif-ExtraLight_
    

完整示例如下：

import { Font } from '@kit.ArkUI';

@Entry
@Component
struct FontExample {
  message: string = 'Hello World';
  private uiContext: UIContext = this.getUIContext();
  private font: Font = this.uiContext.getFont();
  aboutToAppear() {
   _ // 注册青鸟华光简美黑字体_
    this.font.registerFont({
      familyName: 'QingNiaoHuaGuangJianMeiHei', _// 注册的字体名称_
      familySrc: '/font/QingNiaoHuaGuangJianMeiHei-2.ttf' _// font文件夹与pages目录同级_
    });

   _ // 注册字魂龙腾楷书字体_
    this.font.registerFont({
      familyName: 'ZiHunLongTengKaiShu', _// 注册的字体名称_
      familySrc: '/font/ZiHunLongTengKaiShu(ShangYongXuShouQuan)-2.ttf' _// font文件夹与pages目录同级_
    });

   _ // 注册手写字体_
    this.font.registerFont({
      familyName: $r('app.string.font\_name'), _// 注册的字体名称_
      familySrc: $r('app.string.font\_src')_// font文件夹与pages目录同级_
    });

  _  // 注册ExtraLight字体_
    this.font.registerFont({
      familyName: 'SourceHanSerif-ExtraLight', _// 注册的字体名称_
      familySrc: $rawfile('font/SourceHanSerif-ExtraLight.otf') _// resources/rawfile目录下_
    });
  }
  build() {
    Column({ space: 10 }) {

      Text('青鸟华光简美黑')
        .align(Alignment.Center)
        .fontSize(50)
        .fontColor(Color.Blue)
        .fontFamily('QingNiaoHuaGuangJianMeiHei') _// 使用青鸟华光简美黑字体_
      Text('字魂龙腾楷书')
        .align(Alignment.Center)
        .fontSize(50)
        .fontColor(Color.Blue)
        .fontFamily('ZiHunLongTengKaiShu') _// 使用字魂龙腾楷书字体_
      Text('手写字体')
        .align(Alignment.Center)
        .fontSize(30)
        .fontColor(Color.Blue)
        .fontFamily('HandwrittenFont') _// 使用手写字体_
      Text(this.message)
        .align(Alignment.Center)
        .fontSize(50)
        .fontColor(Color.Blue)
        .fontFamily('SourceHanSerif-ExtraLight') _// 使用注册字体SourceHanSerif-ExtraLight_
      Text(this.message)
        .align(Alignment.Center)
        .fontSize(50)
        .fontColor(Color.Blue)
    }
    .width('100%')
    .margin({ top: 30 })
  }
}

**注意事项**：

通过downloadFile下载的字体文件，在调用registerFont注册自定义字体时，若直接使用下载时使用的沙箱路径，会出现设置失败的现象，解决上述问题需要在沙箱路径前加上file://来使用实时沙箱路径字体，示例如下：

import { BusinessError, request } from '@kit.BasicServicesKit';
import { zlib } from '@kit.BasicServicesKit';
import { Font } from '@kit.ArkUI';

function getFileNameFromUrl(url: string): string {
  const segments = url.split('/');
  let tmp = segments.pop() || '';
  if (tmp.indexOf('?') != -1) {
    return tmp.split('?')\[0\];
  }
  return tmp;
};

@Entry
@Component
struct Index {
  private url: string = 'xxxxxxxxxxxxxx';_// 需替换成自己的下载网址https://ziyuan.guwendao.net/font/yyys.zip'_
  private uiContext: UIContext = this.getUIContext();
  private context: Context | undefined = this.getUIContext().getHostContext();
  private font: Font = this.uiContext.getFont();
  message: string = 'Go Next Page';

  DownLoad() {
    let config: request.agent.Config = {
      action: request.agent.Action.DOWNLOAD,
      url: this.url,
      overwrite: true,
      method: 'GET',
      saveas: this.context?.filesDir + '/myFonts/' + getFileNameFromUrl(this.url),
      mode: request.agent.Mode.BACKGROUND,
      gauge: true,
      retry: false
    };
    request.agent.create(this.context, config).then((task: request.agent.Task) => {
      task.start((err: BusinessError) => {
        if (err) {
          console.error(\`Failed to start the upload task, Code: ${err.code}  message: ${err.message}\`);
          return;
        }
      });
      task.on('progress', async (progress) => {
        console.warn(\`/Request download status ${progress.state}, downloaded ${progress.processed}\`);
      });
      task.on('completed', async () => {
        console.warn(\`/Request download completed\`);
       _ // 解压字体文件_
        let inFile = this.context?.filesDir + '/myFonts/' + getFileNameFromUrl(this.url);
        let outFileDir = this.context?.filesDir + '/myFonts';
        let options: zlib.Options = {
          level: zlib.CompressLevel.COMPRESS\_LEVEL\_DEFAULT\_COMPRESSION
        };
        try {
          zlib.decompressFile(inFile, outFileDir, options).then(() => {
            let uri = 'file:/' + this.context?.filesDir + '/myFonts/' + 'yyys.ttf';
            console.info('沙箱路径' + uri);
            this.url = uri;
          }).then(() => {
          _  // 注册字体_
            this.font.registerFont({
              familyName: 'yyys',
              familySrc: this.url
            });
          }
          ).catch((errData: BusinessError) => {
            console.error(\`errData is errCode:${errData.code}  message:${errData.message}\`);
          });
        } catch (errData) {
          let code = (errData as BusinessError).code;
          let message = (errData as BusinessError).message;
          console.error(\`errData is errCode:${code}  message:${message}\`);
        }
      });
    }).catch((err: BusinessError) => {
      console.error(\`Failed to create a download task, Code: ${err.code}, message: ${err.message}\`);
    });
  }

  build() {
    Column({ space: 30 }) {
      Button('点击下载使用字体')
        .onClick(() => {
          this.DownLoad();
        })
      Text(this.message)
        .id('Go Next Page')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .fontFamily('yyys')
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

常见FAQ

Q：API18之后，如何在EntryAbility中怎么注册全局字体，直接使用font会提示警告？

A：font.registerFont从APIversion18开始废弃，建议使用[registerFont](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-font#registerfont)。

Q：在注册自定义字体registerFont时，需传入参数familySrc仅支持string、Resource类型，如何将base64编码的字体注册为可供使用的自定义字体？

A：将base64编码的字体数据转换为字体文件，通过buffer.from的方法，将base64编码格式的字符串创建为新的Buffer对象，用fileIo.writeSync方法将转换好的Buffer对象写入文件。转换代码如下：

import { buffer } from '@kit.ArkTS'; 
import { fileIo } from '@kit.CoreFileKit'; 
import { common } from '@kit.AbilityKit'; 
import { fileUri } from "@kit.CoreFileKit"; 
import { hilog } from '@kit.PerformanceAnalysisKit'; 

_// 获取应用上下文和沙箱文件目录_
let context = this.getUIContext() as common.UIAbilityContext; 
let filesDir = context.filesDir; 

_/\*\*_
 _\* 将Base64编码的字体文件写入沙箱文件系统_
 _\* @param data Base64 字符串（含数据类型前缀，如"data:font/ttf;base64,"）_
 _\* @returns 返回文件的沙箱路径URI_
 _\*/_
export async function writeFontFile(data: string): Promise<string> { 
  let uri = ''; 
  try { 
   _ // 构造文件路径（目标文件名改为字体格式，如font.ttf）_
    let filePath = filesDir + "/font.ttf"; 
   _ // 获取文件URI_
    uri = fileUri.getUriFromPath(filePath); 
   _ // 以读写模式打开文件（若文件不存在则创建）_
    let file = fileIo.openSync(filePath, fileIo.OpenMode.READ\_WRITE | fileIo.OpenMode.CREATE); 
 _   // 正则表达式去除Base64数据前缀（如"data:font/ttf;base64,"）_
    const reg = new RegExp("data:font/\\\\w+;base64,"); 
    const base64 = data.replace(reg, "");
 _   // 将Base64字符串转换为字节数组_
    const dataBuffer = buffer.from(base64, 'base64'); 
  _  // 将字节数组写入文件_
    let writeLen = fileIo.writeSync(file.fd, dataBuffer.buffer); 
  _  // 关闭文件_
    fileIo.closeSync(file); 
  } 
  catch (error) { 
    hilog.error(0xA0c0d0, 'Error', error.code); 
  } 
  return uri; 
}