From 8c8daa47e5fb194f0be0f38341ccf01fcb890e5b Mon Sep 17 00:00:00 2001 From: rango Date: Thu, 28 May 2026 18:05:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(generator):=20=E6=B7=BB=E5=8A=A0=20Windows?= =?UTF-8?q?=20=E8=B5=84=E6=BA=90=E6=96=87=E4=BB=B6=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 ResourceFileGenerator 类用于生成 .rc 资源文件 - 支持图标文件嵌入功能,可指定 icon 配置项 - 实现版本信息生成功能,支持文件版本、产品版本等属性 - 提供配置示例文档,包括公司名称、版权等元数据 - 实现版本号格式化功能,支持多种输入格式转换 - 添加 UTF-8 编码支持,解决中文乱码问题 - 实现资源头文件生成,便于 C++ 代码引用资源 ID --- src/Php/Generator/ResourceFileGenerator.php | 233 ++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 src/Php/Generator/ResourceFileGenerator.php diff --git a/src/Php/Generator/ResourceFileGenerator.php b/src/Php/Generator/ResourceFileGenerator.php new file mode 100644 index 00000000..d21f8e6c --- /dev/null +++ b/src/Php/Generator/ResourceFileGenerator.php @@ -0,0 +1,233 @@ +config = $config; + $this->projectDir = $projectDir; + } + + /** + * 检查是否有任何资源配置 + */ + public function hasResource(): bool + { + return !empty($this->config['icon']) || !empty($this->config['version-info']); + } + + /** + * 获取图标文件的绝对路径 + */ + public function getIconPath(): ?string + { + $icon = $this->config['icon'] ?? null; + if (empty($icon)) { + return null; + } + + // 如果是绝对路径,直接使用 + if (preg_match('/^[A-Za-z]:\\\\|^\//', $icon)) { + return $icon; + } + + // 相对路径,基于项目目录解析 + return $this->projectDir . DIRECTORY_SEPARATOR . $icon; + } + + /** + * 生成 .rc 资源文件内容 + */ + public function generate(): string + { + $content = ''; + $content .= '// Generated by Swoole Compiler - Windows Resource File' . PHP_EOL; + $content .= '// DO NOT EDIT - This file is auto-generated' . PHP_EOL; + $content .= PHP_EOL; + + // 告诉 rc.exe 此文件使用 UTF-8 编码,避免中文乱码 + $content .= '#pragma code_page(65001)' . PHP_EOL; + $content .= PHP_EOL; + + // 包含 Windows 版本信息头文件 + $content .= '#include ' . PHP_EOL; + $content .= PHP_EOL; + + // 图标资源 + $iconPath = $this->getIconPath(); + if ($iconPath) { + // 使用正斜杠,Windows RC 编译器更兼容 + $iconPathRc = str_replace('\\', '/', $iconPath); + $content .= '// Icon Resource' . PHP_EOL; + $content .= 'MAINICON ICON "' . addslashes($iconPathRc) . '"' . PHP_EOL; + $content .= PHP_EOL; + } + + // 版本信息 + $versionInfo = $this->config['version-info'] ?? []; + if (!empty($versionInfo)) { + $content .= $this->generateVersionInfo($versionInfo); + } + + return $content; + } + + /** + * 生成版本信息块 + */ + private function generateVersionInfo(array $info): string + { + $fileVersion = $info['file-version'] ?? '0.0.0.0'; + $productVersion = $info['product-version'] ?? $fileVersion; + + $content = ''; + $content .= '// Version Information' . PHP_EOL; + $content .= '1 VERSIONINFO' . PHP_EOL; + $content .= 'FILEVERSION ' . $this->formatVersionDots($fileVersion) . PHP_EOL; + $content .= 'PRODUCTVERSION ' . $this->formatVersionDots($productVersion) . PHP_EOL; + $content .= 'FILEFLAGSMASK ' . ($info['file-flags-mask'] ?? '0x3fL') . PHP_EOL; + $content .= 'FILEFLAGS ' . ($info['file-flags'] ?? '0x0L') . PHP_EOL; + $content .= 'FILEOS ' . ($info['file-os'] ?? 'VOS_NT_WINDOWS32') . PHP_EOL; + $content .= 'FILETYPE ' . ($info['file-type'] ?? 'VFT_APP') . PHP_EOL; + $content .= 'FILESUBTYPE ' . ($info['file-subtype'] ?? 'VFT2_UNKNOWN') . PHP_EOL; + $content .= 'BEGIN' . PHP_EOL; + + // StringFileInfo 块 + $content .= ' BLOCK "StringFileInfo"' . PHP_EOL; + $content .= ' BEGIN' . PHP_EOL; + + // 语言代码页(040904b0 = 英文/UTF-8,配合 #pragma code_page(65001) 正确显示中文) + $langCodepage = $info['lang-codepage'] ?? '040904b0'; + $content .= ' BLOCK "' . $langCodepage . '"' . PHP_EOL; + $content .= ' BEGIN' . PHP_EOL; + + // 字符串值 + $stringFields = [ + 'company-name' => 'CompanyName', + 'file-description' => 'FileDescription', + 'file-version' => 'FileVersion', + 'internal-name' => 'InternalName', + 'legal-copyright' => 'LegalCopyright', + 'legal-trademarks' => 'LegalTrademarks', + 'original-filename' => 'OriginalFilename', + 'product-name' => 'ProductName', + 'product-version' => 'ProductVersion', + 'comments' => 'Comments', + ]; + + foreach ($stringFields as $yamlKey => $rcKey) { + if (isset($info[$yamlKey])) { + $value = $info[$yamlKey]; + $content .= ' VALUE "' . $rcKey . '", "' . addslashes($value) . '\\0"' . PHP_EOL; + } + } + + // 如果没有设置 FileVersion,从 file-version 字段自动填入 + if (!isset($info['file-version-str']) && $fileVersion) { + // 已在上面通过 file-version 键处理 + } + + $content .= ' END' . PHP_EOL; + $content .= ' END' . PHP_EOL; + + // VarFileInfo 块 + $content .= ' BLOCK "VarFileInfo"' . PHP_EOL; + $content .= ' BEGIN' . PHP_EOL; + // 0x0409 = English(US),1200 = Unicode(UTF-16) + // 配合 StringFileInfo 中的 040904b0 代码页,确保中文在 UTF-8 源文件中正确编码 + $content .= ' VALUE "Translation", 0x0409, 1200' . PHP_EOL; + $content .= ' END' . PHP_EOL; + + $content .= 'END' . PHP_EOL; + + return $content; + } + + /** + * 将版本号格式化为逗号分隔的格式(1,0,0,0) + * 支持以下输入格式: + * - "1.0.0.0" → "1,0,0,0" + * - "1,0,0,0" → "1,0,0,0" + * - "v1052" → "1052,0,0,0" (去掉 v 前缀) + * - "1.0" → "1,0,0,0" + */ + private function formatVersionDots(string $version): string + { + // 去掉 v/V 前缀(如 v1052 → 1052) + $version = ltrim($version, 'vV'); + + // 如果已经是逗号分隔格式,直接返回 + if (str_contains($version, ',')) { + return $version; + } + + // 用点号分隔 + $parts = explode('.', $version); + // 确保每个部分都是数字(过滤掉非数字字符) + $parts = array_map(function ($p) { + return preg_replace('/[^0-9]/', '', $p) ?: '0'; + }, $parts); + + // 确保恰好有4个部分 + while (count($parts) < 4) { + $parts[] = '0'; + } + + return implode(',', array_slice($parts, 0, 4)); + } + + /** + * 生成 resource.h 头文件内容(可选,供 C++ 代码引用资源 ID) + */ + public function generateHeader(): string + { + $content = ''; + $content .= '// Generated by Swoole Compiler - Resource Header' . PHP_EOL; + $content .= '// DO NOT EDIT - This file is auto-generated' . PHP_EOL; + $content .= PHP_EOL; + + if ($this->getIconPath()) { + $content .= '#define MAINICON 101' . PHP_EOL; + } + + return $content; + } +}