@formicoidea/labre-core 0.58.0 → 0.60.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 +175 -144
- package/architecture/domain.c4 +15 -9
- 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/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-surface/extensions/index.js +1 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend-label.d.ts +11 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend-label.js +16 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +1 -11
- package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -11
- package/dist/_pkgs/affine-block-surface/extensions/reading-toolbar.js +12 -12
- package/dist/_pkgs/affine-block-surface/extensions/toolbox.d.ts +62 -0
- package/dist/_pkgs/affine-block-surface/extensions/toolbox.js +1 -0
- 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-surface/interchange-headless.d.ts +2 -0
- package/dist/_pkgs/affine-block-surface/interchange-headless.js +2 -0
- 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-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-model/elements/c4/board.d.ts +0 -22
- package/dist/_pkgs/affine-model/elements/c4/board.js +1 -22
- package/dist/_pkgs/affine-model/elements/c4/consts.d.ts +29 -0
- package/dist/_pkgs/affine-model/elements/c4/consts.js +29 -0
- package/dist/_pkgs/affine-model/elements/c4/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/elements/c4/index.js +1 -0
- package/dist/_pkgs/affine-model/headless.d.ts +1 -0
- package/dist/_pkgs/affine-model/headless.js +1 -0
- 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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { backgroundInstanceZones, backgroundPlot, backgroundPoint, DEFAULT_BACKGROUND_WRAP_MAX_LINES, } from './def.js';
|
|
2
|
+
import { BACKGROUND_TEXT_FIT_PADDING, wrapToWidth } from './ellipsis.js';
|
|
3
|
+
import { backgroundInVariant, backgroundLabelText, backgroundVisible, } from './labels.js';
|
|
4
|
+
/**
|
|
5
|
+
* The line height of a wrapped label, as a multiple of its font size: the CSS
|
|
6
|
+
* `normal` line height. Not the 1.25 the slides use — at 1.25 a single 13-unit
|
|
7
|
+
* lane name would need 24.25 units and widen the BPMN pool's 24-unit lane strip
|
|
8
|
+
* by a quarter unit for a name that fits it; a band only grows for a SECOND
|
|
9
|
+
* line.
|
|
10
|
+
*/
|
|
11
|
+
export const BACKGROUND_WRAP_LINE_HEIGHT = 1.2;
|
|
12
|
+
/** The family a declaration that names none falls back to. */
|
|
13
|
+
export const DEFAULT_BACKGROUND_FONT_FAMILY = 'Inter, sans-serif';
|
|
14
|
+
/** The CSS font a background text is drawn in. */
|
|
15
|
+
export function backgroundFont(style, fallbackFamily = DEFAULT_BACKGROUND_FONT_FAMILY) {
|
|
16
|
+
const italic = style.italic ? 'italic ' : '';
|
|
17
|
+
const weight = style.weight ? `${style.weight} ` : '';
|
|
18
|
+
return `${italic}${weight}${style.size}px ${style.family ?? fallbackFamily}`;
|
|
19
|
+
}
|
|
20
|
+
/** Distance between two wrapped lines of this style, in model units. */
|
|
21
|
+
export function backgroundWrapLineHeight(style) {
|
|
22
|
+
return style.size * BACKGROUND_WRAP_LINE_HEIGHT;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The room a fitted declared text has: from its anchor to the card's edges
|
|
26
|
+
* along the direction it reads, on the side(s) its alignment grows towards,
|
|
27
|
+
* less {@link BACKGROUND_TEXT_FIT_PADDING} at each end. A vertical text reads
|
|
28
|
+
* bottom-to-top, so `left` grows UP from its anchor.
|
|
29
|
+
*/
|
|
30
|
+
export function backgroundTextRoom(ax, ay, align, vertical, w, h) {
|
|
31
|
+
const pad = BACKGROUND_TEXT_FIT_PADDING;
|
|
32
|
+
if (vertical) {
|
|
33
|
+
const up = ay - pad;
|
|
34
|
+
const down = h - ay - pad;
|
|
35
|
+
const side = align ?? 'center';
|
|
36
|
+
if (side === 'left')
|
|
37
|
+
return up;
|
|
38
|
+
if (side === 'right')
|
|
39
|
+
return down;
|
|
40
|
+
return 2 * Math.min(up, down);
|
|
41
|
+
}
|
|
42
|
+
const leftward = ax - pad;
|
|
43
|
+
const rightward = w - ax - pad;
|
|
44
|
+
const side = align ?? 'left';
|
|
45
|
+
if (side === 'left')
|
|
46
|
+
return rightward;
|
|
47
|
+
if (side === 'right')
|
|
48
|
+
return leftward;
|
|
49
|
+
return 2 * Math.min(leftward, rightward);
|
|
50
|
+
}
|
|
51
|
+
/** Rough advance per code point, as a fraction of the font size. */
|
|
52
|
+
const ESTIMATED_ADVANCE = 0.55;
|
|
53
|
+
/**
|
|
54
|
+
* A rough width with no canvas at all: 0.55 × the font size per code point, the
|
|
55
|
+
* ratio `svg-sketch.ts` estimates with. The default of every headless caller
|
|
56
|
+
* (this file is reachable from the `./interchange` entry, which loads no DOM);
|
|
57
|
+
* a caller with a document passes `measureBackgroundText` (`measure-text.ts`),
|
|
58
|
+
* which measures with the canvas's own metrics.
|
|
59
|
+
*/
|
|
60
|
+
export function estimateBackgroundText(text, font) {
|
|
61
|
+
const size = Number(/(\d+(?:\.\d+)?)px/.exec(font)?.[1]) || 12;
|
|
62
|
+
return Array.from(text).length * size * ESTIMATED_ADVANCE;
|
|
63
|
+
}
|
|
64
|
+
const NO_LABELS = new Map();
|
|
65
|
+
const NO_ZONE_LINES = new Map();
|
|
66
|
+
/**
|
|
67
|
+
* THE resolver of band thickness. Pure in its inputs: the declaration, the
|
|
68
|
+
* instance's props, its size, and how to measure text — the renderer passes its
|
|
69
|
+
* context, a hit test outside a paint `measureBackgroundText` (the same metrics
|
|
70
|
+
* from an offscreen canvas), and a caller with no DOM gets the estimate.
|
|
71
|
+
*
|
|
72
|
+
* - a SIDE band whose label wraps is `max(declared margin, lines × line height
|
|
73
|
+
* + 2 × padding)` thick, the lines broken over the room the label has along
|
|
74
|
+
* the band (`backgroundTextRoom`, from its anchor in the declared plot);
|
|
75
|
+
* - every INSTANCE-ZONE strip is one thickness: the declared width, or the
|
|
76
|
+
* most any zone's wrapped name needs, each name broken over its own zone's
|
|
77
|
+
* length less the padding. A lane with a long name widens every lane's strip,
|
|
78
|
+
* so the leading edges stay aligned.
|
|
79
|
+
*
|
|
80
|
+
* A declaration with no `'wrap'` anywhere gets its declared numbers back
|
|
81
|
+
* without a single measurement, so the frameworks that never asked for this
|
|
82
|
+
* pay nothing for it.
|
|
83
|
+
*
|
|
84
|
+
* ponytail: a text's room is measured against the DECLARED plot, so a
|
|
85
|
+
* declaration wrapping both a left and a top band would size each without the
|
|
86
|
+
* other's growth. Nothing declares two; upgrade by resolving the sides in turn
|
|
87
|
+
* against the plot resolved so far, the day one does.
|
|
88
|
+
*/
|
|
89
|
+
export function backgroundBandWidths(def, model, w, h, measure = estimateBackgroundText, translation) {
|
|
90
|
+
const declared = def.geometry.margin;
|
|
91
|
+
const zoneLabel = def.instanceZones?.label;
|
|
92
|
+
const wrapsBand = (def.chrome?.sideBands ?? []).some(band => band.label?.fit === 'wrap');
|
|
93
|
+
const wrapsZones = zoneLabel?.band !== undefined && zoneLabel.fit === 'wrap';
|
|
94
|
+
if (!wrapsBand && !wrapsZones) {
|
|
95
|
+
return {
|
|
96
|
+
margin: declared,
|
|
97
|
+
zoneBandWidth: zoneLabel?.band?.width,
|
|
98
|
+
labels: NO_LABELS,
|
|
99
|
+
zoneLines: NO_ZONE_LINES,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const family = def.chrome?.fontFamily ?? DEFAULT_BACKGROUND_FONT_FAMILY;
|
|
103
|
+
const pad = BACKGROUND_TEXT_FIT_PADDING;
|
|
104
|
+
const measurer = (font) => ({
|
|
105
|
+
measureText: (text) => ({ width: measure(text, font) }),
|
|
106
|
+
});
|
|
107
|
+
const thicknessFor = (lines, style) => lines > 0 ? lines * backgroundWrapLineHeight(style) + 2 * pad : 0;
|
|
108
|
+
const margin = { ...declared };
|
|
109
|
+
const wrapped = [];
|
|
110
|
+
const lines = new Map();
|
|
111
|
+
if (wrapsBand) {
|
|
112
|
+
const plot = backgroundPlot(def, w, h);
|
|
113
|
+
for (const band of def.chrome?.sideBands ?? []) {
|
|
114
|
+
const label = band.label;
|
|
115
|
+
if (label?.fit !== 'wrap')
|
|
116
|
+
continue;
|
|
117
|
+
// A label the instance does not paint holds nothing open.
|
|
118
|
+
if (!backgroundInVariant(def, label.variants, model))
|
|
119
|
+
continue;
|
|
120
|
+
if (!backgroundVisible(label.visibleProp, model))
|
|
121
|
+
continue;
|
|
122
|
+
const vertical = label.vertical === true;
|
|
123
|
+
const [ax, ay] = backgroundPoint(label.anchor, plot);
|
|
124
|
+
const shown = wrapToWidth(measurer(backgroundFont(label.style, family)), backgroundLabelText(label, model, translation), backgroundTextRoom(ax, ay, label.align, vertical, w, h), label.maxLines ?? DEFAULT_BACKGROUND_WRAP_MAX_LINES);
|
|
125
|
+
lines.set(label, shown);
|
|
126
|
+
wrapped.push({ label, side: band.side });
|
|
127
|
+
margin[band.side] = Math.max(margin[band.side], thicknessFor(shown.length, label.style));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const labels = new Map();
|
|
131
|
+
for (const { label, side } of wrapped) {
|
|
132
|
+
const back = (declared[side] - margin[side]) / 2;
|
|
133
|
+
labels.set(label, {
|
|
134
|
+
lines: lines.get(label) ?? [],
|
|
135
|
+
shift: side === 'left' ? [back, 0] : [0, back],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
let zoneBandWidth = zoneLabel?.band?.width;
|
|
139
|
+
const zoneLines = new Map();
|
|
140
|
+
if (wrapsZones && zoneLabel && zoneBandWidth !== undefined) {
|
|
141
|
+
const plot = backgroundPlot(def, w, h, margin);
|
|
142
|
+
const stackY = def.instanceZones?.stack === 'y';
|
|
143
|
+
const ctx = measurer(backgroundFont(zoneLabel.style, family));
|
|
144
|
+
for (const zone of backgroundInstanceZones(def, model)) {
|
|
145
|
+
if (zone.name === undefined || zone.name === '')
|
|
146
|
+
continue;
|
|
147
|
+
const length = stackY
|
|
148
|
+
? zone.rect.h * plot.height
|
|
149
|
+
: zone.rect.w * plot.width;
|
|
150
|
+
const shown = wrapToWidth(ctx, zone.name, length - 2 * pad, zoneLabel.maxLines ?? DEFAULT_BACKGROUND_WRAP_MAX_LINES);
|
|
151
|
+
zoneLines.set(zone.id, shown);
|
|
152
|
+
zoneBandWidth = Math.max(zoneBandWidth, thicknessFor(shown.length, zoneLabel.style));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { margin, zoneBandWidth, labels, zoneLines };
|
|
156
|
+
}
|
|
@@ -122,7 +122,41 @@ export interface BackgroundTextDef extends BackgroundLabelSource {
|
|
|
122
122
|
vertical?: boolean;
|
|
123
123
|
/** Model prop gating this text. Absent means always drawn. */
|
|
124
124
|
visibleProp?: string;
|
|
125
|
+
/**
|
|
126
|
+
* What happens to words longer than the room they are written in.
|
|
127
|
+
*
|
|
128
|
+
* Absent — what every declaration written before this existed says — draws
|
|
129
|
+
* the words whole, however far they run. `'ellipsis'` cuts them to the room
|
|
130
|
+
* between the anchor and the card's edges along the text's direction, minus
|
|
131
|
+
* a small padding, ending with "…" (`ellipsis.ts`). Opt-in, because the
|
|
132
|
+
* frameworks that do not ask for it have exact-drawing tests and labels that
|
|
133
|
+
* are meant to overhang.
|
|
134
|
+
*
|
|
135
|
+
* `'wrap'` breaks the words into lines over the same room and stacks them
|
|
136
|
+
* ACROSS the text's direction, at most {@link maxLines}, the last one cut
|
|
137
|
+
* with "…" if the words still do not fit. It is honoured on a SIDE BAND's
|
|
138
|
+
* label ({@link BackgroundSideBandDef}), whose band then grows to hold the
|
|
139
|
+
* lines (`backgroundBandWidths`); any other text has no band to grow and
|
|
140
|
+
* treats `'wrap'` as `'ellipsis'`. Named consumer: the BPMN participant name,
|
|
141
|
+
* written up its band, which thickens as the name needs more lines (PO,
|
|
142
|
+
* 2026-10-27) rather than cutting it as PR #532 did.
|
|
143
|
+
*
|
|
144
|
+
* Append-only, like every enum-like value a declaration states.
|
|
145
|
+
*/
|
|
146
|
+
fit?: 'ellipsis' | 'wrap';
|
|
147
|
+
/**
|
|
148
|
+
* Under `fit: 'wrap'`, the most lines the words may take before the last one
|
|
149
|
+
* is cut with "…". Defaults to {@link DEFAULT_BACKGROUND_WRAP_MAX_LINES}.
|
|
150
|
+
* Ignored by every other fit.
|
|
151
|
+
*/
|
|
152
|
+
maxLines?: number;
|
|
125
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* How many lines a `fit: 'wrap'` label takes when its declaration names no
|
|
156
|
+
* `maxLines`: enough for a sentence-long name, few enough that a pasted
|
|
157
|
+
* paragraph does not turn a title band into a column.
|
|
158
|
+
*/
|
|
159
|
+
export declare const DEFAULT_BACKGROUND_WRAP_MAX_LINES = 3;
|
|
126
160
|
/** One graduation on an axis. */
|
|
127
161
|
export interface BackgroundTickDef {
|
|
128
162
|
/** Position along the axis, as a ratio of the plot span it runs on. */
|
|
@@ -261,7 +295,10 @@ export interface BackgroundInstanceZoneItem {
|
|
|
261
295
|
* wrong.
|
|
262
296
|
*/
|
|
263
297
|
export interface BackgroundInstanceZoneBandDef {
|
|
264
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* Strip thickness in FIXED model units — across the stacking direction. The
|
|
300
|
+
* MINIMUM thickness under a `fit: 'wrap'` label, which may grow it.
|
|
301
|
+
*/
|
|
265
302
|
width: number;
|
|
266
303
|
/** The line between the strip and the rest of the zone. Absent means none. */
|
|
267
304
|
divider?: BackgroundStroke;
|
|
@@ -329,6 +366,28 @@ export interface BackgroundInstanceZonesDef {
|
|
|
329
366
|
dy?: number;
|
|
330
367
|
/** Present = band placement. */
|
|
331
368
|
band?: BackgroundInstanceZoneBandDef;
|
|
369
|
+
/**
|
|
370
|
+
* `'ellipsis'` cuts a name longer than its room — the strip's length under
|
|
371
|
+
* a band, the zone's width from the inset under the corner — and ends it
|
|
372
|
+
* with "…", as {@link BackgroundTextDef.fit} does. Absent draws the name
|
|
373
|
+
* whole.
|
|
374
|
+
*
|
|
375
|
+
* `'wrap'` breaks a name into lines stacked across the strip, at most
|
|
376
|
+
* {@link maxLines}, and every strip of the instance grows to the SAME
|
|
377
|
+
* thickness: the one its most demanding name needs, never less than
|
|
378
|
+
* {@link BackgroundInstanceZoneBandDef.width} (`backgroundBandWidths`). One
|
|
379
|
+
* thickness, because strips of different widths would stagger the lanes'
|
|
380
|
+
* leading edges and read as lanes nested at different depths. Band
|
|
381
|
+
* placement only: the corner placement has no strip to grow and treats
|
|
382
|
+
* `'wrap'` as `'ellipsis'`. Named consumer: the BPMN lane title (PO,
|
|
383
|
+
* 2026-10-27).
|
|
384
|
+
*/
|
|
385
|
+
fit?: 'ellipsis' | 'wrap';
|
|
386
|
+
/**
|
|
387
|
+
* Under `fit: 'wrap'`, the most lines a name may take. Defaults to
|
|
388
|
+
* {@link DEFAULT_BACKGROUND_WRAP_MAX_LINES}.
|
|
389
|
+
*/
|
|
390
|
+
maxLines?: number;
|
|
332
391
|
};
|
|
333
392
|
/**
|
|
334
393
|
* Prefix of the reported zone ids: `'lane'` reports `lane:sales`.
|
|
@@ -390,6 +449,17 @@ export interface BackgroundSurfaceDef {
|
|
|
390
449
|
* and one day will not. An element smaller than its own margin clamps the band
|
|
391
450
|
* to the element, which is the only case where the two can disagree.
|
|
392
451
|
*
|
|
452
|
+
* ## A band that grows with its label
|
|
453
|
+
*
|
|
454
|
+
* Under a `fit: 'wrap'` label the declared margin is the MINIMUM thickness:
|
|
455
|
+
* the band grows to `lines × line height + 2 × padding` when the words need
|
|
456
|
+
* more lines than it holds, and the plot gives up the extra room with it.
|
|
457
|
+
* Derived at paint time from the measured words, never stored — the one
|
|
458
|
+
* resolver is `backgroundBandWidths`, and the renderer, the label hit boxes
|
|
459
|
+
* and a framework's own hit tests all read the margin it returns. The label
|
|
460
|
+
* keeps its offset from the MIDDLE of the band, so a label centred in the
|
|
461
|
+
* declared band stays centred in the grown one.
|
|
462
|
+
*
|
|
393
463
|
* A band is part of the CARD, not a layer over it: it is painted over the
|
|
394
464
|
* card's fill and under its border, so the frame keeps outlining the whole
|
|
395
465
|
* element. Its {@link label} is a text like any other, drawn with the rest of
|
|
@@ -499,8 +569,17 @@ export interface FrameworkBackgroundDef {
|
|
|
499
569
|
* fill, no border, no radius.
|
|
500
570
|
*/
|
|
501
571
|
export declare const DEFAULT_BACKGROUND_SURFACE: BackgroundSurfaceDef;
|
|
502
|
-
/**
|
|
503
|
-
|
|
572
|
+
/**
|
|
573
|
+
* The plot rectangle of a background of size `w × h`, in element-local units.
|
|
574
|
+
*
|
|
575
|
+
* `margin` defaults to the DECLARED one. The painted chrome — the renderer, the
|
|
576
|
+
* label hit boxes, a framework's own hit tests — passes the margin
|
|
577
|
+
* `backgroundBandWidths` resolved instead, which a wrapped band label may have
|
|
578
|
+
* grown. The semantic readers (facts, audit, rules, interchange exports) keep
|
|
579
|
+
* the declared plot: where an artefact sits in a frame is a question about the
|
|
580
|
+
* frame, and its answer must not change because somebody renamed it.
|
|
581
|
+
*/
|
|
582
|
+
export declare function backgroundPlot(def: FrameworkBackgroundDef, w: number, h: number, margin?: BackgroundInset): {
|
|
504
583
|
x0: number;
|
|
505
584
|
y0: number;
|
|
506
585
|
x1: number;
|
|
@@ -592,8 +671,12 @@ export interface BackgroundRect {
|
|
|
592
671
|
* Clamped to the plot, the way a side band clamps to an element narrower than
|
|
593
672
|
* its own margin ({@link BackgroundSideBandDef}) — the same degenerate case,
|
|
594
673
|
* and the same answer.
|
|
674
|
+
*
|
|
675
|
+
* `bandWidth` defaults to the DECLARED strip width; the painted chrome passes
|
|
676
|
+
* the one `backgroundBandWidths` resolved, which a wrapped name may have grown
|
|
677
|
+
* — and which is the same for every zone of the instance.
|
|
595
678
|
*/
|
|
596
|
-
export declare function backgroundInstanceZoneBand(def: FrameworkBackgroundDef, zone: BackgroundInstanceZone, plot: ReturnType<typeof backgroundPlot
|
|
679
|
+
export declare function backgroundInstanceZoneBand(def: FrameworkBackgroundDef, zone: BackgroundInstanceZone, plot: ReturnType<typeof backgroundPlot>, bandWidth?: number): BackgroundRect | null;
|
|
597
680
|
/**
|
|
598
681
|
* Whether the resize handles are offered for this element.
|
|
599
682
|
*
|
|
@@ -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;
|