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
|
||||
|
||||
## 0.1.0 (2026-06-18)
|
||||
## 0.1.0 (2026-06-19)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -59,4 +59,6 @@
|
||||
- Web:axum 0.8 + tokio,编译期 HTML 嵌入 (include_str!)
|
||||
- 82 个测试(58 单元 + 24 集成)
|
||||
- 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"
|
||||
edition = "2021"
|
||||
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">
|
||||
<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/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/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/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>
|
||||
|
||||
---
|
||||
@@ -271,6 +272,48 @@ QRGen/
|
||||
| 自动版本选择 | 根据数据长度 + 纠错级别 |
|
||||
| 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
|
||||
|
||||
+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
|
||||
license.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]
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[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};
|
||||
|
||||
/// 版本选择模式
|
||||
///
|
||||
/// 控制 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)]
|
||||
pub enum VersionMode {
|
||||
/// 根据数据量自动选择最小可用版本
|
||||
Auto,
|
||||
/// 强制固定版本(1~40)
|
||||
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)]
|
||||
pub struct QrConfig {
|
||||
/// 纠错级别(L/M/Q/H)
|
||||
pub level: EcLevel,
|
||||
/// 版本选择模式
|
||||
pub version: VersionMode,
|
||||
/// 静区边距(模块数),默认 4
|
||||
pub margin: u8,
|
||||
}
|
||||
|
||||
impl Default for QrConfig {
|
||||
/// 默认配置:M 级纠错 + 自动版本 + 4 模块边距
|
||||
fn default() -> Self {
|
||||
QrConfig {
|
||||
level: EcLevel::M,
|
||||
@@ -37,16 +77,61 @@ impl Default for QrConfig {
|
||||
}
|
||||
|
||||
/// 生成的 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 {
|
||||
/// QR 码版本(1~40)
|
||||
pub version: Version,
|
||||
/// 纠错级别
|
||||
pub level: EcLevel,
|
||||
/// 选中的掩码编号(0~7)
|
||||
pub mask: u8,
|
||||
matrix: Matrix,
|
||||
/// 静区边距(模块数)
|
||||
pub margin: u8,
|
||||
}
|
||||
|
||||
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> {
|
||||
// 1. 分段
|
||||
let segments = segment_text(text);
|
||||
@@ -127,22 +212,54 @@ impl QrCode {
|
||||
})
|
||||
}
|
||||
|
||||
/// 返回 QR 码的布尔矩阵(`true` = 深色模块)
|
||||
///
|
||||
/// 矩阵尺寸为 `size() × size()`,包含功能图案、数据模块和静区。
|
||||
pub fn modules(&self) -> &[Vec<bool>] {
|
||||
&self.matrix.modules
|
||||
}
|
||||
|
||||
/// 返回 QR 码的边长(模块数,含静区)
|
||||
///
|
||||
/// 取值范围:21(版本 1 + 4×2 边距)到 185(版本 40 + 4×2 边距)
|
||||
pub fn size(&self) -> u8 {
|
||||
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 {
|
||||
crate::render::svg::render_svg(self)
|
||||
}
|
||||
|
||||
/// 导出为终端 ASCII 文本
|
||||
///
|
||||
/// 深色模块用 `██`,浅色模块用 ` `(双空格)。
|
||||
/// `invert=true` 时反转颜色(白底黑码 → 黑底白码)。
|
||||
pub fn to_ascii(&self, invert: bool) -> String {
|
||||
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> {
|
||||
crate::render::png::render_png(self, module_size)
|
||||
}
|
||||
|
||||
+73
-8
@@ -1,17 +1,33 @@
|
||||
use serde::Serialize;
|
||||
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)]
|
||||
pub enum EcLevel {
|
||||
L, // 约 7%
|
||||
M, // 约 15%
|
||||
Q, // 约 25%
|
||||
H, // 约 30%
|
||||
/// L 级 — 约 7% 纠错能力
|
||||
L,
|
||||
/// M 级 — 约 15% 纠错能力(默认)
|
||||
M,
|
||||
/// Q 级 — 约 25% 纠错能力
|
||||
Q,
|
||||
/// H 级 — 约 30% 纠错能力
|
||||
H,
|
||||
}
|
||||
|
||||
impl EcLevel {
|
||||
/// 格式信息中使用的指示位
|
||||
/// 格式信息中使用的 2-bit 指示位
|
||||
pub fn indicator_bits(self) -> u8 {
|
||||
match self {
|
||||
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)]
|
||||
pub struct Version(pub u8);
|
||||
|
||||
@@ -104,16 +136,27 @@ impl Version {
|
||||
}
|
||||
}
|
||||
|
||||
/// 纠错分组信息
|
||||
///
|
||||
/// 一组 QR 数据被分为多个块,每个块独立计算 RS 纠错码。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EcInfo {
|
||||
/// 数据码字总数
|
||||
pub total_codewords: u16,
|
||||
/// 每个块的纠错码字数
|
||||
pub ec_per_block: u8,
|
||||
/// 分组信息(1~2 组,不同数据码字数)
|
||||
pub blocks: Vec<BlockInfo>,
|
||||
}
|
||||
|
||||
/// 单个分组信息
|
||||
///
|
||||
/// 所有块的数据码字总数 + 纠错码字总数 = QR 码总容量。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockInfo {
|
||||
/// 该组块数
|
||||
pub count: u16,
|
||||
/// 每个块的数据码字数
|
||||
pub data_codewords: u16,
|
||||
}
|
||||
|
||||
@@ -1290,6 +1333,16 @@ fn init_capacity_table() -> [[u16; 4]; 40] {
|
||||
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 变体枚举
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
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]
|
||||
}
|
||||
|
||||
/// 自动选择最小版本:返回能容纳 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> {
|
||||
for v in 1..=40 {
|
||||
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,
|
||||
}
|
||||
|
||||
fn default_level() -> String { "M".into() }
|
||||
fn default_margin() -> u8 { 4 }
|
||||
fn default_size() -> u8 { 8 }
|
||||
fn default_level() -> String {
|
||||
"M".into()
|
||||
}
|
||||
fn default_margin() -> u8 {
|
||||
4
|
||||
}
|
||||
fn default_size() -> u8 {
|
||||
8
|
||||
}
|
||||
|
||||
fn parse_level(s: &str) -> Result<EcLevel, String> {
|
||||
match s.to_uppercase().as_str() {
|
||||
|
||||
Reference in New Issue
Block a user