Skip to content

feat(tools): 成就图片生成器改为本地渲染并支持任意语言 [Deepseek-V4.1-Flash] - #3655

Open
meixuan1234 wants to merge 3 commits into
PCL-Community:devfrom
meixuan1234:feat/achievement-render
Open

meixuan1234 wants to merge 3 commits into
PCL-Community:devfrom
meixuan1234:feat/achievement-render

Conversation

@meixuan1234

@meixuan1234 meixuan1234 commented Oct 5, 2026 •

Copy link
Copy Markdown

背景

成就图片生成器原先调用 minecraft-api.com,该接口不支持非 ASCII 字符,输入中文返回 404,界面因此只能标注「仅支持英文」。改为 WPF 本地渲染后不再依赖第三方接口。

改动

  • 本地绘制成就卡片,支持任意语言
  • 文字按像素宽度自动换行,卡片高度随内容自适应
  • 单行超过 67 字符时截取并追加 ...
  • 物品图标依次回退:实例资源 → 在线接口 → 屏障占位
  • 字体可切换为本机已安装的字体
  • 顺带修复两处既有缺陷:
    • RubbishClear:dirInfo2.Name == dirInfo2.Name + "-natives" 恒为 false,该清理分支从未生效
    • CmbHeadSize_SelectionChanged:skinPath is not null 无效(其初始值为空字符串)

AI 使用与状态

使用 Deepseek-V4.1-Flash 辅助实现、整理和验证。已使用 GitHub 签名。人工已核查每行代码并测试功能。

Fixes #1566

Summary by Sourcery

将成就图片生成迁移至本地渲染并增强多语言、字体和物品图标支持,同时修复资源清理与头像加载缺陷。

New Features:

  • 将成就图片生成改为本地渲染,支持任意语言、像素宽度换行和内容自适应高度。
  • 支持从本机实例资源或在线来源加载物品图标,并提供屏障及几何图标兜底。
  • 支持选择本机已安装字体,并在预览和保存时生成成就图片。

Bug Fixes:

  • 修复原有成就图片接口对非 ASCII 文本支持不足的问题。
  • 修复原生资源目录清理分支无法匹配的问题。
  • 修复头像尺寸切换在皮肤路径为空字符串时仍触发加载的问题。

Enhancements:

  • 改进成就图片文本处理,包括 Minecraft 格式码清理、超长文本截断、RTL 文字方向识别和图标缓存。
Original summary in English

Summary by Sourcery

将成就图片生成迁移至本地渲染并增强多语言、字体和物品图标支持,同时修复资源清理与头像加载缺陷。

New Features:

  • 将成就图片生成改为本地渲染,支持任意语言、像素宽度换行和内容自适应高度。
  • 支持从本机实例资源或在线来源加载物品图标,并提供屏障及几何图标兜底。
  • 支持选择本机已安装字体,并在预览和保存时生成成就图片。

Bug Fixes:

  • 修复原有成就图片接口对非 ASCII 文本支持不足的问题。
  • 修复原生资源目录清理分支无法匹配的问题。
  • 修复头像尺寸切换在皮肤路径为空字符串时仍触发加载的问题。

Enhancements:

  • 改进成就图片文本处理,包括 Minecraft 格式码清理、超长文本截断、RTL 文字方向识别和图标缓存。
Original summary in English

- RubbishClear 中 natives 目录的判断条件恒为 false,该清理分支从未生效
- CmbHeadSize_SelectionChanged 中 skinPath 判空无效,其初始值是空字符串而非 null
原先调用 minecraft-api.com 生成图片,该接口不支持非 ASCII 字符,输入中文返回 404,
界面因此只能标注「仅支持英文」。改为完全本地渲染后不再依赖第三方接口。

- 用 WPF 本地绘制成就卡片,支持任意语言
- 文字按像素宽度自动换行(含连续 ASCII 串),卡片高度随内容自适应
- 单行超过 67 字符时截取并追加省略号
- 物品图标按实例资源、在线接口、屏障占位依次回退
- 字体可切换为本机已安装的字体
- 在线图标缓存使用 ConcurrentDictionary,生成期间禁用预览与保存按钮

BREAKING CHANGE: 移除对 minecraft-api.com 的依赖
@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 size: XL PR 大小评估:超大型 labels Oct 5, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

审查者指南

该 PR 移除成就图片生成对 minecraft-api.com 的依赖,改用 WPF 本地渲染实现任意语言、自适应排版和可选字体,并通过本地资源、在线服务与占位图标提供多级图标回退;同时修复原生目录清理及头像尺寸切换问题。

本地成就图片渲染时序图

sequenceDiagram
    participant User
    participant PageToolsTest
    participant IconLoader
    participant Renderer
    participant AchievementImage

    User->>PageToolsTest: BtnAchievementPreview_Click
    PageToolsTest->>IconLoader: _LoadAchievementIconAsync(itemId)
    IconLoader->>IconLoader: _TryLoadVanillaIcon(itemId)
    alt local icon unavailable
        IconLoader->>IconLoader: _TryLoadOnlineIcon(itemId)
        alt online icon unavailable
            IconLoader->>IconLoader: _TryLoadBuiltInBarrier()
            alt built-in barrier unavailable
                IconLoader->>IconLoader: _LoadOnlineBarrier()
            end
        end
    end
    PageToolsTest->>Renderer: _RenderAchievementImage(icon)
    Renderer->>Renderer: _WrapTextByWidth(text, typeface, fontSize, maxWidth, pixelsPerDip)
    Renderer->>AchievementImage: set Source
Loading

成就图标回退流程图

flowchart TD
    A[Item ID] --> B[_TryLoadVanillaIcon]
    B -->|found| E[Render achievement image]
    B -->|not found| C[_TryLoadOnlineIcon]
    C -->|found| E
    C -->|not found| D[_TryLoadBuiltInBarrier]
    D -->|found| E
    D -->|not found| F[_LoadOnlineBarrier]
    F -->|found| E
    F -->|not found| G[Draw fallback cube]
    G --> E
Loading

文件级变更

变更 详情 文件
将成就图片生成从第三方接口改为 WPF 本地绘制,并增加多语言文本排版与字体选择。
  • 使用 DrawingVisual 和 RenderTargetBitmap 绘制带边框、阴影、图标及自适应高度的成就卡片。
  • 按字体实际像素宽度逐字符换行,并对各文本字段执行 67 字符截断和省略。
  • 枚举本机字体并支持在成就卡片中使用所选字体。
  • 更新成就输入校验、预览/保存流程及渲染期间的按钮和加载状态。
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs
PCL.Core/App/Localization/Languages/en-US.xaml
PCL.Core/App/Localization/Languages/zh-CN.xaml
实现物品图标的本地资源、在线服务和占位图标多级回退机制。
  • 根据 Minecraft 资源索引从当前实例加载物品或方块贴图。
  • 本地缺失时依次请求两个在线图标源,并缓存成功结果。
  • 增加内置屏障图标、在线屏障图标及程序绘制立方体作为最终兜底。
  • 为网络图标请求增加超时、异常处理和未找到提示。
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs
修复清理原生目录和头像尺寸切换的既有逻辑缺陷。
  • 将原生目录判断改为不区分大小写的 -natives 后缀匹配。
  • 将头像尺寸切换的皮肤路径判断改为空字符串检查。
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs

对照关联 issue 的评估

Issue 目标 已解决 说明
#1566 让百宝箱中的自定义成就图片生成器支持输入和生成中文等非 ASCII 语言文字。 ✅
#1566 移除对不支持非 ASCII 字符的第三方成就图片接口的依赖,并改为本地生成成就图片。 ✅
#1566 确保生成器的中文文本能够正确排版并可预览、保存为图片。 ✅

可能关联的 issue


提示与命令

与 Sourcery 交互

  • 触发新的审查: 在拉取请求中评论 @sourcery-ai review。
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 回复审查评论,请 Sourcery 根据该评论创建 issue。你也可以回复 @sourcery-ai issue,从该评论创建 issue。
  • 生成拉取请求标题: 在拉取请求标题的任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在拉取请求中评论 @sourcery-ai title,以随时生成或重新生成标题。
  • 生成拉取请求摘要: 在拉取请求正文的任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以在拉取请求中评论 @sourcery-ai summary,以随时生成或重新生成摘要。
  • 生成审查者指南: 在拉取请求中评论 @sourcery-ai guide,即可随时生成或重新生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,以解决所有 Sourcery 评论。如果你已经处理完所有评论并且不想再看到它们,这一功能会很有用。
  • 忽略所有 Sourcery 审查: 在拉取请求中评论 @sourcery-ai dismiss,以忽略所有现有的 Sourcery 审查。如果你想重新开始一次新的审查,这一功能尤其有用——别忘了评论 @sourcery-ai review 来触发新的审查!

自定义使用体验

访问你的控制面板以:

  • 启用或禁用审查功能,例如 Sourcery 生成的拉取请求摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

该 PR 移除成就图片生成对 minecraft-api.com 的依赖,改用 WPF 本地渲染实现任意语言、自适应排版和可选字体,并通过本地资源、在线服务与占位图标提供多级图标回退;同时修复原生目录清理及头像尺寸切换问题。

Sequence diagram for local achievement image rendering

sequenceDiagram
    participant User
    participant PageToolsTest
    participant IconLoader
    participant Renderer
    participant AchievementImage

    User->>PageToolsTest: BtnAchievementPreview_Click
    PageToolsTest->>IconLoader: _LoadAchievementIconAsync(itemId)
    IconLoader->>IconLoader: _TryLoadVanillaIcon(itemId)
    alt local icon unavailable
        IconLoader->>IconLoader: _TryLoadOnlineIcon(itemId)
        alt online icon unavailable
            IconLoader->>IconLoader: _TryLoadBuiltInBarrier()
            alt built-in barrier unavailable
                IconLoader->>IconLoader: _LoadOnlineBarrier()
            end
        end
    end
    PageToolsTest->>Renderer: _RenderAchievementImage(icon)
    Renderer->>Renderer: _WrapTextByWidth(text, typeface, fontSize, maxWidth, pixelsPerDip)
    Renderer->>AchievementImage: set Source
Loading

Flow diagram for achievement icon fallback

flowchart TD
    A[Item ID] --> B[_TryLoadVanillaIcon]
    B -->|found| E[Render achievement image]
    B -->|not found| C[_TryLoadOnlineIcon]
    C -->|found| E
    C -->|not found| D[_TryLoadBuiltInBarrier]
    D -->|found| E
    D -->|not found| F[_LoadOnlineBarrier]
    F -->|found| E
    F -->|not found| G[Draw fallback cube]
    G --> E
Loading

File-Level Changes

Change Details Files
将成就图片生成从第三方接口改为 WPF 本地绘制,并增加多语言文本排版与字体选择。
  • 使用 DrawingVisual 和 RenderTargetBitmap 绘制带边框、阴影、图标及自适应高度的成就卡片。
  • 按字体实际像素宽度逐字符换行,并对各文本字段执行 67 字符截断和省略。
  • 枚举本机字体并支持在成就卡片中使用所选字体。
  • 更新成就输入校验、预览/保存流程及渲染期间的按钮和加载状态。
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs
PCL.Core/App/Localization/Languages/en-US.xaml
PCL.Core/App/Localization/Languages/zh-CN.xaml
实现物品图标的本地资源、在线服务和占位图标多级回退机制。
  • 根据 Minecraft 资源索引从当前实例加载物品或方块贴图。
  • 本地缺失时依次请求两个在线图标源,并缓存成功结果。
  • 增加内置屏障图标、在线屏障图标及程序绘制立方体作为最终兜底。
  • 为网络图标请求增加超时、异常处理和未找到提示。
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs
修复清理原生目录和头像尺寸切换的既有逻辑缺陷。
  • 将原生目录判断改为不区分大小写的 -natives 后缀匹配。
  • 将头像尺寸切换的皮肤路径判断改为空字符串检查。
Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#1566 让百宝箱中的自定义成就图片生成器支持输入和生成中文等非 ASCII 语言文字。 ✅
#1566 移除对不支持非 ASCII 字符的第三方成就图片接口的依赖,并改为本地生成成就图片。 ✅
#1566 确保生成器的中文文本能够正确排版并可预览、保存为图片。 ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

嘿——我发现了 3 个问题

面向 AI Agent 的提示
请处理这次代码审查中的评论:

## 单独评论

### 评论 1
<location path="Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs" line_range="741" />
<code_context>
+        Media.FormattedText MakeText(string text, Media.Color color) => new(
+            _WrapTextByWidth(text, typeface, fontSize, maxTextWidth, superSample),
+            CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeface, fontSize,
+            new Media.SolidColorBrush(color), superSample)
+        {
+            MaxTextWidth = maxTextWidth,
</code_context>
<issue_to_address>
**问题 (bug_risk):**RTL 成就文本始终按从左到右的方向进行格式化,因此即使该功能声称支持任意语言,阿拉伯语和希伯来语文本仍会以错误的双向文本顺序和方向显示。

**触发条件:**标题或描述包含从右到左的文本时。

**建议修复:**根据文本或输入区域性选择流方向,或者仅对 LTR 内容使用 `FlowDirection.LeftToRight`,对 RTL 内容使用 `FlowDirection.RightToLeft`。

```suggestion
            CultureInfo.CurrentUICulture, CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft ? FlowDirection.RightToLeft : FlowDirection.LeftToRight, typeface, fontSize,
```
</issue_to_address>

### 评论 2
<location path="Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs" line_range="601-606" />
<code_context>
+        try
+        {
+            await Task.Yield(); // 先把加载指示绘制出来
+            var itemId = AchievementBlockTextBox.Text.Trim();
+            var (icon, notFound) = await _LoadAchievementIconAsync(itemId);
+            if (notFound && !string.IsNullOrEmpty(itemId))
+                HintService.Hint(Lang.Text("Tools.Test.Achievement.IconNotFound", itemId), HintType.Info);
+
+            AchievementImage.Source = _RenderAchievementImage(icon);
+            AchievementImage.Visibility = Visibility.Visible;
+        }
</code_context>
<issue_to_address>
**问题 (bug_risk):**项目 ID 在异步加载图标之前被捕获,但 `_RenderAchievementImage` 在等待完成后又重新读取文本框。因此,在网络请求进行期间编辑项目 ID 或成就文本,会将旧项目的图标与新文本组合在一起,从而生成内容不一致的预览或保存图片。

**触发条件:**在线图标或屏障请求进行期间,用户编辑成就字段时。

**建议修复:**在等待异步操作之前快照保存所有成就输入,并将该不可变快照传递给图标加载和渲染逻辑;或者在操作期间禁用输入控件。
</issue_to_address>

### 评论 3
<location path="Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs" line_range="826-844" />
<code_context>
-                ModBase.CopyFile(savePath, path);
-                File.Delete(savePath);
-                HintService.Hint(Lang.Text("Tools.Test.Achievement.Saved"), HintType.Success);
+        foreach (var ch in text)
+        {
+            if (ch == '\n')
+            {
+                result.Append(line).Append('\n');
+                line.Clear();
+                continue;
             }
-            // 下载成功,返回 True
-            else if (response.StatusCode == HttpStatusCode.NotFound)
</code_context>
<issue_to_address>
**问题 (bug_risk):**换行逻辑遍历的是 UTF-16 `char` 值,可能会将代理项对中的一半移动到下一行。这样生成的字符串会在代理项对之间插入换行符,导致表情符号和其他补充平面字符在换行边界处显示为损坏字符或替换字符。

**触发条件:**补充 Unicode 字符恰好位于行宽边界时。

**建议修复:**按 Unicode 标量值或字素簇进行换行,在测量和移动文本时确保每个代理项对保持完整。
</issue_to_address>

Sourcery 对开源项目免费——如果您喜欢我们的审查结果,请考虑分享它们 ✨
Original comment in English

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs" line_range="741" />
<code_context>
+        Media.FormattedText MakeText(string text, Media.Color color) => new(
+            _WrapTextByWidth(text, typeface, fontSize, maxTextWidth, superSample),
+            CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeface, fontSize,
+            new Media.SolidColorBrush(color), superSample)
+        {
+            MaxTextWidth = maxTextWidth,
</code_context>
<issue_to_address>
**issue (bug_risk):** RTL achievement text is always formatted as left-to-right, so Arabic and Hebrew text is displayed with incorrect bidi ordering and direction despite the feature claiming support for arbitrary languages.

**Triggers:** When the title or description contains right-to-left text.

**Suggested fix:** Choose the flow direction from the text or input culture, or use `FlowDirection.LeftToRight` only for LTR content and `FlowDirection.RightToLeft` for RTL content.

```suggestion
            CultureInfo.CurrentUICulture, CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft ? FlowDirection.RightToLeft : FlowDirection.LeftToRight, typeface, fontSize,
```
</issue_to_address>

### Comment 2
<location path="Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs" line_range="601-606" />
<code_context>
+        try
+        {
+            await Task.Yield(); // 先把加载指示绘制出来
+            var itemId = AchievementBlockTextBox.Text.Trim();
+            var (icon, notFound) = await _LoadAchievementIconAsync(itemId);
+            if (notFound && !string.IsNullOrEmpty(itemId))
+                HintService.Hint(Lang.Text("Tools.Test.Achievement.IconNotFound", itemId), HintType.Info);
+
+            AchievementImage.Source = _RenderAchievementImage(icon);
+            AchievementImage.Visibility = Visibility.Visible;
+        }
</code_context>
<issue_to_address>
**issue (bug_risk):** The item ID is captured before the asynchronous icon load, but `_RenderAchievementImage` reads the text boxes again after the await. Editing the item ID or achievement text while the network request is running therefore combines the old item's icon with the new text, producing an internally inconsistent preview or saved image.

**Triggers:** When the user edits an achievement field while an online icon or barrier request is in progress.

**Suggested fix:** Snapshot all achievement inputs before awaiting and pass that immutable snapshot through icon loading and rendering, or disable the input controls for the duration of the operation.
</issue_to_address>

### Comment 3
<location path="Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs" line_range="826-844" />
<code_context>
-                ModBase.CopyFile(savePath, path);
-                File.Delete(savePath);
-                HintService.Hint(Lang.Text("Tools.Test.Achievement.Saved"), HintType.Success);
+        foreach (var ch in text)
+        {
+            if (ch == '\n')
+            {
+                result.Append(line).Append('\n');
+                line.Clear();
+                continue;
             }
-            // 下载成功,返回 True
-            else if (response.StatusCode == HttpStatusCode.NotFound)
</code_context>
<issue_to_address>
**issue (bug_risk):** Wrapping iterates UTF-16 `char` values and can move only one half of a surrogate pair to the next line. The resulting string contains a newline between the pair, so emoji and other supplementary-plane characters render as broken or replacement glyphs at a wrap boundary.

**Triggers:** When a supplementary Unicode character falls exactly at a line-width boundary.

**Suggested fix:** Wrap by Unicode scalar values or grapheme clusters, keeping each surrogate pair together while measuring and moving text.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs Outdated
Comment thread Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs Outdated
Comment thread Plain Craft Launcher 2/Pages/PageTools/PageToolsTest.xaml.cs Outdated
- 书写方向固定为 LeftToRight,阿拉伯文、希伯来文等 RTL 文本会显示反掉;
  改为按文本内容检测(U+0590–U+08FF 区段)决定方向
- 图标异步加载完成后才读取文本框,若用户在此期间编辑,会得到
  「旧图标配新文字」的不一致结果;改为等待前快照全部输入
- 按 char 遍历换行会从中间拆开代理项对,emoji 等补充平面字符落在
  换行边界时会显示为乱码;改为按 Unicode 标量值(Rune)遍历
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: XL PR 大小评估:超大型 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查

Projects

None yet

Development

Successfully merging this pull request may close these issues.

在百宝箱-自定义成就图片生成器中添加中文支持

1 participant