Skip to content

课程 16 - 文本的高级特性

在上一节课中,我们介绍了基于 SDF 的文本渲染的原理,也尝试使用了 ESDT 和 MSDF 提升渲染质量,另外也提到过 CanvasKit 相比 Canvas 提供的文本高级绘制特性。

在本节课中,我们首先会来看看 SDF 之外的绘制方式,然后将讨论并尝试实现这些特性:装饰线、阴影、文本跟随路径,最后文本不光要能渲染,也要有良好的交互,我们讲讨论输入框、文本选中以及 A11y 这些话题。

首先我们来看看除了 SDF 之外,还有哪些文本渲染方式。

使用贝塞尔曲线渲染文本

使用 Figma 的导出 SVG 功能可以发现,它的文本也是使用 Path 渲染的。如果不考虑渲染性能和 CJK 字符,使用贝塞尔曲线渲染文本确实是不错的选择。为了得到字符的矢量信息,在浏览器环境可以使用:

opentype.js

ts
opentype.load('fonts/Roboto-Black.ttf', function (err, font) {
    const path = font.getPath('Hello, World!', 0, 0, 32); // x, y, fontSize
});

harfbuzzjs

ts
import init from 'harfbuzzjs/hb.wasm?init';
import hbjs, { HBBlob, HBFace, HBFont, HBHandle } from 'harfbuzzjs/hbjs.js';

init().then((instance) => {
    const hb = hbjs(instance);
});

装饰线

text-decoration

阴影

Pixi.js 提供了 [DropShadowFilter] 来实现阴影效果。

glsl
// @see https://github.com/soimy/pixi-msdf-text/blob/master/src/msdf.frag#L49
vec3 shadowSample = texture2D(uSampler, vTextureCoord - shadowOffset).rgb;
float shadowDist = median(shadowSample.r, shadowSample.g, shadowSample.b);
float distAlpha = smoothstep(0.5 - shadowSmoothing, 0.5 + shadowSmoothing, shadowDist);
vec4 shadow = vec4(shadowColor, shadowAlpha * distAlpha);
gl_FragColor = mix(shadow, text, text.a);

文本跟随路径

在 Figma 社区中,很多用户都在期待这个特性,例如:Make text follow a path or a circle

在 SVG 中可以通过 textPath 实现,详见:Curved Text Along a Path

html
<path
    id="curve"
    d="M73.2,148.6c4-6.1,65.5-96.8,178.6-95.6c111.3,1.2,170.8,90.3,175.1,97"
/>
<text width="500">
    <textPath xlink:href="#curve"> Dangerous Curves Ahead </textPath>
</text>

Skia 提供了 MakeOnPath 方法,详见 Draw text along a path

ts
const textblob = CanvasKit.TextBlob.MakeOnPath(text, skPath, skFont);
canvas.drawTextBlob(textblob, 0, 0, textPaint);

在 Mapbox 中沿道路河流放置 label 是很常见的场景,详见 Map Label Placement in Mapbox GL

Map Label Placement in Mapbox GL

TeX math rendering

更友好的交互方式

输入框

目前我们只实现了文本的绘制,实际在应用中,文本输入框是必不可少的。下图来自 Figma

textarea in figma

文本选中

特殊效果

加载 Web 字体

webfontloader

ts
import WebFont from 'webfontloader';
WebFont.load({
    google: {
        families: ['Gaegu'],
    },
    active: () => {
        const text = new Text({
            x: 150,
            y: 150,
            content: 'Hello, world',
            fontFamily: 'Gaegu',
            fontSize: 55,
            fill: '#F67676',
        });
    },
});

Material Design on the GPU

Material Design on the GPU

扩展阅读

Released under the MIT License.