@react-x11/components 0.4.0 → 0.6.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/README.md +143 -8
- package/dist/charts/components.d.ts +3 -1
- package/dist/charts/components.d.ts.map +1 -1
- package/dist/charts/components.js +8 -4
- package/dist/charts/components.js.map +1 -1
- package/dist/charts/node.d.ts +13 -6
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +75 -24
- package/dist/charts/node.js.map +1 -1
- package/dist/charts/render.d.ts +4 -0
- package/dist/charts/render.d.ts.map +1 -1
- package/dist/charts/render.js +11 -6
- package/dist/charts/render.js.map +1 -1
- package/dist/code-editor/node.d.ts +47 -16
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +121 -51
- package/dist/code-editor/node.js.map +1 -1
- package/dist/flow/draw.d.ts +19 -1
- package/dist/flow/draw.d.ts.map +1 -1
- package/dist/flow/draw.js +64 -35
- package/dist/flow/draw.js.map +1 -1
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +24 -8
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/model.d.ts +4 -2
- package/dist/flow/model.d.ts.map +1 -1
- package/dist/flow/model.js +4 -2
- package/dist/flow/model.js.map +1 -1
- package/dist/flow/node.d.ts +40 -9
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +142 -53
- package/dist/flow/node.js.map +1 -1
- package/dist/flow/types.d.ts +46 -21
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/formula/index.d.ts +4 -2
- package/dist/formula/index.d.ts.map +1 -1
- package/dist/formula/index.js.map +1 -1
- package/dist/formula/node.d.ts +5 -1
- package/dist/formula/node.d.ts.map +1 -1
- package/dist/formula/node.js +19 -3
- package/dist/formula/node.js.map +1 -1
- package/dist/html/controls.d.ts +4 -1
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +11 -5
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +18 -8
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/parse.d.ts +15 -10
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +41 -16
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +3 -0
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +3 -1
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/values.d.ts +14 -1
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +11 -8
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +1 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +12 -5
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +7 -4
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +22 -1
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +22 -8
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +97 -15
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +3 -0
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +2 -2
- package/dist/html/paint.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/maps/clip.d.ts +43 -0
- package/dist/maps/clip.d.ts.map +1 -0
- package/dist/maps/clip.js +171 -0
- package/dist/maps/clip.js.map +1 -0
- package/dist/maps/gzip.d.ts +15 -0
- package/dist/maps/gzip.d.ts.map +1 -0
- package/dist/maps/gzip.js +56 -0
- package/dist/maps/gzip.js.map +1 -0
- package/dist/maps/index.d.ts +55 -0
- package/dist/maps/index.d.ts.map +1 -0
- package/dist/maps/index.js +141 -0
- package/dist/maps/index.js.map +1 -0
- package/dist/maps/labels.d.ts +123 -0
- package/dist/maps/labels.d.ts.map +1 -0
- package/dist/maps/labels.js +323 -0
- package/dist/maps/labels.js.map +1 -0
- package/dist/maps/mvt.d.ts +244 -0
- package/dist/maps/mvt.d.ts.map +1 -0
- package/dist/maps/mvt.js +820 -0
- package/dist/maps/mvt.js.map +1 -0
- package/dist/maps/node.d.ts +278 -0
- package/dist/maps/node.d.ts.map +1 -0
- package/dist/maps/node.js +1459 -0
- package/dist/maps/node.js.map +1 -0
- package/dist/maps/overlay.d.ts +157 -0
- package/dist/maps/overlay.d.ts.map +1 -0
- package/dist/maps/overlay.js +472 -0
- package/dist/maps/overlay.js.map +1 -0
- package/dist/maps/paint.d.ts +226 -0
- package/dist/maps/paint.d.ts.map +1 -0
- package/dist/maps/paint.js +705 -0
- package/dist/maps/paint.js.map +1 -0
- package/dist/maps/proj.d.ts +281 -0
- package/dist/maps/proj.d.ts.map +1 -0
- package/dist/maps/proj.js +451 -0
- package/dist/maps/proj.js.map +1 -0
- package/dist/maps/sources.d.ts +256 -0
- package/dist/maps/sources.d.ts.map +1 -0
- package/dist/maps/sources.js +210 -0
- package/dist/maps/sources.js.map +1 -0
- package/dist/maps/style.d.ts +141 -0
- package/dist/maps/style.d.ts.map +1 -0
- package/dist/maps/style.js +147 -0
- package/dist/maps/style.js.map +1 -0
- package/dist/maps/styles.d.ts +82 -0
- package/dist/maps/styles.d.ts.map +1 -0
- package/dist/maps/styles.js +898 -0
- package/dist/maps/styles.js.map +1 -0
- package/dist/maps/tiles.d.ts +283 -0
- package/dist/maps/tiles.d.ts.map +1 -0
- package/dist/maps/tiles.js +469 -0
- package/dist/maps/tiles.js.map +1 -0
- package/dist/maps/types.d.ts +244 -0
- package/dist/maps/types.d.ts.map +1 -0
- package/dist/maps/types.js +2 -0
- package/dist/maps/types.js.map +1 -0
- package/dist/markdown/ast.d.ts +80 -6
- package/dist/markdown/ast.d.ts.map +1 -1
- package/dist/markdown/ast.js +13 -4
- package/dist/markdown/ast.js.map +1 -1
- package/dist/markdown/expressions.d.ts +27 -0
- package/dist/markdown/expressions.d.ts.map +1 -0
- package/dist/markdown/expressions.js +203 -0
- package/dist/markdown/expressions.js.map +1 -0
- package/dist/markdown/index.d.ts +61 -2
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +101 -2
- package/dist/markdown/index.js.map +1 -1
- package/dist/markdown/parse.d.ts +1 -1
- package/dist/markdown/parse.d.ts.map +1 -1
- package/dist/markdown/parse.js +163 -15
- package/dist/markdown/parse.js.map +1 -1
- package/dist/markdown/spans.d.ts.map +1 -1
- package/dist/markdown/spans.js +4 -0
- package/dist/markdown/spans.js.map +1 -1
- package/dist/markdown/tags.d.ts +35 -0
- package/dist/markdown/tags.d.ts.map +1 -0
- package/dist/markdown/tags.js +213 -0
- package/dist/markdown/tags.js.map +1 -0
- package/dist/reorder/hx.d.ts +18 -0
- package/dist/reorder/hx.d.ts.map +1 -0
- package/dist/reorder/hx.js +10 -0
- package/dist/reorder/hx.js.map +1 -0
- package/dist/reorder/index.d.ts +314 -0
- package/dist/reorder/index.d.ts.map +1 -0
- package/dist/reorder/index.js +1539 -0
- package/dist/reorder/index.js.map +1 -0
- package/dist/reorder/model.d.ts +107 -0
- package/dist/reorder/model.d.ts.map +1 -0
- package/dist/reorder/model.js +162 -0
- package/dist/reorder/model.js.map +1 -0
- package/dist/richtext/node.d.ts +15 -5
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +40 -11
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +35 -13
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +83 -27
- package/dist/richtext/runs.js.map +1 -1
- package/dist/tabs/index.d.ts +17 -0
- package/dist/tabs/index.d.ts.map +1 -1
- package/dist/tabs/index.js +849 -186
- package/dist/tabs/index.js.map +1 -1
- package/dist/terminal/vt/fonts.d.ts +45 -15
- package/dist/terminal/vt/fonts.d.ts.map +1 -1
- package/dist/terminal/vt/fonts.js +89 -19
- package/dist/terminal/vt/fonts.js.map +1 -1
- package/dist/terminal/vt/node.d.ts +42 -0
- package/dist/terminal/vt/node.d.ts.map +1 -1
- package/dist/terminal/vt/node.js +95 -11
- package/dist/terminal/vt/node.js.map +1 -1
- package/dist/terminal/vt/renderer.d.ts.map +1 -1
- package/dist/terminal/vt/renderer.js +13 -3
- package/dist/terminal/vt/renderer.js.map +1 -1
- package/package.json +14 -3
- package/src/charts/components.ts +14 -7
- package/src/charts/node.ts +81 -26
- package/src/charts/render.ts +16 -6
- package/src/code-editor/node.ts +147 -53
- package/src/flow/draw.ts +104 -35
- package/src/flow/index.ts +33 -13
- package/src/flow/model.ts +4 -2
- package/src/flow/node.ts +158 -59
- package/src/flow/types.ts +46 -21
- package/src/formula/index.ts +4 -2
- package/src/formula/node.ts +24 -5
- package/src/html/controls.ts +4 -1
- package/src/html/css/cascade.ts +18 -7
- package/src/html/css/parse.ts +46 -21
- package/src/html/css/style.ts +6 -1
- package/src/html/css/values.ts +25 -9
- package/src/html/index.ts +1 -0
- package/src/html/layout/boxes.ts +19 -9
- package/src/html/layout/inline.ts +23 -1
- package/src/html/node.ts +110 -17
- package/src/html/paint.ts +17 -2
- package/src/index.ts +102 -0
- package/src/maps/clip.ts +184 -0
- package/src/maps/gzip.ts +66 -0
- package/src/maps/index.ts +316 -0
- package/src/maps/labels.ts +442 -0
- package/src/maps/mvt.ts +857 -0
- package/src/maps/node.ts +1819 -0
- package/src/maps/overlay.ts +639 -0
- package/src/maps/paint.ts +978 -0
- package/src/maps/proj.ts +641 -0
- package/src/maps/sources.ts +425 -0
- package/src/maps/style.ts +309 -0
- package/src/maps/styles.ts +1040 -0
- package/src/maps/tiles.ts +676 -0
- package/src/maps/types.ts +260 -0
- package/src/markdown/ast.ts +85 -9
- package/src/markdown/expressions.ts +223 -0
- package/src/markdown/index.ts +190 -5
- package/src/markdown/parse.ts +230 -15
- package/src/markdown/spans.ts +4 -0
- package/src/markdown/tags.ts +240 -0
- package/src/reorder/hx.ts +31 -0
- package/src/reorder/index.ts +2223 -0
- package/src/reorder/model.ts +232 -0
- package/src/richtext/node.ts +49 -14
- package/src/richtext/runs.ts +115 -30
- package/src/tabs/index.ts +1040 -204
- package/src/terminal/vt/fonts.ts +128 -36
- package/src/terminal/vt/node.ts +105 -12
- package/src/terminal/vt/renderer.ts +13 -3
package/src/flow/node.ts
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
XK_UP,
|
|
39
39
|
} from 'react-x11/keysyms';
|
|
40
40
|
|
|
41
|
-
import { createPainter, measureText } from './draw.js';
|
|
41
|
+
import { createPainter, measureText, toDevice } from './draw.js';
|
|
42
42
|
import type { CachedText, FontsLike, PainterOptions } from './draw.js';
|
|
43
43
|
import {
|
|
44
44
|
boundsOf,
|
|
@@ -608,6 +608,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
608
608
|
this._themeString('fontFamily') ??
|
|
609
609
|
'sans-serif',
|
|
610
610
|
color: this._palette().text,
|
|
611
|
+
scale: this._scale,
|
|
611
612
|
cache: this._textCache,
|
|
612
613
|
};
|
|
613
614
|
}
|
|
@@ -891,17 +892,71 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
891
892
|
return this._prop<Viewport>('viewport') ?? this._vp;
|
|
892
893
|
}
|
|
893
894
|
|
|
895
|
+
/**
|
|
896
|
+
* Device pixels per logical pixel — the display scale this pane's window
|
|
897
|
+
* resolved to (react-x11's docs/scale.md): `1` on an ordinary display,
|
|
898
|
+
* `2` on a retina panel, fractional on a desktop configured to 1.5.
|
|
899
|
+
*
|
|
900
|
+
* The pane thinks in logical pixels throughout. They are the unit its API
|
|
901
|
+
* speaks (a viewport, a node's size, a `NodeBodyRect`), the unit a
|
|
902
|
+
* synthetic event's `x`/`y` arrive in, and the unit the sibling boxes
|
|
903
|
+
* `<Flow>` mounts bodies in are laid out in. What core hands this element
|
|
904
|
+
* is device pixels — `abs`, `contentBox()`, `this.style`, the paint
|
|
905
|
+
* context, `paintDamage()`, a rect given to `invalidate` or
|
|
906
|
+
* `scrollContents`, an a11y scene rect — so each of those crossings
|
|
907
|
+
* converts, once, in the helpers below, and nothing in between knows the
|
|
908
|
+
* factor. At 1x every conversion is the identity, which is how a pane
|
|
909
|
+
* that compared `ev.x` with `contentBox()` passed every test and then
|
|
910
|
+
* hovered at half the distance, panned at half speed and framed the graph
|
|
911
|
+
* at half size the day a native backend reported a retina panel.
|
|
912
|
+
*/
|
|
913
|
+
private get _scale(): number {
|
|
914
|
+
return this.scale > 0 ? this.scale : 1;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/** A logical rect on the device grid, grown outward to whole pixels —
|
|
918
|
+
* what a damage claim has to cover for an antialiased edge to repaint. */
|
|
919
|
+
private _device(rect: FlowRect): FlowRect {
|
|
920
|
+
const s = this._scale;
|
|
921
|
+
const x = Math.floor(toDevice(rect.x, s));
|
|
922
|
+
const y = Math.floor(toDevice(rect.y, s));
|
|
923
|
+
return {
|
|
924
|
+
x,
|
|
925
|
+
y,
|
|
926
|
+
width: Math.ceil(toDevice(rect.x + rect.width, s)) - x,
|
|
927
|
+
height: Math.ceil(toDevice(rect.y + rect.height, s)) - y,
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/** One of core's device rects in the pane's logical units. */
|
|
932
|
+
private _logical(rect: FlowRect): FlowRect {
|
|
933
|
+
const s = this._scale;
|
|
934
|
+
return {
|
|
935
|
+
x: rect.x / s,
|
|
936
|
+
y: rect.y / s,
|
|
937
|
+
width: rect.width / s,
|
|
938
|
+
height: rect.height / s,
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/** Claim a logical rect as damage. Every partial repaint the pane asks
|
|
943
|
+
* for goes through here; the full ones pass `abs`, which is device
|
|
944
|
+
* already. */
|
|
945
|
+
private _claim(rect: FlowRect, reason: string): void {
|
|
946
|
+
this.invalidate(false, this._device(rect), reason);
|
|
947
|
+
}
|
|
948
|
+
|
|
894
949
|
/**
|
|
895
950
|
* The pane proper: `abs` inset by border and padding, off core's own
|
|
896
951
|
* resolved layout (react-x11#254) — so a `<Flow style={{ borderWidth: 1,
|
|
897
952
|
* padding: 8 }}>` keeps its border, and its padding means what it does on
|
|
898
|
-
* a `<box>`.
|
|
953
|
+
* a `<box>`. In logical pixels, like everything the pane computes.
|
|
899
954
|
*/
|
|
900
955
|
private _pane(): FlowRect {
|
|
901
|
-
return this.contentBox();
|
|
956
|
+
return this._logical(this.contentBox());
|
|
902
957
|
}
|
|
903
958
|
|
|
904
|
-
/** Graph point to window pixels. */
|
|
959
|
+
/** Graph point to logical window pixels. */
|
|
905
960
|
private _toScreen(p: XYPosition): XYPosition {
|
|
906
961
|
const v = this._viewport();
|
|
907
962
|
const pane = this._pane();
|
|
@@ -911,7 +966,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
911
966
|
};
|
|
912
967
|
}
|
|
913
968
|
|
|
914
|
-
/**
|
|
969
|
+
/** Logical window pixels to graph point. */
|
|
915
970
|
private _toGraph(x: number, y: number): XYPosition {
|
|
916
971
|
const v = this._viewport();
|
|
917
972
|
const pane = this._pane();
|
|
@@ -978,7 +1033,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
978
1033
|
}
|
|
979
1034
|
|
|
980
1035
|
/**
|
|
981
|
-
* A node's box on screen, **on whole pixels**.
|
|
1036
|
+
* A node's box on screen, **on whole device pixels**.
|
|
982
1037
|
*
|
|
983
1038
|
* The rounding is not cosmetic. ntk draws a rounded box as cached corner
|
|
984
1039
|
* glyphs plus `FillRectangles` when its geometry is integral, and
|
|
@@ -987,22 +1042,26 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
987
1042
|
* six hundred mask uploads a frame and about four megabytes on the wire;
|
|
988
1043
|
* `react-x11/debug`'s trace names it as `fell back … fractional`.
|
|
989
1044
|
*
|
|
990
|
-
*
|
|
991
|
-
*
|
|
1045
|
+
* The grid is the panel's, not the logical one: at 2x a logical half is
|
|
1046
|
+
* a whole pixel, and at 1.5x a logical integer is not always one. Hit
|
|
1047
|
+
* testing reads the same rect, so what is drawn and what is clicked still
|
|
1048
|
+
* agree to the pixel.
|
|
992
1049
|
*/
|
|
993
1050
|
private _screenRect(entry: NodeEntry): FlowRect {
|
|
994
1051
|
const v = this._viewport();
|
|
995
1052
|
const rect = this.rectOf(entry);
|
|
996
1053
|
const p = this._toScreen(rect);
|
|
997
|
-
const
|
|
998
|
-
const
|
|
1054
|
+
const s = this._scale;
|
|
1055
|
+
const grid = (value: number): number => Math.round(value * s) / s;
|
|
1056
|
+
const x = grid(p.x);
|
|
1057
|
+
const y = grid(p.y);
|
|
999
1058
|
return {
|
|
1000
1059
|
x,
|
|
1001
1060
|
y,
|
|
1002
1061
|
// rounded as edges rather than as a size, so two nodes that share a
|
|
1003
1062
|
// column still share it after rounding
|
|
1004
|
-
width:
|
|
1005
|
-
height:
|
|
1063
|
+
width: grid(p.x + rect.width * v.zoom) - x,
|
|
1064
|
+
height: grid(p.y + rect.height * v.zoom) - y,
|
|
1006
1065
|
};
|
|
1007
1066
|
}
|
|
1008
1067
|
|
|
@@ -1051,8 +1110,8 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
1051
1110
|
* panels repaint in place.
|
|
1052
1111
|
*
|
|
1053
1112
|
* Still a full repaint when:
|
|
1054
|
-
* - the zoom moved (scaling is not a blit) or the shift is fractional
|
|
1055
|
-
* every real pan gesture is whole pixels;
|
|
1113
|
+
* - the zoom moved (scaling is not a blit) or the shift is fractional on
|
|
1114
|
+
* the device grid — every real pan gesture is whole device pixels;
|
|
1056
1115
|
* - mounted node bodies exist: they are core-owned siblings whose
|
|
1057
1116
|
* gesture-time commits claim *inside* the rect every step, which
|
|
1058
1117
|
* declines the blit anyway — bailing early skips the churn;
|
|
@@ -1062,10 +1121,16 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
1062
1121
|
private _blitPan(previous: Viewport, next: Viewport): boolean {
|
|
1063
1122
|
if (next.zoom !== previous.zoom) return false;
|
|
1064
1123
|
if (this._bodiesKey !== '') return false;
|
|
1065
|
-
|
|
1066
|
-
|
|
1124
|
+
// Device pixels: the blit copies the backing store, and its grid is the
|
|
1125
|
+
// panel's. A pointer step lands on it whatever the scale — it came off
|
|
1126
|
+
// the wire as whole device pixels — so every real pan gesture blits.
|
|
1127
|
+
const s = this._scale;
|
|
1128
|
+
const shiftX = toDevice(next.x - previous.x, s);
|
|
1129
|
+
const shiftY = toDevice(next.y - previous.y, s);
|
|
1130
|
+
const dx = Math.round(shiftX);
|
|
1131
|
+
const dy = Math.round(shiftY);
|
|
1067
1132
|
if (dx === 0 && dy === 0) return true; // sub-pixel: nothing to show yet
|
|
1068
|
-
if (
|
|
1133
|
+
if (shiftX !== dx || shiftY !== dy) return false;
|
|
1069
1134
|
const pane = this._pane();
|
|
1070
1135
|
|
|
1071
1136
|
// The horizontal bands the furniture lives in. Full-width, because the
|
|
@@ -1100,32 +1165,39 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
1100
1165
|
this._controlsOptions()?.position ?? 'bottom-left',
|
|
1101
1166
|
);
|
|
1102
1167
|
}
|
|
1168
|
+
// From here on, device pixels: `scrollContents` and the claims speak
|
|
1169
|
+
// core's units. The bands round *up* and the blit is what is left, so
|
|
1170
|
+
// the three sit edge to edge on whole pixels — a claim overlapping the
|
|
1171
|
+
// blit rect by a pixel is a foreign claim, and declines the blit.
|
|
1172
|
+
const box = this.contentBox();
|
|
1173
|
+
const top = Math.ceil(toDevice(topBand, s));
|
|
1174
|
+
const bottom = Math.ceil(toDevice(bottomBand, s));
|
|
1103
1175
|
const blit: FlowRect = {
|
|
1104
|
-
x:
|
|
1105
|
-
y:
|
|
1106
|
-
width:
|
|
1107
|
-
height:
|
|
1176
|
+
x: box.x,
|
|
1177
|
+
y: box.y + top,
|
|
1178
|
+
width: box.width,
|
|
1179
|
+
height: box.height - top - bottom,
|
|
1108
1180
|
};
|
|
1109
|
-
if (blit.width < 64 || blit.height < 64) return false;
|
|
1181
|
+
if (blit.width < 64 * s || blit.height < 64 * s) return false;
|
|
1110
1182
|
if (Math.abs(dx) >= blit.width || Math.abs(dy) >= blit.height) {
|
|
1111
1183
|
return false;
|
|
1112
1184
|
}
|
|
1113
1185
|
this.scrollContents(blit, dx, dy);
|
|
1114
|
-
if (
|
|
1186
|
+
if (top > 0) {
|
|
1115
1187
|
this.invalidate(
|
|
1116
1188
|
false,
|
|
1117
|
-
{ x:
|
|
1189
|
+
{ x: box.x, y: box.y, width: box.width, height: top },
|
|
1118
1190
|
'scroll',
|
|
1119
1191
|
);
|
|
1120
1192
|
}
|
|
1121
|
-
if (
|
|
1193
|
+
if (bottom > 0) {
|
|
1122
1194
|
this.invalidate(
|
|
1123
1195
|
false,
|
|
1124
1196
|
{
|
|
1125
|
-
x:
|
|
1197
|
+
x: box.x,
|
|
1126
1198
|
y: blit.y + blit.height,
|
|
1127
|
-
width:
|
|
1128
|
-
height:
|
|
1199
|
+
width: box.width,
|
|
1200
|
+
height: bottom,
|
|
1129
1201
|
},
|
|
1130
1202
|
'scroll',
|
|
1131
1203
|
);
|
|
@@ -1881,8 +1953,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
1881
1953
|
// The box outline is all that moves this step; a node crossing the
|
|
1882
1954
|
// boundary changes `selected`, and that repaints through the
|
|
1883
1955
|
// controlled round trip like any other selection change.
|
|
1884
|
-
this.
|
|
1885
|
-
false,
|
|
1956
|
+
this._claim(
|
|
1886
1957
|
inflateRect(unionRects(oldBox, selectBoxRect(gesture)), CULL_MARGIN),
|
|
1887
1958
|
'style-state',
|
|
1888
1959
|
);
|
|
@@ -2093,7 +2164,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2093
2164
|
}
|
|
2094
2165
|
this._emitNodes(changes);
|
|
2095
2166
|
if (damage) {
|
|
2096
|
-
this.
|
|
2167
|
+
this._claim(inflateRect(damage, CULL_MARGIN), 'content');
|
|
2097
2168
|
} else {
|
|
2098
2169
|
this._repaint();
|
|
2099
2170
|
}
|
|
@@ -2152,7 +2223,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2152
2223
|
});
|
|
2153
2224
|
}
|
|
2154
2225
|
this._emitNodes(changes);
|
|
2155
|
-
this.
|
|
2226
|
+
this._claim(inflateRect(damage, CULL_MARGIN), 'content');
|
|
2156
2227
|
this._emitBodies();
|
|
2157
2228
|
}
|
|
2158
2229
|
|
|
@@ -2213,8 +2284,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2213
2284
|
const target = to ? this._byId.get(to.nodeId) : null;
|
|
2214
2285
|
if (target) box = unionRects(box, this._screenRect(target));
|
|
2215
2286
|
gesture.box = box;
|
|
2216
|
-
this.
|
|
2217
|
-
false,
|
|
2287
|
+
this._claim(
|
|
2218
2288
|
inflateRect(oldBox ? unionRects(oldBox, box) : box, CULL_MARGIN),
|
|
2219
2289
|
'style-state',
|
|
2220
2290
|
);
|
|
@@ -2346,7 +2416,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2346
2416
|
);
|
|
2347
2417
|
this._hover = next;
|
|
2348
2418
|
if (damage) {
|
|
2349
|
-
this.
|
|
2419
|
+
this._claim(inflateRect(damage, CULL_MARGIN), 'style-state');
|
|
2350
2420
|
} else {
|
|
2351
2421
|
this._repaint('style-state');
|
|
2352
2422
|
}
|
|
@@ -2357,7 +2427,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2357
2427
|
const damage = this._hoverDamage(this._hover);
|
|
2358
2428
|
this._hover = NO_HOVER;
|
|
2359
2429
|
if (damage) {
|
|
2360
|
-
this.
|
|
2430
|
+
this._claim(inflateRect(damage, CULL_MARGIN), 'style-state');
|
|
2361
2431
|
} else {
|
|
2362
2432
|
this._repaint('style-state');
|
|
2363
2433
|
}
|
|
@@ -2403,7 +2473,8 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2403
2473
|
const degree = out.length;
|
|
2404
2474
|
items.push({
|
|
2405
2475
|
id: entry.node.id,
|
|
2406
|
-
|
|
2476
|
+
// the scene's rects are `abs`'s space — device pixels
|
|
2477
|
+
rect: this._device(rect),
|
|
2407
2478
|
role: 'listitem',
|
|
2408
2479
|
name: data?.label ?? entry.node.id,
|
|
2409
2480
|
description:
|
|
@@ -2611,7 +2682,7 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2611
2682
|
}
|
|
2612
2683
|
}
|
|
2613
2684
|
if (damage === 'full') this._repaint('props');
|
|
2614
|
-
else if (damage) this.
|
|
2685
|
+
else if (damage) this._claim(damage, 'props');
|
|
2615
2686
|
// Core re-reads the scene for aria-prop commits; a `nodes`/`edges`
|
|
2616
2687
|
// change is invisible to it, so the re-read is asked for by name.
|
|
2617
2688
|
// Free when no assistive technology is listening.
|
|
@@ -2631,7 +2702,11 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2631
2702
|
this._dashPhase += ANIMATION_SPEED;
|
|
2632
2703
|
// the box the last paint saw animated edges in, not the pane: a
|
|
2633
2704
|
// marching dash should not cost a full grid repaint per tick
|
|
2634
|
-
this.invalidate(
|
|
2705
|
+
this.invalidate(
|
|
2706
|
+
false,
|
|
2707
|
+
this._animBox ? this._device(this._animBox) : this.abs,
|
|
2708
|
+
'animation',
|
|
2709
|
+
);
|
|
2635
2710
|
}, ANIMATION_MS) ?? null;
|
|
2636
2711
|
}
|
|
2637
2712
|
|
|
@@ -2688,7 +2763,13 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2688
2763
|
this._fit(this._prop<FitViewOptions>('fitViewOptions'));
|
|
2689
2764
|
}
|
|
2690
2765
|
}
|
|
2691
|
-
|
|
2766
|
+
// `paintDamage()` speaks device pixels, like `abs`; the culling below
|
|
2767
|
+
// speaks logical ones, like everything the pane draws.
|
|
2768
|
+
this._frameClip = this._fitPending
|
|
2769
|
+
? this._pane()
|
|
2770
|
+
: clip
|
|
2771
|
+
? this._logical(clip)
|
|
2772
|
+
: null;
|
|
2692
2773
|
|
|
2693
2774
|
const palette = this._palette();
|
|
2694
2775
|
const { x, y, width, height } = this._pane();
|
|
@@ -2698,11 +2779,14 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2698
2779
|
// clip is the content box rather than `abs` because the border has been
|
|
2699
2780
|
// stroked already, inside the box, and drawing over it would leave a
|
|
2700
2781
|
// `borderWidth: 1` looking like half of one.
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2782
|
+
// `this.style` arrives in device pixels (react-x11's docs/scale.md);
|
|
2783
|
+
// the painter takes logical ones.
|
|
2784
|
+
const radius =
|
|
2785
|
+
Math.max(
|
|
2786
|
+
0,
|
|
2787
|
+
((this.style.borderRadius as number | undefined) ?? 0) -
|
|
2788
|
+
((this.style.borderWidth as number | undefined) ?? 0),
|
|
2789
|
+
) / this._scale;
|
|
2706
2790
|
// The rounded clip only when this pass can actually reach a corner: a
|
|
2707
2791
|
// non-rectangular clip forfeits ntk's rounded-box fast path for every
|
|
2708
2792
|
// fill under it, which multiplies a rounded *pane* into a per-card
|
|
@@ -2793,8 +2877,8 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2793
2877
|
if (width <= 1 || height <= 1) continue;
|
|
2794
2878
|
bodies.push({
|
|
2795
2879
|
id: entry.node.id,
|
|
2796
|
-
// pane-relative, because that is what an absolutely
|
|
2797
|
-
// beside the pane is laid out against
|
|
2880
|
+
// pane-relative and logical, because that is what an absolutely
|
|
2881
|
+
// positioned box beside the pane is laid out against, and in
|
|
2798
2882
|
x: Math.round(rect.x + inset - pane.x),
|
|
2799
2883
|
y: Math.round(rect.y + header - pane.y),
|
|
2800
2884
|
width: Math.round(width),
|
|
@@ -2803,8 +2887,14 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2803
2887
|
selected: entry.node.selected ?? false,
|
|
2804
2888
|
});
|
|
2805
2889
|
}
|
|
2890
|
+
// The zoom is in the key as well as the rect it produced: it is the
|
|
2891
|
+
// body's subtree scale now, and two zooms a rounding apart can leave a
|
|
2892
|
+
// box on the same integers while the text inside it should have moved.
|
|
2806
2893
|
const key = bodies
|
|
2807
|
-
.map(
|
|
2894
|
+
.map(
|
|
2895
|
+
(b) =>
|
|
2896
|
+
`${b.id}:${b.x},${b.y},${b.width},${b.height},${b.zoom},${b.selected}`,
|
|
2897
|
+
)
|
|
2808
2898
|
.join('|');
|
|
2809
2899
|
if (key === this._bodiesKey) return;
|
|
2810
2900
|
this._bodiesKey = key;
|
|
@@ -2916,8 +3006,13 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2916
3006
|
color: string,
|
|
2917
3007
|
region: FlowRect,
|
|
2918
3008
|
): boolean {
|
|
2919
|
-
|
|
2920
|
-
|
|
3009
|
+
// The tile is a pixmap on the panel's grid, so the pitch has to be a
|
|
3010
|
+
// whole number of *device* pixels, and the phase is where the viewport
|
|
3011
|
+
// origin lands on that grid.
|
|
3012
|
+
const s = this._scale;
|
|
3013
|
+
const pitch = toDevice(step, s);
|
|
3014
|
+
const tileSize = Math.round(pitch);
|
|
3015
|
+
if (Math.abs(pitch - tileSize) > 0.01 || tileSize < 2) return false;
|
|
2921
3016
|
const raw = painter.raw as {
|
|
2922
3017
|
createPattern?: (source: unknown, repetition: string) => PatternLike;
|
|
2923
3018
|
fillStyle?: unknown;
|
|
@@ -2928,9 +3023,11 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2928
3023
|
const v = this._viewport();
|
|
2929
3024
|
const pane = this._pane();
|
|
2930
3025
|
const mod = (a: number, m: number): number => ((a % m) + m) % m;
|
|
2931
|
-
const px = Math.round(mod(pane.x + v.x, tileSize)) % tileSize;
|
|
2932
|
-
const py = Math.round(mod(pane.y + v.y, tileSize)) % tileSize;
|
|
2933
|
-
|
|
3026
|
+
const px = Math.round(mod(toDevice(pane.x + v.x, s), tileSize)) % tileSize;
|
|
3027
|
+
const py = Math.round(mod(toDevice(pane.y + v.y, s), tileSize)) % tileSize;
|
|
3028
|
+
// device pixels per graph unit — what a mark's size is drawn in
|
|
3029
|
+
const unit = v.zoom * s;
|
|
3030
|
+
const key = `${options.variant}|${Math.round(options.size * unit * 4)}|${color}|${px},${py}`;
|
|
2934
3031
|
|
|
2935
3032
|
let tile = this._gridTile;
|
|
2936
3033
|
if (!tile || tile.size !== tileSize) {
|
|
@@ -2947,10 +3044,11 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2947
3044
|
}
|
|
2948
3045
|
if (tile.key !== key) {
|
|
2949
3046
|
tile.key = key;
|
|
2950
|
-
this._renderGridTile(tile, options,
|
|
3047
|
+
this._renderGridTile(tile, options, unit, color, px, py);
|
|
2951
3048
|
}
|
|
2952
3049
|
raw.fillStyle = tile.pattern;
|
|
2953
|
-
|
|
3050
|
+
const fill = this._device(region);
|
|
3051
|
+
raw.fillRect?.(fill.x, fill.y, fill.width, fill.height);
|
|
2954
3052
|
return true;
|
|
2955
3053
|
}
|
|
2956
3054
|
|
|
@@ -2969,11 +3067,12 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2969
3067
|
}
|
|
2970
3068
|
|
|
2971
3069
|
/** Draw one grid cell with the mark at the phase point, wrapped — a mark
|
|
2972
|
-
* near an edge is drawn again a tile over, so the seam never cuts it.
|
|
3070
|
+
* near an edge is drawn again a tile over, so the seam never cuts it.
|
|
3071
|
+
* Device pixels throughout: `unit` is how many of them a graph unit is. */
|
|
2973
3072
|
private _renderGridTile(
|
|
2974
3073
|
tile: { size: number; surface: SurfaceLike },
|
|
2975
3074
|
options: { variant: BackgroundVariant; size: number },
|
|
2976
|
-
|
|
3075
|
+
unit: number,
|
|
2977
3076
|
color: string,
|
|
2978
3077
|
px: number,
|
|
2979
3078
|
py: number,
|
|
@@ -2990,11 +3089,11 @@ export class FlowGraphNode extends Node implements FlowInstance {
|
|
|
2990
3089
|
if (j === 0) ctx.fillRect(cx - 0.5, 0, 1, t);
|
|
2991
3090
|
if (i === 0) ctx.fillRect(0, cy - 0.5, t, 1);
|
|
2992
3091
|
} else if (options.variant === 'cross') {
|
|
2993
|
-
const arm = Math.max(2, options.size * 3 *
|
|
3092
|
+
const arm = Math.max(2, options.size * 3 * unit);
|
|
2994
3093
|
ctx.fillRect(cx - arm, cy - 0.5, arm * 2, 1);
|
|
2995
3094
|
ctx.fillRect(cx - 0.5, cy - arm, 1, arm * 2);
|
|
2996
3095
|
} else {
|
|
2997
|
-
const dot = Math.max(1, Math.round(options.size * 2 *
|
|
3096
|
+
const dot = Math.max(1, Math.round(options.size * 2 * unit));
|
|
2998
3097
|
ctx.fillRect(cx - dot / 2, cy - dot / 2, dot, dot);
|
|
2999
3098
|
}
|
|
3000
3099
|
}
|
package/src/flow/types.ts
CHANGED
|
@@ -30,7 +30,9 @@ export interface XYPosition {
|
|
|
30
30
|
y: number;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** A box in graph space unless the field it is on says
|
|
33
|
+
/** A box in graph space unless the field it is on says window pixels —
|
|
34
|
+
* which are *logical* pixels, the unit every style length is in, whatever
|
|
35
|
+
* the display's scale (see {@link FlowPainter}). */
|
|
34
36
|
export interface FlowRect {
|
|
35
37
|
x: number;
|
|
36
38
|
y: number;
|
|
@@ -40,8 +42,10 @@ export interface FlowRect {
|
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* The pane's transform: graph point `p` is drawn at `p * zoom + (x, y)`,
|
|
43
|
-
* relative to the pane's top-left corner. Translate-
|
|
44
|
-
* order react-flow uses, so a viewport copied from one
|
|
45
|
+
* relative to the pane's top-left corner, in logical pixels. Translate-
|
|
46
|
+
* then-scale, the same order react-flow uses, so a viewport copied from one
|
|
47
|
+
* lands in the other — and one graph unit at zoom 1 is one logical pixel,
|
|
48
|
+
* so a node sized `{ width: 168 }` is as wide as a `<box>` styled the same.
|
|
45
49
|
*/
|
|
46
50
|
export interface Viewport {
|
|
47
51
|
x: number;
|
|
@@ -276,7 +280,7 @@ export interface TextOptions {
|
|
|
276
280
|
/** Where `y` is measured: the top of the line, or its middle. Default
|
|
277
281
|
* `'top'`. */
|
|
278
282
|
baseline?: 'top' | 'middle';
|
|
279
|
-
/** Ellipsize past this many pixels. */
|
|
283
|
+
/** Ellipsize past this many (logical) pixels. */
|
|
280
284
|
maxWidth?: number;
|
|
281
285
|
}
|
|
282
286
|
|
|
@@ -292,9 +296,12 @@ export interface ShapeOptions extends StrokeOptions {
|
|
|
292
296
|
}
|
|
293
297
|
|
|
294
298
|
/**
|
|
295
|
-
* What a custom node type draws through. Everything is in **
|
|
296
|
-
* already scaled by the zoom —
|
|
297
|
-
* `zoom`
|
|
299
|
+
* What a custom node type draws through. Everything is in **logical window
|
|
300
|
+
* pixels**, already scaled by the zoom — the unit a style length is in, so
|
|
301
|
+
* a `13 * zoom` label is the size a `fontSize: 13` label is at zoom 1. A
|
|
302
|
+
* node type multiplies its own sizes by `zoom` and otherwise ignores the
|
|
303
|
+
* viewport, and never thinks about the display: on a 2x panel the painter
|
|
304
|
+
* shapes text and lands geometry on the device grid itself.
|
|
298
305
|
*
|
|
299
306
|
* It is a facade rather than ntk's context because the mock backend used by
|
|
300
307
|
* headless tests has no path API: a node type written against this one is
|
|
@@ -303,8 +310,12 @@ export interface ShapeOptions extends StrokeOptions {
|
|
|
303
310
|
*/
|
|
304
311
|
export interface FlowPainter {
|
|
305
312
|
/** ntk's 2d context, for anything the facade does not offer. Null on a
|
|
306
|
-
* backend that cannot draw paths.
|
|
313
|
+
* backend that cannot draw paths. It draws in **device** pixels — the
|
|
314
|
+
* one place in this API that does — so multiply by {@link scale} there. */
|
|
307
315
|
readonly raw: unknown;
|
|
316
|
+
/** Device pixels per logical pixel: `1` on an ordinary display, `2` on a
|
|
317
|
+
* retina panel. Applied by every method here; only `raw` needs it. */
|
|
318
|
+
readonly scale: number;
|
|
308
319
|
save(): void;
|
|
309
320
|
restore(): void;
|
|
310
321
|
clipRect(x: number, y: number, w: number, h: number, radius?: number): void;
|
|
@@ -346,27 +357,37 @@ export interface FlowPainter {
|
|
|
346
357
|
/** What a node type is handed to draw one node. */
|
|
347
358
|
export interface NodePaintContext<Data = FlowNodeData> {
|
|
348
359
|
node: FlowNode<Data>;
|
|
349
|
-
/** The node's box, in
|
|
360
|
+
/** The node's box, in logical window pixels. */
|
|
350
361
|
rect: FlowRect;
|
|
351
362
|
zoom: number;
|
|
352
363
|
selected: boolean;
|
|
353
364
|
hovered: boolean;
|
|
354
365
|
palette: FlowPalette;
|
|
355
366
|
painter: FlowPainter;
|
|
356
|
-
/** The node's handles, resolved and in
|
|
357
|
-
* after `paint` returns, so a type that wants them somewhere
|
|
358
|
-
* them with `handles`, not by drawing its own. */
|
|
367
|
+
/** The node's handles, resolved and in logical window pixels. Drawn by
|
|
368
|
+
* the pane after `paint` returns, so a type that wants them somewhere
|
|
369
|
+
* else moves them with `handles`, not by drawing its own. */
|
|
359
370
|
handles: readonly HandleAnchor[];
|
|
360
371
|
}
|
|
361
372
|
|
|
362
|
-
/**
|
|
373
|
+
/**
|
|
374
|
+
* What {@link FlowNodeType.render} is handed.
|
|
375
|
+
*
|
|
376
|
+
* Unlike {@link NodePaintContext}, everything here is in **graph units** —
|
|
377
|
+
* the unit the node's own `position` and `width` are in, unscaled by the
|
|
378
|
+
* viewport. The body is mounted in a subtree the zoom scales, so a
|
|
379
|
+
* `fontSize: 11` is 11 graph units at every zoom and the body never
|
|
380
|
+
* multiplies by anything. `zoom` is there for a body that wants to show
|
|
381
|
+
* less of itself when it is small, not for sizing.
|
|
382
|
+
*/
|
|
363
383
|
export interface NodeRenderContext<Data = FlowNodeData> {
|
|
364
384
|
node: FlowNode<Data>;
|
|
365
385
|
selected: boolean;
|
|
366
386
|
zoom: number;
|
|
367
|
-
/** The box the returned tree is mounted in and laid out to, in
|
|
368
|
-
* relative to the pane's top-left corner. Its size is the useful
|
|
369
|
-
* the position is there for a body that wants to know where it
|
|
387
|
+
/** The box the returned tree is mounted in and laid out to, in graph
|
|
388
|
+
* units relative to the pane's top-left corner. Its size is the useful
|
|
389
|
+
* half; the position is there for a body that wants to know where it
|
|
390
|
+
* is. */
|
|
370
391
|
rect: FlowRect;
|
|
371
392
|
}
|
|
372
393
|
|
|
@@ -411,11 +432,12 @@ export interface FlowNodeType<Data = FlowNodeData> {
|
|
|
411
432
|
* - **The node re-renders as the viewport moves.** This is the cost the
|
|
412
433
|
* drawn path exists to avoid, so it is paid only by the nodes that ask
|
|
413
434
|
* for it, and only while they are on screen.
|
|
414
|
-
* - **It
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
435
|
+
* - **It zooms with the pane.** The subtree is mounted under a `scale`
|
|
436
|
+
* box (react-x11 2.6), so it is written in graph units — plain style
|
|
437
|
+
* lengths, no `zoom` in them — and comes out at the size the card is
|
|
438
|
+
* drawn at, text shaped at that size rather than stretched. Below
|
|
439
|
+
* `zoom` 0.6 it is not mounted at all: too small to read, and a
|
|
440
|
+
* mounted subtree per card is what an overview cannot afford.
|
|
419
441
|
*
|
|
420
442
|
* `headerHeight` is what leaves the node draggable: the body starts below
|
|
421
443
|
* it, so there is always somewhere to grab that is not a text field.
|
|
@@ -482,6 +504,9 @@ export interface FitViewOptions {
|
|
|
482
504
|
*/
|
|
483
505
|
export interface NodeBodyRect extends FlowRect {
|
|
484
506
|
id: string;
|
|
507
|
+
/** The viewport zoom the rect was measured at — the scale `<Flow>` gives
|
|
508
|
+
* the box it mounts, so that the body inside it is laid out in graph
|
|
509
|
+
* units. */
|
|
485
510
|
zoom: number;
|
|
486
511
|
selected: boolean;
|
|
487
512
|
}
|
package/src/formula/index.ts
CHANGED
|
@@ -88,8 +88,10 @@ export interface FormulaProps {
|
|
|
88
88
|
* ends: from then on a parse error renders KaTeX's error text.
|
|
89
89
|
*/
|
|
90
90
|
partial?: boolean;
|
|
91
|
-
/**
|
|
92
|
-
*
|
|
91
|
+
/** Logical pixels per em — the unit a `fontSize` is written in; on a
|
|
92
|
+
* retina panel the element shapes at `size` × the display scale, like
|
|
93
|
+
* any style length. Default: theme `fontSize` × 1.21, which is the
|
|
94
|
+
* ratio katex.css uses so mathematics holds its own beside body text. */
|
|
93
95
|
size?: number;
|
|
94
96
|
/** Ink color. Default: theme `text`. */
|
|
95
97
|
color?: string;
|
package/src/formula/node.ts
CHANGED
|
@@ -39,7 +39,8 @@ export interface FormulaElementProps {
|
|
|
39
39
|
/** KaTeX's virtual DOM for the expression. Stable identity per source —
|
|
40
40
|
* the layout cache keys off it. */
|
|
41
41
|
tree: KatexNode;
|
|
42
|
-
/**
|
|
42
|
+
/** Logical pixels per em at the formula's base size — the unit a
|
|
43
|
+
* `fontSize` is written in. The node shapes at `size × scale`. */
|
|
43
44
|
size: number;
|
|
44
45
|
/** Ink color for everything the expression does not color itself. */
|
|
45
46
|
color: string;
|
|
@@ -133,7 +134,7 @@ function registerKatexFonts(
|
|
|
133
134
|
|
|
134
135
|
export class FormulaNode extends Node {
|
|
135
136
|
private _layout: FormulaLayout | null = null;
|
|
136
|
-
private _layoutOf: { tree: KatexNode;
|
|
137
|
+
private _layoutOf: { tree: KatexNode; em: number; color: string } | null =
|
|
137
138
|
null;
|
|
138
139
|
private _widths = new Map<string, number>();
|
|
139
140
|
private _minis = new Map<string, MiniLayout | null>();
|
|
@@ -179,26 +180,44 @@ export class FormulaNode extends Node {
|
|
|
179
180
|
};
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
// --- units ---------------------------------------------------------------
|
|
184
|
+
//
|
|
185
|
+
// The layout, `abs`, the paint and the four text accessors are device
|
|
186
|
+
// pixels (react-x11's docs/scale.md). `size` is pixels per em the way a
|
|
187
|
+
// `fontSize` is written — logical — and, unlike a style length, nothing
|
|
188
|
+
// in core multiplies it on the way in; `_ensureLayout` does, so the
|
|
189
|
+
// glyphs are shaped at `size * scale` and a 17 on a 2x panel is the size
|
|
190
|
+
// the text beside it is, sharper rather than smaller. Everything below
|
|
191
|
+
// reads that one layout, which is what keeps a caret rect, a selection
|
|
192
|
+
// band and the ink agreeing.
|
|
193
|
+
|
|
194
|
+
/** Device pixels per logical pixel — the display scale this element's
|
|
195
|
+
* window resolved to, constant for the node's life. */
|
|
196
|
+
private get _scale(): number {
|
|
197
|
+
return this.scale > 0 ? this.scale : 1;
|
|
198
|
+
}
|
|
199
|
+
|
|
182
200
|
private _ensureLayout(): FormulaLayout | null {
|
|
183
201
|
const { tree, size = 14, color = 'black' } = this._props();
|
|
184
202
|
if (!tree) return null;
|
|
203
|
+
const em = size * this._scale;
|
|
185
204
|
const prev = this._layoutOf;
|
|
186
205
|
if (
|
|
187
206
|
this._layout &&
|
|
188
207
|
prev &&
|
|
189
208
|
prev.tree === tree &&
|
|
190
|
-
prev.
|
|
209
|
+
prev.em === em &&
|
|
191
210
|
prev.color === color
|
|
192
211
|
) {
|
|
193
212
|
return this._layout;
|
|
194
213
|
}
|
|
195
214
|
const fonts = this._fonts();
|
|
196
215
|
this._layout = layoutFormula(tree, {
|
|
197
|
-
em
|
|
216
|
+
em,
|
|
198
217
|
color,
|
|
199
218
|
shaper: fonts ? this._shaper(fonts) : null,
|
|
200
219
|
});
|
|
201
|
-
this._layoutOf = { tree,
|
|
220
|
+
this._layoutOf = { tree, em, color };
|
|
202
221
|
return this._layout;
|
|
203
222
|
}
|
|
204
223
|
|