可独立使用的软件库
均采用 Apache-2.0 许可证,都在 Publira 中实际使用,使用其中任何一个都无需引入整个平台。
@publira/comic-viewer
Publira 章节阅读界面所用的 React 漫画阅读器,已独立出来,界面和数据管线完全由你掌控。
- 无头 UI 架构:组合彼此独立、支持 tree-shaking 的组件,样式全部由你自己定义。
- 响应式跨页,根据容器宽度在单页和双页之间切换。
- 虚拟化渲染,即使加载大量高分辨率页面,移动浏览器也不会崩溃。
- 可插拔的数据管线:通过 definePlugin() 注入获取、解密或分析处理。
- 日式漫画默认从右向左阅读,欧美漫画可设为从左向右,内置滑动、点击和键盘翻页。
- 通过 resolvePage() 延迟加载页面元数据,适用于长篇文档和会过期的签名 URL。
尚未发布 1.0:在 0.x 系列中,补丁版本会尽量保持兼容,次版本则可能变更公开 API。升级前请阅读变更日志。
阅读文档npm install @publira/comic-viewer
import * as ComicViewer from "@publira/comic-viewer";
import "@publira/comic-viewer/core.css";
export function Reader({ pages }: ReaderProps) {
return (
<ComicViewer.Root
initialReadingDirection="rtl"
pages={pages}
>
<ComicViewer.Viewport />
<ComicViewer.Toolbar />
<ComicViewer.PageNavigation />
</ComicViewer.Root>
);
}
publira/epub
用于解码、编码和校验 EPUB 3 的 Go 库,专为不在磁盘上读写文件的服务器设计。
- 不依赖文件系统:Decode 只需要一个 io.ReaderAt 和归档大小。
- 快速失败:遇到结构错误时立即返回 DecodeError。
- 内存高效,可通过 Asset.Open 按需以流的方式读取。
- 严格校验 EBPAJ 和 KADOKAWA 风格的命名与目录规则。
- 固定版式的图片页面用 SVG 包裹,在任何阅读系统中都能保持页面适配。
- 为封面、目录、正文、索引和术语表等 landmark 提供结构语义。
生成的出版物可以通过仓库附带的脚本,用 W3C 官方的 EPUBCheck 进行检查,该工具会缓存在 .tools/ 下。
阅读文档go get github.com/publira/epub
f, err := os.Open("book.epub")
if err != nil {
log.Fatal(err)
}
defer f.Close()
st, err := f.Stat()
if err != nil {
log.Fatal(err)
}
opt := epub.WithCompliance(epub.LevelEBPAJ)
doc, err := epub.Decode(f, st.Size(), opt)
if err != nil {
log.Fatal(err)
}
if err := epub.Encode(out, doc); err != nil {
log.Fatal(err)
}
publira/messageformat-dart
符合 LDML 48.2 的 Unicode MessageFormat 2.0 纯 Dart 实现,用于格式化移动客户端的消息。
- 纯 Dart 实现,不依赖 Flutter,在服务器、命令行和 Flutter 应用中都能运行。
- 通过 MessageFormat 工作组一致性测试套件 (LDML48.2) 的全部 461 个用例,无一跳过,并在 CI 中随每次变更运行。
- 支持规范中标记为 Stable 的全部函数 (:string, :number, :integer, :offset, :currency, :percent),可按所有 CLDR 区域设置格式化数字。
- 处于 Draft 阶段的日期和时间函数放在 messageformat_datetime 中,不格式化日期的应用无需打包其 CLDR 数据。
- formatToParts 返回 sealed 类层次结构的片段,可用于生成带样式的文本等输出。
- 格式化出错时不会抛出异常,而是将错误报告给回调并使用后备值;除默认函数外,还可以注册自定义函数。
尚未发布 1.0。遵循 LDML 48.2 (UTS #35 Part 9, tr35-78) 和 Unicode CLDR 48.2 数据,升级到更新版本的规范时会作为单独的版本发布。
阅读文档dart pub add messageformat
import 'package:messageformat/messageformat.dart';
final mf = MessageFormat(
'en',
r'''
.input {$count :number}
.match $count
0 {{No episodes}}
one {{{$count} episode}}
* {{{$count} episodes}}
''',
options: const MessageFormatOptions(
bidiIsolation: BidiIsolation.none,
),
);
mf.format({'count': 0});
mf.format({'count': 1});
mf.format({'count': 1000});