@weasel-js/svg 1.12.0 → 1.13.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 +5 -2
- package/dist/index.d.ts +63 -26
- package/dist/index.js +142 -91
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -105,13 +105,16 @@ drafts the kit's built-in path, text and image painters draw, and registers
|
|
|
105
105
|
the document's markers. Containers carry no opacity, so an element or group
|
|
106
106
|
`opacity` is multiplied into the paints of the leaves under it. `nextId` is
|
|
107
107
|
handed the node each id is for, and `options.leaf` maps each leaf's kit data
|
|
108
|
-
into data of your own shape, from the source node's metadata too.
|
|
108
|
+
into data of your own shape, from the source node's metadata too. `options.mark`
|
|
109
|
+
says which `<g>`s are marks with children, as the export's `mark` decorator
|
|
110
|
+
tagged them.
|
|
109
111
|
|
|
110
112
|
`svgNodesFromKit(scene)` goes the other way: containers become groups, and
|
|
111
113
|
each leaf is written the way its painter draws it, with the pose baked into
|
|
112
114
|
the geometry and box-relative paints resolved into that box. Hand
|
|
113
115
|
`serializeSvg` the result. Options pick the roots, skip nodes (a hidden
|
|
114
|
-
layer), replace a leaf's lowering or decorate a group
|
|
116
|
+
layer), replace a leaf's lowering, or decorate a group or a mark with children
|
|
117
|
+
so the import can tell the two apart. The per-leaf pieces are
|
|
115
118
|
public too: `svgLeafFromKit`, `svgPaintFromKit`, `svgStrokeFromKit`,
|
|
116
119
|
`svgImageFromKit`.
|
|
117
120
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Affine } from '@weasel-js/geom';
|
|
2
2
|
export { pathToD as serializePathD } from '@weasel-js/geom';
|
|
3
|
-
import { Path, Paint, ScreenLength, StrokeAlign, MarkerRef, VertexWidth, StrokeNib, StyledRun, TextStyle, TextVerticalAlign, Fill, Stroke, PoseLinear, MarkerEntry, TextPoseLayout, RenderNeeds, TilePatternSpec, Inherit, ImageNodeData,
|
|
3
|
+
import { Path, Paint, ScreenLength, StrokeAlign, MarkerRef, VertexWidth, StrokeNib, StyledRun, TextStyle, TextVerticalAlign, Fill, Stroke, PoseLinear, MarkerEntry, TextPoseLayout, RenderNeeds, TilePatternSpec, IngestCtx, Inherit, ImageNodeData, NodeClip } from '@weasel-js/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Media Queries 4 evaluation against a fixed environment.
|
|
@@ -542,6 +542,19 @@ interface SupportsOptions {
|
|
|
542
542
|
*/
|
|
543
543
|
declare function evaluateSupports(condition: string, opts?: SupportsOptions): boolean;
|
|
544
544
|
|
|
545
|
+
/**
|
|
546
|
+
* Parse each file and insert its node tree, one undo step per file. Pass it
|
|
547
|
+
* as `<SceneCanvas ingestion={{ svg: { unpack: unpackSvgFiles } }}>`.
|
|
548
|
+
*
|
|
549
|
+
* Each file is fit-clamped to 90% of the visible viewport and centered on the
|
|
550
|
+
* drop point (or the viewport center), with later files cascading by a fixed
|
|
551
|
+
* offset. A multi-root file is wrapped in one container so it arrives as a
|
|
552
|
+
* single selectable unit. Content the file hides arrives hidden
|
|
553
|
+
* (`SceneNode.hidden`) rather than being left out. A file that fails to parse, or parses to nothing,
|
|
554
|
+
* is skipped with a `console.warn`.
|
|
555
|
+
*/
|
|
556
|
+
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
557
|
+
|
|
545
558
|
/**
|
|
546
559
|
* The kit-to-SVG direction: scene leaves back to `SvgNode`s, the inverse of
|
|
547
560
|
* `svgNodesToKitDrafts`. Each leaf is written as the built-in painter that
|
|
@@ -613,8 +626,11 @@ interface SvgKitTreeNode {
|
|
|
613
626
|
/** The kit's `SceneNode.hidden`: written with `display="none"`, which
|
|
614
627
|
* `parseSvg(…, { keepHidden: true })` reads back as `hidden`. */
|
|
615
628
|
hidden?: boolean;
|
|
616
|
-
/**
|
|
617
|
-
*
|
|
629
|
+
/** The kit's `SceneNode.clip`: `'outline'` writes the node's children
|
|
630
|
+
* clipped to its outline, as core's `clipOutline` reads it. */
|
|
631
|
+
clip?: NodeClip;
|
|
632
|
+
/** The node's copies, as the kit's `SceneNode.copiesFromPose` draws them:
|
|
633
|
+
* each is written as a transformed group of its children. */
|
|
618
634
|
copiesFromPose?(pose: SvgKitPose, node: never): readonly Matrix[] | null;
|
|
619
635
|
}
|
|
620
636
|
/** The read side of a scene that {@link svgNodesFromKit} walks. A kit
|
|
@@ -635,14 +651,24 @@ interface SvgNodesFromKitOptions<TId extends string = string> {
|
|
|
635
651
|
* as these, and the walk marks the result as inheriting where SVG's
|
|
636
652
|
* cascade can carry them. */
|
|
637
653
|
leaf?(id: TId, node: SvgKitTreeNode, inherited: SvgKitInherited): SvgNode | null;
|
|
638
|
-
/** Decorate the
|
|
654
|
+
/** Decorate the `<g>` written for a scene group, e.g. with `meta`. */
|
|
639
655
|
group?(id: TId, group: SvgGroupNode): SvgGroupNode;
|
|
656
|
+
/** Decorate the `<g>` written for a mark with children, so a reader can
|
|
657
|
+
* tell it from a group: `svgNodesToKitDrafts`'s `mark` option reads one
|
|
658
|
+
* back. Given, the `<g>` holds the mark's own element, then one `<g>` of its
|
|
659
|
+
* children carrying the mark's clip, then a transformed `<g>` of that for
|
|
660
|
+
* each copy; a mark whose children are all left out holds its own element
|
|
661
|
+
* alone. Not called for a mark whose own element is itself a group, or that
|
|
662
|
+
* draws nothing. */
|
|
663
|
+
mark?(id: TId, group: SvgGroupNode): SvgGroupNode;
|
|
640
664
|
}
|
|
641
665
|
/**
|
|
642
|
-
* Walk a scene's
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
* otherwise
|
|
666
|
+
* Walk a scene's tree into `SvgNode`s ready for `serializeSvg`: a group
|
|
667
|
+
* becomes a `<g>` of its children, in z-order, drawing nothing of its own; a
|
|
668
|
+
* mark is written by {@link svgLeafFromKit} unless `options.leaf` says
|
|
669
|
+
* otherwise, and a mark with children is a `<g>` holding that element and then
|
|
670
|
+
* its children, or one `<g>` of them when `options.mark` decorates it. A hidden
|
|
671
|
+
* node is written hidden, with its subtree as it is.
|
|
646
672
|
*/
|
|
647
673
|
declare function svgNodesFromKit<TId extends string>(tree: SvgKitTree<TId>, options?: SvgNodesFromKitOptions<TId>): SvgNode[];
|
|
648
674
|
/** What a leaf's `'inherit'` fill and stroke resolve to — its nearest
|
|
@@ -666,30 +692,45 @@ type SvgDraftPose = SvgDraftBounds & {
|
|
|
666
692
|
transform?: PoseLinear;
|
|
667
693
|
};
|
|
668
694
|
|
|
695
|
+
/**
|
|
696
|
+
* Parse an SVG file into **native scene nodes** — path/text leaves under
|
|
697
|
+
* containers that mirror the source `<g>` structure — instead of the default
|
|
698
|
+
* single embedded-image node.
|
|
699
|
+
*
|
|
700
|
+
* Leaf data targets the kit's built-in painters. Poses are absolute AABBs, and
|
|
701
|
+
* the renderer rebases stored geometry into the pose box, so placement and
|
|
702
|
+
* fit-clamping operate on poses alone and never rewrite path coordinates.
|
|
703
|
+
* Stroke widths, dashes, font sizes, and letter spacing live in data, so
|
|
704
|
+
* they take the clamp separately.
|
|
705
|
+
*/
|
|
706
|
+
|
|
669
707
|
/**
|
|
670
708
|
* One node the unpack wants the scene to materialize, in parent-before-child
|
|
671
709
|
* order (a draft's `parentId` always names an earlier draft, or `null` for
|
|
672
710
|
* roots). `hidden` is the source node's, for the caller to map onto whatever
|
|
673
711
|
* it hides a node with.
|
|
674
712
|
*
|
|
675
|
-
* A
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
* rather than its children's.
|
|
713
|
+
* A group's `data` is empty unless its `<g>` is clipped. A clipped one declares
|
|
714
|
+
* `clip: 'outline'` and holds the clip as an unpainted path,
|
|
715
|
+
* `{ path, fill: null }`, which is the outline a group clips to; its `pose` is
|
|
716
|
+
* the clip's box rather than its children's. A mark declares a clip only when
|
|
717
|
+
* it is read from a `<g>` `options.mark` claims.
|
|
679
718
|
*/
|
|
680
719
|
type SvgSceneDraft<TData = Record<string, unknown>> = {
|
|
681
|
-
kind: '
|
|
720
|
+
kind: 'group';
|
|
682
721
|
id: string;
|
|
683
722
|
parentId: string | null;
|
|
684
723
|
pose: SvgDraftPose;
|
|
685
724
|
data: SvgKitLeafData;
|
|
725
|
+
clip?: NodeClip;
|
|
686
726
|
hidden?: true;
|
|
687
727
|
} | {
|
|
688
|
-
kind: '
|
|
728
|
+
kind: 'mark';
|
|
689
729
|
id: string;
|
|
690
730
|
parentId: string | null;
|
|
691
731
|
pose: SvgDraftPose;
|
|
692
732
|
data: TData;
|
|
733
|
+
clip?: NodeClip;
|
|
693
734
|
hidden?: true;
|
|
694
735
|
};
|
|
695
736
|
/** A leaf `SvgNode`: everything but a group. */
|
|
@@ -707,6 +748,14 @@ interface SvgNodesToKitDraftsOptions<TData> {
|
|
|
707
748
|
* whose metadata means something with no children under it. It is drafted
|
|
708
749
|
* with its clip's box, or a zero box at the origin. */
|
|
709
750
|
keepEmpty?(source: SvgGroupNode): boolean;
|
|
751
|
+
/** Whether a `<g>` is a mark with children, as `svgNodesFromKit`'s `mark`
|
|
752
|
+
* option writes one: its first element is drafted as the mark, the
|
|
753
|
+
* children of the `<g>` after it as the mark's, hidden where that `<g>` is,
|
|
754
|
+
* with its clip as the mark's `clip: 'outline'`, and anything after that,
|
|
755
|
+
* such as baked copies, as the mark's further children. A clip on the
|
|
756
|
+
* claimed `<g>` itself clips the mark as a group around it. A `<g>` whose
|
|
757
|
+
* first element is a group, or whose second is not, is a group. */
|
|
758
|
+
mark?(source: SvgGroupNode): boolean;
|
|
710
759
|
}
|
|
711
760
|
/** Lower an `SvgPaint` onto the paint of the `kit:path` painter's
|
|
712
761
|
* `data.fill` — a `Paint`, or `null` for SVG's `fill="none"` (the painter
|
|
@@ -736,17 +785,5 @@ declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftB
|
|
|
736
785
|
* data; a group whose leaves it all leaves out is dropped like an empty one.
|
|
737
786
|
*/
|
|
738
787
|
declare function svgNodesToKitDrafts<TData = Record<string, unknown>>(input: ParseResult | readonly SvgNode[], nextId: (source: SvgNode) => string, options?: SvgNodesToKitDraftsOptions<TData>): SvgSceneDraft<TData>[];
|
|
739
|
-
/**
|
|
740
|
-
* Parse each file and insert its node tree, one undo step per file. Pass it
|
|
741
|
-
* as `<SceneCanvas ingestion={{ svg: { unpack: unpackSvgFiles } }}>`.
|
|
742
|
-
*
|
|
743
|
-
* Each file is fit-clamped to 90% of the visible viewport and centered on the
|
|
744
|
-
* drop point (or the viewport center), with later files cascading by a fixed
|
|
745
|
-
* offset. A multi-root file is wrapped in one container so it arrives as a
|
|
746
|
-
* single selectable unit. Content the file hides arrives hidden
|
|
747
|
-
* (`SceneNode.hidden`) rather than being left out. A file that fails to parse, or parses to nothing,
|
|
748
|
-
* is skipped with a `console.warn`.
|
|
749
|
-
*/
|
|
750
|
-
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
751
788
|
|
|
752
789
|
export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type Specificity, type SupportsOptions, type SvgDraftBounds, type SvgDraftPose, type SvgGroupNode, type SvgImageNode, type SvgKitLeafData, type SvgKitPose, type SvgKitTree, type SvgKitTreeNode, type SvgLeafNode, type SvgMediaEnvironment, type SvgNode, type SvgNodesFromKitOptions, type SvgNodesToKitDraftsOptions, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, compareSpecificity, escapeAttr, escapeText, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, specificity, splitTopLevel, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { identity, multiply, pathToD, invert, splitByClosure, fillsOpenSubpaths, rotateAboutPoint, hasOpenSubpath, splitSubpaths } from '@weasel-js/geom';
|
|
2
2
|
export { pathToD as serializePathD } from '@weasel-js/geom';
|
|
3
|
-
import { isTileKind, boundsOfPath, transformPath, isSolidPaint, warmFonts, warmPaintKinds, isPaintKindKnown, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, resolveStroke, pathThroughPoseFrame, resolveFill, pathInPoseFrame, poseLinearOf, poseFrameOf, INHERIT,
|
|
3
|
+
import { isTileKind, boundsOfPath, transformPath, isSolidPaint, warmFonts, warmPaintKinds, isPaintKindKnown, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, resolveStroke, pathThroughPoseFrame, resolveFill, pathInPoseFrame, poseLinearOf, poseFrameOf, INHERIT, clipOutline, resolveScreenLength, pathFromD, isPatternPaint, resolveTextStyle, strokeToPath, toRuns, resolveRunFace, DEFAULT_TEXT_FILL, parseColor, rgbaToHex, layoutRuns, resolveRuns, resolveAlign, PathBuilder, PATH_M, PATH_L, PATH_Z, scaleVertexWidth, getPaintKind, resolveMarkerSize, paragraphDirection, scriptMetricsFor, vertexHalfWidths, isTilePaint, tileSpecOf } from '@weasel-js/core';
|
|
4
4
|
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
5
5
|
|
|
6
6
|
// src/types.ts
|
|
@@ -3971,16 +3971,6 @@ function groupTakes(group, takes, k) {
|
|
|
3971
3971
|
const stroke = group.inherits?.stroke ? takes.stroke : solidOrNone(group.stroke?.paint);
|
|
3972
3972
|
return { ...fill ? { fill: k } : {}, ...stroke ? { stroke: k } : {} };
|
|
3973
3973
|
}
|
|
3974
|
-
var CASCADE_OFFSET_PX = 24;
|
|
3975
|
-
var VIEWPORT_FIT = 0.9;
|
|
3976
|
-
function readFileText(file) {
|
|
3977
|
-
return new Promise((resolve, reject) => {
|
|
3978
|
-
const reader = new FileReader();
|
|
3979
|
-
reader.onload = () => resolve(String(reader.result ?? ""));
|
|
3980
|
-
reader.onerror = () => reject(reader.error);
|
|
3981
|
-
reader.readAsText(file);
|
|
3982
|
-
});
|
|
3983
|
-
}
|
|
3984
3974
|
function fillDataFromSvg(paint, box) {
|
|
3985
3975
|
if (!paint) return void 0;
|
|
3986
3976
|
if (paint.kind === "none") return null;
|
|
@@ -4032,19 +4022,25 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
4032
4022
|
const id = nextId(source);
|
|
4033
4023
|
const out = options.leaf ? options.leaf(id, { pose, data }, source) : data;
|
|
4034
4024
|
if (out === null) return null;
|
|
4035
|
-
drafts.push({ kind: "
|
|
4025
|
+
drafts.push({ kind: "mark", id, parentId, pose, data: out, ...source.hidden ? { hidden: true } : {} });
|
|
4036
4026
|
return pose;
|
|
4037
4027
|
};
|
|
4038
4028
|
const visit = (n, parentId, inherited2, from) => {
|
|
4039
4029
|
const k = inherited2 * (n.opacity ?? 1);
|
|
4040
4030
|
const takes = takesOf(n, from, k);
|
|
4041
4031
|
if (n.kind === "group") {
|
|
4032
|
+
const [own2, kids, ...more] = n.children;
|
|
4033
|
+
if (own2 && own2.kind !== "group" && (kids === void 0 || kids.kind === "group") && options.mark?.(n)) {
|
|
4034
|
+
if (n.clip) return visit(clippingGroup(n), parentId, inherited2, from);
|
|
4035
|
+
return visitMark(n, own2, kids, more, parentId, k, takes);
|
|
4036
|
+
}
|
|
4042
4037
|
const draft = {
|
|
4043
|
-
kind: "
|
|
4038
|
+
kind: "group",
|
|
4044
4039
|
id: nextId(n),
|
|
4045
4040
|
parentId,
|
|
4046
4041
|
pose: { x: 0, y: 0, width: 0, height: 0 },
|
|
4047
4042
|
data: n.clip ? { path: n.clip, fill: null } : {},
|
|
4043
|
+
...n.clip ? { clip: "outline" } : {},
|
|
4048
4044
|
...n.hidden ? { hidden: true } : {}
|
|
4049
4045
|
};
|
|
4050
4046
|
drafts.push(draft);
|
|
@@ -4060,60 +4056,95 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
4060
4056
|
Object.assign(draft.data, containerPaint(n, takes, draft.pose, k));
|
|
4061
4057
|
return draft.pose;
|
|
4062
4058
|
}
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
const
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
...n.verticalAlign ? { verticalAlign: n.verticalAlign } : {},
|
|
4083
|
-
...n.runs ? { runs: n.runs.map((r) => fadeRun(runInBoxFrame(r, box), k)) } : {},
|
|
4084
|
-
// `null` is the document saying `fill="none"`.
|
|
4085
|
-
...takes.fill ? { fill: INHERIT } : { fill: fill2 && mapFillPaint(fill2, (p) => fadeFill(fillInBoxFrame(p, box), k)) },
|
|
4086
|
-
...takes.stroke ? { stroke: INHERIT } : n.stroke !== void 0 ? { stroke: fadeStroke(strokeInBoxFrame(n.stroke, box), k) } : {}
|
|
4087
|
-
});
|
|
4088
|
-
}
|
|
4089
|
-
if (n.kind === "image") {
|
|
4090
|
-
const pose2 = { x: n.x, y: n.y, width: n.width, height: n.height };
|
|
4091
|
-
if (n.rotation) pose2.rotation = n.rotation;
|
|
4092
|
-
if (n.transform) pose2.transform = n.transform;
|
|
4093
|
-
return pushLeaf(n, parentId, pose2, {
|
|
4094
|
-
image: {
|
|
4095
|
-
src: n.href,
|
|
4096
|
-
...k !== 1 ? { opacity: k } : {},
|
|
4097
|
-
...n.source ? { source: { ...n.source } } : {},
|
|
4098
|
-
...n.flipX ? { flipX: true } : {},
|
|
4099
|
-
...n.flipY ? { flipY: true } : {}
|
|
4100
|
-
}
|
|
4101
|
-
});
|
|
4059
|
+
const { pose, data } = leafDraft(n, takes, k);
|
|
4060
|
+
return pushLeaf(n, parentId, pose, data);
|
|
4061
|
+
};
|
|
4062
|
+
const visitMark = (n, own2, kids, more, parentId, k, takes) => {
|
|
4063
|
+
const inner = groupTakes(n, takes, k);
|
|
4064
|
+
const pose = visit(own2, parentId, k, inner);
|
|
4065
|
+
const mark = pose === null ? null : drafts.at(-1);
|
|
4066
|
+
const under = mark?.id ?? parentId;
|
|
4067
|
+
if (mark && n.hidden) mark.hidden = true;
|
|
4068
|
+
if (mark && kids?.clip) mark.clip = "outline";
|
|
4069
|
+
const poses = [pose];
|
|
4070
|
+
if (kids) {
|
|
4071
|
+
const at = drafts.length;
|
|
4072
|
+
const kk = k * (kids.opacity ?? 1);
|
|
4073
|
+
const below = groupTakes(kids, takesOf(kids, inner, kk), kk);
|
|
4074
|
+
for (const c of kids.children) poses.push(visit(c, under, kk, below));
|
|
4075
|
+
if (kids.hidden) {
|
|
4076
|
+
for (const d of drafts.slice(at)) if (d.parentId === under) d.hidden = true;
|
|
4077
|
+
}
|
|
4102
4078
|
}
|
|
4103
|
-
const
|
|
4104
|
-
|
|
4105
|
-
if (n.rotation) pose.rotation = n.rotation;
|
|
4106
|
-
const fill = fillDataFromSvg(n.fill, b);
|
|
4107
|
-
const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
|
|
4108
|
-
return pushLeaf(n, parentId, pose, {
|
|
4109
|
-
path: n.path,
|
|
4110
|
-
...takes.fill ? { fill: INHERIT } : fill !== void 0 ? { fill: fill && { paint: fadeFill(fill, k) } } : {},
|
|
4111
|
-
...takes.stroke ? { stroke: INHERIT } : strokeFromSvg !== void 0 ? { stroke: fadeStroke(strokeFromSvg, k) } : {}
|
|
4112
|
-
});
|
|
4079
|
+
for (const c of more) poses.push(visit(c, under, k, inner));
|
|
4080
|
+
return unionAABB(poses);
|
|
4113
4081
|
};
|
|
4114
4082
|
for (const n of nodes) visit(n, null, 1, NO_TAKES);
|
|
4115
4083
|
return drafts;
|
|
4116
4084
|
}
|
|
4085
|
+
function leafDraft(n, takes, k) {
|
|
4086
|
+
if (n.kind === "text") {
|
|
4087
|
+
const pose2 = {
|
|
4088
|
+
x: n.x,
|
|
4089
|
+
y: n.y,
|
|
4090
|
+
width: n.width,
|
|
4091
|
+
height: n.height
|
|
4092
|
+
};
|
|
4093
|
+
if (n.rotation) pose2.rotation = n.rotation;
|
|
4094
|
+
if (n.transform) pose2.transform = n.transform;
|
|
4095
|
+
const box = {
|
|
4096
|
+
x: pose2.x,
|
|
4097
|
+
y: pose2.y,
|
|
4098
|
+
width: pose2.width,
|
|
4099
|
+
height: pose2.height
|
|
4100
|
+
};
|
|
4101
|
+
const fill2 = n.fill === void 0 ? DEFAULT_TEXT_FILL : n.fill;
|
|
4102
|
+
return { pose: pose2, data: {
|
|
4103
|
+
text: n.text,
|
|
4104
|
+
...n.style ? { style: n.style } : {},
|
|
4105
|
+
...n.verticalAlign ? { verticalAlign: n.verticalAlign } : {},
|
|
4106
|
+
...n.runs ? { runs: n.runs.map((r) => fadeRun(runInBoxFrame(r, box), k)) } : {},
|
|
4107
|
+
// `null` is the document saying `fill="none"`.
|
|
4108
|
+
...takes.fill ? { fill: INHERIT } : { fill: fill2 && mapFillPaint(fill2, (p) => fadeFill(fillInBoxFrame(p, box), k)) },
|
|
4109
|
+
...takes.stroke ? { stroke: INHERIT } : n.stroke !== void 0 ? { stroke: fadeStroke(strokeInBoxFrame(n.stroke, box), k) } : {}
|
|
4110
|
+
} };
|
|
4111
|
+
}
|
|
4112
|
+
if (n.kind === "image") {
|
|
4113
|
+
const pose2 = { x: n.x, y: n.y, width: n.width, height: n.height };
|
|
4114
|
+
if (n.rotation) pose2.rotation = n.rotation;
|
|
4115
|
+
if (n.transform) pose2.transform = n.transform;
|
|
4116
|
+
return { pose: pose2, data: {
|
|
4117
|
+
image: {
|
|
4118
|
+
src: n.href,
|
|
4119
|
+
...k !== 1 ? { opacity: k } : {},
|
|
4120
|
+
...n.source ? { source: { ...n.source } } : {},
|
|
4121
|
+
...n.flipX ? { flipX: true } : {},
|
|
4122
|
+
...n.flipY ? { flipY: true } : {}
|
|
4123
|
+
}
|
|
4124
|
+
} };
|
|
4125
|
+
}
|
|
4126
|
+
const b = boxOfPath(n.path);
|
|
4127
|
+
const pose = { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
4128
|
+
if (n.rotation) pose.rotation = n.rotation;
|
|
4129
|
+
const fill = fillDataFromSvg(n.fill, b);
|
|
4130
|
+
const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
|
|
4131
|
+
return { pose, data: {
|
|
4132
|
+
path: n.path,
|
|
4133
|
+
...takes.fill ? { fill: INHERIT } : fill !== void 0 ? { fill: fill && { paint: fadeFill(fill, k) } } : {},
|
|
4134
|
+
...takes.stroke ? { stroke: INHERIT } : strokeFromSvg !== void 0 ? { stroke: fadeStroke(strokeFromSvg, k) } : {}
|
|
4135
|
+
} };
|
|
4136
|
+
}
|
|
4137
|
+
function clippingGroup(n) {
|
|
4138
|
+
const { clip, ...unclipped } = n;
|
|
4139
|
+
return {
|
|
4140
|
+
kind: "group",
|
|
4141
|
+
clip,
|
|
4142
|
+
children: [unclipped],
|
|
4143
|
+
...n.fill ? { fill: n.fill } : {},
|
|
4144
|
+
...n.stroke ? { stroke: n.stroke } : {},
|
|
4145
|
+
...n.inherits ? { inherits: n.inherits } : {}
|
|
4146
|
+
};
|
|
4147
|
+
}
|
|
4117
4148
|
function containerPaint(n, takes, box, k) {
|
|
4118
4149
|
const fill = fillDataFromSvg(n.fill, box);
|
|
4119
4150
|
const stroke = strokeDataFromSvg(n.stroke, box);
|
|
@@ -4129,6 +4160,16 @@ function boxOfPath(path) {
|
|
|
4129
4160
|
function isNodeList(input) {
|
|
4130
4161
|
return Array.isArray(input);
|
|
4131
4162
|
}
|
|
4163
|
+
var CASCADE_OFFSET_PX = 24;
|
|
4164
|
+
var VIEWPORT_FIT = 0.9;
|
|
4165
|
+
function readFileText(file) {
|
|
4166
|
+
return new Promise((resolve, reject) => {
|
|
4167
|
+
const reader = new FileReader();
|
|
4168
|
+
reader.onload = () => resolve(String(reader.result ?? ""));
|
|
4169
|
+
reader.onerror = () => reject(reader.error);
|
|
4170
|
+
reader.readAsText(file);
|
|
4171
|
+
});
|
|
4172
|
+
}
|
|
4132
4173
|
function scaleLeafData(data, scale) {
|
|
4133
4174
|
if (scale === 1) return data;
|
|
4134
4175
|
const out = { ...data };
|
|
@@ -4157,7 +4198,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
4157
4198
|
const wrapperId = ctx.scene.mintId();
|
|
4158
4199
|
const union2 = unionAABB(roots.map((d) => d.pose));
|
|
4159
4200
|
drafts = [
|
|
4160
|
-
{ kind: "
|
|
4201
|
+
{ kind: "group", id: wrapperId, parentId: null, pose: union2, data: {} },
|
|
4161
4202
|
...drafts.map((d) => d.parentId === null ? { ...d, parentId: wrapperId } : d)
|
|
4162
4203
|
];
|
|
4163
4204
|
}
|
|
@@ -4189,8 +4230,9 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
4189
4230
|
kind: d.kind,
|
|
4190
4231
|
layer,
|
|
4191
4232
|
pose: place(d.pose),
|
|
4192
|
-
data: d.kind === "
|
|
4233
|
+
data: d.kind === "mark" ? scaleLeafData(d.data, scale) : d.data,
|
|
4193
4234
|
parent: d.parentId,
|
|
4235
|
+
...d.clip ? { clip: d.clip } : {},
|
|
4194
4236
|
...d.hidden ? { hidden: true } : {}
|
|
4195
4237
|
},
|
|
4196
4238
|
label: "Insert SVG"
|
|
@@ -4308,42 +4350,51 @@ function svgTextFromKit(data, pose) {
|
|
|
4308
4350
|
return node;
|
|
4309
4351
|
}
|
|
4310
4352
|
function svgNodesFromKit(tree, options = {}) {
|
|
4311
|
-
const { include, leaf, group } = options;
|
|
4353
|
+
const { include, leaf, group, mark } = options;
|
|
4312
4354
|
const emit = (id, up) => {
|
|
4313
4355
|
const node = tree.get(id);
|
|
4314
4356
|
const out2 = node && (!include || include(id)) ? lower(id, node, up) : null;
|
|
4315
4357
|
return out2 && node?.hidden === true ? { ...out2, hidden: true } : out2;
|
|
4316
4358
|
};
|
|
4317
4359
|
const lower = (id, node, up) => {
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
const
|
|
4333
|
-
const
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
let g = { ...content(), ...groupPaintFromKit(own2, below, node.pose) };
|
|
4337
|
-
if (copies.length > 0) {
|
|
4338
|
-
g = {
|
|
4339
|
-
kind: "group",
|
|
4340
|
-
children: [g, ...copies.map((transform) => ({ kind: "group", transform, children: [content()] }))]
|
|
4341
|
-
};
|
|
4360
|
+
const own2 = node.data;
|
|
4361
|
+
const isGroup = node.kind === "group";
|
|
4362
|
+
const self = isGroup ? null : markInherits(
|
|
4363
|
+
leaf ? leaf(id, node, up) : svgLeafFromKit(resolvedData(own2 ?? {}, up), node.pose),
|
|
4364
|
+
own2 ?? {},
|
|
4365
|
+
up
|
|
4366
|
+
);
|
|
4367
|
+
const kids = tree.childrenOf(id);
|
|
4368
|
+
if (!isGroup && kids.length === 0) return self;
|
|
4369
|
+
const below = {
|
|
4370
|
+
fill: own2?.fill === INHERIT ? up.fill : resolveFill(own2?.fill),
|
|
4371
|
+
stroke: own2?.stroke === INHERIT ? up.stroke : resolveStroke(own2?.stroke)
|
|
4372
|
+
};
|
|
4373
|
+
const emitChildren = () => {
|
|
4374
|
+
const children = [];
|
|
4375
|
+
for (const c of kids) {
|
|
4376
|
+
const child = emit(c, below);
|
|
4377
|
+
if (child) children.push(child);
|
|
4342
4378
|
}
|
|
4379
|
+
return children;
|
|
4380
|
+
};
|
|
4381
|
+
const clip = node.clip === "outline" ? clipOutline(node, node.pose) ?? void 0 : void 0;
|
|
4382
|
+
const copies = node.copiesFromPose?.(node.pose, node) ?? [];
|
|
4383
|
+
const content = () => ({ kind: "group", children: emitChildren(), ...clip ? { clip } : {} });
|
|
4384
|
+
const paint = groupPaintFromKit(own2, below, node.pose);
|
|
4385
|
+
const copied = () => copies.map((transform) => ({ kind: "group", transform, children: [content()] }));
|
|
4386
|
+
const withCopies = (g) => copies.length === 0 ? g : { kind: "group", children: [g, ...copied()] };
|
|
4387
|
+
if (isGroup) {
|
|
4388
|
+
const g = withCopies({ ...content(), ...paint });
|
|
4343
4389
|
return group ? group(id, g) : g;
|
|
4344
4390
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4391
|
+
if (mark && self && self.kind !== "group") {
|
|
4392
|
+
const kids2 = content();
|
|
4393
|
+
return mark(id, { kind: "group", children: kids2.children.length === 0 ? [self] : [self, kids2, ...copied()], ...paint });
|
|
4394
|
+
}
|
|
4395
|
+
const inner = withCopies(content());
|
|
4396
|
+
const parts = clip || copies.length > 0 ? [inner] : inner.children;
|
|
4397
|
+
return { kind: "group", children: self ? [self, ...parts] : parts, ...paint };
|
|
4347
4398
|
};
|
|
4348
4399
|
const out = [];
|
|
4349
4400
|
for (const id of options.roots ?? tree.roots) {
|