[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图标生成器 - 文字转PNG/ICO/SVG</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
color: #333;
min-height: 100vh;
padding: 10px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
header {
background: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
color: white;
padding: 25px 30px;
text-align: center;
}
header h1 {
font-size: 2.2rem;
margin-bottom: 10px;
}
header p {
opacity: 0.9;
font-size: 1.1rem;
}
.app-content {
display: flex;
flex-wrap: wrap;
padding: 20px;
min-height: calc(100vh - 180px);
}
.controls {
flex: 1;
min-width: 300px;
padding: 20px;
border-right: 1px solid #eee;
overflow-y: auto;
max-height: calc(100vh - 180px);
}
.preview {
flex: 1;
min-width: 300px;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
}
.control-group {
margin-bottom: 25px;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.control-group h3 {
margin-bottom: 15px;
color: #4b6cb7;
border-bottom: 1px solid #eaeaea;
padding-bottom: 8px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input, select, button, textarea {
width: 100%;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1rem;
}
textarea {
resize: vertical;
min-height: 80px;
font-family: inherit;
line-height: 1.4;
}
button {
background: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
color: white;
border: none;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
margin-top: 10px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
#loadSystemFonts {
background: linear-gradient(90deg, #27ae60 0%, #219653 100%);
}
#loadSystemFonts:hover {
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.2);
}
#uploadFontBtn {
background: linear-gradient(90deg, #e67e22 0%, #d35400 100%);
}
#uploadFontBtn:hover {
box-shadow: 0 5px 15px rgba(230, 126, 34, 0.2);
}
.color-input {
display: flex;
align-items: center;
}
.color-input input {
flex: 1;
}
.color-preview {
width: 30px;
height: 30px;
border-radius: 5px;
margin-left: 10px;
border: 1px solid #ddd;
}
.shape-options, .size-options {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.position-options {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
width: 100%;
}
.shape-option, .size-option {
flex: 1;
min-width: 80px;
text-align: center;
padding: 10px;
border: 2px solid #ddd;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s;
}
.position-option {
padding: 12px 5px;
font-size: 0.9rem;
border: 2px solid #ddd;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s;
text-align: center;
background: #f8f9fa;
}
.shape-option:hover, .size-option:hover, .position-option:hover {
border-color: #4b6cb7;
}
.shape-option.active, .size-option.active, .position-option.active {
border-color: #4b6cb7;
background-color: rgba(75, 108, 183, 0.1);
}
.checkbox-group {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.checkbox-group input {
width: auto;
margin-right: 10px;
}
#transparentBgGroup {
padding: 8px;
background: rgba(75, 108, 183, 0.05);
border-radius: 5px;
margin-top: 5px;
}
.char-count {
font-size: 0.8rem;
color: #666;
margin-top: 5px;
}
.max-chars {
color: #e74c3c;
}
.stretch-controls, .font-size-controls {
display: flex;
gap: 15px;
margin-top: 10px;
}
.stretch-control, .font-size-control {
flex: 1;
text-align: center;
}
.stretch-control label, .font-size-control label {
font-size: 0.9rem;
margin-bottom: 5px;
}
.font-size-note {
font-size: 0.8rem;
color: #e67e22;
margin-top: 5px;
padding: 5px;
background: rgba(230, 126, 34, 0.05);
border-radius: 4px;
}
.stretch-control input, .font-size-control input {
width: 100%;
}
.stretch-value, .font-size-value {
font-size: 0.8rem;
color: #666;
margin-top: 5px;
}
.preview-area {
width: 256px;
height: 256px;
border: 1px solid #ddd;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background:
linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
position: relative;
}
canvas {
max-width: 100%;
max-height: 100%;
}
.download-options {
display: flex;
gap: 10px;
width: 100%;
max-width: 400px;
}
.download-options button {
flex: 1;
}
.size-preview {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
width: 100%;
max-width: 400px;
}
.size-preview-item {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 10px;
}
.size-preview-canvas {
border: 1px solid #ddd;
margin-bottom: 5px;
background:
linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
background-size: 8px 8px;
background-position: 0 0, 0 4px, 4px -4px, -4px 0px;
}
footer {
text-align: center;
padding: 20px;
color: #666;
font-size: 0.9rem;
border-top: 1px solid #eee;
height: 80px;
}
.font-loading {
display: inline-block;
margin-left: 10px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.font-group {
margin: 5px 0;
padding: 8px;
background: #e9ecef;
border-radius: 4px;
font-weight: 600;
color: #2d3436;
}
.font-upload-area {
margin: 15px 0;
padding: 15px;
border: 2px dashed #e67e22;
border-radius: 5px;
text-align: center;
transition: all 0.3s;
}
.font-upload-area:hover {
background: rgba(230, 126, 34, 0.05);
}
#fontFileInput {
display: none;
}
.upload-hint {
font-size: 0.85rem;
color: #666;
margin-top: 8px;
}
.upload-success {
margin-top: 10px;
padding: 8px;
background: rgba(46, 204, 113, 0.1);
color: #27ae60;
border-radius: 4px;
font-size: 0.9rem;
display: none;
}
/* 进度条样式(优化动画) */
.progress-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.progress-modal.active {
opacity: 1;
visibility: visible;
}
.progress-container {
background: white;
padding: 30px;
border-radius: 10px;
width: 90%;
max-width: 500px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.progress-title {
font-size: 1.2rem;
font-weight: 600;
color: #333;
margin-bottom: 15px;
text-align: center;
}
.progress-bar-container {
width: 100%;
height: 12px;
background: #f1f1f1;
border-radius: 6px;
overflow: hidden;
margin-bottom: 15px;
position: relative;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
width: 0%;
border-radius: 6px;
position: absolute;
left: 0;
top: 0;
transition: width 2s ease-out; /* 改为2秒平滑动画 */
}
.progress-text {
font-size: 0.9rem;
color: #666;
text-align: center;
}
.progress-success {
color: #27ae60;
font-weight: 600;
display: none;
}
/* 新增:手动换行提示样式 */
.manual-wrap-hint {
font-size: 0.8rem;
color: #666;
margin-top: 5px;
padding: 5px;
background: rgba(52, 152, 219, 0.05);
border-radius: 4px;
display: none;
}
.manual-wrap-hint.active {
display: block;
}
/* 响应式适配 */
@media (max-width: 768px) {
.app-content {
flex-direction: column;
min-height: auto;
}
.controls {
border-right: none;
border-bottom: 1px solid #eee;
max-height: none;
}
.preview {
min-height: 500px;
justify-content: flex-start;
padding-top: 30px;
}
.stretch-controls, .font-size-controls {
flex-direction: column;
}
.download-options {
flex-direction: column;
max-width: 100%;
}
.progress-container {
padding: 20px;
}
.position-options {
grid-template-columns: repeat(3, 1fr);
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>图标生成器 - 文字转PNG/ICO/SVG (增强版)</h1>
</header>
<div class="app-content">
<div class="controls">
<div class="control-group">
<h3>文字设置</h3>
<div class="form-group">
<label for="textInput">输入文字或Emoji表情符号(≤20个字符)</label>
<textarea id="textInput" maxlength="20" placeholder="可输入文字或Emoji,支持手动换行">图</textarea>
<div class="char-count">当前字符数: <span id="charCount">1</span>/20</div>
<div class="manual-wrap-hint" id="manualWrapHint">
💡 手动换行已启用:按Enter键换行,支持多行显示
</div>
</div>
<div class="form-group">
<label for="fontSelect">选择字体(内置+本机+上传)</label>
<select id="fontSelect" size="8" style="height: 200px; overflow-y: auto;">
<!-- 字体选项通过JS动态填充 -->
</select>
</div>
<!-- 本机字体加载区域 -->
<div class="form-group">
<button id="loadSystemFonts">
加载本机已安装字体库
<span id="systemFontLoading" class="font-loading" style="display: none;">⟳</span>
</button>
<p style="font-size: 0.8rem; color: #666; margin-top: 5px;">
📌 支持Chrome/Edge/Opera最新版 | 仅本地读取不上传 | 加载后永久可用
</p>
</div>
<!-- 手动上传字体区域 -->
<div class="font-upload-area">
<label for="fontFileInput" style="cursor: pointer; font-weight: 600; color: #e67e22;">
点击选择本地字体文件(TTF/OTF)
</label>
<input type="file" id="fontFileInput" accept=".ttf,.otf,.ttc" multiple>
<p class="upload-hint">
支持格式:TTF/OTF/TTC | 可批量上传 | 仅临时使用(刷新后失效)
</p>
<div id="uploadSuccessMsg" class="upload-success">
✅ 字体上传成功!已添加到字体列表末尾
</div>
</div>
<div class="stretch-controls">
<div class="stretch-control">
<label for="colorInput">文字颜色(点击选择)</label>
<div class="color-input">
<input type="color" id="colorInput" value="#ea1026">
<div class="color-preview" id="colorPreview"></div>
</div>
</div>
<div class="stretch-control">
<label for="bgColorInput">背景颜色</label>
<div class="color-input">
<input type="color" id="bgColorInput" value="#ffffff">
<div class="color-preview" id="bgColorPreview"></div>
</div>
</div>
</div>
<!-- 透明背景选项 -->
<div class="checkbox-group" id="transparentBgGroup">
<input type="checkbox" id="transparentBg" checked>
<label for="transparentBg">启用透明背景(取消则显示上方背景色)</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="autoWrap" checked>
<label for="autoWrap">自动换行显示多个文字</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="autoStretch" checked>
<label for="autoStretch">自动拉伸文字以适应图标</label>
</div>
<!-- 新增:字体大小调整 -->
<div class="font-size-controls">
<div class="font-size-control">
<label for="fontSizeSlider">字体大小</label>
<input type="range" id="fontSizeSlider" min="10" max="200" value="80">
<div class="font-size-value" id="fontSizeValue">80%</div>
<div class="font-size-note">💡 提示:需先关闭"自动拉伸"才能手动调整字体大小</div>
</div>
</div>
<div class="stretch-controls">
<div class="stretch-control">
<label for="horizontalStretch">水平拉伸</label>
<input type="range" id="horizontalStretch" min="50" max="500" value="141">
<div class="stretch-value" id="horizontalValue">141%</div>
</div>
<div class="stretch-control">
<label for="verticalStretch">垂直拉伸</label>
<input type="range" id="verticalStretch" min="50" max="500" value="126">
<div class="stretch-value" id="verticalValue">126%</div>
</div>
</div>
<div class="stretch-controls">
<div class="stretch-control">
<label for="horizontalOffset">横向偏移</label>
<input type="range" id="horizontalOffset" min="-50" max="50" value="-5">
<div class="stretch-value" id="horizontalOffsetValue">-5px</div>
</div>
<div class="stretch-control">
<label for="verticalOffset">垂直偏移</label>
<input type="range" id="verticalOffset" min="-50" max="50" value="2">
<div class="stretch-value" id="verticalOffsetValue">2px</div>
</div>
</div>
<!-- 新增:9方位位置选择 -->
<div class="form-group" style="margin-top: 20px;">
<label>文字位置(九宫格)</label>
<div class="position-options">
<div class="position-option" data-position="top-left">左上</div>
<div class="position-option" data-position="top-center">中上</div>
<div class="position-option" data-position="top-right">右上</div>
<div class="position-option" data-position="center-left">左中</div>
<div class="position-option active" data-position="center-center">居中</div>
<div class="position-option" data-position="center-right">右中</div>
<div class="position-option" data-position="bottom-left">左下</div>
<div class="position-option" data-position="bottom-center">中下</div>
<div class="position-option" data-position="bottom-right">右下</div>
</div>
</div>
<h3>图标形状</h3>
<div class="shape-options">
<div class="shape-option" data-shape="square">方形</div>
<div class="shape-option" data-shape="circle">圆形</div>
<div class="shape-option active" data-shape="rounded">圆角方形</div>
</div>
</div>
<button id="generateBtn" style="display:none;">生成图标</button>
</div>
<div class="preview">
<div class="preview-area">
<canvas id="previewCanvas" width="256" height="256"></canvas>
</div>
<div class="control-group">
<h3>图标尺寸</h3>
<div class="size-options">
<div class="size-option" data-size="16">16×16</div>
<div class="size-option" data-size="32">32×32</div>
<div class="size-option" data-size="48">48×48</div>
<div class="size-option" data-size="64">64×64</div>
<div class="size-option" data-size="128">128×128</div>
<div class="size-option active" data-size="256">256×256</div>
</div>
</div>
<div class="download-options">
<button id="downloadPngBtn">下载PNG</button>
<button id="downloadIcoBtn">下载ICO</button>
<button id="downloadSvgBtn">下载SVG</button>
</div>
<div class="size-preview">
<!-- 尺寸预览区域通过JS动态生成 -->
</div>
</div>
</div>
<footer>
<p>图标生成器 © 2025 - 支持PNG、ICO和SVG格式输出 | 增强版:进度条+字体大小+9方位调整+手动换行</p>
</footer>
</div>
<!-- 下载进度条弹窗 -->
<div class="progress-modal" id="downloadProgressModal">
<div class="progress-container">
<div class="progress-title" id="progressTitle">正在准备下载...</div>
<div class="progress-bar-container">
<div class="progress-bar" id="progressBar"></div>
</div>
<div class="progress-text">
<span id="progressPercent">0%</span> |
<span id="progressStep">初始化...</span>
<span id="progressSuccess" class="progress-success">下载准备完成!</span>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 获取DOM元素
const textInput = document.getElementById('textInput');
const charCount = document.getElementById('charCount');
const manualWrapHint = document.getElementById('manualWrapHint');
const autoWrap = document.getElementById('autoWrap');
const fontSelect = document.getElementById('fontSelect');
const loadSystemFontsBtn = document.getElementById('loadSystemFonts');
const systemFontLoading = document.getElementById('systemFontLoading');
const fontFileInput = document.getElementById('fontFileInput');
const uploadSuccessMsg = document.getElementById('uploadSuccessMsg');
const colorInput = document.getElementById('colorInput');
const colorPreview = document.getElementById('colorPreview');
const bgColorInput = document.getElementById('bgColorInput');
const bgColorPreview = document.getElementById('bgColorPreview');
const transparentBg = document.getElementById('transparentBg');
const autoStretch = document.getElementById('autoStretch');
const fontSizeSlider = document.getElementById('fontSizeSlider'); // 新增
const fontSizeValue = document.getElementById('fontSizeValue'); // 新增
const horizontalStretch = document.getElementById('horizontalStretch');
const verticalStretch = document.getElementById('verticalStretch');
const horizontalOffset = document.getElementById('horizontalOffset');
const verticalOffset = document.getElementById('verticalOffset');
const horizontalValue = document.getElementById('horizontalValue');
const verticalValue = document.getElementById('verticalValue');
const horizontalOffsetValue = document.getElementById('horizontalOffsetValue');
const verticalOffsetValue = document.getElementById('verticalOffsetValue');
const shapeOptions = document.querySelectorAll('.shape-option');
const sizeOptions = document.querySelectorAll('.size-option');
const positionOptions = document.querySelectorAll('.position-option'); // 新增
const generateBtn = document.getElementById('generateBtn');
const downloadPngBtn = document.getElementById('downloadPngBtn');
const downloadIcoBtn = document.getElementById('downloadIcoBtn');
const downloadSvgBtn = document.getElementById('downloadSvgBtn');
const previewCanvas = document.getElementById('previewCanvas');
const sizePreview = document.querySelector('.size-preview');
// 进度条相关元素
const progressModal = document.getElementById('downloadProgressModal');
const progressTitle = document.getElementById('progressTitle');
const progressBar = document.getElementById('progressBar');
const progressPercent = document.getElementById('progressPercent');
const progressStep = document.getElementById('progressStep');
const progressSuccess = document.getElementById('progressSuccess');
// 初始化变量
let selectedShape = 'rounded';
let selectedSize = 256;
let selectedPosition = 'center-center'; // 新增
let ctx = previewCanvas.getContext('2d');
let systemFontsLoaded = false;
let uploadedFonts = new Map();
let fontCounter = 1;
let progressInterval = null;
let progressPercentInterval = null;
let downloadTimeout = null; // 新增:控制下载延迟
// 内置字体库(分类整理)
const builtInFonts = [
{ group: '常用中文字体', fonts: [
{ family: 'Microsoft YaHei', name: '微软雅黑' },
{ family: '微软雅黑 Light', name: '微软雅黑 Light' },
{ family: 'SimSun', name: '宋体' },
{ family: '新宋体', name: '新宋体' },
{ family: 'FangSong', name: '仿宋' },
{ family: 'KaiTi', name: '楷体' },
{ family: 'LiSu', name: '隶书' },
{ family: 'YouYuan', name: '幼圆' },
{ family: '黑体', name: '黑体' },
{ family: '等线', name: '等线' },
{ family: '等线 Light', name: '等线 Light' }
]},
{ group: '思源字体', fonts: [
{ family: "'Noto Sans SC', sans-serif", name: '思源黑体 (Noto Sans SC)' },
{ family: "'Noto Serif SC', serif", name: '思源宋体 (Noto Serif SC)' }
]},
{ group: '华文系列', fonts: [
{ family: '华文中宋', name: '华文中宋' },
{ family: '华文仿宋', name: '华文仿宋' },
{ family: '华文宋体', name: '华文宋体' },
{ family: '华文彩云', name: '华文彩云' },
{ family: '华文新魏', name: '华文新魏' },
{ family: '华文楷体', name: '华文楷体' },
{ family: '华文琥珀', name: '华文琥珀' },
{ family: '华文细黑', name: '华文细黑' },
{ family: '华文行楷', name: '华文行楷' },
{ family: '华文隶书', name: '华文隶书' }
]},
{ group: '方正系列', fonts: [
{ family: '方正姚体', name: '方正姚体' },
{ family: '方正舒体', name: '方正舒体' }
]},
{ group: '英文字体', fonts: [
{ family: 'Arial', name: 'Arial' },
{ family: 'Verdana', name: 'Verdana' },
{ family: 'Helvetica', name: 'Helvetica' },
{ family: 'Georgia', name: 'Georgia' },
{ family: 'Times New Roman', name: 'Times New Roman' },
{ family: 'Courier New', name: 'Courier New' },
{ family: 'Impact', name: 'Impact' },
{ family: 'Comic Sans MS', name: 'Comic Sans MS' }
]}
];
// 初始化字体选择框
function initFontSelect() {
fontSelect.innerHTML = '';
// 添加内置字体(分组显示)
builtInFonts.forEach(group => {
const groupOption = document.createElement('option');
groupOption.disabled = true;
groupOption.innerHTML = `📁 ${group.group}`;
groupOption.className = 'font-group';
fontSelect.appendChild(groupOption);
group.fonts.forEach(font => {
const option = document.createElement('option');
option.value = font.family;
option.textContent = ` ${font.name}`;
if (font.family === 'SimSun') {
option.selected = true;
}
fontSelect.appendChild(option);
});
});
// 添加分隔线(本机字体区域)
const systemDivider = document.createElement('option');
systemDivider.disabled = true;
systemDivider.textContent = '────────── 本机已安装字体(点击上方按钮加载) ──────────';
fontSelect.appendChild(systemDivider);
// 添加分隔线(上传字体区域)
const uploadDivider = document.createElement('option');
uploadDivider.disabled = true;
uploadDivider.textContent = '────────── 本地上传字体(临时使用) ──────────';
fontSelect.appendChild(uploadDivider);
}
// 初始化字体选择框
initFontSelect();
// 进度条初始化(优化动画)
function initProgressBar(format, fileName) {
// 清除之前的定时器
if (progressInterval) clearInterval(progressInterval);
if (progressPercentInterval) clearInterval(progressPercentInterval);
if (downloadTimeout) clearTimeout(downloadTimeout);
// 重置进度条状态
progressModal.classList.add('active');
progressTitle.textContent = `正在准备下载 ${format} 文件:${fileName}`;
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
progressSuccess.style.display = 'none';
progressStep.textContent = '初始化...';
// 进度步骤提示
const steps = [
'初始化下载参数...',
'正在渲染图标内容...',
'正在优化图像质量...',
'正在处理文件数据...',
'正在生成下载链接...',
'最后检查文件完整性...'
];
// 分步更新进度条
let stepIndex = 0;
let currentProgress = 0;
// 每300ms更新一次进度
progressInterval = setInterval(() => {
currentProgress += 2; // 每次增加2%
if (currentProgress > 100) currentProgress = 100;
progressBar.style.width = `${currentProgress}%`;
progressPercent.textContent = `${currentProgress}%`;
// 更新步骤提示
const stepPosition = Math.floor(currentProgress / (100 / steps.length));
if (stepPosition < steps.length) {
progressStep.textContent = steps[stepPosition];
} else {
progressStep.textContent = '准备完成...';
}
// 进度完成后停止
if (currentProgress >= 100) {
clearInterval(progressInterval);
progressInterval = null;
}
}, 30);
}
// 关闭进度条弹窗(增加延迟)
function closeProgressBar() {
progressStep.textContent = '文件生成完成!';
progressSuccess.style.display = 'inline-block';
// 保持进度条显示2秒后关闭
setTimeout(() => {
progressModal.classList.remove('active');
// 重置进度条状态
setTimeout(() => {
progressSuccess.style.display = 'none';
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
}, 300);
}, 800); // 显示完成状态800ms
}
// 错误状态进度条
function errorProgressBar(format, errorMsg) {
if (progressInterval) clearInterval(progressInterval);
if (progressPercentInterval) clearInterval(progressPercentInterval);
progressModal.classList.add('active');
progressTitle.textContent = `下载 ${format} 文件失败`;
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
progressStep.textContent = `❌ ${errorMsg}`;
progressStep.style.color = '#e74c3c';
progressSuccess.style.display = 'none';
setTimeout(() => {
progressModal.classList.remove('active');
setTimeout(() => {
progressStep.style.color = '';
}, 300);
}, 3000);
}
// 获取自定义下载文件名
function getCustomFileName(format) {
let content = textInput.value.trim() || '图标';
// 获取第一行作为文件名(如果有多行)
content = content.split('\n')[0];
const invalidChars = /[\\/:*?"<>|]/g;
content = content.replace(invalidChars, '-');
if (content.length > 10) {
content = content.substring(0, 10) + '...';
}
return `${content}-${selectedSize}.${format}`;
}
// 下载PNG(优化进度条显示)
downloadPngBtn.addEventListener('click', async function() {
try {
const format = 'png';
const fileName = getCustomFileName(format);
initProgressBar('PNG', fileName);
// 生成文件
const canvas = createCanvas(selectedSize);
drawIcon(canvas, selectedSize);
const link = document.createElement('a');
link.download = fileName;
link.href = canvas.toDataURL(`image/${format}`);
// 等待进度条动画完成后触发下载
setTimeout(() => {
link.click();
closeProgressBar();
}, 1200); // 等待1.2秒让进度条动画完成
} catch (error) {
console.error('PNG下载失败:', error);
errorProgressBar('PNG', '文件生成失败,请重试');
}
});
// 下载ICO(优化进度条显示)
downloadIcoBtn.addEventListener('click', async function() {
try {
const format = 'ico';
const downloadSize = parseInt(selectedSize);
const fileName = getCustomFileName(format);
initProgressBar('ICO', fileName);
// 生成文件
const canvas = document.createElement('canvas');
canvas.width = downloadSize;
canvas.height = downloadSize;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, downloadSize, downloadSize);
drawIcon(canvas, downloadSize);
const pngData = canvas.toDataURL('image/png').split(',')[1];
const pngBuffer = Uint8Array.from(atob(pngData), c => c.charCodeAt(0));
const icoHeader = new Uint8Array([0, 0, 1, 0, 1, 0]);
const icoDir = new Uint8Array(16);
icoDir[0] = downloadSize > 255 ? 0 : downloadSize;
icoDir[1] = downloadSize > 255 ? 0 : downloadSize;
icoDir[2] = 0;
icoDir[3] = 0;
icoDir[4] = 1; icoDir[5] = 0;
icoDir[6] = 32; icoDir[7] = 0;
const size = pngBuffer.length;
icoDir[8] = size & 0xFF;
icoDir[9] = (size >> 8) & 0xFF;
icoDir[10] = (size >> 16) & 0xFF;
icoDir[11] = (size >> 24) & 0xFF;
const offset = icoHeader.length + icoDir.length;
icoDir[12] = offset & 0xFF;
icoDir[13] = (offset >> 8) & 0xFF;
icoDir[14] = (offset >> 16) & 0xFF;
icoDir[15] = (offset >> 24) & 0xFF;
const icoData = new Uint8Array(icoHeader.length + icoDir.length + pngBuffer.length);
icoData.set(icoHeader);
icoData.set(icoDir, icoHeader.length);
icoData.set(pngBuffer, icoHeader.length + icoDir.length);
const blob = new Blob([icoData], { type: 'image/x-icon' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
// 等待进度条动画完成后触发下载
setTimeout(() => {
link.click();
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
closeProgressBar();
}, 100);
}, 1200); // 等待1.2秒
} catch (error) {
console.error('ICO下载失败:', error);
errorProgressBar('ICO', '文件生成失败,请重试');
}
});
// 下载SVG(优化进度条显示)
downloadSvgBtn.addEventListener('click', async function() {
try {
const format = 'svg';
const fileName = getCustomFileName(format);
initProgressBar('SVG', fileName);
// 生成文件
const svgString = generateSvgString(selectedSize);
const blob = new Blob([svgString], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
// 等待进度条动画完成后触发下载
setTimeout(() => {
link.click();
URL.revokeObjectURL(url);
closeProgressBar();
}, 1200); // 等待1.2秒
} catch (error) {
console.error('SVG下载失败:', error);
errorProgressBar('SVG', '文件生成失败,请重试');
}
});
// 字体上传功能
fontFileInput.addEventListener('change', async function(e) {
const files = e.target.files;
if (!files || files.length === 0) return;
let successCount = 0;
const validExtensions = ['ttf', 'otf', 'ttc'];
for (const file of files) {
const fileName = file.name;
const fileExt = fileName.split('.').pop().toLowerCase();
if (!validExtensions.includes(fileExt)) {
alert(`❌ 文件 "${fileName}" 格式不支持\n仅支持TTF/OTF/TTC格式`);
continue;
}
try {
const fontUniqueName = `UploadedFont_${fontCounter++}_${fileName.replace(/\.[^/.]+$/, "")}`;
const fontUrl = URL.createObjectURL(file);
const fontFace = new FontFace(fontUniqueName, `url(${fontUrl})`);
await fontFace.load();
document.fonts.add(fontFace);
uploadedFonts.set(fontUniqueName, {
face: fontFace,
url: fontUrl,
fileName: fileName
});
const option = document.createElement('option');
option.value = fontUniqueName;
option.textContent = ` 📤 ${fileName}`;
const uploadDivider = Array.from(fontSelect.options).find(
opt => opt.textContent.includes('本地上传字体')
);
if (uploadDivider) {
fontSelect.insertBefore(option, uploadDivider.nextSibling);
} else {
fontSelect.appendChild(option);
}
successCount++;
} catch (error) {
console.error(`加载字体 ${fileName} 失败:`, error);
alert(`❌ 加载字体 "${fileName}" 失败\n可能是损坏的字体文件或浏览器不支持`);
}
}
if (successCount > 0) {
uploadSuccessMsg.textContent = `✅ 成功上传 ${successCount} 个字体!已添加到字体列表`;
uploadSuccessMsg.style.display = 'block';
setTimeout(() => {
uploadSuccessMsg.style.display = 'none';
}, 3000);
}
fontFileInput.value = '';
generateIcon();
});
// 本机字体加载功能
loadSystemFontsBtn.addEventListener('click', async function() {
if (systemFontsLoaded) {
alert('✅ 本机字体已加载完成!可直接在字体列表中选择使用');
return;
}
try {
systemFontLoading.style.display = 'inline-block';
loadSystemFontsBtn.disabled = true;
if (!('queryLocalFonts' in window)) {
alert('❌ 您的浏览器不支持加载本机字体功能\n\n推荐使用:\n• Chrome 103+ 浏览器\n• Edge 103+ 浏览器\n• Opera 89+ 浏览器\n\n请升级浏览器后重试');
return;
}
try {
const permission = await navigator.permissions.query({ name: 'local-fonts' });
if (permission.state !== 'granted') {
alert('🔒 需要您授予字体访问权限\n\n请在浏览器弹出的权限请求中选择"允许",以便读取本机字体库');
return;
}
} catch (permErr) {
console.log('权限查询不支持,直接尝试获取字体');
}
console.log('正在读取本机字体库...');
const fonts = await window.queryLocalFonts();
const uniqueFonts = Array.from(new Map(
fonts.map(font => [font.family.toLowerCase(), font])
)).map(([_, font]) => font)
.sort((a, b) => (a.fullName || a.family).localeCompare(b.fullName || b.family));
const systemFontGroups = {
'中文字体': [],
'英文字体': [],
'其他字体': []
};
const chineseRegex = /[\u4e00-\u9fa5]/;
uniqueFonts.forEach(font => {
const fontName = font.fullName || font.family;
const fontFamily = font.family;
if (chineseRegex.test(fontName) || chineseRegex.test(fontFamily)) {
systemFontGroups['中文字体'].push({ family: fontFamily, name: fontName });
} else if (/^[a-zA-Z\s]+$/.test(fontName)) {
systemFontGroups['英文字体'].push({ family: fontFamily, name: fontName });
} else {
systemFontGroups['其他字体'].push({ family: fontFamily, name: fontName });
}
});
const systemDivider = Array.from(fontSelect.options).find(
opt => opt.textContent.includes('本机已安装字体')
);
if (systemDivider) {
Object.entries(systemFontGroups).forEach(([groupName, fonts]) => {
if (fonts.length === 0) return;
const groupOption = document.createElement('option');
groupOption.disabled = true;
groupOption.innerHTML = `🖥️ 本机${groupName}`;
groupOption.className = 'font-group';
fontSelect.insertBefore(groupOption, systemDivider.nextSibling);
fonts.forEach(font => {
const option = document.createElement('option');
option.value = font.family;
option.textContent = ` ${font.name}`;
fontSelect.insertBefore(option, systemDivider.nextSibling);
});
});
}
systemFontsLoaded = true;
alert(`✅ 成功加载 ${uniqueFonts.length} 个本机字体!\n已添加到字体列表中`);
} catch (error) {
console.error('加载本机字体失败:', error);
alert(`❌ 加载本机字体失败:${error.message}\n\n可能原因:\n1. 浏览器权限未授予\n2. 浏览器版本过低\n3. 系统字体库访问受限`);
} finally {
systemFontLoading.style.display = 'none';
loadSystemFontsBtn.disabled = false;
}
});
// 透明背景切换
transparentBg.addEventListener('change', function() {
if (this.checked) {
bgColorInput.disabled = true;
bgColorPreview.style.backgroundColor = 'transparent';
bgColorPreview.style.border = '1px dashed #ddd';
} else {
bgColorInput.disabled = false;
bgColorPreview.style.backgroundColor = bgColorInput.value;
bgColorPreview.style.border = '1px solid #ddd';
}
generateIcon();
});
// 初始化背景色预览
bgColorPreview.style.backgroundColor = 'transparent';
bgColorPreview.style.border = '1px dashed #ddd';
bgColorInput.disabled = true;
// 字符计数
textInput.addEventListener('input', function() {
const count = this.value.length;
charCount.textContent = count;
if (count > 20) {
charCount.classList.add('max-chars');
} else {
charCount.classList.remove('max-chars');
}
generateIcon();
});
// 自动换行切换 - 显示/隐藏手动换行提示
autoWrap.addEventListener('change', function() {
if (this.checked) {
manualWrapHint.classList.remove('active');
} else {
manualWrapHint.classList.add('active');
}
generateIcon();
});
// 初始化手动换行提示显示状态
if (!autoWrap.checked) {
manualWrapHint.classList.add('active');
}
// 颜色预览更新
colorInput.addEventListener('input', function() {
colorPreview.style.backgroundColor = colorInput.value;
generateIcon();
});
bgColorInput.addEventListener('input', function() {
if (!transparentBg.checked) {
bgColorPreview.style.backgroundColor = this.value;
generateIcon();
}
});
// 新增:自动拉伸切换时禁用/启用字体大小滑块
autoStretch.addEventListener('change', function() {
fontSizeSlider.disabled = this.checked;
if (this.checked) {
document.querySelector('.font-size-note').style.opacity = '0.7';
} else {
document.querySelector('.font-size-note').style.opacity = '1';
}
generateIcon();
});
// 初始化字体大小滑块状态
fontSizeSlider.disabled = autoStretch.checked;
if (autoStretch.checked) {
document.querySelector('.font-size-note').style.opacity = '0.7';
}
// 新增:字体大小滑块更新
fontSizeSlider.addEventListener('input', function() {
fontSizeValue.textContent = `${this.value}%`;
generateIcon();
});
// 拉伸滑块更新
horizontalStretch.addEventListener('input', function() {
horizontalValue.textContent = `${this.value}%`;
generateIcon();
});
verticalStretch.addEventListener('input', function() {
verticalValue.textContent = `${this.value}%`;
generateIcon();
});
// 偏移滑块更新
horizontalOffset.addEventListener('input', function() {
horizontalOffsetValue.textContent = `${this.value}px`;
generateIcon();
});
verticalOffset.addEventListener('input', function() {
verticalOffsetValue.textContent = `${this.value}px`;
generateIcon();
});
// 形状选择
shapeOptions.forEach(option => {
option.addEventListener('click', function() {
shapeOptions.forEach(opt => opt.classList.remove('active'));
this.classList.add('active');
selectedShape = this.dataset.shape;
generateIcon();
});
});
// 尺寸选择
sizeOptions.forEach(option => {
option.addEventListener('click', function() {
sizeOptions.forEach(opt => opt.classList.remove('active'));
this.classList.add('active');
selectedSize = parseInt(this.dataset.size);
generateIcon();
});
});
// 新增:位置选择
positionOptions.forEach(option => {
option.addEventListener('click', function() {
positionOptions.forEach(opt => opt.classList.remove('active'));
this.classList.add('active');
selectedPosition = this.dataset.position;
generateIcon();
});
});
// 生成图标按钮
generateBtn.addEventListener('click', generateIcon);
// 创建Canvas元素
function createCanvas(size) {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
return canvas;
}
// 修复:获取位置偏移(上下颠倒问题)
function getPositionOffset(size) {
const offsets = {
'top-left': { x: size * 0.25, y: size * 0.25 },
'top-center': { x: size * 0.5, y: size * 0.25 },
'top-right': { x: size * 0.75, y: size * 0.25 },
'center-left': { x: size * 0.25, y: size * 0.5 },
'center-center': { x: size * 0.5, y: size * 0.5 },
'center-right': { x: size * 0.75, y: size * 0.5 },
'bottom-left': { x: size * 0.25, y: size * 0.75 },
'bottom-center': { x: size * 0.5, y: size * 0.75 },
'bottom-right': { x: size * 0.75, y: size * 0.75 }
};
return offsets[selectedPosition] || offsets['center-center'];
}
// 绘制图标(增强版)- 支持手动换行
function drawIcon(canvas, size) {
const ctx = canvas.getContext('2d');
const text = textInput.value || '图标';
const wrapEnabled = autoWrap.checked;
let selectedFont = fontSelect.value;
ctx.clearRect(0, 0, size, size);
// 绘制背景
if (!transparentBg.checked) {
ctx.fillStyle = bgColorInput.value;
switch(selectedShape) {
case 'square':
ctx.fillRect(0, 0, size, size);
break;
case 'circle':
ctx.beginPath();
ctx.arc(size/2, size/2, size/2, 0, Math.PI * 2);
ctx.fill();
break;
case 'rounded':
const radius = size * 0.1;
ctx.beginPath();
ctx.moveTo(radius, 0);
ctx.lineTo(size - radius, 0);
ctx.quadraticCurveTo(size, 0, size, radius);
ctx.lineTo(size, size - radius);
ctx.quadraticCurveTo(size, size, size - radius, size);
ctx.lineTo(radius, size);
ctx.quadraticCurveTo(0, size, 0, size - radius);
ctx.lineTo(0, radius);
ctx.quadraticCurveTo(0, 0, radius, 0);
ctx.closePath();
ctx.fill();
break;
}
}
// 设置裁剪区域
ctx.save();
switch(selectedShape) {
case 'circle':
ctx.beginPath();
ctx.arc(size/2, size/2, size/2, 0, Math.PI * 2);
ctx.clip();
break;
case 'rounded':
const radius = size * 0.1;
ctx.beginPath();
ctx.moveTo(radius, 0);
ctx.lineTo(size - radius, 0);
ctx.quadraticCurveTo(size, 0, size, radius);
ctx.lineTo(size, size - radius);
ctx.quadraticCurveTo(size, size, size - radius, size);
ctx.lineTo(radius, size);
ctx.quadraticCurveTo(0, size, 0, size - radius);
ctx.lineTo(0, radius);
ctx.quadraticCurveTo(0, 0, radius, 0);
ctx.closePath();
ctx.clip();
break;
}
// 绘制文字
ctx.fillStyle = colorInput.value;
// 计算字体大小
let fontSize;
if (autoStretch.checked) {
fontSize = calculateOptimalFontSize(ctx, text, size, selectedShape, wrapEnabled, selectedFont);
} else {
// 使用手动设置的字体大小
fontSize = (size * parseInt(fontSizeSlider.value) / 100) * 0.8;
}
// 处理字体名称
let fontFamily = selectedFont;
if (!uploadedFonts.has(selectedFont) && fontFamily.includes(' ') && !fontFamily.startsWith("'") && !fontFamily.startsWith('"')) {
fontFamily = `'${fontFamily}'`;
}
ctx.font = `bold ${fontSize}px ${fontFamily}`;
// 获取位置偏移
const position = getPositionOffset(size);
// 应用拉伸和偏移
const hStretch = parseInt(horizontalStretch.value) / 100;
const vStretch = parseInt(verticalStretch.value) / 100;
const hOffset = parseInt(horizontalOffset.value);
const vOffset = parseInt(verticalOffset.value);
ctx.translate(position.x, position.y);
ctx.scale(hStretch, vStretch);
const offsetX = hOffset * (size / 256);
const offsetY = vOffset * (size / 256);
// 设置文字对齐方式
const posParts = selectedPosition.split('-');
const hAlign = posParts[1] === 'left' ? 'left' : posParts[1] === 'right' ? 'right' : 'center';
const vAlign = posParts[0] === 'top' ? 'alphabetic' : posParts[0] === 'bottom' ? 'bottom' : 'middle';
ctx.textAlign = hAlign;
ctx.textBaseline = vAlign;
// 绘制文字 - 根据是否启用自动换行选择绘制方式
if (!wrapEnabled && text.includes('\n')) {
// 手动换行模式:按用户输入的换行符显示
drawManualWrappedText(ctx, text, offsetX, offsetY, fontSize, size, fontFamily, hAlign, vAlign);
} else if (wrapEnabled && text.length > 1) {
// 自动换行模式:忽略手动换行符,按自动逻辑排列
drawAutoWrappedText(ctx, text.replace(/\n/g, ''), offsetX, offsetY, fontSize, size, fontFamily, hAlign, vAlign);
} else {
// 单行模式:不换行
ctx.fillText(text.replace(/\n/g, ' '), offsetX, offsetY);
}
ctx.restore();
}
// 新增:绘制手动换行文字
function drawManualWrappedText(ctx, text, offsetX, offsetY, fontSize, size, fontFamily, hAlign, vAlign) {
const lines = text.split('\n').filter(line => line.trim() !== '');
if (lines.length === 0) return;
const lineHeight = fontSize * 1.2;
const totalHeight = lines.length * lineHeight;
// 根据垂直对齐方式调整起始位置(修复上下颠倒)
let startY = offsetY;
if (vAlign === 'middle') {
startY -= (totalHeight - lineHeight) / 2;
} else if (vAlign === 'bottom') {
startY -= totalHeight - lineHeight;
} else if (vAlign === 'alphabetic') {
startY += lineHeight * 0.2; // 顶部对齐时微调
}
ctx.font = `bold ${fontSize}px ${fontFamily}`;
ctx.textAlign = hAlign;
ctx.textBaseline = vAlign;
lines.forEach((line, index) => {
ctx.fillText(line, offsetX, startY + index * lineHeight);
});
}
// 修复:绘制自动换行文字(上下颠倒问题)
function drawAutoWrappedText(ctx, text, offsetX, offsetY, fontSize, size, fontFamily, hAlign, vAlign) {
const lines = [];
const charsPerLine = text.length <= 2 ? 1 : 2;
for (let i = 0; i < text.length; i += charsPerLine) {
lines.push(text.substring(i, i + charsPerLine));
}
const lineHeight = fontSize * 1.2;
const totalHeight = lines.length * lineHeight;
// 根据垂直对齐方式调整起始位置(修复上下颠倒)
let startY = offsetY;
if (vAlign === 'middle') {
startY -= (totalHeight - lineHeight) / 2;
} else if (vAlign === 'bottom') {
startY -= totalHeight - lineHeight;
} else if (vAlign === 'alphabetic') {
startY += lineHeight * 0.2; // 顶部对齐时微调
}
ctx.font = `bold ${fontSize}px ${fontFamily}`;
ctx.textAlign = hAlign;
ctx.textBaseline = vAlign;
lines.forEach((line, index) => {
ctx.fillText(line, offsetX, startY + index * lineHeight);
});
}
// 计算最佳字体大小
function calculateOptimalFontSize(ctx, text, size, shape, wrapEnabled, selectedFont) {
let availableWidth, availableHeight;
switch(shape) {
case 'square':
case 'rounded':
availableWidth = size * 0.9;
availableHeight = size * 0.9;
break;
case 'circle':
availableWidth = size * 0.8;
availableHeight = size * 0.8;
break;
}
const textWithoutNewlines = text.replace(/\n/g, '');
if (!wrapEnabled && text.includes('\n')) {
// 手动换行模式:考虑行数
const lines = text.split('\n').filter(line => line.trim() !== '');
availableHeight = availableHeight / lines.length * 0.8;
} else if (wrapEnabled && textWithoutNewlines.length > 1) {
// 自动换行模式
const lines = Math.ceil(textWithoutNewlines.length / (textWithoutNewlines.length <= 2 ? 1 : 2));
availableHeight = availableHeight / lines * 0.8;
}
let fontSize = Math.min(availableWidth, availableHeight);
let minFont = 1;
let maxFont = fontSize * 2;
let fontFamily = selectedFont;
if (!uploadedFonts.has(selectedFont) && fontFamily.includes(' ') && !fontFamily.startsWith("'") && !fontFamily.startsWith('"')) {
fontFamily = `'${fontFamily}'`;
}
for (let i = 0; i < 10; i++) {
ctx.font = `bold ${fontSize}px ${fontFamily}`;
let textWidth;
if (!wrapEnabled && text.includes('\n')) {
// 手动换行模式:取最宽的一行
const lines = text.split('\n').filter(line => line.trim() !== '');
let maxLineWidth = 0;
lines.forEach(line => {
const metrics = ctx.measureText(line);
maxLineWidth = Math.max(maxLineWidth, metrics.width);
});
textWidth = maxLineWidth;
} else if (wrapEnabled && textWithoutNewlines.length > 1) {
// 自动换行模式
const charsPerLine = textWithoutNewlines.length <= 2 ? 1 : 2;
let maxLineWidth = 0;
for (let i = 0; i < textWithoutNewlines.length; i += charsPerLine) {
const line = textWithoutNewlines.substring(i, i + charsPerLine);
const metrics = ctx.measureText(line);
maxLineWidth = Math.max(maxLineWidth, metrics.width);
}
textWidth = maxLineWidth;
} else {
// 单行模式
const metrics = ctx.measureText(textWithoutNewlines);
textWidth = metrics.width;
}
if (textWidth <= availableWidth && fontSize <= availableHeight) {
minFont = fontSize;
} else {
maxFont = fontSize;
}
fontSize = (minFont + maxFont) / 2;
}
return Math.floor(fontSize);
}
// 生成SVG字符串(支持新功能)
function generateSvgString(size) {
const text = textInput.value || '图标';
let selectedFont = fontSelect.value;
const textColor = colorInput.value;
const bgColor = transparentBg.checked ? 'none' : bgColorInput.value;
const shape = selectedShape;
const wrapEnabled = autoWrap.checked;
let fontFamily = selectedFont;
if (!uploadedFonts.has(selectedFont) && fontFamily.includes(' ') && !fontFamily.startsWith("'") && !fontFamily.startsWith('"')) {
fontFamily = `'${fontFamily}'`;
}
const tempCanvas = document.createElement('canvas');
tempCanvas.width = size;
tempCanvas.height = size;
const tempCtx = tempCanvas.getContext('2d');
let fontSize;
if (autoStretch.checked) {
fontSize = calculateOptimalFontSize(tempCtx, text, size, shape, wrapEnabled, selectedFont);
} else {
fontSize = (size * parseInt(fontSizeSlider.value) / 100) * 0.8;
}
const hStretch = parseInt(horizontalStretch.value) / 100;
const vStretch = parseInt(verticalStretch.value) / 100;
const hOffset = parseInt(horizontalOffset.value);
const vOffset = parseInt(verticalOffset.value);
const position = getPositionOffset(size);
// 构建SVG
let svg = `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">`;
// 裁剪路径
let clipPath = '';
switch(shape) {
case 'circle':
clipPath = `<circle cx="${size/2}" cy="${size/2}" r="${size/2}"/>`;
break;
case 'rounded':
const radius = size * 0.1;
clipPath = `<rect width="100%" height="100%" rx="${radius}" ry="${radius}"/>`;
break;
default:
clipPath = `<rect width="100%" height="100%"/>`;
}
svg += `<defs><clipPath id="shapeClip">${clipPath}</clipPath></defs>`;
// 背景
if (!transparentBg.checked) {
switch(shape) {
case 'square':
svg += `<rect width="100%" height="100%" fill="${bgColor}"/>`;
break;
case 'circle':
svg += `<circle cx="${size/2}" cy="${size/2}" r="${size/2}" fill="${bgColor}"/>`;
break;
case 'rounded':
const radius = size * 0.1;
svg += `<rect width="100%" height="100%" rx="${radius}" ry="${radius}" fill="${bgColor}"/>`;
break;
}
}
// 文字层
svg += `<g clip-path="url(#shapeClip)">`;
svg += `<g transform="translate(${position.x} ${position.y}) scale(${hStretch} ${vStretch}) translate(${hOffset * (size / 256)} ${vOffset * (size / 256)})">`;
// 获取对齐方式(修复上下颠倒)
const posParts = selectedPosition.split('-');
const hAlign = posParts[1] === 'left' ? 'start' : posParts[1] === 'right' ? 'end' : 'middle';
const vAlign = posParts[0] === 'top' ? 'hanging' : posParts[0] === 'bottom' ? 'baseline' : 'middle';
// 换行处理
if (!wrapEnabled && text.includes('\n')) {
// 手动换行模式
const lines = text.split('\n').filter(line => line.trim() !== '');
if (lines.length === 0) return svg + '</g></g></svg>';
const lineHeight = fontSize * 1.2;
const totalHeight = lines.length * lineHeight;
let startY = 0;
if (vAlign === 'middle') {
startY -= (totalHeight - lineHeight) / 2;
} else if (vAlign === 'baseline') {
startY -= totalHeight - lineHeight;
} else if (vAlign === 'hanging') {
startY += lineHeight * 0.2;
}
lines.forEach((line, index) => {
svg += `<text x="0" y="${startY + index * lineHeight}" font-family="${fontFamily}" font-size="${fontSize}" fill="${textColor}" text-anchor="${hAlign}" dominant-baseline="${vAlign}">${line}</text>`;
});
} else if (wrapEnabled && text.replace(/\n/g, '').length > 1) {
// 自动换行模式:忽略手动换行符
const textWithoutNewlines = text.replace(/\n/g, '');
const lines = [];
const charsPerLine = textWithoutNewlines.length <= 2 ? 1 : 2;
for (let i = 0; i < textWithoutNewlines.length; i += charsPerLine) {
lines.push(textWithoutNewlines.substring(i, i + charsPerLine));
}
const lineHeight = fontSize * 1.2;
const totalHeight = lines.length * lineHeight;
let startY = 0;
if (vAlign === 'middle') {
startY -= (totalHeight - lineHeight) / 2;
} else if (vAlign === 'baseline') {
startY -= totalHeight - lineHeight;
} else if (vAlign === 'hanging') {
startY += lineHeight * 0.2;
}
lines.forEach((line, index) => {
svg += `<text x="0" y="${startY + index * lineHeight}" font-family="${fontFamily}" font-size="${fontSize}" fill="${textColor}" text-anchor="${hAlign}" dominant-baseline="${vAlign}">${line}</text>`;
});
} else {
// 单行模式:不换行
svg += `<text x="0" y="0" font-family="${fontFamily}" font-size="${fontSize}" fill="${textColor}" text-anchor="${hAlign}" dominant-baseline="${vAlign}">${text.replace(/\n/g, ' ')}</text>`;
}
svg += `</g></g></svg>`;
return svg;
}
// 生成图标并更新预览
function generateIcon() {
drawIcon(previewCanvas, selectedSize);
updateSizePreview();
}
// 更新尺寸预览
function updateSizePreview() {
sizePreview.innerHTML = '';
const sizes = [16, 32, 48, 64, 128, 256];
sizes.forEach(size => {
const previewItem = document.createElement('div');
previewItem.className = 'size-preview-item';
const previewCanvas = document.createElement('canvas');
previewCanvas.className = 'size-preview-canvas';
previewCanvas.width = Math.min(64, size);
previewCanvas.height = Math.min(64, size);
const scale = Math.min(1, 64 / size);
const ctx = previewCanvas.getContext('2d');
ctx.save();
ctx.scale(scale, scale);
drawIcon(previewCanvas, size);
ctx.restore();
const sizeLabel = document.createElement('div');
sizeLabel.textContent = `${size}×${size}`;
sizeLabel.style.fontSize = '12px';
sizeLabel.style.color = '#666';
previewItem.appendChild(previewCanvas);
previewItem.appendChild(sizeLabel);
sizePreview.appendChild(previewItem);
});
}
// 输入变化实时更新
textInput.addEventListener('input', generateIcon);
fontSelect.addEventListener('change', generateIcon);
colorInput.addEventListener('input', generateIcon);
autoStretch.addEventListener('change', generateIcon);
fontSizeSlider.addEventListener('input', generateIcon); // 新增
// 初始化生成图标
generateIcon();
// 清理资源
window.addEventListener('beforeunload', function() {
if (progressInterval) clearInterval(progressInterval);
if (progressPercentInterval) clearInterval(progressPercentInterval);
if (downloadTimeout) clearTimeout(downloadTimeout);
uploadedFonts.forEach((fontData) => {
URL.revokeObjectURL(fontData.url);
document.fonts.delete(fontData.face);
});
});
});
</script>
</body>
</html>