@embedpdf/core-annotation

中文文档
3.2.0发布于 2026/11/12Apache-2.0TypeScriptESM + CJS近 30 天下载 6.7万

The pure annotation brain: model · update(msg)→[model,effects] · view. Content-space geometry per family, hit-testing, and tools. No DOM, engine, or framework dependencies.

原始使用文档
文档目录4 节
  1. The contract
  2. Where each message is handled
  3. What a create gesture makes
  4. Tests

@embedpdf/core-annotation

The pure annotation core: gestures, geometry, hit-testing and the scene, as plain functions. No DOM, no engine, no framework, no clock. The annotation plugin (@embedpdf/plugin-annotation) runs it against the engine.

The contract

update(model: Model, message: Message): UpdateResult;

interface UpdateResult {
  session: Session; // selection, hover, the gesture in progress, tool settings
  change: ChangeSet; // { put: records the message changed or created, drop: ids it deleted }
  effects: Effect[]; // the engine work to do: create, patch, flags, text, delete, …
}

type Model = Session & AnnotationView; // AnnotationView = { byId, order }

A message goes in with the model it acts on; the next session, the records it changed and the work to do come out. The core never stores a record: the records in model.byId are what the plugin shows (the engine's records with this session's pending changes on top), and the change set says what this message changed about them. The plugin turns the effects into one change for the engine, which it shows until the engine answers.

Consequences worth knowing:

  • A message that changes no record returns EMPTY_CHANGE, and a pointer move during a drag changes only the session (the gesture's draft), so it costs one comparison.
  • A record the core creates takes the first object number the session holds (Session.objectNumbers): its ref, and so its key, is final from its first frame, and it is marked unconfirmed until the engine wrote it. The plugin hands the session the numbers a message may need before it runs (newRecordsAtMost); the core refuses to make a record it can't name. A replace-text strikeout answers its caret by the caret's number, so both are written in one change. When records leave the view, the plugin sends forget.
  • How a record is drawn after a change is one rule, in src/appearance.ts, for gestures, sidebar edits and code edits alike: a move keeps the raster and moves it, any other visible change draws live, and stamps and form widgets always show their raster (a stamp's at its own proportions, as the engine fits it contain). It asks the engine's own verdict (appearanceChangeOf), so the view keeps an appearance exactly where the engine does. Whether the engine's re-baked raster changed is the engine's answer, never the core's.

Where each message is handled

src/update/ holds one module per kind of work. update/index.ts is the entry and the message switch.

ModuleMessages
edit.tseditPointer down and move: select, move, resize, rotate, caption, leader
edit-commit.tseditPointer up: the gesture's result becomes the records' geometry
marquee.tsmarqueePointer: rubber-band selection
draw.tscreatePointer, finishInkDraft, finishCreationDraft
draw-callout.tsthe free-text callout gesture
draw-distance.tsthe distance measurement gesture
text-markup.tscreateMarkup, createCaret, createReplaceText, the markup preview
selection-edits.tssetFields, setFlags, rotateSelection, resetRotation, delete
text.tssetText, setRichText
session.tsthe initial session, tool defaults, forget
changes.tswhat every record-changing transition shares
page-bound.tskeeping gestures inside the page they started on

The reads (view.ts, hit.ts, scene.ts, geometry.ts, …) take the same Model and never change it.

What a create gesture makes

A create tool answers one question: if the gesture ended now, what would exist? placement.ts answers where: gesturePlacement gives the dragged box or segment once the gesture is a drag, else the tool's click default (resolveClickPlacement: its size, and which part lands on the click). Each shape family answers what (ShapeFamily.placed, through placedShape): a box, a circle, a text box, a line with the tool's endings, a round radio button. The commit (draw.ts), the drawing in progress (view.ts) and the tool's ghost (the plugin's tools/ghost.ts) make these same calls, and unmadeItem paints an annotation not made yet as the made one will paint.

Tests

test/support.ts drives the core the way the plugin does: modelWith(records) builds a model, and step(model, message) applies a message's result (its session, and its change set on the records).

版本与发布记录

@embedpdf/core-annotation 共发布 27 个版本,首次发布于 2026/08/06;过去 12 个月发布了 27 个版本。

版本发布日期距今
3.2.0latest2026/11/121 天前
3.1.02026/11/094 天前
3.0.02026/11/076 天前
3.0.0-next.23next2026/11/076 天前
3.0.0-next.222026/11/049 天前
3.0.0-next.212026/11/0310 天前

关于 @embedpdf/core-annotation 的常见问题

@embedpdf/core-annotation 是什么?

The pure annotation brain: model · update(msg)→[model,effects] · view. Content-space geometry per family, hit-testing, and tools. No DOM, engine, or framework dependencies.

如何安装 @embedpdf/core-annotation?

在项目目录运行 npm install @embedpdf/core-annotation,或使用 pnpm add / yarn add / bun add。

@embedpdf/core-annotation 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@embedpdf/core-annotation 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@embedpdf/core-annotation 可以商用吗?

@embedpdf/core-annotation 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@embedpdf/core-annotation 的下载量有多少?

根据 npm 官方下载统计,@embedpdf/core-annotation 在 2026/10/14 – 2026/11/12 这 30 天内被下载 66,898 次,日均约 2,230 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@embedpdf/core-annotation 还在维护吗?

最新版本 3.2.0 发布于 2026/11/12(1 天前);至今共发布 27 个版本,其中过去 12 个月发布了 27 个;首次发布于 2026/08/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。