高级文本排版?text 模块帮你搞定一切

你有没有遇到过这样的需求:在一个自定义绘制的界面上,需要精确控制文字的排版?比如做一个电子书阅读器,需要支持各种字体、字号、行间距;或者做一个图片编辑器,需要在图片上叠加文字,还要控制文字的位置和样式。

HarmonyOS 的 @ohos.graphics.text 模块就是干这个的。它提供了一套完整的文本排版和字体管理 API,能让你在自定义绘制场景中实现高质量的文本渲染。

导入模块

import { text } from '@kit.ArkGraphics2D'

核心概念:从字体到段落

下面是 text 模块排版的整体流程:

获取字体集 FontCollection

加载自定义字体

定义文本样式 TextStyle

定义段落样式 ParagraphStyle

创建段落构建器 ParagraphBuilder

添加文字内容 addText

构建段落对象 build

执行排版 layoutSync

查询结果或绘制到画布

在开始写代码之前,先理解一下这个模块的核心概念:

  • FontCollection(字体集):管理所有可用的字体,包括系统字体和自定义字体。
  • TextStyle(文本样式):定义文字的外观,比如字体、字号、颜色、字重等。
  • ParagraphStyle(段落样式):定义段落的排版规则,比如对齐方式、断行策略等。
  • ParagraphBuilder(段落生成器):用来构建段落对象的工具,你可以往里面添加文字和样式。
  • Paragraph(段落):最终的排版结果,可以测量尺寸、绘制到画布上。

打个比方,如果把排版比作盖房子:

  • FontCollection 是你的建材库(有哪些砖、哪些瓦)
  • TextStyle 是装修方案(用什么材料、什么颜色)
  • ParagraphStyle 是建筑规范(房间怎么布局)
  • ParagraphBuilder 是施工队(按照方案和规范来盖)
  • Paragraph 是盖好的房子(可以测量面积、可以展示)

第一步:获取字体集

字体集是排版的基础,你得先告诉系统"我能用哪些字体"。

获取全局字体集:

import { text } from '@kit.ArkGraphics2D'

function textFunc() {
  let fontCollection = text.FontCollection.getGlobalInstance();
}

@Entry
@Component
struct Index {
  fun: Function = textFunc;
  build() {
    Column() {
      Button().onClick(() => {
        this.fun();
      })
    }
  }
}

FontCollection.getGlobalInstance() 返回应用全局的字体集实例。这个实例在整个应用生命周期内都有效,包含了系统预装的所有字体。

获取本地字体集(推荐卡片场景):

import { text } from '@kit.ArkGraphics2D'
let fontCollection = text.FontCollection.getLocalInstance();

如果你在做 ArkTS 卡片,建议用 getLocalInstance(),它会创建一个独立的字体集实例,不会影响主应用。

第二步:加载自定义字体

系统自带的字体可能满足不了你的需求。比如你想用一个特殊的艺术字体来做标题,就需要加载自定义字体。

同步加载:

import { text } from '@kit.ArkGraphics2D'

let fontCollection: text.FontCollection = new text.FontCollection();

@Entry
@Component
struct RenderTest {
  LoadFontSyncTest() {
    fontCollection.loadFontSync('Clock_01', 'file:///system/fonts/HarmonyClock_01.ttf')
    let fontFamilies: Array<string> = ["Clock_01"]
    let myTextStyle: text.TextStyle = {
      fontFamilies: fontFamilies
    };
    let myParagraphStyle: text.ParagraphStyle = {
      textStyle: myTextStyle,
    }
    let paragraphBuilder: text.ParagraphBuilder = new text.ParagraphBuilder(myParagraphStyle, fontCollection);

    let textData = "测试 loadFontSync 加载字体HarmonyClock_01.ttf";
    paragraphBuilder.addText(textData);
    let paragraph: text.Paragraph = paragraphBuilder.build();
    paragraph.layoutSync(600);
  }

  aboutToAppear() {
    this.LoadFontSyncTest();
  }

  build() {
  }
}

loadFontSync 的第一个参数是字体的别名(随便取),第二个参数是字体文件的路径。加载后,在 TextStylefontFamilies 里用这个别名就能使用这个字体了。

异步加载:

import { text } from '@kit.ArkGraphics2D'

let fontCollection: text.FontCollection = new text.FontCollection();

@Entry
@Component
struct RenderTest {
  async loadFontPromise() {
    fontCollection.loadFont('testName', 'file:///system/fonts/a.ttf').then((data) => {
      console.info(`Succeeded in doing loadFont ${JSON.stringify(data)} `);
    }).catch((error: Error) => {
      console.error(`Failed to do loadFont, error: ${JSON.stringify(error)} message: ${error.message}`);
    });
  }

  aboutToAppear() {
    this.loadFontPromise();
  }

  build() {
  }
}

异步加载不会阻塞 UI 线程,适合加载大字体文件的场景。

第三步:构建段落

有了字体集,就可以开始构建段落了。

let fontCollection: text.FontCollection = text.FontCollection.getGlobalInstance();

// 定义文本样式
let myTextStyle: text.TextStyle = {
  fontFamilies: ["HarmonyOS Sans"],
  fontSize: 24,
  fontWeight: text.FontWeight.W400,
  color: { alpha: 255, red: 0, green: 0, blue: 0 }
};

// 定义段落样式
let myParagraphStyle: text.ParagraphStyle = {
  textStyle: myTextStyle,
  align: text.TextAlign.LEFT,
  maxLines: 5
};

// 创建段落构建器
let paragraphBuilder: text.ParagraphBuilder = new text.ParagraphBuilder(myParagraphStyle, fontCollection);

// 添加文字
paragraphBuilder.addText("这是一段测试文字,用来演示 text 模块的排版功能。");

// 构建段落
let paragraph: text.Paragraph = paragraphBuilder.build();

// 设置排版宽度并执行排版
paragraph.layoutSync(600);

这段代码做了以下几件事:

  1. 定义了一个 TextStyle,设置了字体、字号、字重和颜色。
  2. 定义了一个 ParagraphStyle,设置了文本样式、对齐方式和最大行数。
  3. 创建了一个 ParagraphBuilder,传入段落样式和字体集。
  4. addText 添加文字内容。
  5. 调用 build() 构建段落对象。
  6. 调用 layoutSync(600) 设置排版宽度为 600 像素,并执行排版。

第四步:查询排版结果

段落排版完成后,你可以查询各种排版信息:

  • paragraph.getHeight():获取段落总高度
  • paragraph.getLongestLine():获取最长一行的宽度
  • paragraph.getLineCount():获取行数
  • paragraph.getTextLines():获取所有行对象(TextLine)

每一行(TextLine)又可以进一步获取:

  • textLine.getGlyphRuns():获取这一行的所有排版单元(Run)

每个 Run 包含了连续的、使用相同样式的文字片段。通过 Run 你可以获取每个字符的位置信息,这在做文字选中、文字点击等交互时非常有用。

字体管理:查询系统字体

系统字体和自定义字体的加载方式有所不同,下面是字体加载的决策流程:

系统字体

自定义字体文件

资源引用字体

阻塞加载

非阻塞加载

需要使用字体

字体来源?

getSystemFontFullNamesByType 查询

加载方式?

loadFontSync 同步加载

loadFontSync

loadFont 异步加载

获取字体描述符 FontDescriptor

在 TextStyle 中使用字体别名

除了加载自定义字体,你还可以查询系统已有的字体。

按类型获取字体名称列表:

import { text } from '@kit.ArkGraphics2D'
import { BusinessError } from '@kit.BasicServicesKit'

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button("get font list")
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .width(300)
          .height(80)
          .onClick(() => {
            let fontType:text.SystemFontType = text.SystemFontType.GENERIC
            let promise = text.getSystemFontFullNamesByType(fontType)
            promise.then((data) => {
              console.info(`then font list size: ${data.length}`)
              data.forEach((fontItem) => {
                console.info(fontItem)
              })
            }).catch((error: BusinessError) => {
              console.error(`Failed to get font fullNames by type, error: ${JSON.stringify(error)}`);
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

根据字体名称获取字体描述符:

import { text } from '@kit.ArkGraphics2D'
import { BusinessError } from '@kit.BasicServicesKit'

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button("get fontDescriptor")
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .width(300)
          .height(80)
          .onClick(() => {
            let fontType:text.SystemFontType = text.SystemFontType.GENERIC
            let promise = text.getFontDescriptorByFullName("HarmonyOS Sans", fontType)
            promise.then((fontDescriptor) => {
              console.info(`desc: ${JSON.stringify(fontDescriptor)}`)
            }).catch((error: BusinessError) => {
              console.error(`Failed to get fontDescriptor by fullName, error: ${JSON.stringify(error)}`);
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

字体描述符(FontDescriptor)包含了字体的详细信息:路径、名称、字重、宽度、是否斜体、是否等宽等。你可以用它来判断某个字体是否满足你的需求。

匹配字体描述符:

import { text } from '@kit.ArkGraphics2D'
import { BusinessError } from '@kit.BasicServicesKit'

@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button("font descriptor")
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
          .width(300)
          .height(80)
          .onClick(() => {
            console.info(`Get font descriptor start`)
            let promise = text.matchFontDescriptors({
              weight: text.FontWeight.W400,
            })
            promise.then((data) => {
              console.info(`Font descriptor array size: ${data.length}`);
              console.info(`Font descriptor result: ${JSON.stringify(data)}`)
            }).catch((error: BusinessError) => {
              console.error(`Failed to match the font descriptor, error: ${JSON.stringify(error)}`);
            });
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

matchFontDescriptors 可以根据你指定的条件(比如字重、是否等宽等)来搜索系统中符合条件的字体。这在做一个"字体选择器"功能时特别有用。

全局配置:高对比度文字和未定义字形

设置文字高对比度模式:

text.setTextHighContrast(text.TextHighContrast.TEXT_APP_DISABLE_HIGH_CONTRAST)

这个设置会影响整个进程的文字渲染。如果你的 APP 面向视力不好的用户,可以开启高对比度模式让文字更清晰。

设置未定义字形的显示方式:

text.setTextUndefinedGlyphDisplay(text.TextUndefinedGlyphDisplay.USE_TOFU)

当字体中没有某个字符的字形时,默认行为是用字体内部的 .notdef 字形(可能是空框或空格)。如果你设置成 USE_TOFU,就会强制用"豆腐块"(方框)来显示,方便你在开发阶段发现缺失的字符。

小结

text 模块是一个功能非常完整的文本排版引擎。它的核心流程是:

  1. 获取或创建字体集(FontCollection)
  2. 加载需要的字体(loadFontSync / loadFont)
  3. 定义文本样式和段落样式
  4. 用 ParagraphBuilder 构建段落
  5. 调用 layoutSync 排版
  6. 查询排版结果或绘制到画布

如果你的 APP 需要在自定义绘制场景中处理文本——不管是做电子书阅读器、图片编辑器、还是自定义 UI 组件——这个模块都能满足你的需求。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐