From a06f28355dc2b4e080912d0dc6b1f155814a1576 Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 19 Oct 2026 00:04:16 +0800 Subject: [PATCH] fix(markdown): align heading slugs with GitHub anchors --- Cargo.lock | 1 + Cargo.toml | 1 + src/render/markdown/anchors.rs | 11 ++++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6bb48e2a..48782c38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,7 @@ dependencies = [ "pdf-inspector", "quick-xml", "sha2 0.11.0", + "unicode-properties", "zip", ] diff --git a/Cargo.toml b/Cargo.toml index 2a334b72..cedf7af9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ log = "0.4" pdf-inspector = "1.14.2" quick-xml = "0.41.0" zip = { version = "8.6.0", default-features = false, features = ["deflate"] } +unicode-properties = "0.1.4" [profile.release] lto = "thin" diff --git a/src/render/markdown/anchors.rs b/src/render/markdown/anchors.rs index d56a21cb..1517d365 100644 --- a/src/render/markdown/anchors.rs +++ b/src/render/markdown/anchors.rs @@ -9,6 +9,7 @@ use crate::model::{Block, Document, Inline, LinkTarget, inlines_to_plain_text}; use std::collections::{HashMap, HashSet}; +use unicode_properties::{GeneralCategory, UnicodeGeneralCategory}; pub(crate) struct AnchorMap { resolved: HashMap, @@ -185,7 +186,10 @@ fn gfm_slug(text: &str) -> String { .filter_map(|c| match c { ' ' => Some('-'), '-' => Some(c), - c if c.is_alphanumeric() || is_combining_mark(c) || is_connector_punctuation(c) => { + c if (c.is_alphanumeric() && c.general_category() != GeneralCategory::OtherNumber) + || is_combining_mark(c) + || is_connector_punctuation(c) => + { Some(c) } _ => None, @@ -208,7 +212,9 @@ fn is_combining_mark(c: char) -> bool { | 0x1AB0..=0x1AFF | 0x1DC0..=0x1DFF | 0x20D0..=0x20FF + | 0xFE00..=0xFE0F | 0xFE20..=0xFE2F + | 0xE0100..=0xE01EF ) } @@ -253,6 +259,9 @@ mod tests { assert_eq!(gfm_slug("combining n\u{0303} tilde"), "combining-n\u{0303}-tilde"); assert_eq!(gfm_slug("देवनागरी क्षि"), "देवनागरी-क्षि"); assert_eq!(gfm_slug("日本語の見出し"), "日本語の見出し"); + assert_eq!(gfm_slug("❤️ Love"), "️-love"); + assert_eq!(gfm_slug("2️⃣ Second"), "2️⃣-second"); + assert_eq!(gfm_slug("x² formula ½"), "x-formula-"); } #[test]