如何在.NET MAUI中用SkiaSharp+HarfBuzz正确渲染阿拉伯文本?
.NET MAUI中SkiaSharp+HarfBuzz阿拉伯文本渲染错误的解决方法
问题分析
你遇到的阿拉伯文本重叠、塑形错误,核心原因是HarfBuzz的字形坐标系统与SkiaSharp的布局逻辑没有正确对齐,包括单位转换、RTL文本的位置计算、HarfBuzz属性设置顺序这几个关键点。
修正方案
以下是调整后的完整代码,关键修改点已标注:
public byte[] ImageArabic() { try { int width = 400, height = 100, textSize = 40; string arabicText = "مرحبا بالعالم"; // 阿拉伯文本 using var surface = SKSurface.Create(new SKImageInfo(width, height)); var canvas = surface.Canvas; canvas.Clear(SKColors.White); // 加载阿拉伯字体 var assembly = typeof(App).GetTypeInfo().Assembly; var resourceName = "PrintReceipt.Resources.Fonts.GE_SS_Two_Light.ttf"; using Stream fontStream = assembly.GetManifestResourceStream(resourceName); if (fontStream == null) throw new FileNotFoundException($"字体文件 {resourceName} 未找到!"); using MemoryStream memoryStream = new MemoryStream(); fontStream.CopyTo(memoryStream); memoryStream.Position = 0; byte[] fontBytes = memoryStream.ToArray(); using SKTypeface typeface = SKTypeface.FromStream(new MemoryStream(fontBytes)); // 初始化HarfBuzz using var hbBlob = HarfBuzzSharp.Blob.FromStream(new MemoryStream(fontBytes)); using var hbFace = new HarfBuzzSharp.Face(hbBlob, 0); using var hbFont = new HarfBuzzSharp.Font(hbFace); // 🔴 修改:HarfBuzz缩放与Skia文本大小对齐,匹配HarfBuzz的1/64em单位 float scale = textSize * 64; hbFont.SetScale((int)scale, (int)scale); // 🔴 修改:先添加文本,再设置布局属性,避免GuessSegmentProperties覆盖手动配置 using var hbBuffer = new HarfBuzzSharp.Buffer(); hbBuffer.AddUtf16(arabicText); hbBuffer.Direction = HarfBuzzSharp.Direction.RightToLeft; hbBuffer.Script = HarfBuzzSharp.Script.Arabic; hbBuffer.Language = new HarfBuzzSharp.Language("ar"); // 无需调用GuessSegmentProperties,已手动指定所有必要属性 hbFont.Shape(hbBuffer); var glyphInfos = hbBuffer.GlyphInfos; var glyphPositions = hbBuffer.GlyphPositions; using var paint = new SKPaint { Color = SKColors.Black, IsAntialias = true, Typeface = typeface, TextSize = textSize }; // 构建定位字形的TextBlob using var textBlobBuilder = new SKTextBlobBuilder(); var runBuffer = textBlobBuilder.AllocatePositionedRun(typeface, textSize, glyphInfos.Length); // 🔴 修改:计算文本总宽度,确保RTL文本从右侧正确对齐 float totalAdvance = 0; foreach (var pos in glyphPositions) { totalAdvance += pos.XAdvance / 64.0f; } float startX = width - 10 - totalAdvance; // 右侧留10px边距,文本整体左对齐到该位置 float startY = height / 2 - paint.FontMetrics.Descent / 2; // 基于FontMetrics实现精准垂直居中 float currentX = startX; for (int i = 0; i < glyphInfos.Length; i++) { runBuffer.Glyphs[i] = (ushort)glyphInfos[i].Codepoint; // 🔴 修改:XOffset是相对于currentX的偏移,直接累加计算位置 float xPos = currentX + glyphPositions[i].XOffset / 64.0f; float yPos = startY + glyphPositions[i].YOffset / 64.0f; runBuffer.Positions[i] = new SKPoint(xPos, yPos); // 更新currentX到下一个字形的起始位置 currentX += glyphPositions[i].XAdvance / 64.0f; } using var textBlob = textBlobBuilder.Build(); canvas.DrawText(textBlob, 0, 0, paint); using var image = surface.Snapshot(); using var data = image.Encode(SKEncodedImageFormat.Png, 100); return data.ToArray(); } catch (Exception ex) { Console.WriteLine($"错误:{ex.Message}"); return null; } }
关键修改说明
- HarfBuzz缩放对齐:将HarfBuzz的缩放设置为
textSize * 64,匹配其默认的1/64em单位,确保字形大小与SkiaSharp的textSize完全一致。 - 属性设置顺序调整:先添加文本到HarfBuzz Buffer,再手动设置RTL方向、阿拉伯脚本和语言,避免自动检测方法覆盖手动配置。
- 垂直居中优化:使用
SKPaint.FontMetrics计算垂直居中位置,比简单的textSize/2更精准,避免文本上下偏移。 - RTL布局修正:先计算文本总宽度,再从右侧预留边距的位置开始布局,同时按HarfBuzz返回的XAdvance依次累加当前位置,彻底解决字符重叠问题。
内容的提问来源于stack exchange,提问作者Shahzaib Rasool
相关产品推荐
相关产品推荐

