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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user