docs: 开源规范化 — doc comments + 社区文件 + 示例代码 + crates.io 就绪
- 为 core 公开 API 添加完整 doc comments(rustdoc 可用) - 新增 .editorconfig / CONTRIBUTING / CODE_OF_CONDUCT / SECURITY - 新增 Issue 模板(bug + feature)+ PR 模板 - 新增 3 个代码示例(examples/) - 更新 Cargo.toml 元数据(description/repository/keywords/categories/MSRV) - 更新 README + CHANGELOG
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
# EditorConfig — 跨编辑器统一编码风格
|
||||||
|
# https://editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.{ts,tsx,js,jsx,json,css,html,md,yaml,yml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.toml]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.{png,jpg,jpeg,gif,svg,ico,ttf,woff,woff2,eot}]
|
||||||
|
insert_final_newline = false
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: Bug 报告
|
||||||
|
about: 报告一个问题,帮助我们改进
|
||||||
|
title: "[Bug] "
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
**描述问题**
|
||||||
|
一句话描述你遇到什么 bug。
|
||||||
|
|
||||||
|
**复现步骤**
|
||||||
|
1. 执行 `...`
|
||||||
|
2. 输入 `...`
|
||||||
|
3. 看到错误 `...`
|
||||||
|
|
||||||
|
**预期行为**
|
||||||
|
你期望发生什么?
|
||||||
|
|
||||||
|
**截图或日志**
|
||||||
|
如果适用,粘贴错误日志或截图。
|
||||||
|
|
||||||
|
**环境**
|
||||||
|
- OS: [如 Windows 11, macOS 15]
|
||||||
|
- Rust 版本: [`rustc --version` 输出]
|
||||||
|
- QRGen 版本: [v0.1.0 或 commit hash]
|
||||||
|
|
||||||
|
**补充信息**
|
||||||
|
其他相关内容。
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: 功能请求
|
||||||
|
about: 提出一个新功能或改进建议
|
||||||
|
title: "[Feature] "
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
**动机**
|
||||||
|
这个功能解决什么问题?你的使用场景是什么?
|
||||||
|
|
||||||
|
**方案描述**
|
||||||
|
描述你期望的方案。如果有 API 设计,请写出伪代码。
|
||||||
|
|
||||||
|
**备选方案**
|
||||||
|
你考虑过哪些替代方案?
|
||||||
|
|
||||||
|
**补充信息**
|
||||||
|
截图、参考链接等。
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
## 概述
|
||||||
|
|
||||||
|
<!-- 一句话描述这个 PR 做了什么 -->
|
||||||
|
|
||||||
|
## 变更类型
|
||||||
|
|
||||||
|
- [ ] Bug 修复
|
||||||
|
- [ ] 新功能
|
||||||
|
- [ ] 重构(无功能变更)
|
||||||
|
- [ ] 文档更新
|
||||||
|
- [ ] 测试
|
||||||
|
- [ ] 其他:
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
<!-- 描述你如何验证这个 PR -->
|
||||||
|
|
||||||
|
- [ ] `cargo test` 全部通过
|
||||||
|
- [ ] `cargo clippy -- -D warnings` 无警告
|
||||||
|
- [ ] `cargo fmt` 已运行
|
||||||
|
- [ ] 新增测试:
|
||||||
|
|
||||||
|
## 截图(如适用)
|
||||||
|
|
||||||
|
## 关联 Issue
|
||||||
|
|
||||||
|
Closes #
|
||||||
|
|
||||||
|
## 检查清单
|
||||||
|
|
||||||
|
- [ ] 代码已自审
|
||||||
|
- [ ] 注释清晰(中文)
|
||||||
|
- [ ] 文档已更新
|
||||||
|
- [ ] 无硬编码密钥
|
||||||
|
- [ ] 无破坏性变更(或已标注)
|
||||||
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.1.0 (2026-06-18)
|
## 0.1.0 (2026-06-19)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -59,4 +59,6 @@
|
|||||||
- Web:axum 0.8 + tokio,编译期 HTML 嵌入 (include_str!)
|
- Web:axum 0.8 + tokio,编译期 HTML 嵌入 (include_str!)
|
||||||
- 82 个测试(58 单元 + 24 集成)
|
- 82 个测试(58 单元 + 24 集成)
|
||||||
- NSIS Windows 安装包 + Docker Alpine 镜像
|
- NSIS Windows 安装包 + Docker Alpine 镜像
|
||||||
- GitHub Actions 就绪(测试 + clippy + fmt)
|
- 文档:API doc comments(rustdoc 可用)+ 3 个代码示例
|
||||||
|
- 社区:CONTRIBUTING / CODE_OF_CONDUCT / SECURITY / Issue & PR 模板
|
||||||
|
- 工程:.editorconfig / MSRV=1.87 / crates.io 就绪
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# 行为准则
|
||||||
|
|
||||||
|
## 承诺
|
||||||
|
|
||||||
|
为了营造一个开放和友好的环境,我们承诺让所有人无论年龄、体型、身体条件、种族、性别认同与表达、经验水平、教育程度、社会经济地位、国籍、个人外貌、宗教信仰或性取向,都能无骚扰地参与本项目。
|
||||||
|
|
||||||
|
## 标准
|
||||||
|
|
||||||
|
**鼓励的行为:**
|
||||||
|
- 使用友好和包容的语言
|
||||||
|
- 尊重不同的观点和经验
|
||||||
|
- 优雅地接受建设性批评
|
||||||
|
- 关注对社区最有利的事
|
||||||
|
- 对其他社区成员表达同理心
|
||||||
|
|
||||||
|
**不可接受的行为:**
|
||||||
|
- 使用带有性别色彩、性暗示的语言或图像
|
||||||
|
- 人身攻击、政治抨击或网络暴力
|
||||||
|
- 公开或私下骚扰
|
||||||
|
- 未经明确许可发布他人私人信息
|
||||||
|
- 在专业场合中不合理的其他行为
|
||||||
|
|
||||||
|
## 执行
|
||||||
|
|
||||||
|
项目维护者有责任澄清可接受行为的标准,并对任何不可接受的行为采取适当且公平的纠正措施。
|
||||||
|
|
||||||
|
如遇违规行为,请联系:**lhy3364451258@163.com**
|
||||||
|
|
||||||
|
本行为准则改编自 [Contributor Covenant](https://www.contributor-covenant.org)。
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
# 贡献指南
|
||||||
|
|
||||||
|
感谢你对 QRGen 的关注!这份指南帮助你了解如何参与项目。
|
||||||
|
|
||||||
|
## 项目架构
|
||||||
|
|
||||||
|
QRGen 是 Rust workspace,四层架构:
|
||||||
|
|
||||||
|
| crate | 用途 | 技术栈 |
|
||||||
|
|-------|------|--------|
|
||||||
|
| `qr-core` | 核心算法库 | Rust,纯算法无三方依赖 |
|
||||||
|
| `qrgen` | CLI 命令行 | clap + anyhow |
|
||||||
|
| `qrgen-gui` | 桌面应用 | Tauri 2.x + React 18 + TypeScript |
|
||||||
|
| `qrgen-web` | Web 服务 | axum 0.8 + tokio |
|
||||||
|
|
||||||
|
详细架构见 [CLAUDE.md](CLAUDE.md)。
|
||||||
|
|
||||||
|
## 开发环境
|
||||||
|
|
||||||
|
- **Rust** 1.95+ (`stable-x86_64-pc-windows-gnu` 或 `stable-x86_64-unknown-linux-gnu`)
|
||||||
|
- **Node.js** 22+(仅 GUI 前端需要)
|
||||||
|
- **pnpm**(GUI 前端包管理)
|
||||||
|
|
||||||
|
## 本地构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆
|
||||||
|
git clone git@lhy-git.liuhangyv.top:Serendipity/QRGen.git
|
||||||
|
cd QRGen
|
||||||
|
|
||||||
|
# 安装 GUI 前端依赖
|
||||||
|
cd gui/src-frontend && pnpm install && cd ../..
|
||||||
|
|
||||||
|
# 编译检查
|
||||||
|
cargo check
|
||||||
|
|
||||||
|
# CLI
|
||||||
|
cargo build -p qrgen
|
||||||
|
|
||||||
|
# Web
|
||||||
|
cargo build -p qrgen-web
|
||||||
|
|
||||||
|
# GUI(需要前端依赖)
|
||||||
|
cd gui/src-frontend && pnpm build && cd ../..
|
||||||
|
cargo build -p qrgen-gui
|
||||||
|
```
|
||||||
|
|
||||||
|
## 提交规范
|
||||||
|
|
||||||
|
使用 Conventional Commits 格式:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>: <简短描述>
|
||||||
|
|
||||||
|
feat: 添加 Logo 图片支持
|
||||||
|
fix: 修复 PNG margin 全黑问题
|
||||||
|
refactor: 提取 fill_module 辅助函数
|
||||||
|
docs: 更新 README 安装说明
|
||||||
|
test: 添加格式信息 roundtrip 测试
|
||||||
|
```
|
||||||
|
|
||||||
|
类型:`feat` `fix` `refactor` `docs` `test` `chore` `perf` `ci`
|
||||||
|
|
||||||
|
## 代码风格
|
||||||
|
|
||||||
|
- **Rust**: `cargo fmt` + `cargo clippy -- -D warnings` 必须通过
|
||||||
|
- **TypeScript**: `pnpm tsc --noEmit` 必须通过(strict 模式)
|
||||||
|
- 注释用中文,标识符用英文
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 全量测试
|
||||||
|
cargo test # 所有 crate
|
||||||
|
|
||||||
|
# 仅核心库
|
||||||
|
cargo test -p qr-core
|
||||||
|
|
||||||
|
# 特定测试
|
||||||
|
cargo test -p qr-core -- mask # 掩码相关测试
|
||||||
|
|
||||||
|
# 覆盖率
|
||||||
|
cargo tarpaulin --out Html
|
||||||
|
```
|
||||||
|
|
||||||
|
测试覆盖率目标 ≥ 80%。
|
||||||
|
|
||||||
|
## 提交流程
|
||||||
|
|
||||||
|
1. Fork 仓库
|
||||||
|
2. 创建功能分支 (`git checkout -b feat/my-feature`)
|
||||||
|
3. 编码 + 测试(遵循 TDD)
|
||||||
|
4. `cargo fmt` + `cargo clippy -- -D warnings`
|
||||||
|
5. `cargo test` 全部通过
|
||||||
|
6. 提交并 push
|
||||||
|
7. 创建 Pull Request
|
||||||
|
|
||||||
|
## Issue 规范
|
||||||
|
|
||||||
|
- Bug 报告:附重现步骤 + 预期/实际行为
|
||||||
|
- 功能请求:描述使用场景和期望效果
|
||||||
|
|
||||||
|
## 感谢
|
||||||
|
|
||||||
|
所有贡献者将在 [README.md](README.md) 中致谢。
|
||||||
+9
-1
@@ -6,4 +6,12 @@ members = ["core", "cli", "gui", "web"]
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["刘航宇"]
|
authors = ["刘航宇 <lhy3364451258@163.com>"]
|
||||||
|
description = "从零手写的 QR 码生成器 — ISO/IEC 18004 完整实现"
|
||||||
|
repository = "https://github.com/LHY0125/QRGen"
|
||||||
|
homepage = "https://github.com/LHY0125/QRGen"
|
||||||
|
documentation = "https://github.com/LHY0125/QRGen"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["qr", "qr-code", "qrcode", "encoding", "barcode"]
|
||||||
|
categories = ["encoding", "command-line-utilities", "gui", "web-programming"]
|
||||||
|
rust-version = "1.87"
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/version-0.1.0-blue" alt="version">
|
<img src="https://img.shields.io/badge/version-0.1.0-blue" alt="version">
|
||||||
|
<img src="https://img.shields.io/badge/rust-1.95-000000" alt="rust">
|
||||||
<img src="https://img.shields.io/badge/tauri-2.x-ffa03a" alt="tauri">
|
<img src="https://img.shields.io/badge/tauri-2.x-ffa03a" alt="tauri">
|
||||||
<img src="https://img.shields.io/badge/react-18-61dafb" alt="react">
|
<img src="https://img.shields.io/badge/react-18-61dafb" alt="react">
|
||||||
<img src="https://img.shields.io/badge/axum-0.8-ff6b35" alt="axum">
|
<img src="https://img.shields.io/badge/axum-0.8-ff6b35" alt="axum">
|
||||||
<img src="https://img.shields.io/badge/rust-1.95-000000" alt="rust">
|
|
||||||
<img src="https://img.shields.io/badge/docker-ready-2496ed" alt="docker">
|
<img src="https://img.shields.io/badge/docker-ready-2496ed" alt="docker">
|
||||||
<img src="https://img.shields.io/badge/license-MIT-green" alt="license">
|
<img src="https://img.shields.io/badge/license-MIT-green" alt="license">
|
||||||
<img src="https://img.shields.io/badge/tests-82%20passed-brightgreen" alt="tests">
|
<img src="https://img.shields.io/badge/tests-58%20passed-brightgreen" alt="tests">
|
||||||
|
<img src="https://img.shields.io/badge/clippy-clean-brightgreen" alt="clippy">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -271,6 +272,48 @@ QRGen/
|
|||||||
| 自动版本选择 | 根据数据长度 + 纠错级别 |
|
| 自动版本选择 | 根据数据长度 + 纠错级别 |
|
||||||
| Docker 镜像 | ~18MB (alpine) |
|
| Docker 镜像 | ~18MB (alpine) |
|
||||||
|
|
||||||
|
## 代码示例
|
||||||
|
|
||||||
|
`examples/` 目录包含三个示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基础编码(默认配置)
|
||||||
|
cargo run --example basic_qr
|
||||||
|
|
||||||
|
# 高纠错级别(H 级,30% 容错)
|
||||||
|
cargo run --example high_ecc
|
||||||
|
|
||||||
|
# 自定义配置(固定版本 + 大尺寸 PNG)
|
||||||
|
cargo run --example custom_config
|
||||||
|
```
|
||||||
|
|
||||||
|
作为程序库使用:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use qr_core::qr::{QrCode, QrConfig};
|
||||||
|
|
||||||
|
let qr = QrCode::encode("Hello QR!", QrConfig::default())?;
|
||||||
|
println!("{}", qr.to_ascii(false)); // 终端预览
|
||||||
|
qr.to_png_bytes(8)?; // PNG 字节
|
||||||
|
let svg = qr.to_svg(); // SVG 字符串
|
||||||
|
```
|
||||||
|
|
||||||
|
## 发布到 crates.io
|
||||||
|
|
||||||
|
`qr-core` 可作为 Rust 库被其他项目引用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo add qr-core
|
||||||
|
```
|
||||||
|
|
||||||
|
## 社区
|
||||||
|
|
||||||
|
- [贡献指南](CONTRIBUTING.md) — 如何参与开发
|
||||||
|
- [行为准则](CODE_OF_CONDUCT.md) — 社区规范
|
||||||
|
- [安全策略](SECURITY.md) — 漏洞报告流程
|
||||||
|
- [变更日志](CHANGELOG.md) — 版本历史
|
||||||
|
- [Issue 模板](.github/ISSUE_TEMPLATE/) — Bug 报告 / 功能请求
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# 安全策略
|
||||||
|
|
||||||
|
## 报告漏洞
|
||||||
|
|
||||||
|
如果你发现 QRGen 的安全漏洞,**请勿公开提交 Issue**。
|
||||||
|
|
||||||
|
请发送邮件至:**lhy3364451258@163.com**
|
||||||
|
|
||||||
|
在邮件中包含:
|
||||||
|
- 漏洞描述
|
||||||
|
- 复现步骤
|
||||||
|
- 影响范围评估
|
||||||
|
- 建议修复方案(如有)
|
||||||
|
|
||||||
|
我会在 **48 小时内回复**,并在确认后尽快发布修复。
|
||||||
|
|
||||||
|
## 安全考量
|
||||||
|
|
||||||
|
QRGen 是一个 QR 码生成工具,不处理用户隐私数据。关注的安全领域:
|
||||||
|
|
||||||
|
- **输入验证**:恶意构造的超长文本可能导致内存问题
|
||||||
|
- **文件路径**:CLI 输出路径防止目录遍历(已内置 `..` 拒绝)
|
||||||
|
- **Web 服务**:如果公网部署,需限制速率(建议通过反向代理实现)
|
||||||
|
- **依赖安全**:定期 `cargo audit` 检查 Rust 依赖漏洞
|
||||||
|
|
||||||
|
## 支持版本
|
||||||
|
|
||||||
|
| 版本 | 安全更新 |
|
||||||
|
|------|----------|
|
||||||
|
| 最新版 | ✅ |
|
||||||
|
| 旧版本 | ❌ 请升级到最新版 |
|
||||||
|
|
||||||
|
## 披露策略
|
||||||
|
|
||||||
|
- 修复发布后,漏洞细节将在 CHANGELOG 中公开
|
||||||
|
- 严重漏洞会优先发布补丁,延迟细节披露
|
||||||
@@ -4,9 +4,29 @@ version.workspace = true
|
|||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
|
description.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
documentation.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "basic_qr"
|
||||||
|
path = "../examples/basic_qr.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "high_ecc"
|
||||||
|
path = "../examples/high_ecc.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "custom_config"
|
||||||
|
path = "../examples/custom_config.rs"
|
||||||
|
|||||||
+119
-2
@@ -12,21 +12,61 @@ use crate::matrix::placement::place_data;
|
|||||||
use crate::version::{get_data_capacity, EcLevel, Version};
|
use crate::version::{get_data_capacity, EcLevel, Version};
|
||||||
|
|
||||||
/// 版本选择模式
|
/// 版本选择模式
|
||||||
|
///
|
||||||
|
/// 控制 QR 码版本(尺寸)的确定方式:
|
||||||
|
/// - `Auto`:根据数据量和纠错级别自动选择最小可用版本
|
||||||
|
/// - `Fixed(v)`:强制使用指定版本(1~40),数据超限时返回错误
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::qr::{QrCode, QrConfig, VersionMode};
|
||||||
|
/// use qr_core::version::EcLevel;
|
||||||
|
///
|
||||||
|
/// // 自动选择版本
|
||||||
|
/// let qr = QrCode::encode("HELLO", QrConfig::default()).unwrap();
|
||||||
|
/// assert_eq!(qr.version.0, 1);
|
||||||
|
///
|
||||||
|
/// // 强制指定版本
|
||||||
|
/// let config = QrConfig {
|
||||||
|
/// version: VersionMode::Fixed(5),
|
||||||
|
/// ..Default::default()
|
||||||
|
/// };
|
||||||
|
/// let qr = QrCode::encode("VERSION 5", config).unwrap();
|
||||||
|
/// assert_eq!(qr.version.0, 5);
|
||||||
|
/// ```
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum VersionMode {
|
pub enum VersionMode {
|
||||||
|
/// 根据数据量自动选择最小可用版本
|
||||||
Auto,
|
Auto,
|
||||||
|
/// 强制固定版本(1~40)
|
||||||
Fixed(u8),
|
Fixed(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// QR 码配置
|
/// QR 码编码配置
|
||||||
|
///
|
||||||
|
/// 控制纠错级别、版本选择策略和静区边距。
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::qr::QrConfig;
|
||||||
|
/// use qr_core::version::EcLevel;
|
||||||
|
///
|
||||||
|
/// let config = QrConfig {
|
||||||
|
/// level: EcLevel::H, // 30% 纠错能力
|
||||||
|
/// margin: 8, // 8 模块静区
|
||||||
|
/// ..Default::default()
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct QrConfig {
|
pub struct QrConfig {
|
||||||
|
/// 纠错级别(L/M/Q/H)
|
||||||
pub level: EcLevel,
|
pub level: EcLevel,
|
||||||
|
/// 版本选择模式
|
||||||
pub version: VersionMode,
|
pub version: VersionMode,
|
||||||
|
/// 静区边距(模块数),默认 4
|
||||||
pub margin: u8,
|
pub margin: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for QrConfig {
|
impl Default for QrConfig {
|
||||||
|
/// 默认配置:M 级纠错 + 自动版本 + 4 模块边距
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
QrConfig {
|
QrConfig {
|
||||||
level: EcLevel::M,
|
level: EcLevel::M,
|
||||||
@@ -37,16 +77,61 @@ impl Default for QrConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 生成的 QR 码
|
/// 生成的 QR 码
|
||||||
|
///
|
||||||
|
/// 包含编码后的矩阵数据和元信息,可通过方法导出为 SVG / PNG / ASCII。
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::qr::{QrCode, QrConfig};
|
||||||
|
///
|
||||||
|
/// let qr = QrCode::encode("https://example.com", QrConfig::default()).unwrap();
|
||||||
|
/// println!("版本: {}, 尺寸: {}×{}", qr.version.0, qr.size(), qr.size());
|
||||||
|
///
|
||||||
|
/// // 导出为 SVG
|
||||||
|
/// let svg = qr.to_svg();
|
||||||
|
/// assert!(svg.starts_with("<svg"));
|
||||||
|
///
|
||||||
|
/// // 导出为 PNG 字节
|
||||||
|
/// let png = qr.to_png_bytes(4).unwrap();
|
||||||
|
/// assert!(png.len() > 100);
|
||||||
|
///
|
||||||
|
/// // 终端 ASCII 输出
|
||||||
|
/// let ascii = qr.to_ascii(false);
|
||||||
|
/// assert!(!ascii.is_empty());
|
||||||
|
/// ```
|
||||||
pub struct QrCode {
|
pub struct QrCode {
|
||||||
|
/// QR 码版本(1~40)
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
|
/// 纠错级别
|
||||||
pub level: EcLevel,
|
pub level: EcLevel,
|
||||||
|
/// 选中的掩码编号(0~7)
|
||||||
pub mask: u8,
|
pub mask: u8,
|
||||||
matrix: Matrix,
|
matrix: Matrix,
|
||||||
|
/// 静区边距(模块数)
|
||||||
pub margin: u8,
|
pub margin: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QrCode {
|
impl QrCode {
|
||||||
/// 编码字符串生成 QR 码
|
/// 编码字符串为 QR 码
|
||||||
|
///
|
||||||
|
/// 执行完整的 9 步流水线:分段 → 版本选择 → 模式编码 → 纠错 → 矩阵布局 →
|
||||||
|
/// 蛇形排列 → 掩码评分 → 格式信息 → 版本信息。
|
||||||
|
///
|
||||||
|
/// # 参数
|
||||||
|
/// - `text`:要编码的文本,支持汉字(Shift JIS)、数字、字母、字节
|
||||||
|
/// - `config`:[`QrConfig`] 编码配置
|
||||||
|
///
|
||||||
|
/// # 错误
|
||||||
|
/// - 输入为空时返回 `"输入为空"`
|
||||||
|
/// - 版本无效时返回 `"无效版本号 (1-40)"`
|
||||||
|
/// - 数据超出版本容量时返回 `"数据过长,超出 QR 码最大容量"`
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::qr::{QrCode, QrConfig};
|
||||||
|
///
|
||||||
|
/// let qr = QrCode::encode("Hello QR!", QrConfig::default()).unwrap();
|
||||||
|
/// assert_eq!(qr.version.0, 1);
|
||||||
|
/// assert_eq!(qr.size(), 21);
|
||||||
|
/// ```
|
||||||
pub fn encode(text: &str, config: QrConfig) -> Result<Self, String> {
|
pub fn encode(text: &str, config: QrConfig) -> Result<Self, String> {
|
||||||
// 1. 分段
|
// 1. 分段
|
||||||
let segments = segment_text(text);
|
let segments = segment_text(text);
|
||||||
@@ -127,22 +212,54 @@ impl QrCode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 返回 QR 码的布尔矩阵(`true` = 深色模块)
|
||||||
|
///
|
||||||
|
/// 矩阵尺寸为 `size() × size()`,包含功能图案、数据模块和静区。
|
||||||
pub fn modules(&self) -> &[Vec<bool>] {
|
pub fn modules(&self) -> &[Vec<bool>] {
|
||||||
&self.matrix.modules
|
&self.matrix.modules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 返回 QR 码的边长(模块数,含静区)
|
||||||
|
///
|
||||||
|
/// 取值范围:21(版本 1 + 4×2 边距)到 185(版本 40 + 4×2 边距)
|
||||||
pub fn size(&self) -> u8 {
|
pub fn size(&self) -> u8 {
|
||||||
self.matrix.size
|
self.matrix.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 导出为 SVG 字符串
|
||||||
|
///
|
||||||
|
/// SVG 内含 `viewBox`、深色模块用 `#000` 填充。
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::qr::{QrCode, QrConfig};
|
||||||
|
///
|
||||||
|
/// let qr = QrCode::encode("test", QrConfig::default()).unwrap();
|
||||||
|
/// let svg = qr.to_svg();
|
||||||
|
/// assert!(svg.starts_with("<svg"));
|
||||||
|
/// ```
|
||||||
pub fn to_svg(&self) -> String {
|
pub fn to_svg(&self) -> String {
|
||||||
crate::render::svg::render_svg(self)
|
crate::render::svg::render_svg(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 导出为终端 ASCII 文本
|
||||||
|
///
|
||||||
|
/// 深色模块用 `██`,浅色模块用 ` `(双空格)。
|
||||||
|
/// `invert=true` 时反转颜色(白底黑码 → 黑底白码)。
|
||||||
pub fn to_ascii(&self, invert: bool) -> String {
|
pub fn to_ascii(&self, invert: bool) -> String {
|
||||||
crate::render::ascii::render_ascii(self, invert)
|
crate::render::ascii::render_ascii(self, invert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 导出为 PNG 字节数据
|
||||||
|
///
|
||||||
|
/// `module_size` 控制每个模块的像素大小(2~20),越大文件越大。
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::qr::{QrCode, QrConfig};
|
||||||
|
///
|
||||||
|
/// let qr = QrCode::encode("PNG test", QrConfig::default()).unwrap();
|
||||||
|
/// let bytes = qr.to_png_bytes(4).unwrap();
|
||||||
|
/// std::fs::write("test.png", &bytes).unwrap();
|
||||||
|
/// ```
|
||||||
pub fn to_png_bytes(&self, module_size: u8) -> Result<Vec<u8>, image::ImageError> {
|
pub fn to_png_bytes(&self, module_size: u8) -> Result<Vec<u8>, image::ImageError> {
|
||||||
crate::render::png::render_png(self, module_size)
|
crate::render::png::render_png(self, module_size)
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-8
@@ -1,17 +1,33 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
/// 纠错级别
|
/// QR 码纠错级别
|
||||||
|
///
|
||||||
|
/// 决定 QR 码被遮挡/损毁后仍可扫描的能力。纠错越强,可存储的数据越少。
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::version::{EcLevel, Version};
|
||||||
|
///
|
||||||
|
/// let ver = Version::new(1).unwrap();
|
||||||
|
/// let h = ver.ec_info(EcLevel::H);
|
||||||
|
/// let l = ver.ec_info(EcLevel::L);
|
||||||
|
/// // H 级纠错码字更多,数据码字更少
|
||||||
|
/// assert!(h.ec_per_block > l.ec_per_block);
|
||||||
|
/// ```
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||||
pub enum EcLevel {
|
pub enum EcLevel {
|
||||||
L, // 约 7%
|
/// L 级 — 约 7% 纠错能力
|
||||||
M, // 约 15%
|
L,
|
||||||
Q, // 约 25%
|
/// M 级 — 约 15% 纠错能力(默认)
|
||||||
H, // 约 30%
|
M,
|
||||||
|
/// Q 级 — 约 25% 纠错能力
|
||||||
|
Q,
|
||||||
|
/// H 级 — 约 30% 纠错能力
|
||||||
|
H,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EcLevel {
|
impl EcLevel {
|
||||||
/// 格式信息中使用的指示位
|
/// 格式信息中使用的 2-bit 指示位
|
||||||
pub fn indicator_bits(self) -> u8 {
|
pub fn indicator_bits(self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
EcLevel::L => 0b01,
|
EcLevel::L => 0b01,
|
||||||
@@ -22,7 +38,23 @@ impl EcLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 版本号 1~40
|
/// QR 码版本号(1~40)
|
||||||
|
///
|
||||||
|
/// 版本决定 QR 码的物理尺寸:`side = 17 + version × 4` 模块。
|
||||||
|
/// 版本 1 是 21×21,版本 40 是 177×177。
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::version::Version;
|
||||||
|
///
|
||||||
|
/// let v1 = Version::new(1).unwrap();
|
||||||
|
/// assert_eq!(v1.size(), 21);
|
||||||
|
///
|
||||||
|
/// let v40 = Version::new(40).unwrap();
|
||||||
|
/// assert_eq!(v40.size(), 177);
|
||||||
|
///
|
||||||
|
/// assert!(Version::new(0).is_none());
|
||||||
|
/// assert!(Version::new(41).is_none());
|
||||||
|
/// ```
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||||
pub struct Version(pub u8);
|
pub struct Version(pub u8);
|
||||||
|
|
||||||
@@ -104,16 +136,27 @@ impl Version {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 纠错分组信息
|
||||||
|
///
|
||||||
|
/// 一组 QR 数据被分为多个块,每个块独立计算 RS 纠错码。
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EcInfo {
|
pub struct EcInfo {
|
||||||
|
/// 数据码字总数
|
||||||
pub total_codewords: u16,
|
pub total_codewords: u16,
|
||||||
|
/// 每个块的纠错码字数
|
||||||
pub ec_per_block: u8,
|
pub ec_per_block: u8,
|
||||||
|
/// 分组信息(1~2 组,不同数据码字数)
|
||||||
pub blocks: Vec<BlockInfo>,
|
pub blocks: Vec<BlockInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 单个分组信息
|
||||||
|
///
|
||||||
|
/// 所有块的数据码字总数 + 纠错码字总数 = QR 码总容量。
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BlockInfo {
|
pub struct BlockInfo {
|
||||||
|
/// 该组块数
|
||||||
pub count: u16,
|
pub count: u16,
|
||||||
|
/// 每个块的数据码字数
|
||||||
pub data_codewords: u16,
|
pub data_codewords: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1290,6 +1333,16 @@ fn init_capacity_table() -> [[u16; 4]; 40] {
|
|||||||
table
|
table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 查询指定版本+纠错级别的数据码字容量
|
||||||
|
///
|
||||||
|
/// 返回数据码字数(非比特数),乘以 8 得比特容量。
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::version::{get_data_capacity, Version, EcLevel};
|
||||||
|
///
|
||||||
|
/// let cap = get_data_capacity(Version(1), EcLevel::M);
|
||||||
|
/// assert_eq!(cap, 16); // 版本 1-M 有 16 个数据码字
|
||||||
|
/// ```
|
||||||
// SAFETY: version.0 ∈ [1,40] 由 Version::new() 保证; level 是 4 变体枚举
|
// SAFETY: version.0 ∈ [1,40] 由 Version::new() 保证; level 是 4 变体枚举
|
||||||
#[allow(clippy::indexing_slicing)]
|
#[allow(clippy::indexing_slicing)]
|
||||||
pub fn get_data_capacity(version: Version, level: EcLevel) -> u16 {
|
pub fn get_data_capacity(version: Version, level: EcLevel) -> u16 {
|
||||||
@@ -1298,7 +1351,19 @@ pub fn get_data_capacity(version: Version, level: EcLevel) -> u16 {
|
|||||||
cap[version.0 as usize - 1][level as usize]
|
cap[version.0 as usize - 1][level as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 自动选择最小版本:返回能容纳 data_bits 比特的最小版本
|
/// 自动选择能容纳 `data_bits` 比特的最小版本
|
||||||
|
///
|
||||||
|
/// 返回 `None` 表示数据量超出 QR 码最大容量(版本 40)。
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use qr_core::version::{pick_version, EcLevel};
|
||||||
|
///
|
||||||
|
/// let v = pick_version(100, EcLevel::M).unwrap();
|
||||||
|
/// assert_eq!(v.0, 1); // 版本 1-M 可容纳 16×8=128 bit >= 100
|
||||||
|
///
|
||||||
|
/// let too_big = pick_version(50_000, EcLevel::H);
|
||||||
|
/// assert!(too_big.is_none());
|
||||||
|
/// ```
|
||||||
pub fn pick_version(data_bits: u16, level: EcLevel) -> Option<Version> {
|
pub fn pick_version(data_bits: u16, level: EcLevel) -> Option<Version> {
|
||||||
for v in 1..=40 {
|
for v in 1..=40 {
|
||||||
let cap_bits = get_data_capacity(Version(v), level) * 8;
|
let cap_bits = get_data_capacity(Version(v), level) * 8;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//! QRGen 基础示例:生成 QR 码并导出为多种格式
|
||||||
|
//!
|
||||||
|
//! 运行: `cargo run --example basic_qr`
|
||||||
|
|
||||||
|
use qr_core::qr::{QrCode, QrConfig};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let text = "https://github.com/LHY0125/QRGen";
|
||||||
|
|
||||||
|
// 使用默认配置(M 级纠错,自动版本,4 模块边距)
|
||||||
|
let qr = QrCode::encode(text, QrConfig::default())?;
|
||||||
|
|
||||||
|
println!("版本: {}", qr.version.0);
|
||||||
|
println!("尺寸: {}×{} 模块", qr.size(), qr.size());
|
||||||
|
println!("掩码: {}", qr.mask);
|
||||||
|
|
||||||
|
// 终端 ASCII 预览
|
||||||
|
println!("\n--- ASCII 预览 ---");
|
||||||
|
println!("{}", qr.to_ascii(false));
|
||||||
|
|
||||||
|
// 导出 PNG
|
||||||
|
qr.to_png_bytes(8)?;
|
||||||
|
println!("\nPNG 生成成功");
|
||||||
|
|
||||||
|
// 导出 SVG
|
||||||
|
let svg = qr.to_svg();
|
||||||
|
println!("SVG 长度: {} 字节", svg.len());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//! QRGen 自定义配置:强制指定版本、模块大小
|
||||||
|
//!
|
||||||
|
//! 运行: `cargo run --example custom_config`
|
||||||
|
|
||||||
|
use qr_core::qr::{QrCode, QrConfig, VersionMode};
|
||||||
|
use qr_core::version::EcLevel;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// 强制使用版本 10(57×57 模块)
|
||||||
|
let config = QrConfig {
|
||||||
|
level: EcLevel::Q,
|
||||||
|
version: VersionMode::Fixed(10),
|
||||||
|
margin: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
let qr = QrCode::encode("固定版本 10 的 QR 码", config)?;
|
||||||
|
assert_eq!(qr.version.0, 10);
|
||||||
|
|
||||||
|
// 导出大尺寸 PNG(每个模块 8 像素)
|
||||||
|
let png = qr.to_png_bytes(8)?;
|
||||||
|
println!("版本 10 QR 码 PNG: {} 字节", png.len());
|
||||||
|
|
||||||
|
// 反转色终端输出(白底黑码 → 黑底白码)
|
||||||
|
println!("\n{}", qr.to_ascii(true));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//! QRGen 高纠错示例:生成可抵抗 30% 损坏的 QR 码
|
||||||
|
//!
|
||||||
|
//! 运行: `cargo run --example high_ecc`
|
||||||
|
|
||||||
|
use qr_core::qr::{QrCode, QrConfig, VersionMode};
|
||||||
|
use qr_core::version::EcLevel;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let config = QrConfig {
|
||||||
|
level: EcLevel::H, // 30% 纠错能力
|
||||||
|
version: VersionMode::Auto,
|
||||||
|
margin: 6, // 更大的静区
|
||||||
|
};
|
||||||
|
|
||||||
|
let qr = QrCode::encode("重要数据 - High ECC", config)?;
|
||||||
|
println!("版本: {}, 纠错: {:?}, 尺寸: {}×{}", qr.version.0, qr.level, qr.size(), qr.size());
|
||||||
|
|
||||||
|
let svg = qr.to_svg();
|
||||||
|
println!("SVG 生成成功: {} 字节", svg.len());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+9
-3
@@ -24,9 +24,15 @@ struct QrParams {
|
|||||||
fmt: String,
|
fmt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_level() -> String { "M".into() }
|
fn default_level() -> String {
|
||||||
fn default_margin() -> u8 { 4 }
|
"M".into()
|
||||||
fn default_size() -> u8 { 8 }
|
}
|
||||||
|
fn default_margin() -> u8 {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
fn default_size() -> u8 {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_level(s: &str) -> Result<EcLevel, String> {
|
fn parse_level(s: &str) -> Result<EcLevel, String> {
|
||||||
match s.to_uppercase().as_str() {
|
match s.to_uppercase().as_str() {
|
||||||
|
|||||||
Reference in New Issue
Block a user