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:
2026-06-19 18:56:28 +08:00
parent cbcd4e5123
commit ce8063431e
17 changed files with 640 additions and 18 deletions
+20
View File
@@ -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
View File
@@ -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
View File
@@ -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;