Skip to content

Repository files navigation

TextMark

📜 TextMark

给任意文字加隐形水印 —— 纯浏览器本地运行,无服务器、无构建、零网络请求

License Tests Static Dependencies Spec

🚀 在线试用 · 📖 公开规范 · 🚢 部署指南 · 🧪 测试报告

简体中文 · English


✨ 这是什么

TextMark 是一个事后文字水印方案:对任意已有文本加水印,无需模型参与、无需改动一个可见字符。水印分两层,可单独使用也可叠加:

载体 特性
隐形层 Unicode 变体选择符(零宽,肉眼不可见) Reed-Solomon 纠错(每块纠 16 字节)· 可选 deflate 压缩 · 可选 AES-GCM-256 加密 · CRC32 完整性校验
AI 可读层 📜 开头的 emoji 序列(每个 emoji = 1 字节) 刻意保持简单:无压缩、无加密、无纠错,任何 AI 仅凭公开规范即可手工解码

Note

三条核心保证

  1. 带水印文本的可见内容与原文逐字一致
  2. 所有计算在你的浏览器里完成,文本不会离开本机
  3. 编码规范完全公开,没有秘密算法。

🎯 功能特性

能力 说明
🫥 隐形水印 变体选择符编码,肉眼完全不可见,复制粘贴后依然存活
🧮 Reed-Solomon 纠错 GF(2⁸),每块 223 + 32 字节,可纠正 16 字节随机错误
🔐 可选加密 AES-GCM-256,密钥由 PBKDF2-SHA256(10 万次迭代)派生
🗜️ 可选压缩 长消息自动 deflate,帧更短、隐形字符更少
🧩 分段嵌入 每个段落末尾各放一份完整副本(最多 20 份),任一段落被单独复制也能解码
🤖 AI 可读 emoji 层 可选(默认关闭):把文本 + 公开提示词发给任意够强的 AI,它就能读出水印
🧼 一键清除 移除两层水印并逐字还原原文;❤️ 等真实变体选择符不会被误删,加密水印无需密码也能清除
🖥️ 纯本地运行 无服务器、无构建、无依赖、零网络请求,file:// 协议下全功能可用
🎨 手作暖色界面 赤茶配色、衬线大标题配无衬线正文、手绘虚线边框、大字引用、全圆角纸感底纹;字体全部走系统字体栈
📱 响应式 + 深色模式 桌面标签导航、手机悬浮底部标签栏、安全区适配、≥44px 触控目标、可安装为 PWA
无障碍 完整 ARIA tablist、方向键导航、aria-live 提示、尊重 prefers-reduced-motion
🌐 中英双语 界面一键切换

🚀 快速开始

本地直接用

下载仓库,双击 index.html。完事 —— 包括加密在内的所有功能在 file:// 下都可用。

自托管 / GitHub Pages

把 6 个站点文件放到任意静态托管即可(GitHub Pages / Vercel / Netlify / Cloudflare Pages):

index.html  app.js  codec.js  style.css  icon.svg  manifest.webmanifest
git clone https://github.com/88lin/textmark.git

Tip

部署后把规范页 URL 公开分享出去 —— 方案越公开,第三方 AI 解码越可靠。详细步骤见 DEPLOY.md


📝 怎么用

加水印

  1. 粘贴原文(也可「打开文件」或把 .txt / .md 直接拖进页面);
  2. 填写要藏进文本里的水印消息,例如 © 2026 张三 · 文档编号 A-17
  3. 可选:设密码(即加密)、选插入方式(文末追加 / 分段嵌入)、勾选 emoji 签名条(默认关闭);
  4. 点「生成带水印文本」或按 ⌘/Ctrl + Enter —— 结果会自动自检一次解码,再复制、下载,或一键跳到检测页验证。

检测解码

粘贴任意文本,页面自动扫描两层水印并解码;同一条消息的多份副本会合并显示份数,加密水印会提示输入密码。

清除水印

粘贴文本 → 点「清除水印」,两层水印被移除、其余字符逐字保留。加密水印无需密码即可清除。

让任意 AI 解码

把带水印文本 + SPEC.md 里的「AI 解码提示词」一起发给任意够强的 AI,它就能读出 emoji 签名条里的消息 —— 无需运行任何代码

Important

AI 可读层建议只用于 ≤140 字节的短消息:更长的 emoji 序列会让 AI 手工解码容易出错。长消息请依赖隐形层 + 本站检测。


🔍 怎么工作的

隐形层帧结构

MAGIC "TM" (2B) | VERSION (1B) | FLAGS (1B)
| [SALT(16B) + IV(12B) — 仅加密时]
| LEN (4B, 大端) | PAYLOAD | CRC32 (4B, IEEE)

整帧经 Reed-Solomon 保护:GF(2⁸),本原多项式 0x11D,每块 223 数据 + 32 校验字节,可纠正 16 字节错误。每个字节编码为一个变体选择符:b < 16U+FE00+b,否则 → U+E0100+(b−16)

AI 可读层帧结构

EMAGIC "Te" (2B) | LEN (2B, 大端) | 消息 UTF-8 字节 | SUM8 (1B)

每个 emoji = 1 字节,字节值 = emoji 在 256 表中的序号。表规则无需查表即可换算:

字节范围 码位
b < 80 U+1F600 + b
80 ≤ b < 208 U+1F300 + (b − 80)
b ≥ 208 U+1F380 + (b − 208)

完整规范(含检测策略、插入策略、清除语义与 AI 提示词)见 SPEC.md


📦 作为库使用

codec.js 是零依赖 UMD 模块:浏览器里挂在 window.TextMarkCodec,Node 里直接 require()

const TM = require('./codec.js');   // 浏览器:const TM = window.TextMarkCodec;

// 加水印
const { text, stats } = await TM.encode(cover, '© 2026 张三', {
  password: '',        // 非空则 AES-GCM-256 加密(并自动关闭 emoji 层)
  mode: 'end',         // 'end' 文末追加 | 'paragraphs' 每段末尾各一份
  emojiLayer: true     // 是否附加 AI 可读 emoji 签名条
});
// stats: { frameBytes, invisibleCharsPerCopy, invisibleCharsTotal, replicas,
//          emojiChars, encrypted, compressed, emojiLayer }

// 检测解码
const { found, results, errors, needsPassword } = await TM.decode(text, { password: '' });
// results: [{ layer: 'invisible' | 'emoji', message, copies, encrypted?, compressed? }]

// 清除水印(同步)
const { text: clean, removed } = TM.strip(text);
// removed: { invisible, invisibleChars, emoji, emojiChars }

Note

decode() 永不静默出错:损坏的副本进 errors,加密但没给密码时 needsPassword === true


🗂️ 项目结构

textmark/
├── index.html              # 页面(4 个标签页 + 完整 ARIA)
├── app.js                  # UI 逻辑、i18n、主题、标签路由
├── codec.js                # 编解码核心(UMD,浏览器 + Node 通用)
├── style.css               # 设计变量、深色模式、响应式
├── icon.svg                # 图标(favicon / apple-touch-icon / PWA)
├── manifest.webmanifest    # PWA 清单
├── SPEC.md                 # 公开规范
├── DEPLOY.md               # 部署指南
├── TEST_REPORT.md          # 测试报告
├── README.md               # 本文件(中文)
├── README.en.md            # English README
├── LICENSE                 # PolyForm Noncommercial 1.0.0
└── tests/
    ├── codec.test.js       # 单元测试(Node,33 用例)
    ├── e2e_browser.py      # 浏览器端到端(Playwright,含移动端)
    └── shots/              # 截图

零依赖:不引用任何 npm / CDN 包,加密使用浏览器原生 SubtleCrypto,单元测试只用 Node 内置 node:assert


🧪 测试

node tests/codec.test.js        # 单元测试(零依赖)
python tests/e2e_browser.py     # 浏览器端到端(需 playwright)
33 passed, 0 failed

单元测试覆盖:emoji 表完整性 · CRC32 标准向量 · RS 纠错(1/4/8/16 字节错误 + 20 字节明确报错)· 帧/加密/压缩往返 · VS16 冲突 · 抗 3% 随机损坏 · 分段嵌入(每段都有副本 / 单段幸存 / 20 份上限)· 段内相邻多帧 · emoji 层校验 · 清除后逐字还原 · 10KB 端到端。

端到端另外验证:加水印后自检解码、分段副本数、双层检测、阴性对照、清除后与原文全等、256 格 emoji 表与过滤、深浅色与中英切换,以及 390×844 手机视口下的悬浮底栏、无横向溢出、≥40px 触控目标。

完整报告见 TEST_REPORT.md


🛡️ 鲁棒性

场景 隐形层 emoji 层
浏览器 / 编辑器之间复制粘贴 ✅ 存活 ✅ 存活
Markdown 渲染后复制 ✅ 预期存活 ✅ 存活
微信 / 备忘录 / Google Docs 复制 ✅ 预期存活 ✅ 预期存活
短信 / 激进的清洗管道 ⚠️ 可能被剥离 ✅ 存活
轻度编辑(改几个字) ✅ 分段模式下幸存 ⚠️ 视编辑位置
AI 改写 / 翻译 / 重排 ❌ 破坏 ❌ 破坏

⚠️ 已知限制

  1. RS 纠错覆盖随机字节错误;删除或插入隐形字符会让该副本不可解码(响亮报错,绝不错误解码)—— 分段嵌入可缓解;
  2. emoji 层无加密、无纠错(为方便 AI 手工解码刻意简化),仅有 SUM8 完整性校验;
  3. 加密与「任何 AI 可读」互斥:设置密码时 emoji 层自动关闭。

Caution

规范公开的必然代价:任何人都能移除水印(本站「清除水印」页就是干这个的)。TextMark 解决的是溯源与泄露追踪,不是防篡改,也不是 DRM。


📄 许可证

本项目采用 PolyForm Noncommercial License 1.0.0仅授权非商业用途

  • ✅ 允许:个人学习研究、实验测试、业余项目,以及慈善机构、学校、公共研究机构、政府部门等非营利组织的使用;
  • ✅ 允许:在保留版权与许可声明(Required Notice)的前提下修改、二次开发与再分发;
  • ❌ 不允许:任何商业用途 —— 包括集成进商业产品或付费服务、公司内部生产环境使用、以本项目为基础提供收费服务。

Warning

需要商业授权请通过 Issue 联系作者另行取得书面许可。

历史说明:v1 的早期提交曾以 MIT 发布,已按 MIT 取得的那些副本继续受 MIT 约束;自本许可证生效后的版本按 PolyForm Noncommercial 授权。

Copyright © 2026 88lin

About

📜 给任意文字加隐形水印:Unicode 变体选择符隐形层(Reed-Solomon 纠错 + 可选 AES-GCM-256)+ AI 可读 emoji 签名条。纯浏览器本地运行,无服务器 / 无构建 / 零网络请求,编码规范完全公开。Post-hoc invisible text watermarking — 100% client-side, zero network requests, fully public spec.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages