
1. Font와 내부 Shape

글자의 윤곽을 Shape의 경로로 구성한 뒤, 일반 도형과 같은 renderer 경로로 그립니다.
- Font Loader: 글자 윤곽과 배치 정보 해석
- Shape: MoveTo, LineTo, CubicTo, Close로 구성한 경로
- CPU Renderer: 경로의 coverage 계산과 RLE span 생성
1.1. 여러 글자 → 하나의 Shape 경로

각 glyph의 경로에 현재 글자 위치와 positioning offset을 더해 출력 경로에 붙입니다. 글자마다 별도의 Shape를 만드는 대신, Text 내부 Shape에 여러 글자의 contour를 모으는 방식입니다.
코드 보기: glyph 경로를 내부 Shape에 추가
줄바꿈을 사용하지 않는 wrapNone() 경로 기준입니다.
Canvas::update()
-> Canvas::Impl::update()
-> Paint::Impl::update() [Canvas 내부 Scene]
-> SceneImpl::update()
-> Paint::Impl::update() [Text]
-> TextImpl::update()
-> TextImpl::load()
-> SfntLoader::get()
-> SfntLoader::wrapNone()
-> _build()static void _build(const RenderPath& in, const Point& cursor, const Point& offset, RenderPath& out)
{
out.cmds.push(in.cmds);
out.pts.grow(in.pts.count);
ARRAY_FOREACH(p, in.pts) {
out.pts.push(*p + cursor + offset);
}
}in: 개별 glyph의 경로cursor,offset: 문자열 내 배치 위치와 글자 간 위치 보정out: Text 내부 Shape에 전달된 출력 경로wrapNone(): UTF-8 해석, glyph 요청, 경로 추가, advance에 따른 cursor 이동
1.2. Text → Shape 위임

Text는 구성된 Shape에 update와 render를 위임합니다. CPU renderer에서는 일반 도형과 같은 Fill·Stroke 처리로 coverage와 RLE span을 생성합니다.
Update가 Shape의 impl.rd를 준비하고, Draw가 Shape와 rd를 사용합니다.
코드 보기: Update를 내부 Shape에 위임
Canvas::update()
-> Canvas::Impl::update()
-> Paint::Impl::update() [Canvas 내부 Scene]
-> SceneImpl::update()
-> Paint::Impl::update() [Text]
-> TextImpl::update()TextImpl::update의 핵심 호출입니다. 글자 경로 구성과 Text 속성 반영이 끝난 뒤 실행됩니다.
PAINT(shape)->update(renderer, transform, clips, opacity, flag, false);코드 보기: Render를 내부 Shape에 위임
Canvas::draw()
-> Canvas::Impl::draw()
-> Paint::Impl::render() [Canvas 내부 Scene]
-> SceneImpl::render()
-> Paint::Impl::render() [Text]
-> TextImpl::render()TextImpl::render의 핵심 호출입니다.
return PAINT(shape)->render(renderer);Appendix. TTF의 곡선 → Shape의 CubicTo
TTF의 quadratic 곡선은 제어점이 하나인 2차 베지어입니다. ThorVG는 이를 제어점 두 개의 3차 베지어로 변환해 Shape의 CubicTo로 저장합니다.

코드 보기: TTF point에서 LineTo·CubicTo 생성
TTF 폰트에서 아직 캐시되지 않은 glyph를 요청하는 경로입니다.
Canvas::update()
-> Canvas::Impl::update()
-> Paint::Impl::update() [Canvas 내부 Scene]
-> SceneImpl::update()
-> Paint::Impl::update() [Text]
-> TextImpl::update()
-> TextImpl::load()
-> SfntLoader::get()
-> SfntLoader::wrapNone()
-> SfntLoader::request()
-> TtfReader::convert()참고: tvgTtfReader.cpp
if (flags[begin + x] & ON_CURVE) {
if (offCurve) {
path.cubicTo(
path.pts.last() + (2.0f / 3.0f) * (pts[begin + x - 1] - path.pts.last()),
pts[begin + x] + (2.0f / 3.0f) * (pts[begin + x - 1] - pts[begin + x]),
pts[begin + x]);
offCurve = false;
} else {
path.lineTo(pts[begin + x]);
}
} else {
if (offCurve) {
auto end = (pts[begin + x] + pts[begin + x - 1]) * 0.5f;
path.cubicTo(
path.pts.last() + (2.0f / 3.0f) * (pts[begin + x - 1] - path.pts.last()),
end + (2.0f / 3.0f) * (pts[begin + x - 1] - end),
end);
} else {
offCurve = true;
}
}OTF의 CFF 윤곽은 OtfReader::convert에서 charStrings()로 해석합니다. 포맷별 해석 과정은 다르지만 출력은 동일한 RenderPath입니다.