@formicoidea/labre-core 0.57.0 → 0.59.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.
- package/architecture/code.c4 +94 -65
- package/architecture/domain.c4 +2510 -0
- package/architecture/likec4.config.json +3 -3
- package/dist/_pkgs/affine-block-attachment/edgeless-clipboard-config.d.ts +3 -2
- package/dist/_pkgs/affine-block-attachment/edgeless-clipboard-config.js +18 -13
- package/dist/_pkgs/affine-block-callout/adapters/markdown.js +4 -1
- package/dist/_pkgs/affine-block-code/adapters/html.js +1 -1
- package/dist/_pkgs/affine-block-code/adapters/markdown/preprocessor.js +13 -1
- package/dist/_pkgs/affine-block-code/adapters/notion-html.js +1 -1
- package/dist/_pkgs/affine-block-code/code-block.d.ts +17 -2
- package/dist/_pkgs/affine-block-code/code-block.js +28 -23
- package/dist/_pkgs/affine-block-code/code-toolbar/components/lang-button.js +17 -4
- package/dist/_pkgs/affine-block-code/code-toolbar/components/preview-button.js +1 -1
- package/dist/_pkgs/affine-block-data-view/data-source.js +12 -5
- package/dist/_pkgs/affine-block-edgeless-text/edgeless-clipboard-config.d.ts +3 -2
- package/dist/_pkgs/affine-block-edgeless-text/edgeless-clipboard-config.js +8 -5
- package/dist/_pkgs/affine-block-image/asset-picker.d.ts +23 -2
- package/dist/_pkgs/affine-block-image/asset-picker.js +52 -12
- package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +8 -5
- package/dist/_pkgs/affine-block-image/configs/replace-action.js +8 -8
- package/dist/_pkgs/affine-block-image/configs/toolbar.js +2 -1
- package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +15 -0
- package/dist/_pkgs/affine-block-image/crop/crop-controller.js +40 -2
- package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +22 -0
- package/dist/_pkgs/affine-block-image/crop/crop-math.js +55 -2
- package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +17 -3
- package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +64 -6
- package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.d.ts +3 -2
- package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +20 -14
- package/dist/_pkgs/affine-block-image/image-block.js +1 -0
- package/dist/_pkgs/affine-block-image/image-edgeless-block.js +3 -0
- package/dist/_pkgs/affine-block-image/index.d.ts +2 -0
- package/dist/_pkgs/affine-block-image/index.js +2 -0
- package/dist/_pkgs/affine-block-image/replace/commands.d.ts +68 -0
- package/dist/_pkgs/affine-block-image/replace/commands.js +200 -0
- package/dist/_pkgs/affine-block-image/replace/replace-model.d.ts +44 -0
- package/dist/_pkgs/affine-block-image/replace/replace-model.js +168 -0
- package/dist/_pkgs/affine-block-image/replace/toolbar.d.ts +21 -0
- package/dist/_pkgs/affine-block-image/replace/toolbar.js +159 -0
- package/dist/_pkgs/affine-block-image/translations.d.ts +15 -2
- package/dist/_pkgs/affine-block-image/translations.js +35 -2
- package/dist/_pkgs/affine-block-image/utils.d.ts +7 -0
- package/dist/_pkgs/affine-block-image/utils.js +13 -0
- package/dist/_pkgs/affine-block-image/view.js +7 -0
- package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.d.ts +14 -5
- package/dist/_pkgs/affine-block-note/components/edgeless-page-block-title.js +18 -11
- package/dist/_pkgs/affine-block-note/config.d.ts +9 -2
- package/dist/_pkgs/affine-block-note/edgeless-clipboard-config.d.ts +3 -2
- package/dist/_pkgs/affine-block-note/edgeless-clipboard-config.js +8 -5
- package/dist/_pkgs/affine-block-paragraph/adapters/markdown.js +11 -0
- package/dist/_pkgs/affine-block-root/commands/invisible-characters-commands.d.ts +17 -0
- package/dist/_pkgs/affine-block-root/commands/invisible-characters-commands.js +123 -0
- package/dist/_pkgs/affine-block-root/edgeless/clipboard/command.js +82 -65
- package/dist/_pkgs/affine-block-root/edgeless/edgeless-keyboard.js +2 -0
- package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-root/index.js +1 -0
- package/dist/_pkgs/affine-block-root/translations.d.ts +6 -0
- package/dist/_pkgs/affine-block-root/translations.js +10 -0
- package/dist/_pkgs/affine-block-root/view.js +6 -1
- package/dist/_pkgs/affine-block-surface/commands/index.d.ts +1 -1
- package/dist/_pkgs/affine-block-surface/commands/index.js +1 -1
- package/dist/_pkgs/affine-block-surface/commands/reassociate-connectors.d.ts +9 -0
- package/dist/_pkgs/affine-block-surface/commands/reassociate-connectors.js +21 -12
- package/dist/_pkgs/affine-block-surface/extensions/clipboard-config.d.ts +33 -0
- package/dist/_pkgs/affine-block-surface/extensions/clipboard-config.js +53 -0
- package/dist/_pkgs/affine-block-surface/extensions/reading-toolbar.js +12 -12
- package/dist/_pkgs/affine-block-surface/framework-background/background-view.js +2 -1
- package/dist/_pkgs/affine-block-surface/framework-background/band-widths.d.ts +104 -0
- package/dist/_pkgs/affine-block-surface/framework-background/band-widths.js +156 -0
- package/dist/_pkgs/affine-block-surface/framework-background/def.d.ts +87 -4
- package/dist/_pkgs/affine-block-surface/framework-background/def.js +26 -7
- package/dist/_pkgs/affine-block-surface/framework-background/ellipsis.d.ts +58 -0
- package/dist/_pkgs/affine-block-surface/framework-background/ellipsis.js +110 -0
- package/dist/_pkgs/affine-block-surface/framework-background/index.d.ts +3 -0
- package/dist/_pkgs/affine-block-surface/framework-background/index.js +3 -0
- package/dist/_pkgs/affine-block-surface/framework-background/labels.d.ts +12 -2
- package/dist/_pkgs/affine-block-surface/framework-background/labels.js +29 -5
- package/dist/_pkgs/affine-block-surface/framework-background/measure-text.d.ts +14 -0
- package/dist/_pkgs/affine-block-surface/framework-background/measure-text.js +31 -0
- package/dist/_pkgs/affine-block-surface/framework-background/renderer.d.ts +14 -1
- package/dist/_pkgs/affine-block-surface/framework-background/renderer.js +84 -31
- package/dist/_pkgs/affine-block-table/adapters/html.js +26 -14
- package/dist/_pkgs/affine-block-table/adapters/utils.d.ts +1 -0
- package/dist/_pkgs/affine-block-table/adapters/utils.js +23 -5
- package/dist/_pkgs/affine-block-table/header-menu.d.ts +13 -0
- package/dist/_pkgs/affine-block-table/header-menu.js +25 -0
- package/dist/_pkgs/affine-block-table/selection-controller.d.ts +15 -0
- package/dist/_pkgs/affine-block-table/selection-controller.js +62 -0
- package/dist/_pkgs/affine-block-table/table-cell-css.d.ts +7 -0
- package/dist/_pkgs/affine-block-table/table-cell-css.js +10 -0
- package/dist/_pkgs/affine-block-table/table-cell.d.ts +8 -0
- package/dist/_pkgs/affine-block-table/table-cell.js +35 -3
- package/dist/_pkgs/affine-block-table/table-data-manager.d.ts +14 -0
- package/dist/_pkgs/affine-block-table/table-data-manager.js +44 -3
- package/dist/_pkgs/affine-block-table/translations.d.ts +14 -0
- package/dist/_pkgs/affine-block-table/translations.js +22 -0
- package/dist/_pkgs/affine-components/highlight-dropdown-menu/highlight-duotone-icon.js +8 -8
- package/dist/_pkgs/affine-components/highlight-dropdown-menu/text-duotone-icon.js +11 -11
- package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.d.ts +3 -1
- package/dist/_pkgs/affine-fragment-doc-title/title-band/title-band.js +15 -2
- package/dist/_pkgs/affine-gfx-mindmap/toolbar/mindmap-tool-button.d.ts +2 -2
- package/dist/_pkgs/affine-gfx-template/builtin/other.d.ts +4 -0
- package/dist/_pkgs/affine-gfx-template/builtin/other.js +1 -1
- package/dist/_pkgs/affine-gfx-template/builtin/slides/build.d.ts +81 -0
- package/dist/_pkgs/affine-gfx-template/builtin/slides/build.js +276 -0
- package/dist/_pkgs/affine-gfx-template/builtin/slides/consts.d.ts +126 -0
- package/dist/_pkgs/affine-gfx-template/builtin/slides/consts.js +101 -0
- package/dist/_pkgs/affine-gfx-template/builtin/slides/index.d.ts +8 -0
- package/dist/_pkgs/affine-gfx-template/builtin/slides/index.js +27 -0
- package/dist/_pkgs/affine-gfx-template/builtin/slides/masks.d.ts +83 -0
- package/dist/_pkgs/affine-gfx-template/builtin/slides/masks.js +674 -0
- package/dist/_pkgs/affine-gfx-template/contribute.js +12 -17
- package/dist/_pkgs/affine-gfx-template/make-snapshot.d.ts +14 -2
- package/dist/_pkgs/affine-gfx-template/make-snapshot.js +6 -2
- package/dist/_pkgs/affine-gfx-template/services/template-middlewares.d.ts +12 -0
- package/dist/_pkgs/affine-gfx-template/services/template-middlewares.js +48 -0
- package/dist/_pkgs/affine-gfx-template/services/template.js +2 -1
- package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +15 -1
- package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +33 -37
- package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +6 -0
- package/dist/_pkgs/affine-gfx-template/translations.d.ts +94 -0
- package/dist/_pkgs/affine-gfx-template/translations.js +441 -2
- package/dist/_pkgs/affine-model/blocks/table/table-model.d.ts +4 -0
- package/dist/_pkgs/affine-model/blocks/table/table-model.js +4 -0
- package/dist/_pkgs/affine-model/elements/bpmn/node.d.ts +46 -3
- package/dist/_pkgs/affine-model/elements/bpmn/node.js +47 -4
- package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +20 -0
- package/dist/_pkgs/affine-model/elements/bpmn/pool.js +33 -4
- package/dist/_pkgs/affine-shared/services/edit-props-store.d.ts +3 -16
- package/dist/_pkgs/affine-shared/services/edit-props-store.js +16 -16
- package/dist/_pkgs/affine-shared/services/editor-setting-service.d.ts +3 -0
- package/dist/_pkgs/affine-shared/services/editor-setting-service.js +4 -0
- package/dist/_pkgs/affine-shared/services/index.d.ts +1 -0
- package/dist/_pkgs/affine-shared/services/index.js +1 -0
- package/dist/_pkgs/affine-shared/services/invisible-characters.d.ts +33 -0
- package/dist/_pkgs/affine-shared/services/invisible-characters.js +63 -0
- package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-cache.d.ts +0 -9
- package/dist/_pkgs/affine-shared/services/link-preview-service/link-preview-cache.js +3 -9
- package/dist/_pkgs/affine-shared/services/telemetry-service/lifecycle.d.ts +26 -0
- package/dist/_pkgs/affine-shared/services/telemetry-service/telemetry-service.d.ts +4 -3
- package/dist/_pkgs/affine-shared/services/telemetry-service/types.d.ts +11 -0
- package/dist/_pkgs/affine-shared/utils/index.d.ts +1 -0
- package/dist/_pkgs/affine-shared/utils/index.js +1 -0
- package/dist/_pkgs/affine-shared/utils/invisible-characters.d.ts +42 -0
- package/dist/_pkgs/affine-shared/utils/invisible-characters.js +81 -0
- package/dist/_pkgs/affine-widget-edgeless-zoom-toolbar/index.js +26 -26
- package/dist/_pkgs/affine-widget-edgeless-zoom-toolbar/zoom-toolbar.js +13 -2
- package/dist/_pkgs/affine-widget-keyboard-toolbar/icons.js +98 -98
- package/dist/_pkgs/affine-widget-page-dragging-area/index.js +10 -10
- package/dist/_pkgs/affine-widget-viewport-overlay/index.js +13 -13
- package/dist/_pkgs/data-view/core/utils/menu-title.js +17 -17
- package/dist/_pkgs/data-view/view-presets/table/pc/header/column-header.js +6 -0
- package/dist/_pkgs/data-view/view-presets/table/pc/table-view-ui-logic.js +5 -2
- package/dist/_pkgs/std/clipboard/clipboard.d.ts +15 -1
- package/dist/_pkgs/std/clipboard/clipboard.js +18 -0
- package/dist/_pkgs/std/inline/components/embed-gap.js +1 -1
- package/dist/_pkgs/std/inline/components/v-line.js +1 -1
- package/dist/_pkgs/std/inline/components/v-text.d.ts +15 -2
- package/dist/_pkgs/std/inline/components/v-text.js +147 -15
- package/dist/_pkgs/std/inline/consts.d.ts +5 -0
- package/dist/_pkgs/std/inline/consts.js +5 -0
- package/dist/_pkgs/std/inline/utils/index.d.ts +1 -0
- package/dist/_pkgs/std/inline/utils/index.js +1 -0
- package/dist/_pkgs/std/inline/utils/invisible-characters.d.ts +57 -0
- package/dist/_pkgs/std/inline/utils/invisible-characters.js +162 -0
- package/dist/_pkgs/std/inline/utils/point-conversion.js +3 -3
- package/dist/_pkgs/std/inline/utils/text.d.ts +8 -0
- package/dist/_pkgs/std/inline/utils/text.js +11 -2
- package/dist/_pkgs/store/model/store/crud.d.ts +28 -0
- package/dist/_pkgs/store/model/store/crud.js +97 -30
- package/dist/_pkgs/store/model/store/query.js +3 -2
- package/dist/_pkgs/store/model/store/store.d.ts +21 -0
- package/dist/_pkgs/store/model/store/store.js +88 -21
- package/dist/_pkgs/store/transformer/transformer.d.ts +46 -0
- package/dist/_pkgs/store/transformer/transformer.js +72 -0
- package/dist/commands.js +6 -1
- package/dist/translations.js +2 -0
- package/package.json +3 -4
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { NOTATION_NEUTRALS } from '../../affine-shared/headless.js';
|
|
2
|
+
/**
|
|
3
|
+
* How many lines a `fit: 'wrap'` label takes when its declaration names no
|
|
4
|
+
* `maxLines`: enough for a sentence-long name, few enough that a pasted
|
|
5
|
+
* paragraph does not turn a title band into a column.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_BACKGROUND_WRAP_MAX_LINES = 3;
|
|
2
8
|
/**
|
|
3
9
|
* The default card: a plain rectangle in the shared notation scale's card
|
|
4
10
|
* fill, no border, no radius.
|
|
@@ -6,9 +12,17 @@ import { NOTATION_NEUTRALS } from '../../affine-shared/headless.js';
|
|
|
6
12
|
export const DEFAULT_BACKGROUND_SURFACE = {
|
|
7
13
|
fill: NOTATION_NEUTRALS.cardFill,
|
|
8
14
|
};
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The plot rectangle of a background of size `w × h`, in element-local units.
|
|
17
|
+
*
|
|
18
|
+
* `margin` defaults to the DECLARED one. The painted chrome — the renderer, the
|
|
19
|
+
* label hit boxes, a framework's own hit tests — passes the margin
|
|
20
|
+
* `backgroundBandWidths` resolved instead, which a wrapped band label may have
|
|
21
|
+
* grown. The semantic readers (facts, audit, rules, interchange exports) keep
|
|
22
|
+
* the declared plot: where an artefact sits in a frame is a question about the
|
|
23
|
+
* frame, and its answer must not change because somebody renamed it.
|
|
24
|
+
*/
|
|
25
|
+
export function backgroundPlot(def, w, h, margin = def.geometry.margin) {
|
|
12
26
|
const x0 = margin.left;
|
|
13
27
|
const y0 = margin.top;
|
|
14
28
|
const x1 = w - margin.right;
|
|
@@ -151,16 +165,21 @@ export function backgroundInstanceZones(def, model) {
|
|
|
151
165
|
* Clamped to the plot, the way a side band clamps to an element narrower than
|
|
152
166
|
* its own margin ({@link BackgroundSideBandDef}) — the same degenerate case,
|
|
153
167
|
* and the same answer.
|
|
168
|
+
*
|
|
169
|
+
* `bandWidth` defaults to the DECLARED strip width; the painted chrome passes
|
|
170
|
+
* the one `backgroundBandWidths` resolved, which a wrapped name may have grown
|
|
171
|
+
* — and which is the same for every zone of the instance.
|
|
154
172
|
*/
|
|
155
|
-
export function backgroundInstanceZoneBand(def, zone, plot) {
|
|
173
|
+
export function backgroundInstanceZoneBand(def, zone, plot, bandWidth) {
|
|
156
174
|
const spec = def.instanceZones;
|
|
157
175
|
const band = spec?.label?.band;
|
|
158
176
|
if (spec === undefined || band === undefined)
|
|
159
177
|
return null;
|
|
160
|
-
|
|
178
|
+
const thickness = bandWidth ?? band.width;
|
|
179
|
+
if (!(thickness > 0))
|
|
161
180
|
return null;
|
|
162
181
|
if (spec.stack === 'y') {
|
|
163
|
-
const width = Math.min(
|
|
182
|
+
const width = Math.min(thickness, plot.width);
|
|
164
183
|
if (!(width > 0))
|
|
165
184
|
return null;
|
|
166
185
|
return {
|
|
@@ -170,7 +189,7 @@ export function backgroundInstanceZoneBand(def, zone, plot) {
|
|
|
170
189
|
h: zone.rect.h * plot.height,
|
|
171
190
|
};
|
|
172
191
|
}
|
|
173
|
-
const height = Math.min(
|
|
192
|
+
const height = Math.min(thickness, plot.height);
|
|
174
193
|
if (!(height > 0))
|
|
175
194
|
return null;
|
|
176
195
|
return {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fitting a background label to the room it is given: cut it, or wrap it.
|
|
3
|
+
*
|
|
4
|
+
* A pool's participant name and its lane names are written ALONG a band — the
|
|
5
|
+
* participant band and the lane strip of the BPMN pool. A name longer than the
|
|
6
|
+
* band used to run past its end, across the frame and into the next lane,
|
|
7
|
+
* because the primitive drew every text with a single `fillText` and never
|
|
8
|
+
* measured it. Two answers live here, both opt-in per declaration:
|
|
9
|
+
*
|
|
10
|
+
* - `'ellipsis'` cuts the words to ONE line with "…" (PR #532);
|
|
11
|
+
* - `'wrap'` breaks them on words into several lines stacked ACROSS the band,
|
|
12
|
+
* and the band grows to hold them (`band-widths.ts`), up to a declared
|
|
13
|
+
* `maxLines`, past which the last line is cut with "…" all the same. The PO
|
|
14
|
+
* asked for this one over the cut (2026-10-27): a band that thickens with its
|
|
15
|
+
* name reads as a title, a cut name reads as a bug.
|
|
16
|
+
*
|
|
17
|
+
* Either way the STORED name stays whole (the rename editor shows it in full),
|
|
18
|
+
* and nothing is written to the document: the band's thickness is derived at
|
|
19
|
+
* paint time from what is painted.
|
|
20
|
+
*
|
|
21
|
+
* Measured on the context the label is painted on, with the font already set,
|
|
22
|
+
* so the fit matches what is drawn — on screen and in the SVG export, which
|
|
23
|
+
* paints through these same renderers with real text metrics. The text package
|
|
24
|
+
* has its own `truncateTextByWidth`, but `gfx/text` sits above this package and
|
|
25
|
+
* cannot be imported from it; this is the same idea, measured by the context.
|
|
26
|
+
*/
|
|
27
|
+
/** The mark a cut label ends with. */
|
|
28
|
+
export declare const BACKGROUND_TEXT_ELLIPSIS = "\u2026";
|
|
29
|
+
/**
|
|
30
|
+
* Model units kept clear at each end of the room a fitted label is given, so a
|
|
31
|
+
* cut name never touches the frame it is written against. A wrapped label keeps
|
|
32
|
+
* the same clearance on each side ACROSS its band.
|
|
33
|
+
*/
|
|
34
|
+
export declare const BACKGROUND_TEXT_FIT_PADDING = 4;
|
|
35
|
+
/**
|
|
36
|
+
* `words` as drawn in at most `room` units of the context's current font: the
|
|
37
|
+
* text itself when it fits, otherwise its longest prefix that still fits once
|
|
38
|
+
* an ellipsis is appended, and nothing when even the ellipsis is too wide.
|
|
39
|
+
*
|
|
40
|
+
* Cut on code points, not UTF-16 units, so an emoji in a lane name is never
|
|
41
|
+
* split into a lone surrogate.
|
|
42
|
+
*/
|
|
43
|
+
export declare function ellipsizeToWidth(ctx: Pick<CanvasRenderingContext2D, 'measureText'>, words: string, room: number): string;
|
|
44
|
+
/**
|
|
45
|
+
* `words` broken on whitespace into at most `maxLines` lines of at most `room`
|
|
46
|
+
* units each, in the context's current font.
|
|
47
|
+
*
|
|
48
|
+
* Greedy, which is what every text renderer in the product does: a word goes
|
|
49
|
+
* on the current line while the line still fits. A single word wider than the
|
|
50
|
+
* room keeps a line of its own and is cut with "…" — a lane named by one long
|
|
51
|
+
* identifier still shows its start rather than nothing. When the words do not
|
|
52
|
+
* fit in `maxLines`, the last line takes everything left over and is cut, so
|
|
53
|
+
* the "…" says there is more, exactly as the one-line cut always did.
|
|
54
|
+
*
|
|
55
|
+
* Empty for empty or blank words and for a room with no width: there is
|
|
56
|
+
* nothing to draw, and no line to make a band thicker for.
|
|
57
|
+
*/
|
|
58
|
+
export declare function wrapToWidth(ctx: Pick<CanvasRenderingContext2D, 'measureText'>, words: string, room: number, maxLines: number): string[];
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fitting a background label to the room it is given: cut it, or wrap it.
|
|
3
|
+
*
|
|
4
|
+
* A pool's participant name and its lane names are written ALONG a band — the
|
|
5
|
+
* participant band and the lane strip of the BPMN pool. A name longer than the
|
|
6
|
+
* band used to run past its end, across the frame and into the next lane,
|
|
7
|
+
* because the primitive drew every text with a single `fillText` and never
|
|
8
|
+
* measured it. Two answers live here, both opt-in per declaration:
|
|
9
|
+
*
|
|
10
|
+
* - `'ellipsis'` cuts the words to ONE line with "…" (PR #532);
|
|
11
|
+
* - `'wrap'` breaks them on words into several lines stacked ACROSS the band,
|
|
12
|
+
* and the band grows to hold them (`band-widths.ts`), up to a declared
|
|
13
|
+
* `maxLines`, past which the last line is cut with "…" all the same. The PO
|
|
14
|
+
* asked for this one over the cut (2026-10-27): a band that thickens with its
|
|
15
|
+
* name reads as a title, a cut name reads as a bug.
|
|
16
|
+
*
|
|
17
|
+
* Either way the STORED name stays whole (the rename editor shows it in full),
|
|
18
|
+
* and nothing is written to the document: the band's thickness is derived at
|
|
19
|
+
* paint time from what is painted.
|
|
20
|
+
*
|
|
21
|
+
* Measured on the context the label is painted on, with the font already set,
|
|
22
|
+
* so the fit matches what is drawn — on screen and in the SVG export, which
|
|
23
|
+
* paints through these same renderers with real text metrics. The text package
|
|
24
|
+
* has its own `truncateTextByWidth`, but `gfx/text` sits above this package and
|
|
25
|
+
* cannot be imported from it; this is the same idea, measured by the context.
|
|
26
|
+
*/
|
|
27
|
+
/** The mark a cut label ends with. */
|
|
28
|
+
export const BACKGROUND_TEXT_ELLIPSIS = '…';
|
|
29
|
+
/**
|
|
30
|
+
* Model units kept clear at each end of the room a fitted label is given, so a
|
|
31
|
+
* cut name never touches the frame it is written against. A wrapped label keeps
|
|
32
|
+
* the same clearance on each side ACROSS its band.
|
|
33
|
+
*/
|
|
34
|
+
export const BACKGROUND_TEXT_FIT_PADDING = 4;
|
|
35
|
+
/**
|
|
36
|
+
* `words` as drawn in at most `room` units of the context's current font: the
|
|
37
|
+
* text itself when it fits, otherwise its longest prefix that still fits once
|
|
38
|
+
* an ellipsis is appended, and nothing when even the ellipsis is too wide.
|
|
39
|
+
*
|
|
40
|
+
* Cut on code points, not UTF-16 units, so an emoji in a lane name is never
|
|
41
|
+
* split into a lone surrogate.
|
|
42
|
+
*/
|
|
43
|
+
export function ellipsizeToWidth(ctx, words, room) {
|
|
44
|
+
if (!words)
|
|
45
|
+
return words;
|
|
46
|
+
if (!(room > 0))
|
|
47
|
+
return '';
|
|
48
|
+
if (ctx.measureText(words).width <= room)
|
|
49
|
+
return words;
|
|
50
|
+
if (ctx.measureText(BACKGROUND_TEXT_ELLIPSIS).width > room)
|
|
51
|
+
return '';
|
|
52
|
+
const chars = Array.from(words);
|
|
53
|
+
// Binary search on the prefix length: the widest prefix whose ellipsized
|
|
54
|
+
// form still fits. Width grows with the prefix, so the predicate is monotone.
|
|
55
|
+
let lo = 0;
|
|
56
|
+
let hi = chars.length - 1;
|
|
57
|
+
while (lo < hi) {
|
|
58
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
59
|
+
const candidate = chars.slice(0, mid).join('') + BACKGROUND_TEXT_ELLIPSIS;
|
|
60
|
+
if (ctx.measureText(candidate).width <= room)
|
|
61
|
+
lo = mid;
|
|
62
|
+
else
|
|
63
|
+
hi = mid - 1;
|
|
64
|
+
}
|
|
65
|
+
return chars.slice(0, lo).join('').trimEnd() + BACKGROUND_TEXT_ELLIPSIS;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `words` broken on whitespace into at most `maxLines` lines of at most `room`
|
|
69
|
+
* units each, in the context's current font.
|
|
70
|
+
*
|
|
71
|
+
* Greedy, which is what every text renderer in the product does: a word goes
|
|
72
|
+
* on the current line while the line still fits. A single word wider than the
|
|
73
|
+
* room keeps a line of its own and is cut with "…" — a lane named by one long
|
|
74
|
+
* identifier still shows its start rather than nothing. When the words do not
|
|
75
|
+
* fit in `maxLines`, the last line takes everything left over and is cut, so
|
|
76
|
+
* the "…" says there is more, exactly as the one-line cut always did.
|
|
77
|
+
*
|
|
78
|
+
* Empty for empty or blank words and for a room with no width: there is
|
|
79
|
+
* nothing to draw, and no line to make a band thicker for.
|
|
80
|
+
*/
|
|
81
|
+
export function wrapToWidth(ctx, words, room, maxLines) {
|
|
82
|
+
if (!(room > 0))
|
|
83
|
+
return [];
|
|
84
|
+
const tokens = words.split(/\s+/).filter(Boolean);
|
|
85
|
+
if (tokens.length === 0)
|
|
86
|
+
return [];
|
|
87
|
+
const limit = Math.max(1, Math.floor(maxLines));
|
|
88
|
+
const lines = [];
|
|
89
|
+
let current = '';
|
|
90
|
+
let i = 0;
|
|
91
|
+
for (; i < tokens.length; i++) {
|
|
92
|
+
const candidate = current ? `${current} ${tokens[i]}` : tokens[i];
|
|
93
|
+
if (!current || ctx.measureText(candidate).width <= room) {
|
|
94
|
+
current = candidate;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (lines.length === limit - 1)
|
|
98
|
+
break;
|
|
99
|
+
lines.push(current);
|
|
100
|
+
current = tokens[i];
|
|
101
|
+
}
|
|
102
|
+
// Out of lines with words still waiting: the last line carries them all, and
|
|
103
|
+
// the cut below ends it with "…".
|
|
104
|
+
if (i < tokens.length)
|
|
105
|
+
current = [current, ...tokens.slice(i)].join(' ');
|
|
106
|
+
lines.push(current);
|
|
107
|
+
return lines
|
|
108
|
+
.map(line => ellipsizeToWidth(ctx, line, room))
|
|
109
|
+
.filter(line => line !== '');
|
|
110
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export * from './background-view.js';
|
|
2
|
+
export * from './band-widths.js';
|
|
2
3
|
export * from './def.js';
|
|
4
|
+
export * from './ellipsis.js';
|
|
3
5
|
export * from './facts.js';
|
|
4
6
|
export * from './fit.js';
|
|
5
7
|
export * from './interaction.js';
|
|
6
8
|
export * from './labels.js';
|
|
9
|
+
export * from './measure-text.js';
|
|
7
10
|
export * from './renderer.js';
|
|
8
11
|
export * from './stacking.js';
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export * from './background-view.js';
|
|
2
|
+
export * from './band-widths.js';
|
|
2
3
|
export * from './def.js';
|
|
4
|
+
export * from './ellipsis.js';
|
|
3
5
|
export * from './facts.js';
|
|
4
6
|
export * from './fit.js';
|
|
5
7
|
export * from './interaction.js';
|
|
6
8
|
export * from './labels.js';
|
|
9
|
+
export * from './measure-text.js';
|
|
7
10
|
export * from './renderer.js';
|
|
8
11
|
export * from './stacking.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TranslationService } from '../../affine-shared/services/index.js';
|
|
2
2
|
import type { BackgroundLabelSource, BackgroundTextDef, FrameworkBackgroundDef } from './def.js';
|
|
3
|
+
import { type BackgroundTextMeasure } from './band-widths.js';
|
|
3
4
|
/** A background element, seen from the primitive: a bag of declared props. */
|
|
4
5
|
export type BackgroundModelLike = Record<string, unknown>;
|
|
5
6
|
/**
|
|
@@ -107,8 +108,17 @@ export declare function backgroundTexts(def: FrameworkBackgroundDef): Background
|
|
|
107
108
|
* not user text, and is not offered for editing.
|
|
108
109
|
*
|
|
109
110
|
* Positions come from the same declaration the renderer paints from, so the
|
|
110
|
-
* boxes track the drawn text by construction
|
|
111
|
+
* boxes track the drawn text by construction — including a band grown by a
|
|
112
|
+
* wrapped label, read off the same resolver (`backgroundBandWidths`, measured
|
|
113
|
+
* here without a painting context), with one box round all of its lines.
|
|
111
114
|
*/
|
|
112
|
-
export declare function backgroundLabelHits(def: FrameworkBackgroundDef, model: BackgroundModelLike, w: number, h: number, translation?: TranslationService | null
|
|
115
|
+
export declare function backgroundLabelHits(def: FrameworkBackgroundDef, model: BackgroundModelLike, w: number, h: number, translation?: TranslationService | null,
|
|
116
|
+
/**
|
|
117
|
+
* How a wrapped band label is measured to size its band. The estimate by
|
|
118
|
+
* default, since this function is reachable from the headless `./interchange`
|
|
119
|
+
* entry; a view passes `measureBackgroundText` so its boxes sit on the band
|
|
120
|
+
* as painted.
|
|
121
|
+
*/
|
|
122
|
+
measure?: BackgroundTextMeasure): BackgroundLabelHit[];
|
|
113
123
|
/** First label whose padded box contains the element-local point, or null. */
|
|
114
124
|
export declare function hitTestBackgroundLabel(hits: readonly BackgroundLabelHit[], lx: number, ly: number): BackgroundLabelHit | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { backgroundBandWidths, backgroundWrapLineHeight, estimateBackgroundText, } from './band-widths.js';
|
|
1
2
|
import { backgroundPlot, backgroundPoint } from './def.js';
|
|
2
3
|
/**
|
|
3
4
|
* Complain once per distinct problem.
|
|
@@ -166,10 +167,20 @@ export function backgroundTexts(def) {
|
|
|
166
167
|
* not user text, and is not offered for editing.
|
|
167
168
|
*
|
|
168
169
|
* Positions come from the same declaration the renderer paints from, so the
|
|
169
|
-
* boxes track the drawn text by construction
|
|
170
|
+
* boxes track the drawn text by construction — including a band grown by a
|
|
171
|
+
* wrapped label, read off the same resolver (`backgroundBandWidths`, measured
|
|
172
|
+
* here without a painting context), with one box round all of its lines.
|
|
170
173
|
*/
|
|
171
|
-
export function backgroundLabelHits(def, model, w, h, translation
|
|
172
|
-
|
|
174
|
+
export function backgroundLabelHits(def, model, w, h, translation,
|
|
175
|
+
/**
|
|
176
|
+
* How a wrapped band label is measured to size its band. The estimate by
|
|
177
|
+
* default, since this function is reachable from the headless `./interchange`
|
|
178
|
+
* entry; a view passes `measureBackgroundText` so its boxes sit on the band
|
|
179
|
+
* as painted.
|
|
180
|
+
*/
|
|
181
|
+
measure = estimateBackgroundText) {
|
|
182
|
+
const layout = backgroundBandWidths(def, model, w, h, measure, translation);
|
|
183
|
+
const plot = backgroundPlot(def, w, h, layout.margin);
|
|
173
184
|
const hits = [];
|
|
174
185
|
for (const text of backgroundTexts(def)) {
|
|
175
186
|
if (text.prop === undefined)
|
|
@@ -182,12 +193,25 @@ export function backgroundLabelHits(def, model, w, h, translation) {
|
|
|
182
193
|
if (!backgroundVisible(text.visibleProp, model))
|
|
183
194
|
continue;
|
|
184
195
|
const words = backgroundLabelText(text, model, translation);
|
|
185
|
-
const [
|
|
196
|
+
const [px, py] = backgroundPoint(text.anchor, plot);
|
|
197
|
+
const wrap = layout.labels.get(text);
|
|
198
|
+
const ax = px + (wrap?.shift[0] ?? 0);
|
|
199
|
+
const ay = py + (wrap?.shift[1] ?? 0);
|
|
200
|
+
const lines = wrap && wrap.lines.length > 1 ? wrap.lines : [words];
|
|
201
|
+
const lineHeight = backgroundWrapLineHeight(text.style);
|
|
202
|
+
const boxes = lines.map((line, i) => {
|
|
203
|
+
// The renderer's own stacking: centred on the anchor, across the text.
|
|
204
|
+
const across = (i - (lines.length - 1) / 2) * lineHeight;
|
|
205
|
+
return backgroundTextHitBox(line, text.style.size, text.vertical ? ax + across : ax, text.vertical ? ay : ay + across, text.align, text.vertical);
|
|
206
|
+
});
|
|
186
207
|
hits.push({
|
|
187
208
|
id: text.id,
|
|
188
209
|
prop: text.prop,
|
|
189
210
|
text: words,
|
|
190
|
-
|
|
211
|
+
minX: Math.min(...boxes.map(box => box.minX)),
|
|
212
|
+
minY: Math.min(...boxes.map(box => box.minY)),
|
|
213
|
+
maxX: Math.max(...boxes.map(box => box.maxX)),
|
|
214
|
+
maxY: Math.max(...boxes.map(box => box.maxY)),
|
|
191
215
|
});
|
|
192
216
|
}
|
|
193
217
|
return hits;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measure background text with no painting context at hand — a hit test runs on
|
|
3
|
+
* a pointer event, not inside a paint — for `backgroundBandWidths`.
|
|
4
|
+
*
|
|
5
|
+
* A module-level offscreen 2D context where a document exists, so a hit box is
|
|
6
|
+
* measured with the same metrics the canvas paints with, and a band a long name
|
|
7
|
+
* widened is aimed at where it is drawn; the estimate where there is no
|
|
8
|
+
* document, or where the DOM is a test double that cannot measure.
|
|
9
|
+
*
|
|
10
|
+
* In a file of its own, out of the `./interchange` graph: `band-widths.ts` is
|
|
11
|
+
* reachable from that headless entry and must not touch the DOM
|
|
12
|
+
* (`interchange-headless.unit.spec.ts` in `affine/all`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function measureBackgroundText(text: string, font: string): number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { estimateBackgroundText } from './band-widths.js';
|
|
2
|
+
/** `undefined` until first asked; `null` where there is no canvas to ask. */
|
|
3
|
+
let offscreen;
|
|
4
|
+
/**
|
|
5
|
+
* Measure background text with no painting context at hand — a hit test runs on
|
|
6
|
+
* a pointer event, not inside a paint — for `backgroundBandWidths`.
|
|
7
|
+
*
|
|
8
|
+
* A module-level offscreen 2D context where a document exists, so a hit box is
|
|
9
|
+
* measured with the same metrics the canvas paints with, and a band a long name
|
|
10
|
+
* widened is aimed at where it is drawn; the estimate where there is no
|
|
11
|
+
* document, or where the DOM is a test double that cannot measure.
|
|
12
|
+
*
|
|
13
|
+
* In a file of its own, out of the `./interchange` graph: `band-widths.ts` is
|
|
14
|
+
* reachable from that headless entry and must not touch the DOM
|
|
15
|
+
* (`interchange-headless.unit.spec.ts` in `affine/all`).
|
|
16
|
+
*/
|
|
17
|
+
export function measureBackgroundText(text, font) {
|
|
18
|
+
if (offscreen === undefined) {
|
|
19
|
+
offscreen =
|
|
20
|
+
typeof document === 'undefined'
|
|
21
|
+
? null
|
|
22
|
+
: (document.createElement('canvas').getContext?.('2d') ?? null);
|
|
23
|
+
}
|
|
24
|
+
if (offscreen) {
|
|
25
|
+
offscreen.font = font;
|
|
26
|
+
const width = offscreen.measureText(text)?.width;
|
|
27
|
+
if (typeof width === 'number' && Number.isFinite(width))
|
|
28
|
+
return width;
|
|
29
|
+
}
|
|
30
|
+
return estimateBackgroundText(text, font);
|
|
31
|
+
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import type { GfxPrimitiveElementModel } from '../../std/gfx/index.js';
|
|
2
2
|
import type { ElementRenderer } from '../renderer/elements/index.js';
|
|
3
3
|
import type { FrameworkBackgroundDef } from './def.js';
|
|
4
|
+
import { type BackgroundBandLayout } from './band-widths.js';
|
|
5
|
+
/** What a framework may ask of the renderer beyond the declaration. */
|
|
6
|
+
export interface FrameworkBackgroundRendererOptions<T> {
|
|
7
|
+
/**
|
|
8
|
+
* Called on every paint with the band widths that paint measured
|
|
9
|
+
* (`backgroundBandWidths`), before anything is drawn. Named consumer: the
|
|
10
|
+
* BPMN pool, which caches them on its model (`paintedBands`, a `@local()`
|
|
11
|
+
* field, never stored) so that picking — which asks the headless model —
|
|
12
|
+
* selects by the bands as painted. A consumer that writes must write only
|
|
13
|
+
* when the widths changed: a local write repaints.
|
|
14
|
+
*/
|
|
15
|
+
onBands?: (model: T, layout: BackgroundBandLayout) => void;
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Build the canvas renderer of one declaration. Pure: the same declaration
|
|
6
19
|
* always paints the same picture for the same model and size.
|
|
7
20
|
*/
|
|
8
|
-
export declare function createFrameworkBackgroundRenderer<T extends GfxPrimitiveElementModel>(def: FrameworkBackgroundDef): ElementRenderer<T>;
|
|
21
|
+
export declare function createFrameworkBackgroundRenderer<T extends GfxPrimitiveElementModel>(def: FrameworkBackgroundDef, options?: FrameworkBackgroundRendererOptions<T>): ElementRenderer<T>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { TranslationProvider } from '../../affine-shared/services/index.js';
|
|
2
2
|
import { notationColor, } from '../renderer/notation-tint.js';
|
|
3
3
|
import { backgroundColor, backgroundInstanceZoneBand, backgroundInstanceZones, backgroundPlot, backgroundPoint, BROKEN_BACKGROUND_COLOR, DEFAULT_BACKGROUND_SURFACE, warnBrokenBackgroundColor, } from './def.js';
|
|
4
|
+
import { backgroundBandWidths, backgroundFont, backgroundTextRoom, backgroundWrapLineHeight, DEFAULT_BACKGROUND_FONT_FAMILY, } from './band-widths.js';
|
|
5
|
+
import { BACKGROUND_TEXT_FIT_PADDING, ellipsizeToWidth } from './ellipsis.js';
|
|
4
6
|
import { backgroundInVariant, backgroundLabelText, backgroundTexts, backgroundVisible, } from './labels.js';
|
|
5
7
|
/**
|
|
6
8
|
* The framework-background PRIMITIVE (PF2.1): one renderer, driven entirely by
|
|
@@ -79,13 +81,6 @@ function rgba(hex, alpha) {
|
|
|
79
81
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
80
82
|
return `rgba(${r},${g},${b},${alpha})`;
|
|
81
83
|
}
|
|
82
|
-
function fontOf(style, fallbackFamily) {
|
|
83
|
-
const italic = style.italic ? 'italic ' : '';
|
|
84
|
-
const weight = style.weight ? `${style.weight} ` : '';
|
|
85
|
-
return `${italic}${weight}${style.size}px ${style.family ?? fallbackFamily}`;
|
|
86
|
-
}
|
|
87
|
-
/** The family a declaration that names none falls back to. */
|
|
88
|
-
const DEFAULT_FONT_FAMILY = 'Inter, sans-serif';
|
|
89
84
|
/**
|
|
90
85
|
* Where an instance zone's name sits in its band when the declaration states no
|
|
91
86
|
* offset: 8 model units in from the leading edge, 18 down from the top of the
|
|
@@ -103,9 +98,9 @@ const DEFAULT_INSTANCE_ZONE_LABEL_DY = 18;
|
|
|
103
98
|
* Build the canvas renderer of one declaration. Pure: the same declaration
|
|
104
99
|
* always paints the same picture for the same model and size.
|
|
105
100
|
*/
|
|
106
|
-
export function createFrameworkBackgroundRenderer(def) {
|
|
101
|
+
export function createFrameworkBackgroundRenderer(def, options = {}) {
|
|
107
102
|
const palette = def.chrome?.palette;
|
|
108
|
-
const family = def.chrome?.fontFamily ??
|
|
103
|
+
const family = def.chrome?.fontFamily ?? DEFAULT_BACKGROUND_FONT_FAMILY;
|
|
109
104
|
const surface = def.chrome?.surface ?? DEFAULT_BACKGROUND_SURFACE;
|
|
110
105
|
const declared = (c) => backgroundColor(c, palette);
|
|
111
106
|
const texts = backgroundTexts(def);
|
|
@@ -140,7 +135,16 @@ export function createFrameworkBackgroundRenderer(def) {
|
|
|
140
135
|
.translateSelf(cx, cy)
|
|
141
136
|
.rotateSelf(model.rotate)
|
|
142
137
|
.translateSelf(-cx, -cy));
|
|
143
|
-
|
|
138
|
+
// The bands as thick as their wrapped labels need, measured on this very
|
|
139
|
+
// context so the band holds exactly the lines painted in it. Every piece of
|
|
140
|
+
// chrome below paints from this plot; a declaration that wraps nothing gets
|
|
141
|
+
// its declared margin back without a measurement.
|
|
142
|
+
const layout = backgroundBandWidths(def, props, w, h, (text, font) => {
|
|
143
|
+
ctx.font = font;
|
|
144
|
+
return ctx.measureText(text).width;
|
|
145
|
+
}, translation);
|
|
146
|
+
options.onBands?.(model, layout);
|
|
147
|
+
const plot = backgroundPlot(def, w, h, layout.margin);
|
|
144
148
|
const { x0, y0, x1, y1 } = plot;
|
|
145
149
|
const pw = plot.width;
|
|
146
150
|
const ph = plot.height;
|
|
@@ -150,26 +154,60 @@ export function createFrameworkBackgroundRenderer(def) {
|
|
|
150
154
|
ctx.lineTo(bx, by);
|
|
151
155
|
ctx.stroke();
|
|
152
156
|
};
|
|
153
|
-
const drawText = (words, ax, ay, style, align, vertical
|
|
157
|
+
const drawText = (words, ax, ay, style, align, vertical,
|
|
158
|
+
/**
|
|
159
|
+
* The length the words may take, in model units; absent draws them
|
|
160
|
+
* whole. Measured AFTER the font is set, so the cut is in the font the
|
|
161
|
+
* words are drawn in.
|
|
162
|
+
*/
|
|
163
|
+
room,
|
|
164
|
+
/**
|
|
165
|
+
* The lines a wrapped label was broken into by `backgroundBandWidths`,
|
|
166
|
+
* drawn instead of `words`: stacked ACROSS the text's direction and
|
|
167
|
+
* centred on the anchor, one line height apart. Taken from the resolver
|
|
168
|
+
* rather than wrapped again here, so the band and its lines are one
|
|
169
|
+
* measurement.
|
|
170
|
+
*/
|
|
171
|
+
wrapped) => {
|
|
154
172
|
if (!words)
|
|
155
173
|
return;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
174
|
+
const font = backgroundFont(style, family);
|
|
175
|
+
let shown;
|
|
176
|
+
if (wrapped)
|
|
177
|
+
shown = wrapped;
|
|
178
|
+
else if (room === undefined)
|
|
179
|
+
shown = [words];
|
|
180
|
+
else {
|
|
181
|
+
ctx.font = font;
|
|
182
|
+
shown = [ellipsizeToWidth(ctx, words, room)];
|
|
183
|
+
}
|
|
184
|
+
const lineHeight = backgroundWrapLineHeight(style);
|
|
185
|
+
shown.forEach((text, i) => {
|
|
186
|
+
if (!text)
|
|
187
|
+
return;
|
|
188
|
+
// Zero for a single line, which is drawn exactly where it always was.
|
|
189
|
+
const across = (i - (shown.length - 1) / 2) * lineHeight;
|
|
190
|
+
if (vertical) {
|
|
191
|
+
// Rotated -90°, the next line is to the RIGHT of the one before:
|
|
192
|
+
// the glyphs' tops point left, so a reader turning their head reads
|
|
193
|
+
// the lines left to right.
|
|
194
|
+
ctx.save();
|
|
195
|
+
ctx.translate(ax + across, ay);
|
|
196
|
+
ctx.rotate(-Math.PI / 2);
|
|
197
|
+
ctx.font = font;
|
|
198
|
+
ctx.fillStyle = color(style.color);
|
|
199
|
+
ctx.textAlign = align ?? 'center';
|
|
200
|
+
ctx.textBaseline = style.baseline ?? 'alphabetic';
|
|
201
|
+
ctx.fillText(text, 0, 0);
|
|
202
|
+
ctx.restore();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
ctx.font = font;
|
|
161
206
|
ctx.fillStyle = color(style.color);
|
|
162
|
-
ctx.textAlign = align ?? '
|
|
207
|
+
ctx.textAlign = align ?? 'left';
|
|
163
208
|
ctx.textBaseline = style.baseline ?? 'alphabetic';
|
|
164
|
-
ctx.fillText(
|
|
165
|
-
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
ctx.font = fontOf(style, family);
|
|
169
|
-
ctx.fillStyle = color(style.color);
|
|
170
|
-
ctx.textAlign = align ?? 'left';
|
|
171
|
-
ctx.textBaseline = style.baseline ?? 'alphabetic';
|
|
172
|
-
ctx.fillText(words, ax, ay);
|
|
209
|
+
ctx.fillText(text, ax, ay + across);
|
|
210
|
+
});
|
|
173
211
|
};
|
|
174
212
|
// ── 1. The card, and the bands that dress it ────────────────────────
|
|
175
213
|
const border = surface.border;
|
|
@@ -330,8 +368,18 @@ export function createFrameworkBackgroundRenderer(def) {
|
|
|
330
368
|
continue;
|
|
331
369
|
if (!visible(text.visibleProp))
|
|
332
370
|
continue;
|
|
333
|
-
const [
|
|
334
|
-
|
|
371
|
+
const [px, py] = backgroundPoint(text.anchor, plot);
|
|
372
|
+
// A wrapped band label keeps its place relative to the middle of its
|
|
373
|
+
// band, however much the band grew (`BackgroundWrappedLabel.shift`).
|
|
374
|
+
const wrap = layout.labels.get(text);
|
|
375
|
+
const tx = px + (wrap?.shift[0] ?? 0);
|
|
376
|
+
const ty = py + (wrap?.shift[1] ?? 0);
|
|
377
|
+
const vertical = text.vertical === true;
|
|
378
|
+
drawText(backgroundLabelText(text, props, translation), tx, ty, text.style, text.align, vertical,
|
|
379
|
+
// `'wrap'` on a text with no band to grow is cut like `'ellipsis'`.
|
|
380
|
+
text.fit !== undefined
|
|
381
|
+
? backgroundTextRoom(tx, ty, text.align, vertical, w, h)
|
|
382
|
+
: undefined, wrap?.lines);
|
|
335
383
|
}
|
|
336
384
|
// The instance's own zone names, after the declared texts — the same order
|
|
337
385
|
// the audit reports them in, and the same reason: what the framework says
|
|
@@ -350,6 +398,7 @@ export function createFrameworkBackgroundRenderer(def) {
|
|
|
350
398
|
const dx = zoneLabel.dx ?? DEFAULT_INSTANCE_ZONE_LABEL_DX;
|
|
351
399
|
const dy = zoneLabel.dy ?? DEFAULT_INSTANCE_ZONE_LABEL_DY;
|
|
352
400
|
const stackY = instanceZonesDef?.stack === 'y';
|
|
401
|
+
const fitZone = zoneLabel.fit !== undefined;
|
|
353
402
|
for (const zone of instanceZones) {
|
|
354
403
|
// The strip is drawn for EVERY zone, named or not: it is the lane's
|
|
355
404
|
// title band, and a lane whose name has been cleared still has one —
|
|
@@ -357,7 +406,7 @@ export function createFrameworkBackgroundRenderer(def) {
|
|
|
357
406
|
// a lane that silently loses its structure. The NAME is what is
|
|
358
407
|
// conditional, exactly as under the corner placement.
|
|
359
408
|
const strip = labelBand
|
|
360
|
-
? backgroundInstanceZoneBand(def, zone, plot)
|
|
409
|
+
? backgroundInstanceZoneBand(def, zone, plot, layout.zoneBandWidth)
|
|
361
410
|
: null;
|
|
362
411
|
if (strip && labelBand?.divider) {
|
|
363
412
|
const divider = labelBand.divider;
|
|
@@ -386,10 +435,14 @@ export function createFrameworkBackgroundRenderer(def) {
|
|
|
386
435
|
// strip, and a baseline anchor there would push the words against the
|
|
387
436
|
// divider.
|
|
388
437
|
const style = { ...zoneLabel.style, baseline: 'middle' };
|
|
389
|
-
drawText(zone.name, strip.x + strip.w / 2, strip.y + strip.h / 2, style, 'center', stackY
|
|
438
|
+
drawText(zone.name, strip.x + strip.w / 2, strip.y + strip.h / 2, style, 'center', stackY, fitZone
|
|
439
|
+
? (stackY ? strip.h : strip.w) - 2 * BACKGROUND_TEXT_FIT_PADDING
|
|
440
|
+
: undefined, layout.zoneLines.get(zone.id));
|
|
390
441
|
continue;
|
|
391
442
|
}
|
|
392
|
-
drawText(zone.name, x0 + zone.rect.x * pw + dx, y0 + zone.rect.y * ph + dy, zoneLabel.style, 'left', false
|
|
443
|
+
drawText(zone.name, x0 + zone.rect.x * pw + dx, y0 + zone.rect.y * ph + dy, zoneLabel.style, 'left', false, fitZone
|
|
444
|
+
? zone.rect.w * pw - dx - BACKGROUND_TEXT_FIT_PADDING
|
|
445
|
+
: undefined);
|
|
393
446
|
}
|
|
394
447
|
}
|
|
395
448
|
};
|