feat(build): 添加 Windows 资源文件支持功能

- 在 .gitignore 中添加 node_modules 目录
- 在 Msvc.php 中添加 compileResourceFile 方法用于编译 .rc 资源文件
- 新增 package.json 配置文件及 svg-to-ico.js 转换脚本
- 在 project.yml 中添加 Windows 资源文件配置示例
- 添加 swoole-logo.svg 图标文件
- 在 Translator.php 中集成资源文件生成功能
- 实现资源文件编译和链接到最终可执行文件的流程
- 更新版本号从 1049 到 1051
pull/1/head
韩天峰 4 months ago
parent c79573a9fe
commit b17b4acbac
  1. 1
      .gitignore
  2. 13
      examples/win32-hello/project.yml
  3. 25
      package.json
  4. 18
      project.yml
  5. 34
      src/Php/Backend/Msvc.php
  6. 133
      src/Php/Translator.php
  7. 147
      svg-to-ico.js
  8. BIN
      swoole-logo.ico
  9. 15
      swoole-logo.svg
  10. 2
      version.txt

1
.gitignore vendored

@ -5,6 +5,7 @@
/logs /logs
/build /build
/vendor /vendor
/node_modules
/tmp /tmp
/projects/wordpress /projects/wordpress
/projects/workerman /projects/workerman

@ -1,6 +1,19 @@
name: win32-hello name: win32-hello
version: 0.0.1 version: 0.0.1
mode: bin mode: bin
# Windows 资源文件配置示例
# resource:
# icon: icon.ico
# version-info:
# file-version: 0.0.1.0
# product-version: 0.0.1.0
# company-name: "My Company"
# file-description: "Win32 Hello World Application"
# legal-copyright: "Copyright (C) 2026 My Company"
# product-name: "Win32 Hello"
# original-filename: "win32-hello.exe"
sources: sources:
- hello-win.php - hello-win.php
- ./cpp-src - ./cpp-src

@ -0,0 +1,25 @@
{
"name": "compiler",
"version": "1.0.0",
"description": "- 需要 PHP-8.2 以上版本 - 需要 GCC-9 以上版本,支持 C++17 标准 - 需要 CMake-3.24 以上版本",
"main": "index.js",
"directories": {
"doc": "docs",
"example": "examples",
"test": "tests"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@git.code-galaxy.net:aot/compiler.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"sharp": "^0.34.5"
}
}

@ -5,6 +5,24 @@ cxx-std: c++17
cxx-flags: cxx-flags:
- -Wall - -Wall
# Windows 资源文件配置(图标、版本信息等)
# 仅在 Windows 平台编译 bin 模式时生效
resource:
# 图标文件路径(相对于 project.yml 所在目录,也支持绝对路径)
icon: swoole-logo.ico
# 版本信息
version-info:
file-version: 0.1.0.1052
product-version: 0.1.0
company-name: "上海识沃网络科技有限公司"
file-description: "Swoole AOT Compiler"
internal-name: "swoole-compiler"
legal-copyright: "Copyright (C) 2026 上海识沃网络科技有限公司"
legal-trademarks: "Swoole is a trademark of 上海识沃网络科技有限公司"
original-filename: "swoole_compiler.exe"
product-name: "Swoole Compiler"
comments: "PHP 原生编译器,可将 PHP 项目编译为 Windows/Linux/macOS 平台原生的可执行文件"
sources: sources:
- ./src/Php - ./src/Php
- ./src/Core - ./src/Core

@ -375,28 +375,50 @@ class Msvc extends CompilerBackend
public function buildLinkOptions(array $config = []): string public function buildLinkOptions(array $config = []): string
{ {
$cmd = ''; $cmd = '';
// 调试 // 调试
if (!empty($config['debug'])) { if (!empty($config['debug'])) {
$cmd .= ' /DEBUG'; $cmd .= ' /DEBUG';
} }
// Windows 子系统 // Windows 子系统
if (!empty($config['no_console'])) { if (!empty($config['no_console'])) {
$cmd .= ' ' . $this->platform->getSubsystemOptions(true); $cmd .= ' ' . $this->platform->getSubsystemOptions(true);
} }
// CRT 配置 // CRT 配置
$cmd .= ' ' . $this->platform->getCrtConfig(); $cmd .= ' ' . $this->platform->getCrtConfig();
// 扩展模块选项 // 扩展模块选项
if (!empty($config['build_mode']) && $config['build_mode'] === 'ext') { if (!empty($config['build_mode']) && $config['build_mode'] === 'ext') {
$cmd .= ' /DLL'; $cmd .= ' /DLL';
} }
// nologo // nologo
$cmd .= ' /nologo'; $cmd .= ' /nologo';
return $cmd;
}
/**
* 编译 Windows 资源文件 (.rc) 为目标文件 (.res)
*
* 使用 rc.exe(MSVC 资源编译器)将 .rc 文件编译为 .res 文件
* .res 文件可以直接传给 link.exe 作为输入
*
* @param string $rcFile 资源文件路径 (.rc)
* @param string $resFile 输出资源文件路径 (.res)
* @return string 编译命令
*/
public function compileResourceFile(string $rcFile, string $resFile): string
{
// rc.exe 是 MSVC 自带的资源编译器
// /nologo: 不显示版权信息
// /fo: 指定输出文件
$cmd = 'rc.exe /nologo';
$cmd .= ' /fo ' . escapeshellarg($resFile);
$cmd .= ' ' . escapeshellarg($rcFile);
return $cmd; return $cmd;
} }
} }

@ -19,6 +19,7 @@ use PhpAot\Php\Entity\PropertyDef;
use PhpAot\Php\Exception\Redo; use PhpAot\Php\Exception\Redo;
use PhpAot\Php\Exception\SyntaxError; use PhpAot\Php\Exception\SyntaxError;
use PhpAot\Php\Exception\Unsupported; use PhpAot\Php\Exception\Unsupported;
use PhpAot\Php\Generator\ResourceFileGenerator;
use PhpParser\Modifiers; use PhpParser\Modifiers;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Stmt\Foreach_; use PhpParser\Node\Stmt\Foreach_;
@ -39,6 +40,9 @@ class Translator extends Preprocessor
protected array $argInfoHeaderFiles = []; protected array $argInfoHeaderFiles = [];
protected array $registerSymbols = []; protected array $registerSymbols = [];
// Windows 资源文件配置(图标、版本信息等)
protected array $resourceConfig = [];
// 类静态属性初始值 // 类静态属性初始值
protected array $defaultStaticPropertyList = []; protected array $defaultStaticPropertyList = [];
@ -753,6 +757,9 @@ CODE;
$sourceFiles[] = $this->getPhpxDir() . '/src/misc/ps_title.c'; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/ps_title.c';
} }
// Windows 平台:编译资源文件(图标、版本信息等)
$this->compileResourceFile();
if (!$this->getPlatform()->supportsPcntlParallelCompile() or $job <= 1) { if (!$this->getPlatform()->supportsPcntlParallelCompile() or $job <= 1) {
return $this->compileSourceFile($sourceFiles); return $this->compileSourceFile($sourceFiles);
} }
@ -892,9 +899,115 @@ CODE;
$this->climate->{$style}($message); $this->climate->{$style}($message);
} }
// ========================================================================
// Windows 资源文件支持
// ========================================================================
/**
* 检查是否配置了 Windows 资源信息
*/
public function hasResourceFile(): bool
{
if (!$this->isWindows()) {
return false;
}
$generator = $this->createResourceGenerator();
return $generator !== null && $generator->hasResource();
}
/**
* 获取 .rc 资源文件路径
*/
public function getResourceRcFile(): string
{
return $this->getBuildDir() . DIRECTORY_SEPARATOR . 'app_resource.rc';
}
/**
* 获取 .res 编译后的资源文件路径
*/
public function getResourceResFile(): string
{
return $this->getBuildDir() . DIRECTORY_SEPARATOR . 'app_resource.res';
}
/**
* 创建资源文件生成器
*/
protected function createResourceGenerator(): ?ResourceFileGenerator
{
if (empty($this->resourceConfig)) {
return null;
}
$projectDir = $this->resourceConfig['_projectDir'] ?? getcwd();
return new ResourceFileGenerator($this->resourceConfig, $projectDir);
}
/**
* 编译 Windows 资源文件
*
* 如果配置了 resource 选项,生成 .rc 文件并使用 rc.exe 编译为 .res
* .res 文件会在 build 阶段被链接到最终的 exe 中
*/
protected function compileResourceFile(): void
{
if (!$this->isWindows()) {
return;
}
$generator = $this->createResourceGenerator();
if ($generator === null || !$generator->hasResource()) {
return;
}
// 生成 .rc 文件
$rcFile = $this->getResourceRcFile();
$rcContent = $generator->generate();
// 写入 UTF-8 BOM,确保 rc.exe 正确识别编码,避免中文乱码
$this->writeFile($rcFile, "\xEF\xBB\xBF" . $rcContent);
$this->climate->info('Generated resource file: ' . $rcFile);
// 使用 MSVC 的 rc.exe 编译 .rc -> .res
$backend = $this->getCompilerBackend();
if ($backend instanceof \PhpAot\Php\Backend\Msvc) {
$resFile = $this->getResourceResFile();
$cmd = $backend->compileResourceFile($rcFile, $resFile);
$this->climate->comment($cmd);
exec($cmd . ' 2>&1', $output, $ret);
if (!empty($output)) {
foreach ($output as $line) {
$this->climate->out($line);
}
}
if ($ret !== 0) {
$this->error('Resource compilation failed: ' . $rcFile);
}
if (!file_exists($resFile)) {
$this->error('Resource file not generated: ' . $resFile);
}
$this->climate->green('Resource compiled: ' . $resFile);
} else {
$this->climate->warning('Resource files are only supported with MSVC backend on Windows');
}
}
public function build(array $objectFiles): void public function build(array $objectFiles): void
{ {
$targetFile = $this->getTargetFileName(); $targetFile = $this->getTargetFileName();
// Windows 平台:将 .res 资源文件加入链接
if ($this->isWindows() && $this->hasResourceFile()) {
$resFile = $this->getResourceResFile();
if (file_exists($resFile)) {
$objectFiles[] = $resFile;
}
}
$linkCmd = $this->buildLinkCommand($objectFiles, $targetFile); $linkCmd = $this->buildLinkCommand($objectFiles, $targetFile);
$this->climate->comment($linkCmd); $this->climate->comment($linkCmd);
@ -1241,6 +1354,26 @@ CODE;
} }
} }
// 读取 resource(Windows 资源配置:图标、版本信息等)
$resource = $cfg['resource'] ?? null;
if (!empty($resource)) {
if (!is_array($resource)) {
$this->error('`resource` must be array');
}
// 验证图标文件是否存在
if (!empty($resource['icon'])) {
$iconPath = $resource['icon'];
if (!preg_match('/^[A-Za-z]:\\|^\//', $iconPath)) {
$iconPath = $projectDir . DIRECTORY_SEPARATOR . $iconPath;
}
if (!file_exists($iconPath)) {
$this->error('Icon file not exists: `' . $resource['icon'] . '`');
}
}
$this->resourceConfig = $resource;
$this->resourceConfig['_projectDir'] = $projectDir;
}
return $list; return $list;
} }

@ -0,0 +1,147 @@
/**
* SVG ICO 转换脚本 (Node.js)
*
* 使用 sharp 库将 SVG 渲染为多尺寸 PNG再合并为 ICO
*
* 用法: node svg-to-ico.js <input.svg> [output.ico]
*/
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const sizes = [16, 32, 48, 64, 128, 256];
async function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
console.log('用法: node svg-to-ico.js <input.svg> [output.ico]');
process.exit(1);
}
const inputSvg = path.resolve(args[0]);
const outputIco = args.length >= 2
? path.resolve(args[1])
: path.join(path.dirname(inputSvg), path.basename(inputSvg, '.svg') + '.ico');
if (!fs.existsSync(inputSvg)) {
console.log(`错误: SVG 文件不存在: ${inputSvg}`);
process.exit(1);
}
console.log('=== SVG → ICO 转换工具 ===\n');
console.log(`输入: ${inputSvg}`);
console.log(`输出: ${outputIco}\n`);
const svgBuffer = fs.readFileSync(inputSvg);
// 步骤1: 渲染 SVG 为多个尺寸的 PNG
const pngBuffers = {};
for (const size of sizes) {
console.log(`[1/2] 渲染 SVG → PNG (${size}x${size})...`);
try {
const pngBuffer = await sharp(svgBuffer, { density: 300 })
.resize(size, size, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 1 } })
.png()
.toBuffer();
pngBuffers[size] = pngBuffer;
console.log(` 完成: ${size}x${size} (${pngBuffer.length} bytes)`);
} catch (err) {
console.log(` 警告: ${size}x${size} PNG 生成失败: ${err.message},跳过`);
}
}
const validSizes = Object.keys(pngBuffers).map(Number).sort((a, b) => a - b);
if (validSizes.length === 0) {
console.log('\n错误: 所有尺寸的 PNG 都生成失败');
process.exit(1);
}
// 步骤2: 合并为 ICO
console.log('\n[2/2] 合并 PNG → ICO...');
const icoBuffer = createIcoFromPngs(pngBuffers, validSizes);
fs.writeFileSync(outputIco, icoBuffer);
console.log(`\n成功! ICO 文件已生成: ${outputIco}`);
console.log(`包含尺寸: ${validSizes.join(', ')}`);
console.log(`文件大小: ${icoBuffer.length} bytes`);
}
/**
* 将多个 PNG buffer 合并为 ICO 格式的 Buffer
*/
function createIcoFromPngs(pngBuffers, sizes) {
const imageCount = sizes.length;
// 计算各部分大小
const headerSize = 6; // ICO 头部
const dirEntrySize = 16; // 每个目录条目
const dirSize = dirEntrySize * imageCount;
let dataOffset = headerSize + dirSize;
// 构建目录条目
const dirEntries = [];
for (const size of sizes) {
const pngData = pngBuffers[size];
const dataSize = pngData.length;
// Width/Height: 0 表示 256
const w = size >= 256 ? 0 : size;
const h = size >= 256 ? 0 : size;
dirEntries.push({
width: w,
height: h,
colorCount: 0,
reserved: 0,
planes: 1,
bitCount: 32,
dataSize: dataSize,
offset: dataOffset
});
dataOffset += dataSize;
}
// 计算总大小
let totalSize = headerSize + dirSize;
for (const size of sizes) {
totalSize += pngBuffers[size].length;
}
// 构建 ICO 二进制数据
const buffer = Buffer.alloc(totalSize);
let pos = 0;
// ICO 头部 (6 bytes)
buffer.writeUInt16LE(0, pos); pos += 2; // Reserved
buffer.writeUInt16LE(1, pos); pos += 2; // Type: 1 = ICO
buffer.writeUInt16LE(imageCount, pos); pos += 2; // Image count
// 目录条目
for (const entry of dirEntries) {
buffer.writeUInt8(entry.width, pos); pos += 1;
buffer.writeUInt8(entry.height, pos); pos += 1;
buffer.writeUInt8(entry.colorCount, pos); pos += 1;
buffer.writeUInt8(entry.reserved, pos); pos += 1;
buffer.writeUInt16LE(entry.planes, pos); pos += 2;
buffer.writeUInt16LE(entry.bitCount, pos); pos += 2;
buffer.writeUInt32LE(entry.dataSize, pos); pos += 4;
buffer.writeUInt32LE(entry.offset, pos); pos += 4;
}
// 图像数据
for (const size of sizes) {
const pngData = pngBuffers[size];
pngData.copy(buffer, pos);
pos += pngData.length;
}
return buffer;
}
main().catch(err => {
console.error('错误:', err);
process.exit(1);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="60px" viewBox="0 0 349 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<title>Transfon SWOOLE - Registered</title>
<g id="Transfon-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo" transform="translate(-4.000000, -33.000000)">
<g id="Group">
<path d="M53.1558012,72.1759115 L32.6176561,72.1759115 L32.6176561,64.9102865 C32.6176561,61.5196445 32.3550226,59.3601609 31.8297477,58.4317708 C31.3044728,57.5033808 30.4290278,57.0391927 29.2033864,57.0391927 C27.87269,57.0391927 26.8659283,57.6648375 26.1830709,58.9161458 C25.5002136,60.1674542 25.15879,62.0645706 25.15879,64.6075521 C25.15879,67.8770997 25.5439858,70.3393146 26.314389,71.9942708 C27.0497738,73.649227 29.1333329,75.6472539 32.5651288,77.9884115 C42.4052782,84.7293306 48.6034289,90.2592232 51.1597666,94.5782552 C53.7161044,98.8972872 54.9942541,105.860108 54.9942541,115.466927 C54.9942541,122.450035 54.2851436,127.596468 52.8669014,130.90638 C51.4486593,134.216293 48.7085164,136.99133 44.6463907,139.231576 C40.5842649,141.471821 35.8568619,142.591927 30.4640398,142.591927 C24.5459428,142.591927 19.4946252,141.300273 15.3099353,138.716927 C11.1252454,136.133581 8.38510256,132.8439 7.08942452,128.847786 C5.79374648,124.851673 5.14591718,119.180505 5.14591718,111.834115 L5.14591718,105.416146 L25.6840623,105.416146 L25.6840623,117.34388 C25.6840623,121.017076 25.9729592,123.37838 26.5507615,124.427865 C27.1285639,125.477349 28.1528345,126.002083 29.6236042,126.002083 C31.0943739,126.002083 32.1886801,125.336074 32.9065558,124.004036 C33.6244315,122.671999 33.9833639,120.694154 33.9833639,118.070443 C33.9833639,112.298278 33.3005168,108.524228 31.9348021,106.748177 C30.5340691,104.972127 27.0848158,102.005359 21.5869387,97.8477865 C16.0890616,93.6498488 12.4472104,90.6023532 10.6612758,88.7052083 C8.87534125,86.8080634 7.3958392,84.1843918 6.2227253,80.8341146 C5.0496114,77.4838374 4.46306325,73.2052344 4.46306325,67.9981771 C4.46306325,60.490327 5.294736,55.0007986 6.95810646,51.5294271 C8.62147691,48.0580556 11.3090931,45.3435645 15.0210356,43.3858724 C18.732978,41.4281803 23.2152564,40.449349 28.4680052,40.449349 C34.2110106,40.449349 39.1047482,41.5189997 43.1493647,43.6583333 C47.1939813,45.7976669 49.872843,48.4919759 51.1860302,51.7413411 C52.4992174,54.9907064 53.1558012,60.5105079 53.1558012,68.3009115 L53.1558012,72.1759115 Z M145.130973,42.5079427 L135.308382,140.533333 L107.679061,140.533333 C105.157742,125.477268 102.934111,108.362856 101.008103,89.1895833 C100.132645,97.3836347 98.0841041,114.498047 94.8624181,140.533333 L67.3906793,140.533333 L57.5155609,42.5079427 L78.9991961,42.5079427 L81.2578668,76.7169271 L83.5690647,109.714974 C84.3744862,92.6406698 86.4055186,70.2385501 89.6622228,42.5079427 L112.669148,42.5079427 C112.984312,45.3738425 113.789722,56.1510784 115.0854,74.839974 L117.501652,112.076302 C118.727294,88.3821732 120.775835,65.192952 123.647338,42.5079427 L145.130973,42.5079427 Z" id="SW" fill="#000000"></path>
<path d="M295.686257,38.3746094 L295.686257,116.782812 L309.133227,116.782812 L309.133227,136.4 L273.572295,136.4 L273.572295,38.3746094 L295.686257,38.3746094 Z M314.491004,38.3746094 L351.365116,38.3746094 L351.365116,57.9917969 L336.604966,57.9917969 L336.604966,76.5796875 L350.419626,76.5796875 L350.419626,95.228125 L336.604966,95.228125 L336.604966,116.782812 L352.835878,116.782812 L352.835878,136.4 L314.491004,136.4 L314.491004,38.3746094 Z" id="LE" fill="#000000"></path>
<path d="M206.171009,110.597771 C206.171009,120.446778 205.969657,127.41969 205.566946,131.516716 C205.164235,135.613742 203.903594,139.357519 201.784986,142.748161 C199.666377,146.138803 196.803672,148.742293 193.196784,150.558708 C189.589897,152.375124 185.387761,153.283318 180.59025,153.283318 C176.037868,153.283318 171.94954,152.425579 168.325143,150.710075 C164.700747,148.994572 161.785515,146.421356 159.57936,142.990349 C157.373206,139.559342 156.060038,135.825656 155.639818,131.789177 C155.219598,127.752699 155.009492,120.688967 155.009492,110.597771 L155.009492,93.8262864 C155.009492,83.9772788 155.210844,77.0043668 155.613555,72.9073411 C156.016265,68.8103154 157.276906,65.0665378 159.395515,61.6758958 C161.514124,58.2852538 164.376829,55.6817643 167.983716,53.8653489 C171.590604,52.0489336 175.79274,51.1407396 180.59025,51.1407396 C185.142633,51.1407396 189.230961,51.9984784 192.855357,53.7139817 C196.479754,55.4294851 199.394986,58.0027016 201.60114,61.4337083 C203.807295,64.864715 205.120462,68.5984017 205.540682,72.6348802 C205.960902,76.6713587 206.171009,83.7350901 206.171009,93.8262864 L206.171009,110.597771 Z M184.057047,78.3868333 C184.057047,73.8256126 183.838186,70.9093006 183.400457,69.6378099 C182.962728,68.3663191 182.061019,67.7305833 180.695305,67.7305833 C179.5397,67.7305833 178.655501,68.2452266 178.04268,69.2745286 C177.429859,70.3038306 177.123453,73.3412352 177.123453,78.3868333 L177.123453,124.160271 C177.123453,129.851706 177.324806,133.363389 177.727517,134.695427 C178.130227,136.027465 179.066953,136.693474 180.537723,136.693474 C182.043511,136.693474 183.006501,135.926555 183.42672,134.392693 C183.84694,132.858831 184.057047,129.205873 184.057047,123.433708 L184.057047,78.3868333 Z" id="O" fill="#008DDF" transform="translate(180.590250, 102.212029) rotate(-15.000000) translate(-180.590250, -102.212029) "></path>
<path d="M260.53735,91.2447635 C260.53735,101.093771 260.335998,108.066683 259.933287,112.163709 C259.530576,116.260735 258.269936,120.004512 256.151327,123.395154 C254.032718,126.785796 251.170013,129.389286 247.563126,131.205701 C243.956238,133.022116 239.754102,133.93031 234.956592,133.93031 C230.404209,133.93031 226.315881,133.072572 222.691484,131.357068 C219.067088,129.641565 216.151856,127.068348 213.945701,123.637342 C211.739547,120.206335 210.426379,116.472648 210.00616,112.43617 C209.58594,108.399691 209.375833,101.33596 209.375833,91.2447635 L209.375833,74.4732792 C209.375833,64.6242716 209.577185,57.6513595 209.979896,53.5543339 C210.382607,49.4573082 211.643247,45.7135305 213.761856,42.3228885 C215.880465,38.9322466 218.74317,36.328757 222.350057,34.5123417 C225.956945,32.6959263 230.159081,31.7877323 234.956592,31.7877323 C239.508974,31.7877323 243.597302,32.6454711 247.221699,34.3609745 C250.846095,36.0764779 253.761327,38.6496943 255.967482,42.080701 C258.173636,45.5117078 259.486804,49.2453944 259.907024,53.2818729 C260.327244,57.3183514 260.53735,64.3820829 260.53735,74.4732792 L260.53735,91.2447635 Z M238.423388,59.033826 C238.423388,54.4726053 238.204527,51.5562933 237.766798,50.2848026 C237.329069,49.0133119 236.427361,48.377576 235.061646,48.377576 C233.906041,48.377576 233.021842,48.8922193 232.409021,49.9215214 C231.7962,50.9508234 231.489795,53.9882279 231.489795,59.033826 L231.489795,104.807264 C231.489795,110.498698 231.691147,114.010382 232.093858,115.34242 C232.496569,116.674458 233.433295,117.340467 234.904064,117.340467 C236.409852,117.340467 237.372842,116.573547 237.793062,115.039685 C238.213282,113.505824 238.423388,109.852865 238.423388,104.080701 L238.423388,59.033826 Z" id="O" fill="#008DDF" transform="translate(234.956592, 82.859021) rotate(26.000000) translate(-234.956592, -82.859021) "></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

@ -1 +1 @@
1049 1051
Loading…
Cancel
Save