@sobree/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/__vite-browser-external-DYxpcVy9.js +5 -0
  4. package/dist/__vite-browser-external-DYxpcVy9.js.map +1 -0
  5. package/dist/blob/cache.d.ts +69 -0
  6. package/dist/blob/fetch.d.ts +18 -0
  7. package/dist/blob/hash.d.ts +13 -0
  8. package/dist/blob/index.d.ts +33 -0
  9. package/dist/blob/memory.d.ts +2 -0
  10. package/dist/blob/types.d.ts +80 -0
  11. package/dist/createSobree.d.ts +132 -0
  12. package/dist/doc/api.d.ts +132 -0
  13. package/dist/doc/builders.d.ts +42 -0
  14. package/dist/doc/pageSetupBridge.d.ts +26 -0
  15. package/dist/doc/parts.d.ts +18 -0
  16. package/dist/doc/runs.d.ts +47 -0
  17. package/dist/doc/styles.d.ts +19 -0
  18. package/dist/doc/types.d.ts +800 -0
  19. package/dist/doc/walk.d.ts +30 -0
  20. package/dist/docx/export/contentTypes.d.ts +35 -0
  21. package/dist/docx/export/context.d.ts +59 -0
  22. package/dist/docx/export/document.d.ts +19 -0
  23. package/dist/docx/export/drawings.d.ts +10 -0
  24. package/dist/docx/export/headers.d.ts +19 -0
  25. package/dist/docx/export/index.d.ts +14 -0
  26. package/dist/docx/export/runs.d.ts +8 -0
  27. package/dist/docx/export/styles.d.ts +8 -0
  28. package/dist/docx/export/zip.d.ts +13 -0
  29. package/dist/docx/import/anchoredFrames.d.ts +34 -0
  30. package/dist/docx/import/comments.d.ts +3 -0
  31. package/dist/docx/import/document.d.ts +57 -0
  32. package/dist/docx/import/flowFrames.d.ts +11 -0
  33. package/dist/docx/import/footnotes.d.ts +3 -0
  34. package/dist/docx/import/headers.d.ts +50 -0
  35. package/dist/docx/import/index.d.ts +12 -0
  36. package/dist/docx/import/inlineFrames.d.ts +62 -0
  37. package/dist/docx/import/numbering.d.ts +2 -0
  38. package/dist/docx/import/paragraph.d.ts +24 -0
  39. package/dist/docx/import/paragraphs.d.ts +27 -0
  40. package/dist/docx/import/rels.d.ts +5 -0
  41. package/dist/docx/import/runs.d.ts +64 -0
  42. package/dist/docx/import/settings.d.ts +48 -0
  43. package/dist/docx/import/styles.d.ts +3 -0
  44. package/dist/docx/import/tables.d.ts +12 -0
  45. package/dist/docx/import/unzip.d.ts +13 -0
  46. package/dist/docx/shared/namespaces.d.ts +31 -0
  47. package/dist/docx/shared/pageSize.d.ts +27 -0
  48. package/dist/docx/shared/shading.d.ts +2 -0
  49. package/dist/docx/shared/units.d.ts +35 -0
  50. package/dist/docx/shared/xml.d.ts +29 -0
  51. package/dist/docx/types.d.ts +98 -0
  52. package/dist/editor/index.d.ts +1078 -0
  53. package/dist/editor/internal/blockRegistry.d.ts +91 -0
  54. package/dist/editor/internal/mutations.d.ts +63 -0
  55. package/dist/editor/internal/positionMap.d.ts +35 -0
  56. package/dist/editor/table.d.ts +96 -0
  57. package/dist/editor/view/docRenderer/anchorLayer.d.ts +26 -0
  58. package/dist/editor/view/docRenderer/block.d.ts +13 -0
  59. package/dist/editor/view/docRenderer/fontFallback.d.ts +28 -0
  60. package/dist/editor/view/docRenderer/index.d.ts +18 -0
  61. package/dist/editor/view/docRenderer/inline.d.ts +15 -0
  62. package/dist/editor/view/docRenderer/inlineFrame.d.ts +4 -0
  63. package/dist/editor/view/docRenderer/lists.d.ts +28 -0
  64. package/dist/editor/view/docRenderer/paragraph.d.ts +2 -0
  65. package/dist/editor/view/docRenderer/properties.d.ts +2 -0
  66. package/dist/editor/view/docRenderer/table.d.ts +15 -0
  67. package/dist/editor/view/docRenderer/units.d.ts +48 -0
  68. package/dist/editor/view/docSerialize/block.d.ts +14 -0
  69. package/dist/editor/view/docSerialize/index.d.ts +8 -0
  70. package/dist/editor/view/docSerialize/inline.d.ts +11 -0
  71. package/dist/editor/view/docSerialize/table.d.ts +12 -0
  72. package/dist/editor/view/imageResize.d.ts +16 -0
  73. package/dist/embed/floatingCorner.d.ts +44 -0
  74. package/dist/embed/viewport.d.ts +133 -0
  75. package/dist/fonts/embedAPI.d.ts +33 -0
  76. package/dist/fonts/emit.d.ts +24 -0
  77. package/dist/fonts/fontFaceRegistry.d.ts +20 -0
  78. package/dist/fonts/fsType.d.ts +36 -0
  79. package/dist/fonts/index.d.ts +19 -0
  80. package/dist/fonts/liveness.d.ts +2 -0
  81. package/dist/fonts/odttf.d.ts +33 -0
  82. package/dist/fonts/parse.d.ts +29 -0
  83. package/dist/fonts/types.d.ts +52 -0
  84. package/dist/headless.d.ts +168 -0
  85. package/dist/history/history.d.ts +100 -0
  86. package/dist/history/index.d.ts +4 -0
  87. package/dist/history/types.d.ts +54 -0
  88. package/dist/index.css +1 -0
  89. package/dist/index.d.ts +52 -0
  90. package/dist/index.js +10561 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/markdown/parse.d.ts +6 -0
  93. package/dist/pagination/cost.d.ts +32 -0
  94. package/dist/pagination/index.d.ts +2 -0
  95. package/dist/pagination/paginate.d.ts +10 -0
  96. package/dist/pagination/postConditions.d.ts +10 -0
  97. package/dist/pagination/types.d.ts +94 -0
  98. package/dist/paperStack/pageSetup.d.ts +42 -0
  99. package/dist/paperStack/paginationAdapter/buildItems.d.ts +19 -0
  100. package/dist/paperStack/paginationAdapter/distribute.d.ts +23 -0
  101. package/dist/paperStack/paginationAdapter/index.d.ts +18 -0
  102. package/dist/paperStack/paginationAdapter/paragraphLines.d.ts +23 -0
  103. package/dist/paperStack/paginationAdapter/splitList.d.ts +19 -0
  104. package/dist/paperStack/paginationAdapter/splitParagraph.d.ts +21 -0
  105. package/dist/paperStack/paginationAdapter/types.d.ts +30 -0
  106. package/dist/paperStack/paper.d.ts +107 -0
  107. package/dist/paperStack/paperStack.d.ts +245 -0
  108. package/dist/plugin.d.ts +24 -0
  109. package/dist/plugins/marks.d.ts +49 -0
  110. package/dist/plugins/sections.d.ts +15 -0
  111. package/dist/presence/attach.d.ts +48 -0
  112. package/dist/presence/awareness.d.ts +28 -0
  113. package/dist/presence/index.d.ts +19 -0
  114. package/dist/presence/overlay.d.ts +28 -0
  115. package/dist/presence/state.d.ts +36 -0
  116. package/dist/sobree.d.ts +211 -0
  117. package/dist/tokens.css +144 -0
  118. package/dist/util/selection.d.ts +13 -0
  119. package/dist/ydoc/apply.d.ts +68 -0
  120. package/dist/ydoc/index.d.ts +18 -0
  121. package/dist/ydoc/project.d.ts +41 -0
  122. package/dist/ydoc/runs.d.ts +51 -0
  123. package/dist/ydoc/schema.d.ts +123 -0
  124. package/dist/ydoc/seed.d.ts +45 -0
  125. package/dist/ydoc/textDiff.d.ts +59 -0
  126. package/dist/zoneEdit/index.d.ts +22 -0
  127. package/package.json +61 -0
@@ -0,0 +1,133 @@
1
+ export interface ViewportOptions {
2
+ minScale?: number;
3
+ maxScale?: number;
4
+ /** Scale change per unit of wheel deltaY for shift+wheel. Default 0.005. */
5
+ wheelZoomSensitivity?: number;
6
+ /** Scale change per unit of wheel deltaY for pinch (ctrlKey). Default 0.02. */
7
+ pinchZoomSensitivity?: number;
8
+ onScaleChange?: (scale: number) => void;
9
+ /**
10
+ * Fires when the render tier changes — an integer ≥1 chosen from the
11
+ * current scale. The stage is laid out at that tier via CSS `zoom` so
12
+ * text rasterises at the zoomed size rather than being blitted from a
13
+ * 1× bitmap. Callers that care about layout (e.g. a paginator) should
14
+ * re-run their measurement + layout pass when the tier changes.
15
+ */
16
+ onRenderTierChange?: (tier: number) => void;
17
+ /**
18
+ * Fires whenever the stage transform changes — zoom, pan, programmatic
19
+ * fit, or animated pan. Used by overlays (block toolbar, indicator)
20
+ * that live in viewport coordinates and must follow the page through
21
+ * any transform. Called frequently during gestures, so the handler
22
+ * should be cheap (read-rect-and-write-style cheap).
23
+ */
24
+ onTransformChange?: () => void;
25
+ }
26
+ /**
27
+ * A framework-free zoomable / pannable viewport.
28
+ *
29
+ * Layout:
30
+ * container (overflow:hidden, the element passed in)
31
+ * └ stage (absolutely positioned, transform: translate(tx,ty) scale(s))
32
+ * └ slot (where the embedded content lives — caller mounts here)
33
+ *
34
+ * Gestures:
35
+ * - Zoom: wheel with shiftKey OR ctrlKey (macOS pinch emits ctrlKey).
36
+ * The point under the cursor stays under the cursor (zoom-to-cursor).
37
+ * - Pan: wheel without modifiers — two-finger trackpad scroll deltas move
38
+ * the stage. Also supports click-drag with middle mouse or space.
39
+ */
40
+ export declare class Viewport {
41
+ readonly container: HTMLElement;
42
+ readonly slot: HTMLElement;
43
+ private readonly stage;
44
+ private scale;
45
+ private tx;
46
+ private ty;
47
+ private readonly minScale;
48
+ private readonly maxScale;
49
+ private readonly wheelZoomSensitivity;
50
+ private readonly pinchZoomSensitivity;
51
+ private readonly onScaleChange;
52
+ private readonly onRenderTierChange;
53
+ private readonly onTransformChange;
54
+ private readonly onWheel;
55
+ /** Current layout-side zoom factor (integer ≥ 1). See ViewportOptions. */
56
+ private renderTier;
57
+ /** Suppresses `onTransformChange` during the constructor's initial
58
+ * `applyTransform` so consumers can capture `viewport` in their
59
+ * callback without TDZ traps. Flipped true at the end of the ctor. */
60
+ private constructed;
61
+ /** Timestamp of the last wheel event, used to delimit gestures. */
62
+ private gestureLastTime;
63
+ /** Dominant axis for the current gesture. Null until detected, cleared at gesture end. */
64
+ private gesturePrimaryAxis;
65
+ /**
66
+ * Signed cumulative dx within the current gesture. Drives lock release:
67
+ * wobble (±3-5px back-and-forth) cancels out; sustained one-way motion
68
+ * accumulates past the threshold quickly. Reset at gesture end.
69
+ */
70
+ private gestureSignedDx;
71
+ /**
72
+ * Sticky horizontal-lock flag. Engaged by `fitTo` so alignment survives
73
+ * gentle diagonal trackpad gestures; broken when the gesture's signed
74
+ * cumulative dx crosses `X_RELEASE_THRESHOLD` — the user clearly intends
75
+ * sustained horizontal motion.
76
+ */
77
+ private horizontalLock;
78
+ constructor(container: HTMLElement, options?: ViewportOptions);
79
+ /** Reset pan to origin and scale to 1. */
80
+ reset(): void;
81
+ getScale(): number;
82
+ /** Integer layout-side zoom currently applied via CSS `zoom` on the stage. */
83
+ getRenderTier(): number;
84
+ /**
85
+ * Fit `target` to the viewport.
86
+ * - `"width"`: scale so the target fills the viewport horizontally. The
87
+ * vertical centre of the current view is preserved (no jump to the top
88
+ * of the target).
89
+ * - `"contain"`: scale so the entire target is visible, centred in both
90
+ * axes.
91
+ * With `animate = true`, the transition runs with a CSS ease curve.
92
+ */
93
+ fitTo(target: HTMLElement, mode: "width" | "contain", animate?: boolean): void;
94
+ /**
95
+ * Pan the stage by `(dx, dy)` CSS pixels. Optional `animate` runs the
96
+ * same cubic-ease transition fit-to-page uses; unflagged pans apply
97
+ * instantly.
98
+ */
99
+ panBy(dx: number, dy: number, opts?: {
100
+ animate?: boolean;
101
+ }): void;
102
+ /** Zoom to `nextScale`, anchoring the point at (clientX, clientY) in container space. */
103
+ zoomTo(nextScale: number, clientX: number, clientY: number): void;
104
+ destroy(): void;
105
+ private handleWheel;
106
+ /**
107
+ * Axis-lock for pan gestures:
108
+ * - Within a gesture (events ≤ GESTURE_GAP_MS apart), a clear dominant
109
+ * axis zeros the other axis so a nearly-vertical swipe doesn't also
110
+ * drift the paper sideways.
111
+ * - An explicit `horizontalLock` set by `fitTo` survives across gestures,
112
+ * keeping fit-page / fit-width alignment stable.
113
+ * - Both locks release when the gesture's signed cumulative dx crosses
114
+ * `X_RELEASE_THRESHOLD`. Signed sum cancels wobble (back-and-forth
115
+ * averages to zero) while sustained one-way motion accumulates fast.
116
+ */
117
+ private applyScrollAxisLock;
118
+ private applyTransform;
119
+ /**
120
+ * Apply the current transform with a CSS transition. Used only for
121
+ * programmatic fits — wheel pan/zoom must stay instant or feel sluggish.
122
+ *
123
+ * If the target scale falls into a different render tier, we DON'T flip
124
+ * `stage.style.zoom` mid-flight: that property can't transition, so the
125
+ * layout would snap instantly while the `transform` keeps easing — the
126
+ * visible "weird reset" before the animation. Instead, we keep the
127
+ * current tier through the transition, write the transform expressed in
128
+ * THAT tier's coordinate space (visually identical to the same transform
129
+ * in any other tier — tier is just a layout-quality choice), and snap to
130
+ * the target tier on `transitionend`.
131
+ */
132
+ private applyTransformAnimated;
133
+ }
@@ -0,0 +1,33 @@
1
+ import { SobreeDocument } from '../doc/types';
2
+ export interface EmbedFontFaces {
3
+ regular?: Uint8Array;
4
+ bold?: Uint8Array;
5
+ italic?: Uint8Array;
6
+ boldItalic?: Uint8Array;
7
+ }
8
+ export interface EmbedFontOptions {
9
+ /** Embed even when OS/2 fsType marks the face as restricted. Default false. */
10
+ allowRestricted?: boolean;
11
+ }
12
+ export interface EmbedFontResult {
13
+ /** Next document — same reference as input when nothing was embedded. */
14
+ next: SobreeDocument;
15
+ /** Per-face refusal warnings (e.g. restricted licence). */
16
+ warnings: string[];
17
+ }
18
+ /**
19
+ * Returns the next document with the given font embedded, plus any
20
+ * warnings (e.g. a face refused for licence reasons). When no face
21
+ * could be embedded, `next` is `===` the input doc — caller can
22
+ * skip a setDocument round.
23
+ */
24
+ export declare function embedFontIntoDoc(doc: SobreeDocument, name: string, faces: EmbedFontFaces, opts?: EmbedFontOptions): EmbedFontResult;
25
+ /**
26
+ * Drop a font declaration by name. Returns the same doc reference if
27
+ * the name wasn't present (caller can skip a setDocument round).
28
+ *
29
+ * Font part bytes are NOT removed from `rawParts` — call
30
+ * `pruneOrphanParts(doc)` (or just rely on export-side filtering) to
31
+ * GC them.
32
+ */
33
+ export declare function removeFontFromDoc(doc: SobreeDocument, name: string): SobreeDocument;
@@ -0,0 +1,24 @@
1
+ import { ExportContext } from '../docx/export/context';
2
+ import { SobreeDocument } from '../doc/types';
3
+ interface FontTableEmission {
4
+ /** Inner XML of `word/fontTable.xml`. */
5
+ fontTableXml: string;
6
+ /** Inner XML of `word/_rels/fontTable.xml.rels`. */
7
+ fontTableRelsXml: string;
8
+ }
9
+ /**
10
+ * Stage every font-related artifact into the export context: the
11
+ * fontTable XML + companion rels, the document-level relationship,
12
+ * the content-type override, and the `.odttf` extension flag. Caller
13
+ * (docx/export/index.ts) just calls this once and forgets — no
14
+ * font-aware code needs to live in the export entry point any more.
15
+ *
16
+ * No-op when `doc.fonts` is empty.
17
+ */
18
+ export declare function mountFontTableArtifacts(doc: SobreeDocument, ctx: ExportContext): void;
19
+ /**
20
+ * Render the font table and its companion .rels. Returns null if the
21
+ * document declares no fonts (caller skips emitting the parts).
22
+ */
23
+ export declare function emitFontTable(doc: SobreeDocument): FontTableEmission | null;
24
+ export {};
@@ -0,0 +1,20 @@
1
+ import { FontDeclaration } from './types';
2
+ export declare class FontFaceRegistry {
3
+ private readonly styleEl;
4
+ /** Mostly for cleanup — every blob URL we minted needs revoking. */
5
+ private blobUrls;
6
+ /** Last-applied snapshot — skip re-registration when nothing changed. */
7
+ private lastSerialised;
8
+ constructor();
9
+ /**
10
+ * Sync the registry to the current document's fonts. Idempotent —
11
+ * if the font list hasn't changed since the last call, no work
12
+ * happens (no blob revocation, no DOM churn).
13
+ *
14
+ * Bails out (no-op) when the runtime doesn't expose
15
+ * `URL.createObjectURL` — typically jsdom in tests, where the
16
+ * registry isn't observable anyway.
17
+ */
18
+ sync(fonts: readonly FontDeclaration[], rawParts: Record<string, Uint8Array>): void;
19
+ destroy(): void;
20
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Read the OS/2 table's `fsType` field from a TrueType / OpenType font.
3
+ * The field encodes the font foundry's embedding licence — applications
4
+ * that embed must check it. See OpenType spec, OS/2 table § "fsType":
5
+ * https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fst
6
+ *
7
+ * Bits (mask with 0x000F for the licence triplet — they're mutually
8
+ * exclusive in practice):
9
+ * 0x0000 Installable embedding allowed. ← happy path.
10
+ * 0x0002 Restricted licence — must NOT be embedded.
11
+ * 0x0004 Preview & Print — embed for view/print only, no editing.
12
+ * 0x0008 Editable — embed and allow modifications.
13
+ * 0x0100 No subsetting — must embed the full face.
14
+ * 0x0200 Bitmap embedding only.
15
+ *
16
+ * `readFsType` returns `null` on parse failure (corrupt header, missing
17
+ * OS/2 table, truncated input). Callers treat `null` as "unknown
18
+ * licence — don't embed unless explicitly forced."
19
+ */
20
+ export type EmbedMode = "installable" | "preview" | "editable" | "restricted";
21
+ export interface FsTypeReport {
22
+ allowed: boolean;
23
+ mode: EmbedMode;
24
+ noSubset: boolean;
25
+ bitmapOnly: boolean;
26
+ /** Raw fsType field, for callers that want the bits. */
27
+ raw: number;
28
+ }
29
+ /** Walk the table directory and return `OS/2.fsType`, or null on failure. */
30
+ export declare function readFsType(font: Uint8Array): number | null;
31
+ /**
32
+ * Interpret an `fsType` value into a structured embedding decision.
33
+ * Defensive on `null` input (corrupt font) — reports as restricted so
34
+ * callers fail closed.
35
+ */
36
+ export declare function canEmbed(fsType: number | null): FsTypeReport;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@sobree/core/fonts` — internal module aggregating everything
3
+ * font-related: AST shape, OOXML import + export, ODTTF codec,
4
+ * licensing check, in-memory `@font-face` registration.
5
+ *
6
+ * Consumers import from this `index.ts`, never reach into individual
7
+ * files. The module is internal to `@sobree/core`; the public surface
8
+ * is re-exported through `packages/core/src/index.ts`.
9
+ */
10
+ export type { FontDeclaration, FontEmbedRef } from './types';
11
+ export { deobfuscate, generateFontKey, isUnobfuscated, obfuscate, } from './odttf';
12
+ export { canEmbed, readFsType } from './fsType';
13
+ export type { EmbedMode, FsTypeReport } from './fsType';
14
+ export { mountFontTableFromZip, parseFontTable } from './parse';
15
+ export { emitFontTable, mountFontTableArtifacts } from './emit';
16
+ export { embedFontIntoDoc, removeFontFromDoc, } from './embedAPI';
17
+ export type { EmbedFontFaces, EmbedFontOptions, EmbedFontResult, } from './embedAPI';
18
+ export { fontLivenessPaths } from './liveness';
19
+ export { FontFaceRegistry } from './fontFaceRegistry';
@@ -0,0 +1,2 @@
1
+ import { SobreeDocument } from '../doc/types';
2
+ export declare function fontLivenessPaths(doc: SobreeDocument): Set<string>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * ODTTF (Obfuscated TrueType Font) codec for OOXML font embedding.
3
+ *
4
+ * Per ECMA-376 Part 4 §2.8.1: Word obfuscates embedded font binaries
5
+ * by XORing the first 32 bytes with a 16-byte key derived from a GUID
6
+ * (the `w:fontKey` attribute on `<w:embedRegular/>` / etc.). The key
7
+ * is the GUID's 16 bytes in **reverse** order. The first 16 bytes of
8
+ * the font are XOR'd with the key, then the next 16 bytes are XOR'd
9
+ * with the same key. Bytes 32..end pass through unchanged.
10
+ *
11
+ * Symmetry — the operation is its own inverse (XOR is involutive), so
12
+ * `obfuscate(deobfuscate(x, k), k) === x`.
13
+ *
14
+ * A `fontKey` of all-zeros means "no obfuscation" and the bytes are
15
+ * already a raw TTF/OTF.
16
+ */
17
+ /**
18
+ * XOR-deobfuscate or -obfuscate the first 32 bytes of `bytes` with the
19
+ * key derived from `fontKey`. Returns a fresh `Uint8Array` — the input
20
+ * is not mutated.
21
+ */
22
+ export declare function deobfuscate(bytes: Uint8Array, fontKey: string): Uint8Array;
23
+ /** Symmetric inverse — same operation as `deobfuscate`, named for clarity at call sites. */
24
+ export declare function obfuscate(bytes: Uint8Array, fontKey: string): Uint8Array;
25
+ /**
26
+ * Generate a fresh GUID in the canonical `{XX-...}` form Word uses for
27
+ * `w:fontKey`. Random bytes via `crypto.getRandomValues`; falls back to
28
+ * `Math.random()` if `crypto` isn't present (jsdom often is, Node 19+
29
+ * is too — fallback only matters for ancient runtimes).
30
+ */
31
+ export declare function generateFontKey(): string;
32
+ /** True when `fontKey` is the sentinel "no obfuscation" GUID. */
33
+ export declare function isUnobfuscated(fontKey: string | undefined): boolean;
@@ -0,0 +1,29 @@
1
+ import { FontDeclaration } from './types';
2
+ interface Loaded {
3
+ declarations: FontDeclaration[];
4
+ /** ZIP paths of embedded font parts that should be kept in rawParts. */
5
+ embeddedPartPaths: Set<string>;
6
+ }
7
+ /**
8
+ * Convenience for the import pipeline: pull `word/fontTable.xml` +
9
+ * `word/_rels/fontTable.xml.rels` out of the unzipped text-parts map
10
+ * and return the parsed declarations. Falls back to an empty array
11
+ * when the document carries no font table — most don't.
12
+ *
13
+ * `parseRels` is the same `parseRels` helper the document uses; passed
14
+ * in to avoid a cycle through `docx/import`.
15
+ */
16
+ export declare function mountFontTableFromZip(textParts: Record<string, string>, parseRels: (xml: string) => Map<string, string>): FontDeclaration[];
17
+ /**
18
+ * Returns parsed declarations + the set of font-part ZIP paths the
19
+ * unzipped binary map should retain. `fontTableRels` resolves `r:id`
20
+ * references inside `<w:embed*>` elements to their target paths.
21
+ */
22
+ export declare function parseFontTable(fontTableXml: string, fontTableRels: Map<string, string>): Loaded;
23
+ /**
24
+ * Convenience: parse direct-child `<w:font>` elements, in case some
25
+ * producers use children rather than descendants. Reserved for future
26
+ * "strict mode" callers.
27
+ */
28
+ export declare function readChildFonts(parent: Element): Element[];
29
+ export {};
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Font-table type definitions, lifted out of `doc/types.ts` so the
3
+ * fonts module owns its own AST shapes. Re-exported from
4
+ * `doc/types.ts` for backwards compat — existing consumers keep
5
+ * working unchanged.
6
+ */
7
+ /**
8
+ * One entry in the document's font table — mirrors `<w:font>` in
9
+ * OOXML. Most fields are substitution hints used when the named font
10
+ * isn't installed on the reader's machine; `embed` carries pointers
11
+ * to actual font bytes stored under `rawParts`.
12
+ */
13
+ export interface FontDeclaration {
14
+ /** `<w:font w:name="...">` — the font family this entry describes. */
15
+ name: string;
16
+ altName?: string;
17
+ /** 10-byte PANOSE classification, hex-encoded. */
18
+ panose?: string;
19
+ /** Character set, hex (default "00"). */
20
+ charset?: string;
21
+ family?: "auto" | "decorative" | "modern" | "roman" | "script" | "swiss";
22
+ pitch?: "default" | "fixed" | "variable";
23
+ /** Unicode/codepage subset bitmasks — mirrors `<w:sig>`. */
24
+ sig?: {
25
+ usb0: string;
26
+ usb1: string;
27
+ usb2: string;
28
+ usb3: string;
29
+ csb0: string;
30
+ csb1: string;
31
+ };
32
+ notTrueType?: boolean;
33
+ /** Embedded faces. Optional — most declarations are just name + panose. */
34
+ embed?: {
35
+ regular?: FontEmbedRef;
36
+ bold?: FontEmbedRef;
37
+ italic?: FontEmbedRef;
38
+ boldItalic?: FontEmbedRef;
39
+ };
40
+ }
41
+ export interface FontEmbedRef {
42
+ /** ZIP path of the font bytes (key into `SobreeDocument.rawParts`). */
43
+ partPath: string;
44
+ /**
45
+ * GUID used for ODTTF XOR-obfuscation of the first 32 bytes. Absent
46
+ * means the part is a raw TTF/OTF (no obfuscation). When writing
47
+ * `.docx`, a fresh GUID is generated per embed.
48
+ */
49
+ fontKey?: string;
50
+ /** Round-trip flag — true if the on-disk file is already a subset. */
51
+ subsetted?: boolean;
52
+ }
@@ -0,0 +1,168 @@
1
+ import { BlobCache, BlobStore } from './blob';
2
+ import { BlockRef, EditError, EditResult, Selection } from './doc/api';
3
+ import { ParagraphPropertiesPatch, BlockInfo, CommandBus, OutlineItem } from './editor';
4
+ import { Block, ParagraphAlignment, ParagraphProperties, SobreeDocument } from './doc/types';
5
+ import { History } from './history';
6
+ /**
7
+ * HeadlessSobree — a no-DOM Sobree peer for LLM agents, automation,
8
+ * back-end pipelines, MCP servers, anywhere code needs to read or
9
+ * write a Sobree document without rendering it.
10
+ *
11
+ * # The shape
12
+ *
13
+ * Same mental model as the browser editor: hold a `Y.Doc`, read it
14
+ * as a `SobreeDocument` projection, write through typed mutation
15
+ * methods, get a `change` event on every update (local or remote).
16
+ * The difference: no DOM, no rendering, no selection-from-DOM logic.
17
+ * Selection is just a value field you can read / write.
18
+ *
19
+ * # Use cases
20
+ *
21
+ * 1. **LLM agents.** Connect a Y-websocket provider to a Sobree
22
+ * room; the LLM sees the same doc the user sees. It can read
23
+ * structure (paragraphs, headings, runs) and apply edits that
24
+ * propagate back through Y.
25
+ *
26
+ * 2. **Server-side rendering / export.** Run `editor.toDocx()` in
27
+ * a Node worker against a snapshot loaded from `@sobree/collab-server`'s
28
+ * persistence backend.
29
+ *
30
+ * 3. **Automation pipelines.** Cron job that processes inbound
31
+ * content and writes formatted reports to a Sobree doc.
32
+ *
33
+ * 4. **Tests.** Build a fixture document programmatically without
34
+ * a DOM environment.
35
+ *
36
+ * # What it's NOT
37
+ *
38
+ * - A *relay*. That's `@sobree/collab-server` — a Node server that
39
+ * fans out Y messages between many peers. HeadlessSobree is a
40
+ * single peer with its own Y.Doc.
41
+ * - A *full editor*. The browser `Editor` adds DOM rendering,
42
+ * contentEditable event handling, image-resize handles, paste
43
+ * parsing, etc. HeadlessSobree skips all that — if you need
44
+ * them, mount a real Editor.
45
+ * - A *table editor*. The browser `Editor` has a rich table API
46
+ * (`editor.table.insertRow`, etc.). HeadlessSobree v0 doesn't
47
+ * wrap that — operate on `Table` blocks directly via
48
+ * `replaceBlock` until Phase 4.x adds a parallel table API.
49
+ *
50
+ * # Origin tagging
51
+ *
52
+ * Every mutation writes to the Y.Doc with a configurable origin
53
+ * (default `"headless"`). The local `Y.UndoManager` tracks only that
54
+ * origin — so the peer's `Cmd+Z`-equivalent (`peer.history.undo()`)
55
+ * reverses only its own edits, not the human peers'. Pick a stable
56
+ * per-peer origin string (e.g. `"agent:gpt-4-2024-05"`) if you want
57
+ * post-hoc telemetry to identify the author.
58
+ */
59
+ import * as Y from "yjs";
60
+ export interface HeadlessSobreeOptions {
61
+ /**
62
+ * Origin string used for this peer's Y.Doc mutations. Identifies
63
+ * the source in `afterTransaction` events and scopes the
64
+ * Y.UndoManager. Default `"headless"`.
65
+ */
66
+ origin?: string;
67
+ /**
68
+ * Initial document. Used only if the Y.Doc is empty at construction
69
+ * time — same semantics as `Editor.initialDocument`. If the Y.Doc
70
+ * is non-empty (a peer joining an active room), this is ignored
71
+ * and the existing state is adopted.
72
+ */
73
+ initialDocument?: SobreeDocument;
74
+ /**
75
+ * Override the BlockRegistry's id prefix. Default is
76
+ * `${ydoc.clientID.toString(36)}_` — same convention the browser
77
+ * `Editor` uses, so newly-minted block ids never collide across
78
+ * peers.
79
+ */
80
+ idPrefix?: string;
81
+ /**
82
+ * Optional content-hashed `BlobStore` (Phase 3.2+). Without one,
83
+ * binary parts ride inline in the Y.Doc; with one, the headless
84
+ * peer resolves `partRefs` hashes via a local `BlobCache` and
85
+ * fetches missing bytes from the store on demand. See
86
+ * `EditorOptions.blobStore` for the full contract.
87
+ */
88
+ blobStore?: BlobStore;
89
+ }
90
+ export type HeadlessEvent = "change";
91
+ export interface HeadlessChangePayload {
92
+ doc: SobreeDocument;
93
+ /** Whether the change originated from THIS peer's mutations
94
+ * (true) or arrived via a Y provider from a remote peer (false). */
95
+ local: boolean;
96
+ }
97
+ export type HeadlessUnsubscribe = () => void;
98
+ /**
99
+ * Headless Sobree peer. Construct with a `Y.Doc` — typically one
100
+ * you've already attached a provider to. Reads project through the
101
+ * Y.Doc; writes mirror back inside `Y.Doc.transact` with the
102
+ * configured origin.
103
+ */
104
+ export declare class HeadlessSobree {
105
+ readonly ydoc: Y.Doc;
106
+ readonly commands: CommandBus;
107
+ readonly history: History;
108
+ readonly origin: string;
109
+ /** Optional content-hashed blob layer. Mirrors the browser `Editor`'s
110
+ * `blobStore` field — null when no store is configured. */
111
+ readonly blobStore: BlobStore | null;
112
+ /** Local cache for blob bytes. Null when no `blobStore` is set. */
113
+ readonly blobCache: BlobCache | null;
114
+ private doc;
115
+ private readonly registry;
116
+ private currentSelection;
117
+ private readonly listeners;
118
+ private lastPartRefs;
119
+ private ydocUpdateListener;
120
+ constructor(ydoc: Y.Doc, opts?: HeadlessSobreeOptions);
121
+ /** Current document — a fresh projection of the Y.Doc state. */
122
+ getDocument(): SobreeDocument;
123
+ /** Summary of every top-level block. */
124
+ getBlocks(): BlockInfo[];
125
+ getBlock(index: number): BlockInfo;
126
+ getBlockById(id: string): BlockInfo | null;
127
+ /** Heading outline — one entry per `paragraph` block whose
128
+ * resolved style identifies it as a heading. */
129
+ getOutline(): OutlineItem[];
130
+ /** This peer's stored selection — same shape `editor.selection.get()`
131
+ * returns. `null` when no selection is set. */
132
+ getSelection(): Selection;
133
+ /**
134
+ * Set this peer's selection. Stored as a value (no DOM update);
135
+ * the value is what `history` captures on each mutation and
136
+ * restores on undo.
137
+ */
138
+ setSelection(selection: Selection): void;
139
+ /** Replace the document. */
140
+ setDocument(doc: SobreeDocument): void;
141
+ /** Replace the block at `target`'s index with `block`. */
142
+ replaceBlock(target: BlockRef, block: Block): EditResult<BlockRef>;
143
+ /** Insert `block` before the target block. */
144
+ insertBlockBefore(target: BlockRef, block: Block): EditResult<BlockRef>;
145
+ /** Insert `block` after the target block. */
146
+ insertBlockAfter(target: BlockRef, block: Block): EditResult<BlockRef>;
147
+ /** Delete the target block. */
148
+ deleteBlock(target: BlockRef): EditResult<void>;
149
+ /** Merge a patch into each target paragraph's properties. */
150
+ applyBlockProperties(targets: BlockRef[], patch: ParagraphPropertiesPatch): EditResult<void>;
151
+ on<E extends HeadlessEvent>(event: E, cb: (payload: HeadlessChangePayload) => void): HeadlessUnsubscribe;
152
+ destroy(): void;
153
+ private allBlockIds;
154
+ private mirror;
155
+ private adoptYDocState;
156
+ private resolveCachedPartRefsInto;
157
+ private onBlobResolved;
158
+ /**
159
+ * Wait for every currently-referenced binary part to be available
160
+ * in the local cache. No-op when no `blobStore` is configured.
161
+ */
162
+ ensurePartsLoaded(): Promise<void>;
163
+ private checkRefs;
164
+ private commit;
165
+ private fireChange;
166
+ private summariseBlock;
167
+ }
168
+ export type { Block, BlockInfo, BlockRef, EditError, EditResult, ParagraphAlignment, ParagraphProperties, Selection, SobreeDocument, };
@@ -0,0 +1,100 @@
1
+ import { Selection, Selection as PublicSelection } from '../doc/api';
2
+ import { HistoryConfig, HistoryDepth } from './types';
3
+ /**
4
+ * Undo / redo for the Sobree editor — backed by `Y.UndoManager`.
5
+ *
6
+ * # Why Y.UndoManager
7
+ *
8
+ * Phase 1b.6 swaps the snapshot-stack History for `Y.UndoManager` so:
9
+ *
10
+ * 1. **Per-peer undo.** UndoManager tracks operations by *origin*.
11
+ * When a peer types, those Y operations are tagged `"local"`. A
12
+ * remote peer's edits arrive with a different origin (the
13
+ * provider name), so the local UndoManager doesn't see them as
14
+ * its own. `Cmd+Z` reverses only the local user's edits — exactly
15
+ * what users expect in collab.
16
+ *
17
+ * 2. **CRDT-native.** Undoing produces *inverse Y operations*, which
18
+ * flow through the same broadcast pipeline as forward edits. No
19
+ * separate "undo over the wire" protocol needed.
20
+ *
21
+ * 3. **No snapshot stack.** Memory is bounded by Yjs's internal item
22
+ * list; no separate snapshot bookkeeping.
23
+ *
24
+ * # API compatibility
25
+ *
26
+ * The public surface (`undo`, `redo`, `canUndo`, `canRedo`, `clear`,
27
+ * `depth`, `on`) matches the old History so the Editor's command-bus
28
+ * registrations and the keyboard plugin's `Cmd+Z` mapping work
29
+ * unchanged. The legacy `recordCommit` / `recordTyping` / `flush`
30
+ * methods are kept as no-ops — UndoManager auto-tracks ops by origin
31
+ * and `captureTimeout` handles coalescing without explicit "begin a
32
+ * typing session" calls.
33
+ *
34
+ * # Selection restore
35
+ *
36
+ * Each undo/redo step needs to restore the cursor to its pre-edit
37
+ * position. We capture the live selection in `stack-item-added`'s
38
+ * `meta` and restore it on `stack-item-popped`. The restore happens
39
+ * AFTER the editor has re-projected and re-rendered (the Y observer
40
+ * fires before the popped event — see `Editor.adoptYDocState`).
41
+ */
42
+ import * as Y from "yjs";
43
+ export type HistoryEvent = "change";
44
+ export type HistoryListener = (depth: HistoryDepth) => void;
45
+ export interface HistoryOptions extends Partial<HistoryConfig> {
46
+ /** Y.Doc to track. The UndoManager observes the body, meta, and
47
+ * parts top-level types — every Sobree mutation funnels through
48
+ * one of these, so any local edit produces a stack entry. */
49
+ ydoc: Y.Doc;
50
+ /** Origin string used by the Editor's `mirrorToYDoc()` and other
51
+ * local writes. UndoManager only tracks operations whose origin
52
+ * is in this set. Defaults to `"local"`. */
53
+ localOrigin?: unknown;
54
+ /** Capture the *current* live selection — called as a stack item
55
+ * is being added so we can stash it for restore on undo. */
56
+ captureSelection: () => Selection;
57
+ /** Restore a previously-captured selection to the live DOM /
58
+ * EditorSelection. Called on undo / redo after the Y.Doc has been
59
+ * re-projected and re-rendered. */
60
+ restoreSelection: (sel: Selection) => void;
61
+ }
62
+ export declare class History {
63
+ private readonly mgr;
64
+ private readonly listeners;
65
+ private readonly captureSelection;
66
+ private readonly restoreSelection;
67
+ constructor(opts: HistoryOptions);
68
+ undo(): boolean;
69
+ redo(): boolean;
70
+ canUndo(): boolean;
71
+ canRedo(): boolean;
72
+ clear(): void;
73
+ depth(): HistoryDepth;
74
+ on(_event: HistoryEvent, cb: HistoryListener): () => void;
75
+ destroy(): void;
76
+ /** @deprecated UndoManager auto-tracks ops by origin; no need to
77
+ * call this. Kept as a no-op so the Editor's existing call sites
78
+ * don't break. */
79
+ recordCommit(): void;
80
+ /** @deprecated UndoManager's `captureTimeout` coalesces consecutive
81
+ * ops into one stack item. No explicit recording needed. */
82
+ recordTyping(): void;
83
+ /** @deprecated UndoManager flushes implicitly on `captureTimeout`
84
+ * expiry or on the next non-tracked operation. */
85
+ flush(): void;
86
+ private fire;
87
+ }
88
+ /** @deprecated Selection survives undo/redo via stable block ids now;
89
+ * no snapshot-conversion is needed. Kept as a pass-through stub. */
90
+ export declare function makeEntry(doc: unknown, selection: PublicSelection, reason: string, _indexOfBlock: (id: string) => number): {
91
+ doc: unknown;
92
+ selection: PublicSelection;
93
+ reason: string;
94
+ timestamp: number;
95
+ };
96
+ /** @deprecated Pass-through; selection structure is no longer
97
+ * index-keyed in the new history. */
98
+ export declare function selectionToSnapshot(selection: PublicSelection, _indexOfBlock: (id: string) => number): PublicSelection;
99
+ /** @deprecated Pass-through; selection survives via stable ids. */
100
+ export declare function snapshotToSelection(snap: PublicSelection, _refAt: (idx: number) => unknown): PublicSelection;
@@ -0,0 +1,4 @@
1
+ export { History, makeEntry, selectionToSnapshot, snapshotToSelection, } from './history';
2
+ export type { HistoryEvent, HistoryListener, HistoryOptions, } from './history';
3
+ export type { HistoryConfig, HistoryDepth, HistoryEntry, SnapshotPosition, SnapshotSelection, } from './types';
4
+ export { DEFAULT_HISTORY_CONFIG } from './types';