@beyondwork/docx-react-component 1.0.36 → 1.0.38

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 (107) hide show
  1. package/README.md +103 -13
  2. package/package.json +1 -1
  3. package/src/api/package-version.ts +13 -0
  4. package/src/api/public-types.ts +402 -1
  5. package/src/core/commands/index.ts +18 -1
  6. package/src/core/commands/section-layout-commands.ts +58 -0
  7. package/src/core/commands/table-grid.ts +431 -0
  8. package/src/core/commands/table-structure-commands.ts +815 -55
  9. package/src/core/selection/mapping.ts +6 -0
  10. package/src/io/docx-session.ts +24 -9
  11. package/src/io/export/build-app-properties-xml.ts +88 -0
  12. package/src/io/export/serialize-comments.ts +6 -1
  13. package/src/io/export/serialize-footnotes.ts +10 -9
  14. package/src/io/export/serialize-headers-footers.ts +11 -10
  15. package/src/io/export/serialize-main-document.ts +328 -50
  16. package/src/io/export/serialize-numbering.ts +114 -24
  17. package/src/io/export/serialize-tables.ts +87 -11
  18. package/src/io/export/table-properties-xml.ts +174 -20
  19. package/src/io/export/twip.ts +66 -0
  20. package/src/io/normalize/normalize-text.ts +20 -0
  21. package/src/io/ooxml/parse-footnotes.ts +62 -1
  22. package/src/io/ooxml/parse-headers-footers.ts +62 -1
  23. package/src/io/ooxml/parse-main-document.ts +158 -1
  24. package/src/io/ooxml/parse-tables.ts +249 -0
  25. package/src/legal/bookmarks.ts +78 -0
  26. package/src/model/canonical-document.ts +45 -0
  27. package/src/review/store/scope-tag-diff.ts +130 -0
  28. package/src/runtime/document-layout.ts +4 -2
  29. package/src/runtime/document-navigation.ts +2 -306
  30. package/src/runtime/document-runtime.ts +287 -11
  31. package/src/runtime/layout/default-page-format.ts +96 -0
  32. package/src/runtime/layout/docx-font-loader.ts +143 -0
  33. package/src/runtime/layout/index.ts +233 -0
  34. package/src/runtime/layout/inert-layout-facet.ts +59 -0
  35. package/src/runtime/layout/layout-engine-instance.ts +628 -0
  36. package/src/runtime/layout/layout-invalidation.ts +257 -0
  37. package/src/runtime/layout/layout-measurement-provider.ts +175 -0
  38. package/src/runtime/layout/margin-preset-catalog.ts +178 -0
  39. package/src/runtime/layout/measurement-backend-canvas.ts +307 -0
  40. package/src/runtime/layout/measurement-backend-empirical.ts +208 -0
  41. package/src/runtime/layout/page-format-catalog.ts +233 -0
  42. package/src/runtime/layout/page-fragment-mapper.ts +179 -0
  43. package/src/runtime/layout/page-graph.ts +452 -0
  44. package/src/runtime/layout/page-layout-snapshot-adapter.ts +70 -0
  45. package/src/runtime/layout/page-story-resolver.ts +195 -0
  46. package/src/runtime/layout/paginated-layout-engine.ts +921 -0
  47. package/src/runtime/layout/project-block-fragments.ts +91 -0
  48. package/src/runtime/layout/public-facet.ts +1398 -0
  49. package/src/runtime/layout/resolved-formatting-document.ts +317 -0
  50. package/src/runtime/layout/resolved-formatting-state.ts +430 -0
  51. package/src/runtime/layout/table-render-plan.ts +229 -0
  52. package/src/runtime/render/block-fragment-projection.ts +35 -0
  53. package/src/runtime/render/decoration-resolver.ts +189 -0
  54. package/src/runtime/render/index.ts +57 -0
  55. package/src/runtime/render/pending-op-delta-reader.ts +129 -0
  56. package/src/runtime/render/render-frame-types.ts +317 -0
  57. package/src/runtime/render/render-kernel.ts +755 -0
  58. package/src/runtime/scope-tag-registry.ts +95 -0
  59. package/src/runtime/surface-projection.ts +1 -0
  60. package/src/runtime/text-ack-range.ts +49 -0
  61. package/src/runtime/view-state.ts +67 -0
  62. package/src/runtime/workflow-markup.ts +1 -5
  63. package/src/runtime/workflow-rail-segments.ts +280 -0
  64. package/src/ui/WordReviewEditor.tsx +99 -15
  65. package/src/ui/editor-runtime-boundary.ts +10 -1
  66. package/src/ui/editor-shell-view.tsx +6 -0
  67. package/src/ui/editor-surface-controller.tsx +3 -0
  68. package/src/ui/headless/chrome-registry.ts +501 -0
  69. package/src/ui/headless/scoped-chrome-policy.ts +183 -0
  70. package/src/ui/headless/selection-tool-context.ts +2 -0
  71. package/src/ui/headless/selection-tool-resolver.ts +36 -17
  72. package/src/ui/headless/selection-tool-types.ts +10 -0
  73. package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
  74. package/src/ui-tailwind/chrome/role-action-sets.ts +74 -0
  75. package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
  76. package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +163 -0
  77. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +57 -92
  78. package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
  79. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
  80. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +274 -138
  81. package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +90 -0
  82. package/src/ui-tailwind/chrome-overlay/index.ts +22 -0
  83. package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +86 -0
  84. package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
  85. package/src/ui-tailwind/chrome-overlay/tw-workspace-view-switcher.tsx +95 -0
  86. package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +337 -0
  87. package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +100 -0
  88. package/src/ui-tailwind/editor-surface/perf-probe.ts +27 -1
  89. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +20 -2
  90. package/src/ui-tailwind/editor-surface/pm-decorations.ts +93 -23
  91. package/src/ui-tailwind/editor-surface/predicted-position-map.ts +78 -0
  92. package/src/ui-tailwind/editor-surface/predicted-tag-preflight.ts +63 -0
  93. package/src/ui-tailwind/editor-surface/predicted-tx-gate.ts +39 -0
  94. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +176 -6
  95. package/src/ui-tailwind/index.ts +33 -0
  96. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
  97. package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
  98. package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
  99. package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
  100. package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
  101. package/src/ui-tailwind/theme/editor-theme.css +505 -144
  102. package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +559 -0
  103. package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
  104. package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
  105. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +2 -2
  106. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +304 -166
  107. package/src/ui-tailwind/tw-review-workspace.tsx +163 -2
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Page-format catalog — named page sizes that back the section page-size
3
+ * picker and the real-dimension page frame.
4
+ *
5
+ * The canonical storage of page geometry remains
6
+ * `SectionProperties.pageSize` in twips. This catalog adds a layer of
7
+ * semantic naming on top so the UI can render "A4" or "US Letter" and
8
+ * the render kernel can pick a sensible default per locale.
9
+ *
10
+ * Unit reference (OOXML):
11
+ * - 1 inch = 1440 twips
12
+ * - 1 mm = 56.6929 twips
13
+ *
14
+ * The catalog deliberately ships a single default match tolerance (1 twip)
15
+ * so legacy documents whose page sizes were round-tripped through third-
16
+ * party tools still identify as the named format they were authored at.
17
+ */
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Public types
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export type PageFormatId =
24
+ | "letter"
25
+ | "legal"
26
+ | "tabloid"
27
+ | "executive"
28
+ | "a3"
29
+ | "a4"
30
+ | "a5"
31
+ | "b4-iso"
32
+ | "b5-iso"
33
+ | "custom";
34
+
35
+ export type PageFormatRegion = "us" | "iso" | "jp" | "custom";
36
+
37
+ export type PageFormatLocaleDefault = "en-us" | "en-gb" | "eu" | "jp";
38
+
39
+ export interface PageFormatDisplay {
40
+ inches?: string;
41
+ millimeters?: string;
42
+ }
43
+
44
+ export interface PageFormatDefinition {
45
+ /** Stable identifier. */
46
+ id: PageFormatId;
47
+ /** Human-facing label used in pickers. */
48
+ label: string;
49
+ /** Locale or region hint. */
50
+ region: PageFormatRegion;
51
+ /** Width in twips at portrait orientation. */
52
+ portraitWidthTwips: number;
53
+ /** Height in twips at portrait orientation. */
54
+ portraitHeightTwips: number;
55
+ /** Default-for-locale hint so consumers can pick a sensible default. */
56
+ localeDefault?: PageFormatLocaleDefault;
57
+ /** Pre-computed inches/millimeters strings for the picker. */
58
+ display: PageFormatDisplay;
59
+ }
60
+
61
+ export interface ActivePageFormat {
62
+ sectionIndex: number;
63
+ format: PageFormatDefinition;
64
+ orientation: "portrait" | "landscape";
65
+ /** True when the section matches a catalog format within the match tolerance. */
66
+ matchesCatalog: boolean;
67
+ /** When matchesCatalog is false, the raw twips (custom format). */
68
+ customTwips?: { width: number; height: number };
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Catalog
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Pre-defined named page formats. Ordering intentionally puts the most
77
+ * common sizes (Letter, A4) first because UI dropdowns render in list order.
78
+ */
79
+ export const PAGE_FORMAT_CATALOG: readonly PageFormatDefinition[] = Object.freeze([
80
+ {
81
+ id: "letter",
82
+ label: "US Letter",
83
+ region: "us",
84
+ portraitWidthTwips: 12240,
85
+ portraitHeightTwips: 15840,
86
+ localeDefault: "en-us",
87
+ display: { inches: "8.5 × 11 in", millimeters: "215.9 × 279.4 mm" },
88
+ },
89
+ {
90
+ id: "a4",
91
+ label: "A4",
92
+ region: "iso",
93
+ portraitWidthTwips: 11906,
94
+ portraitHeightTwips: 16838,
95
+ localeDefault: "eu",
96
+ display: { inches: "8.27 × 11.69 in", millimeters: "210 × 297 mm" },
97
+ },
98
+ {
99
+ id: "legal",
100
+ label: "US Legal",
101
+ region: "us",
102
+ portraitWidthTwips: 12240,
103
+ portraitHeightTwips: 20160,
104
+ display: { inches: "8.5 × 14 in", millimeters: "215.9 × 355.6 mm" },
105
+ },
106
+ {
107
+ id: "tabloid",
108
+ label: "Tabloid / Ledger",
109
+ region: "us",
110
+ portraitWidthTwips: 15840,
111
+ portraitHeightTwips: 24480,
112
+ display: { inches: "11 × 17 in", millimeters: "279.4 × 431.8 mm" },
113
+ },
114
+ {
115
+ id: "executive",
116
+ label: "Executive",
117
+ region: "us",
118
+ portraitWidthTwips: 10440,
119
+ portraitHeightTwips: 15120,
120
+ display: { inches: "7.25 × 10.5 in", millimeters: "184.1 × 266.7 mm" },
121
+ },
122
+ {
123
+ id: "a3",
124
+ label: "A3",
125
+ region: "iso",
126
+ portraitWidthTwips: 16838,
127
+ portraitHeightTwips: 23811,
128
+ display: { inches: "11.69 × 16.54 in", millimeters: "297 × 420 mm" },
129
+ },
130
+ {
131
+ id: "a5",
132
+ label: "A5",
133
+ region: "iso",
134
+ portraitWidthTwips: 8391,
135
+ portraitHeightTwips: 11906,
136
+ display: { inches: "5.83 × 8.27 in", millimeters: "148 × 210 mm" },
137
+ },
138
+ {
139
+ id: "b4-iso",
140
+ label: "B4 (ISO)",
141
+ region: "iso",
142
+ portraitWidthTwips: 14173,
143
+ portraitHeightTwips: 20016,
144
+ display: { inches: "9.84 × 13.90 in", millimeters: "250 × 353 mm" },
145
+ },
146
+ {
147
+ id: "b5-iso",
148
+ label: "B5 (ISO)",
149
+ region: "iso",
150
+ portraitWidthTwips: 9977,
151
+ portraitHeightTwips: 14173,
152
+ display: { inches: "6.93 × 9.84 in", millimeters: "176 × 250 mm" },
153
+ },
154
+ {
155
+ id: "custom",
156
+ label: "Custom",
157
+ region: "custom",
158
+ portraitWidthTwips: 0,
159
+ portraitHeightTwips: 0,
160
+ display: {},
161
+ },
162
+ ]);
163
+
164
+ /**
165
+ * Match tolerance in twips. Historic round-trips through third-party tools
166
+ * sometimes drift by sub-twip amounts; a 1-twip window catches those while
167
+ * keeping intentionally custom sizes genuinely custom.
168
+ */
169
+ const MATCH_TOLERANCE_TWIPS = 1;
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Matching
173
+ // ---------------------------------------------------------------------------
174
+
175
+ export interface MatchPageFormatInput {
176
+ sectionIndex: number;
177
+ widthTwips: number;
178
+ heightTwips: number;
179
+ }
180
+
181
+ /**
182
+ * Match a section's page dimensions against the catalog.
183
+ *
184
+ * The matcher tolerates either orientation — if the supplied (width, height)
185
+ * match a catalog entry rotated by 90°, the active format is reported as
186
+ * `landscape`. Custom sizes fall through to the `custom` entry.
187
+ */
188
+ export function matchPageFormat(input: MatchPageFormatInput): ActivePageFormat {
189
+ const { sectionIndex, widthTwips, heightTwips } = input;
190
+
191
+ for (const format of PAGE_FORMAT_CATALOG) {
192
+ if (format.id === "custom") continue;
193
+
194
+ const portraitMatch =
195
+ near(widthTwips, format.portraitWidthTwips) &&
196
+ near(heightTwips, format.portraitHeightTwips);
197
+ const landscapeMatch =
198
+ near(widthTwips, format.portraitHeightTwips) &&
199
+ near(heightTwips, format.portraitWidthTwips);
200
+
201
+ if (portraitMatch || landscapeMatch) {
202
+ return {
203
+ sectionIndex,
204
+ format,
205
+ orientation: portraitMatch ? "portrait" : "landscape",
206
+ matchesCatalog: true,
207
+ };
208
+ }
209
+ }
210
+
211
+ const custom = PAGE_FORMAT_CATALOG.find((f) => f.id === "custom")!;
212
+ return {
213
+ sectionIndex,
214
+ format: custom,
215
+ orientation: widthTwips > heightTwips ? "landscape" : "portrait",
216
+ matchesCatalog: false,
217
+ customTwips: { width: widthTwips, height: heightTwips },
218
+ };
219
+ }
220
+
221
+ function near(a: number, b: number): boolean {
222
+ return Math.abs(a - b) <= MATCH_TOLERANCE_TWIPS;
223
+ }
224
+
225
+ /**
226
+ * Look up a format by id. Returns the `custom` entry for unknown ids so
227
+ * callers can chain `.format` reads without null-checking.
228
+ */
229
+ export function getPageFormatById(id: string): PageFormatDefinition {
230
+ const found = PAGE_FORMAT_CATALOG.find((f) => f.id === id);
231
+ if (found) return found;
232
+ return PAGE_FORMAT_CATALOG.find((f) => f.id === "custom")!;
233
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * PageFragmentMapper — offset ↔ fragment ↔ region mapping.
3
+ *
4
+ * Per runtime-owned-paginated-layout-engine.md §6, this is the missing layer
5
+ * between `pm-position-map.ts` and `DocumentNavigationSnapshot`:
6
+ *
7
+ * runtime position → page fragment → visible page region
8
+ *
9
+ * The mapper is a pure query service over a RuntimePageGraph. It does not
10
+ * mutate state and does not consult DOM or PM.
11
+ */
12
+
13
+ import type { EditorStoryTarget, SelectionSnapshot } from "../../api/public-types";
14
+ import type {
15
+ RuntimeBlockFragment,
16
+ RuntimePageGraph,
17
+ RuntimePageNode,
18
+ } from "./page-graph.ts";
19
+
20
+ export interface PageFragmentLocation {
21
+ pageId: string;
22
+ pageIndex: number;
23
+ fragmentId: string;
24
+ regionKind: "body" | "header" | "footer" | "column" | "footnote-area";
25
+ /** Zero-based index of the line box within the fragment, if line boxes are available. */
26
+ lineBoxIndex?: number;
27
+ }
28
+
29
+ export interface PageSpan {
30
+ firstPageIndex: number;
31
+ lastPageIndex: number;
32
+ pageCount: number;
33
+ }
34
+
35
+ export interface PageFragmentMapper {
36
+ mapOffsetToFragment(offset: number, story?: EditorStoryTarget): PageFragmentLocation | null;
37
+ mapFragmentToOffsetRange(fragmentId: string): { from: number; to: number } | null;
38
+ mapSelectionToPageSpan(selection: SelectionSnapshot): PageSpan | null;
39
+ /**
40
+ * Resolve a page-local anchor for the given offset. Returns the offset
41
+ * unchanged today; retained as a seam for future semantic anchors.
42
+ */
43
+ getAnchorForOffset(offset: number, story?: EditorStoryTarget): { offset: number; pageId: string } | null;
44
+ }
45
+
46
+ export function createPageFragmentMapper(graph: RuntimePageGraph): PageFragmentMapper {
47
+ const fragmentsById = new Map<string, RuntimeBlockFragment>();
48
+ for (const fragment of graph.fragments) {
49
+ fragmentsById.set(fragment.fragmentId, fragment);
50
+ }
51
+
52
+ const pagesById = new Map<string, RuntimePageNode>();
53
+ for (const page of graph.pages) {
54
+ pagesById.set(page.pageId, page);
55
+ }
56
+
57
+ function findFragmentAt(offset: number): RuntimeBlockFragment | undefined {
58
+ // Fragments sort naturally by page then order; a linear scan is O(n) but
59
+ // page counts are small (legal docs rarely exceed a few hundred pages).
60
+ for (const fragment of graph.fragments) {
61
+ if (offset >= fragment.from && offset < fragment.to) {
62
+ return fragment;
63
+ }
64
+ }
65
+ // fall back to the last fragment that starts at or before the offset
66
+ let best: RuntimeBlockFragment | undefined;
67
+ for (const fragment of graph.fragments) {
68
+ if (fragment.from <= offset) {
69
+ if (!best || fragment.from > best.from) best = fragment;
70
+ }
71
+ }
72
+ return best;
73
+ }
74
+
75
+ return {
76
+ mapOffsetToFragment(offset, _story) {
77
+ const fragment = findFragmentAt(offset);
78
+ if (!fragment) {
79
+ // Fallback to coarse page lookup
80
+ const page = findPageForOffsetInternal(graph, offset);
81
+ if (!page) return null;
82
+ return {
83
+ pageId: page.pageId,
84
+ pageIndex: page.pageIndex,
85
+ fragmentId: `${page.pageId}-synthetic`,
86
+ regionKind: "body",
87
+ };
88
+ }
89
+ const page = pagesById.get(fragment.pageId);
90
+ if (!page) return null;
91
+
92
+ // Locate a line box if any
93
+ let lineBoxIndex: number | undefined;
94
+ for (let i = 0; i < page.lineBoxes.length; i += 1) {
95
+ if (page.lineBoxes[i]!.fragmentId === fragment.fragmentId) {
96
+ lineBoxIndex = i;
97
+ break;
98
+ }
99
+ }
100
+
101
+ return {
102
+ pageId: page.pageId,
103
+ pageIndex: page.pageIndex,
104
+ fragmentId: fragment.fragmentId,
105
+ regionKind: fragment.regionKind,
106
+ ...(lineBoxIndex !== undefined ? { lineBoxIndex } : {}),
107
+ };
108
+ },
109
+
110
+ mapFragmentToOffsetRange(fragmentId) {
111
+ const fragment = fragmentsById.get(fragmentId);
112
+ if (!fragment) return null;
113
+ return { from: fragment.from, to: fragment.to };
114
+ },
115
+
116
+ mapSelectionToPageSpan(selection) {
117
+ const from = Math.min(selection.anchor, selection.head);
118
+ const to = Math.max(selection.anchor, selection.head);
119
+ const firstPage = findPageForOffsetInternal(graph, from);
120
+ const lastPage = findPageForOffsetInternal(graph, to);
121
+ if (!firstPage || !lastPage) return null;
122
+ return {
123
+ firstPageIndex: firstPage.pageIndex,
124
+ lastPageIndex: lastPage.pageIndex,
125
+ pageCount: Math.max(1, lastPage.pageIndex - firstPage.pageIndex + 1),
126
+ };
127
+ },
128
+
129
+ getAnchorForOffset(offset, _story) {
130
+ const page = findPageForOffsetInternal(graph, offset);
131
+ if (!page) return null;
132
+ return { offset, pageId: page.pageId };
133
+ },
134
+ };
135
+ }
136
+
137
+ function findPageForOffsetInternal(
138
+ graph: RuntimePageGraph,
139
+ offset: number,
140
+ ): RuntimePageNode | undefined {
141
+ for (const page of graph.pages) {
142
+ if (!page.isBlankFiller && offset < page.endOffset) {
143
+ return page;
144
+ }
145
+ }
146
+ return graph.pages[graph.pages.length - 1];
147
+ }
148
+
149
+ /**
150
+ * Rebuild the mapper for a spliced graph.
151
+ *
152
+ * Correctness baseline: the simplest safe implementation is to fully rebuild
153
+ * the mapper from the new graph. The `prior` and `firstDirtyPage` arguments
154
+ * are accepted for forward compatibility with a future optimization that
155
+ * retains fragment-id indexes for unaffected pages. Today rebuilding is
156
+ * cheap (linear in fragment count) so the naive path is the right trade-off.
157
+ *
158
+ * Renamed from `updateMapperIncremental` (§6 E.5) now that the body is an
159
+ * unconditional full rebuild — the old name implied an incremental strategy
160
+ * that never actually existed in the shipped runtime.
161
+ */
162
+ export function rebuildMapper(
163
+ prior: PageFragmentMapper,
164
+ splicedGraph: RuntimePageGraph,
165
+ firstDirtyPage: number,
166
+ ): PageFragmentMapper {
167
+ // Parameters are intentionally unused by the current implementation;
168
+ // retained as a seam for a future bounded update path.
169
+ void prior;
170
+ void firstDirtyPage;
171
+ return createPageFragmentMapper(splicedGraph);
172
+ }
173
+
174
+ /**
175
+ * @deprecated Renamed to `rebuildMapper` in §6 E.5. Retained as a
176
+ * one-release compatibility alias so external consumers are not broken by
177
+ * the rename; schedule removal after the next published API pin refresh.
178
+ */
179
+ export const updateMapperIncremental = rebuildMapper;