为何macOS 14中HTML创建NSAttributedString与initWithString:attributes:行为不同?
macOS 14下NSAttributedString HTML转换特定字体显示方框问号问题
问题背景
允许用户选择已安装字体,在三个场景中使用:
- 场景1:通过
NSFontDescriptor创建NSAttributedString用于选项列表,功能正常 - 场景2:将设置了body字体为所选字体的小型HTML转为
NSAttributedString并显示在NSTextField,升级至macOS 14后完全失效,所有字符显示为带问号的方框 - 场景3:在
WKWebView中显示同字体的长HTML文档,功能正常
问题仅出现在Calibri、Candara(微软Office字体)、SF Pro等特定字体,即使CSS中设置了serif作为后备字体也无效——系统判定字体可用,但无法正常渲染。该问题为macOS 14新增,此前版本无异常。
问题分析
macOS 14中,NSAttributedString的HTML解析模块(基于旧版WebKit组件)在处理部分字体时存在匹配问题:
- 部分字体的显示名称与PostScript名称不匹配,解析器无法正确定位字体文件
- 对于包含字重/样式变体的字体(如bold、italic),解析器无法自动关联对应变体,导致找不到可渲染的字形,且未触发后备字体逻辑
解决方案
方案1:使用字体的PostScript名称替代显示名称
获取用户所选字体的PostScript名称(而非显示名称),替换HTML中font-family的值,确保解析器能精准定位字体:
// 假设用户选择的字体显示名称为selectedFontDisplayName NSFont *selectedFont = [NSFont fontWithName:selectedFontDisplayName size:14]; NSString *postScriptName = selectedFont.fontDescriptor.postScriptName; // 生成带PostScript名称的HTML NSString *htmlString = [NSString stringWithFormat:@"<!DOCTYPE html>" "<html>" "<head>" "<style>" "body { font-family: '%@', serif; font-size: 14px; }" "</style>" "</head>" "<body>" "<p>Text with <b>bold</b> or <i>italics</i> maybe.</p>" "</body>" "</html>", postScriptName];
方案2:显式指定字体变体的CSS规则
针对HTML中的<b>、<i>标签,显式指定对应字体变体的名称,避免解析器自动匹配失败:
NSString *htmlString = @"<!DOCTYPE html>" "<html>" "<head>" "<style>" "body { font-family: 'Candara', serif; font-size: 14px; }" "b { font-family: 'Candara Bold', serif; font-weight: normal; }" "i { font-family: 'Candara Italic', serif; font-style: normal; }" "</style>" "</head>" "<body>" "<p>Text with <b>bold</b> or <i>italics</i> maybe.</p>" "</body>" "</html>";
注:需根据实际字体的变体名称调整(可通过Font Book查看字体的完整名称)
方案3:转换后手动替换字体属性
若前两种方案无效,可在HTML转换为NSAttributedString后,遍历并替换无法渲染的字体属性:
NSData *htmlData = [htmlString dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *options = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}; NSError *error; NSMutableAttributedString *mutableAttrStr = [[NSAttributedString alloc] initWithData:htmlData options:options documentAttributes:nil error:&error].mutableCopy; // 遍历替换无效字体 [mutableAttrStr enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, mutableAttrStr.length) options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { NSFont *currentFont = value; if (!currentFont) return; // 检查当前字体是否为问题字体(可根据实际情况调整判断逻辑) NSArray *problemFontNames = @[@"Calibri", @"Candara", @"SF Pro"]; if ([problemFontNames containsObject:currentFont.fontDescriptor.displayName]) { // 替换为用户选择的正确字体(保持原字号) NSFont *replacementFont = [NSFont fontWithName:selectedFontDisplayName size:currentFont.pointSize]; if (replacementFont) { [mutableAttrStr addAttribute:NSFontAttributeName value:replacementFont range:range]; } } }]; // 使用mutableAttrStr更新NSTextField [self.textField setAttributedStringValue:mutableAttrStr];
内容的提问来源于stack exchange,提问作者Craig
相关产品推荐
相关产品推荐

