You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用CoreText绘制多段文本时,如何重置坐标与变换矩阵?

解决CoreText绘制多段文本时的坐标系重置问题

先明确几个核心事实:

  • CGContextSaveState和CGContextRestoreState绝对能重置坐标系,你之前没效果大概率是调用时机不对——必须在任何变换操作之前调用SaveState,一段文本绘制完成后立刻RestoreState,不能在中间穿插其他变换后才恢复。
  • TranslateCTM确实是相对变换,每次调用都是在当前坐标系基础上叠加平移,不是直接设置绝对坐标。

正确的多段文本绘制流程

按这个步骤来,绝对能避免坐标混乱:

  1. 初始化CGBitmapContext后,先保存一次全局初始状态(这是所有绘制的基准)
  2. 绘制第一段文本:
    • 用CTFramesetterSuggestFrameSizeWithConstraints计算文本的实际 bounds
    • 调用CGContextSaveState(保存当前干净的坐标系)
    • 用TranslateCTM平移到第一段文本的目标位置(比如居中偏移)
    • 调用CTFrameDraw绘制文本
    • 立刻调用CGContextRestoreState(回到平移前的原始坐标系)
  3. 绘制第二段文本:
    • 重复上述步骤:计算bounds → 保存状态 → 平移到第二段的目标位置 → 绘制 → 恢复状态
  4. 所有文本绘制完成后,可根据需要恢复全局初始状态(可选,上下文用完释放后也会失效)

你之前尝试无效的原因

  • 如果是在绘制第一段之后才调用SaveState,那保存的已经是平移后的坐标系,恢复自然没用。
  • MoveToPoint是针对路径绘制的,CoreText绘制依赖的是CTFrame的坐标系变换,和路径的当前点无关,所以调用它不会改变文本绘制位置。

可复用的代码示例

// 初始化CGBitmapContext
CGContextRef context = CGBitmapContextCreate(...);
// 保存全局初始状态
CGContextSaveState(context);

// ====== 绘制第一段文本 ======
CTFramesetterRef setter1 = CTFramesetterCreateWithAttributedString(attrStr1);
// 计算文本实际占用的尺寸
CGRect bounds1 = CTFramesetterSuggestFrameSizeWithConstraints(setter1, CFRangeMake(0,0), NULL, CGSizeMake(canvasWidth, CGFLOAT_MAX), NULL);
// 计算居中偏移(假设画布尺寸为canvasSize)
CGFloat translateX1 = (canvasSize.width - bounds1.size.width)/2;
CGFloat translateY1 = (canvasSize.height - bounds1.size.height)/2;

// 临时保存当前状态,避免影响后续绘制
CGContextSaveState(context);
// 平移到第一段的位置
CGContextTranslateCTM(context, translateX1, translateY1);
// 创建CTFrame并绘制
CTFrameRef frame1 = CTFramesetterCreateFrame(setter1, CFRangeMake(0,0), CGPathCreateWithRect(bounds1, NULL), NULL);
CTFrameDraw(frame1, context);
// 恢复到平移前的坐标系
CGContextRestoreState(context);

CFRelease(frame1);
CFRelease(setter1);

// ====== 绘制第二段文本 ======
CTFramesetterRef setter2 = CTFramesetterCreateWithAttributedString(attrStr2);
CGRect bounds2 = CTFramesetterSuggestFrameSizeWithConstraints(setter2, CFRangeMake(0,0), NULL, CGSizeMake(canvasWidth, CGFLOAT_MAX), NULL);
// 示例:把第二段放在第一段下方10pt处,保持水平居中
CGFloat translateX2 = (canvasSize.width - bounds2.size.width)/2;
CGFloat translateY2 = translateY1 + bounds1.size.height + 10;

CGContextSaveState(context);
CGContextTranslateCTM(context, translateX2, translateY2);
CTFrameRef frame2 = CTFramesetterCreateFrame(setter2, CFRangeMake(0,0), CGPathCreateWithRect(bounds2, NULL), NULL);
CTFrameDraw(frame2, context);
CGContextRestoreState(context);

CFRelease(frame2);
CFRelease(setter2);

// 恢复全局初始状态(可选)
CGContextRestoreState(context);
CGContextRelease(context);

额外提醒

CoreText的坐标系是左下为原点,如果是和UIKit交互(比如把绘制结果转成UIImage),记得提前用CGContextScaleCTM(context, 1, -1)翻转Y轴,这个操作要放在全局初始状态保存之前,或者单独用Save/Restore包裹,避免影响后续文本的位置计算。

内容的提问来源于stack exchange,提问作者Brad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 11:30:30