做绘画 APP 时怎么控制文字的大小、粗细和倾斜?Font 类帮你搞定

你有没有遇到过这种情况:在做一个绘画类 APP 的时候,想让用户可以在画布上自由地写字、标注,但文字的大小、粗细、倾斜角度都需要灵活控制?比如用户想写个大标题用粗体,写个小注释用细体,或者想让文字有点"意大利斜体"的感觉?

这时候你就需要 HarmonyOS 的 drawing.Font 类了。简单说,这个类就是用来描述"字型绘制时所使用的属性"的——字体大小、粗体、倾斜、边缘效果等等,全都由它来管。

Font 类功能总览

下面是 Font 类的主要功能分类,帮助你快速了解它能做什么:

drawing.Font 类

基础属性设置

渲染质量控制

文本测量

字形操作

setSize 设置大小

enableEmbolden 粗体

setSkewX 倾斜

setScaleX 缩放

enableSubpixel 亚像素

setEdging 边缘效果

setHinting 轮廓对齐

measureText 文本宽度

measureSingleCharacter 单字宽度

textToGlyphs 文本转字形

getTextPath 文字转路径

先把 Font 创建出来

要用 Font,第一步当然是创建一个实例。别忘了导入 drawing 模块:

import { drawing } from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();

就这么简单,一行 new drawing.Font() 搞定。创建出来的是一个默认的 Font 对象,后面你可以根据需要给它设置各种属性。

设置字体大小:setSize

这是最基础的操作了。你想让文字多大,就调用 setSize

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.setSize(5);

这里 setSize(5) 把字体大小设成了 5px。注意,这个单位是物理像素 px,不是逻辑像素。也就是说在高分屏上,5px 可能看起来非常小。

有个细节要注意:如果你传一个负数进去,字体大小会被重置为 0,而字体大小为 0 的时候,绘制的文字是不会显示的。所以别传负数,除非你故意想让文字"消失"。

想读取当前字体大小?用 getSize

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.setSize(5);
let fontSize = font.getSize();

getSize() 返回的是一个浮点数,就是你之前设置的那个值。

让文字变粗:enableEmbolden

有时候你想让文字加粗显示,比如标题或者重点标注。Font 提供了 enableEmbolden 方法来开启粗体效果:

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.enableEmbolden(true);

true 开启粗体,传 false 关闭。这个粗体效果是软件模拟的,不是说你必须用一个粗体字体文件,它会自动把普通字体"加粗"渲染。

想检查当前有没有开启粗体?用 isEmbolden

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.enableEmbolden(true);
console.info("values=" + font.isEmbolden());

返回 true 说明粗体已开启。

让文字倾斜:setSkewX

想做出"斜体"效果?用 setSkewX 就行。这个方法设置的是文字在 X 轴方向上的倾斜比例:

import { RenderNode } from '@kit.ArkUI';
import { common2D, drawing } from '@kit.ArkGraphics2D';

class DrawingRenderNode extends RenderNode {
  draw(context : DrawContext) {
    const canvas = context.canvas;
    const pen = new drawing.Pen();
    pen.setStrokeWidth(5);
    pen.setColor({alpha: 255, red: 255, green: 0, blue: 0});
    canvas.attachPen(pen);
    let font = new drawing.Font();
    font.setSize(100);
    font.setSkewX(1);
    const textBlob = drawing.TextBlob.makeFromString("hello", font, drawing.TextEncoding.TEXT_ENCODING_UTF8);
    canvas.drawTextBlob(textBlob, 200, 200);
  }
}

这里 setSkewX(1) 让文字往左边倾斜。参数是正数就往左斜,负数就往右斜。值越大,倾斜越明显。

想读取当前的倾斜值?用 getSkewX

import {drawing} from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
font.setSkewX(-1)
console.info("values=" + font.getSkewX());

让文字变宽变窄:setScaleX

除了倾斜,你还可以让文字在水平方向上"拉伸"或"压缩"。setScaleX 设置的是 X 轴的缩放比例:

import { RenderNode } from '@kit.ArkUI';
import { common2D, drawing } from '@kit.ArkGraphics2D';

class DrawingRenderNode extends RenderNode {
  draw(context : DrawContext) {
    const canvas = context.canvas;
    const pen = new drawing.Pen();
    pen.setStrokeWidth(5);
    pen.setColor({alpha: 255, red: 255, green: 0, blue: 0});
    canvas.attachPen(pen);
    let font = new drawing.Font();
    font.setSize(100);
    font.setScaleX(2);
    const textBlob = drawing.TextBlob.makeFromString("hello", font, drawing.TextEncoding.TEXT_ENCODING_UTF8);
    canvas.drawTextBlob(textBlob, 200, 200);
  }
}

setScaleX(2) 意味着文字在水平方向上被拉伸到原来的 2 倍。如果你想让文字变窄,可以传一个小于 1 的值,比如 0.5

读取当前缩放比例:

import {drawing} from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
font.setScaleX(2);
console.info("values=" + font.getScaleX());

亚像素渲染:enableSubpixel

这个功能可能听起来有点"技术宅",但它确实能让你的文字看起来更平滑。亚像素渲染(Subpixel Rendering)是一种利用屏幕像素排列特性来提升文字清晰度的技术。

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.enableSubpixel(true);

开启之后,文字边缘会更加细腻,不会那么"锯齿"。不过要注意,这个效果在不同屏幕上可能表现不一样,因为它是和屏幕的物理像素排列相关的。

检查是否开启了亚像素渲染:

import {drawing} from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
font.enableSubpixel(true)
console.info("values=" + font.isSubpixel());

线性缩放:enableLinearMetrics

线性缩放是什么意思呢?简单说,就是让字体在缩放的时候保持"线性"关系,不会因为缩放而产生额外的变换效果。

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.enableLinearMetrics(true);

在做一些需要精确控制文字大小的场景(比如图表标注、CAD 软件)时,开启线性缩放可以保证文字在不同缩放级别下的表现一致。

检查状态:

import {drawing} from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
font.enableLinearMetrics(true)
console.info("values=" + font.isLinearMetrics());

边缘效果:setEdging

文字的边缘处理有几种模式,setEdging 可以控制文字边缘的抗锯齿效果:

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.setEdging(drawing.FontEdging.SUBPIXEL_ANTI_ALIAS);

FontEdging 有几个可选值,SUBPIXEL_ANTI_ALIAS 是亚像素抗锯齿,效果最细腻。你可以根据实际需求选择不同的边缘处理模式。

读取当前边缘效果:

import { drawing } from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
console.info("values=" + font.getEdging());

轮廓效果:setHinting

Hinting 是字体渲染中的一个重要概念,它决定了字体轮廓如何对齐到像素网格上:

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.setHinting(drawing.FontHinting.FULL);

FontHinting.FULL 表示完全 hinting,文字会尽量对齐到像素网格上,在低分辨率屏幕上看起来更清晰。如果你追求更"原汁原味"的字体轮廓,可以用 NONE

读取当前 hinting 设置:

import { drawing } from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
console.info("values=" + font.getHinting());

测量文本宽度:measureText

这个方法在实际开发中用得非常多。你想知道一段文字在当前字体设置下占多宽,就用 measureText

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.measureText("drawing", drawing.TextEncoding.TEXT_ENCODING_UTF8);

你需要传入文本内容和编码格式。返回值是一个浮点数,表示文本的宽度(单位是 px)。

有个小提示:这个方法测量的是"原始字符串"的宽度。如果你的文本经过了排版处理(比如自动换行),那测量结果可能和实际显示的宽度不一样。那种情况下建议用 measure.measureText 来替代。

测量单个字符宽度:measureSingleCharacter

如果你只需要测量一个字符的宽度,用这个方法更高效:

import { RenderNode } from '@kit.ArkUI';
import { drawing } from '@kit.ArkGraphics2D';

class DrawingRenderNode extends RenderNode {
  draw(context : DrawContext) {
    const canvas = context.canvas;
    const font = new drawing.Font();
    font.setSize(20);
    let width = font.measureSingleCharacter("你");
  }
}

注意参数必须是单个字符,字符串长度为 1。如果当前字体不支持这个字符,系统会自动退回到系统字体来测量。

还有一个更高级的版本 measureSingleCharacterWithFeatures,可以传入字体特征参数:

import { RenderNode } from '@kit.ArkUI';
import { drawing } from '@kit.ArkGraphics2D';

class DrawingRenderNode extends RenderNode {
  draw(context : DrawContext) {
    const font = new drawing.Font();
    font.setSize(20);
    let fontFeatures : Array<drawing.FontFeature> = [];
    fontFeatures.push({name: 'calt', value: 0});
    let width = font.measureSingleCharacterWithFeatures("你", fontFeatures);
  }
}

FontFeature 可以让你控制一些 OpenType 字体特性,比如连字、数字样式等。传空数组就使用字体文件里的默认设置。

获取文本字符数量:countText

想知道一个字符串里有多少个字符?用 countText

import { drawing } from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
let resultNumber: number = font.countText('ABCDE');
console.info("count text number: " + resultNumber);

这个方法会正确处理各种编码的字符,包括中文、emoji 等。

文本转字形索引:textToGlyphs

这个方法把文本字符串转换成字形索引数组。字形(Glyph)是字体文件中实际存储的字符图形,一个字符可能对应一个或多个字形:

import { drawing } from '@kit.ArkGraphics2D';

let font : drawing.Font = new drawing.Font();
let text : string = 'hello world';
let glyphs : number[] = font.textToGlyphs(text);
console.info("drawing text toglyphs OnTestFunction num =  " + glyphs.length );

返回的数组里每个元素都是一个字形索引。你可以用这些索引来获取每个字形的宽度、边界等信息。

获取字形宽度:getWidths

拿到字形索引之后,你可以获取每个字形的宽度:

import { drawing } from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
let text: string = 'hello world';
let glyphs: number[] = font.textToGlyphs(text);
let fontWidths: Array<number> = font.getWidths(glyphs);
for (let index = 0; index < fontWidths.length; index++) {
  console.info("get fontWidths[", index, "]:", fontWidths[index]);
}

这个方法返回一个数组,长度和传入的字形索引数组一样,每个元素对应一个字形的宽度。

获取字形边界:getBounds

除了宽度,你还可以获取每个字形的完整边界矩形:

import { common2D, drawing } from '@kit.ArkGraphics2D';

let font: drawing.Font = new drawing.Font();
let text: string = 'hello world';
let glyphs: number[] = font.textToGlyphs(text);
let fontBounds: Array<common2D.Rect> = font.getBounds(glyphs);
for (let index = 0; index < fontBounds.length; index++) {
  console.info("get fontWidths[", index, "] left:", fontBounds[index].left, " top:", fontBounds[index].top,
    " right:", fontBounds[index].right, " bottom:", fontBounds[index].bottom);
}

每个 Rect 包含 lefttoprightbottom 四个值,描述了字形的边界框。这在做文字碰撞检测、自定义排版时非常有用。

获取文字轮廓路径:getTextPath

这个功能很有意思——它能把文字转换成路径(Path)。也就是说,你可以把文字当成图形来处理:

import { drawing } from '@kit.ArkGraphics2D';
import { buffer } from '@kit.ArkTS';
import { RenderNode } from '@kit.ArkUI';

class DrawingRenderNode extends RenderNode {
  draw(context : DrawContext) {
    const canvas = context.canvas;
    let font = new drawing.Font();
    font.setSize(50);
    let myString: string = "Hello";
    let length: number = buffer.from(myString).length;
    let path = font.getTextPath(myString, length, 0, 100);
    canvas.drawPath(path);
  }
}

这里有个关键点:byteLength 参数是文本的字节长度,不是字符数量。所以用 buffer.from(myString).length 来获取。xy 是文字在画布上的起始坐标。

拿到 Path 之后,你可以对它做各种操作——描边、填充、甚至做路径动画。

获取单个字形的路径:createPathForGlyph

如果你想更精细地控制,可以获取单个字形的路径:

import { RenderNode } from '@kit.ArkUI';
import { drawing } from '@kit.ArkGraphics2D';

class DrawingRenderNode extends RenderNode {
  draw(context : DrawContext) {
    const canvas = context.canvas;
    let font = new drawing.Font();
    font.setSize(50)
    let text: string = '你好';
    let glyphs: number[] = font.textToGlyphs(text);
    for (let index = 0; index < glyphs.length; index++) {
      let path: drawing.Path = font.createPathForGlyph(glyphs[index])
      canvas.drawPath(path)
    }
  }
}

这段代码把"你好"两个字分别转换成路径,然后逐个绘制。你可以对每个字形的路径做不同的处理,比如给每个字不同的颜色、做不同的动画效果。

设置和获取字体样式:setTypeface / getTypeface

Font 可以关联一个 Typeface(字体样式),包括字体名称、粗细、斜体等:

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
font.setTypeface(new drawing.Typeface());

读取当前关联的字体:

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
let typeface = font.getTypeface();

Typeface 的具体用法我们在后面的文章里会详细讲。

获取字体度量信息:getMetrics

getMetrics 返回一个 FontMetrics 对象,里面包含了字体的各种度量信息:

import { drawing } from '@kit.ArkGraphics2D';

let font = new drawing.Font();
let metrics = font.getMetrics();

FontMetrics 里有 ascent(上升高度)、descent(下降高度)、leading(行间距)等信息,这些在做自定义文字排版时非常关键。

基线对齐:setBaselineSnap

这个功能控制文字基线是否与像素对齐:

import { drawing } from '@kit.ArkGraphics2D';

let font : drawing.Font = new drawing.Font();
font.setBaselineSnap(true);
console.info("drawing font isBaselineSnap: " + font.isBaselineSnap());

开启基线对齐后,当画布矩阵是轴对齐的时候,文字的基线会对齐到最近的像素上,这样文字看起来会更清晰。特别是在小字号的情况下,这个设置能显著提升可读性。

嵌入位图:setEmbeddedBitmaps

有些字体文件里嵌入了位图版本的字形,这个设置控制是否使用这些位图:

import { drawing } from '@kit.ArkGraphics2D';

let font : drawing.Font = new drawing.Font();
font.setTypeface(new drawing.Typeface());
font.setEmbeddedBitmaps(false);
console.info("draw isEmbeddedBitmaps: " + font.isEmbeddedBitmaps());

在某些场景下,使用嵌入位图可以获得更好的渲染效果,特别是在小字号时。但大多数情况下,使用矢量轮廓(设置为 false)效果更好。

自动调整轮廓:setForceAutoHinting

这个设置强制对字型轮廓进行自动调整:

import { drawing } from '@kit.ArkGraphics2D';

let font : drawing.Font = new drawing.Font();
font.setTypeface(new drawing.Typeface());
font.setForceAutoHinting(false);
console.info("drawing isForceAutoHinting:  " + font.isForceAutoHinting());

即使字体文件里包含了 hinting 信息,开启这个选项后也会使用自动 hinting 算法。这在某些情况下可以获得更一致的渲染效果。

跟随主题字体:setThemeFontFollowed

从 API version 15 开始,Font 支持跟随系统主题字体:

import { drawing } from '@kit.ArkGraphics2D';

let font : drawing.Font = new drawing.Font();
font.setThemeFontFollowed(true);
console.info("font is theme font followed: " + font.isThemeFontFollowed());

开启这个功能后,如果用户在系统设置里切换了主题字体,你的 APP 里的文字也会跟着变。这对需要和系统风格保持一致的应用很有用。

文字绘制的完整流程

在实际开发中,使用 Font 绘制文字的典型流程如下:

创建 Font 对象

设置字体属性

setSize 设置大小

enableEmbolden 设置粗体

setSkewX 设置倾斜

创建 TextBlob

配置画笔 Pen

绑定到 Canvas

绘制文字

需要测量?

measureText 获取宽度

完成

小结

Font 类是 HarmonyOS drawing 模块里处理文字渲染的核心工具。通过它你可以:

  • 控制字体大小、粗细、倾斜、缩放
  • 调整渲染质量(亚像素、边缘效果、hinting)
  • 测量文本和字形的宽度、边界
  • 把文字转换成路径做高级处理
  • 关联字体样式、获取度量信息

记住,Font 使用的是物理像素单位 px,而且是单线程模型,需要你自己管理线程安全。在实际开发中,建议创建一个 Font 对象后重复使用,而不是每次都 new 一个新的。

Logo

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

更多推荐