diff --git a/.gitignore b/.gitignore index cf7fd1be..83d721ec 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ bin/ # Node node_modules/ +!skills/lark-slides/scripts/artboard_renderer/dist/ +!skills/lark-slides/scripts/artboard_renderer/dist/render.mjs # OS diff --git a/cmd/api/api.go b/cmd/api/api.go index 072117d7..e227e768 100644 --- a/cmd/api/api.go +++ b/cmd/api/api.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "io" + "net/http" "regexp" "strings" @@ -42,6 +43,7 @@ type APIOptions struct { JqExpr string DryRun bool File string + Headers []string } var urlPrefixRe = regexp.MustCompile(`https?://[^/]+(/open-apis/.+)`) @@ -94,6 +96,7 @@ func NewCmdApiWithContext(ctx context.Context, f *cmdutil.Factory, runF func(*AP cmd.Flags().StringVarP(&opts.JqExpr, "jq", "q", "", "jq expression to filter JSON output") cmd.Flags().BoolVar(&opts.DryRun, "dry-run", false, "print request without executing") cmd.Flags().StringVar(&opts.File, "file", "", "file to upload as multipart/form-data ([field=]path, supports - for stdin)") + cmd.Flags().StringArrayVar(&opts.Headers, "request-header", nil, "internal request header for controlled lanes; repeat key=value, currently only x-tt-env=ppe_pure_svg is allowed") cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { @@ -140,6 +143,13 @@ func buildAPIRequest(opts *APIOptions) (client.RawApiRequest, *cmdutil.FileUploa Params: params, As: opts.As, } + headers, err := parseAPIRequestHeaders(opts.Headers) + if err != nil { + return client.RawApiRequest{}, nil, err + } + if len(headers) > 0 { + request.ExtraOpts = append(request.ExtraOpts, larkcore.WithHeaders(headers)) + } if opts.File != "" { // File upload path: build formdata. @@ -187,6 +197,30 @@ func buildAPIRequest(opts *APIOptions) (client.RawApiRequest, *cmdutil.FileUploa return request, nil, nil } +func parseAPIRequestHeaders(values []string) (http.Header, error) { + headers := http.Header{} + for _, raw := range values { + item := strings.TrimSpace(raw) + if item == "" { + return nil, output.ErrValidation("--request-header cannot be empty") + } + key, value, ok := strings.Cut(item, "=") + if !ok { + return nil, output.ErrValidation("--request-header %q must use key=value", item) + } + key = strings.TrimSpace(key) + value = strings.TrimSpace(value) + if !strings.EqualFold(key, "x-tt-env") { + return nil, output.ErrValidation("--request-header %q is not supported; only x-tt-env is allowed", key) + } + if value != "ppe_pure_svg" { + return nil, output.ErrValidation("--request-header x-tt-env must be ppe_pure_svg") + } + headers.Set("x-tt-env", value) + } + return headers, nil +} + func apiRun(opts *APIOptions) error { f := opts.Factory opts.As = f.ResolveAs(opts.Ctx, opts.Cmd, opts.As) diff --git a/cmd/api/api_test.go b/cmd/api/api_test.go index c3530c32..8561a0cd 100644 --- a/cmd/api/api_test.go +++ b/cmd/api/api_test.go @@ -88,6 +88,46 @@ func TestApiCmd_BotMode(t *testing.T) { } } +func TestApiCmd_RequestHeaderPassesToCall(t *testing.T) { + f, stdout, _, reg := cmdutil.TestFactory(t, &core.CliConfig{ + AppID: "test-app", AppSecret: "test-secret", Brand: core.BrandFeishu, + }) + + stub := &httpmock.Stub{ + URL: "/open-apis/test", + Body: map[string]interface{}{"code": 0, "msg": "ok", "data": map[string]interface{}{"result": "success"}}, + } + reg.Register(stub) + + cmd := NewCmdApi(f, nil) + cmd.SetArgs([]string{"GET", "/open-apis/test", "--as", "user", "--request-header", "x-tt-env=ppe_pure_svg"}) + if err := cmd.Execute(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := stub.CapturedHeaders.Get("x-tt-env"); got != "ppe_pure_svg" { + t.Fatalf("x-tt-env header = %q, want ppe_pure_svg", got) + } + if !strings.Contains(stdout.String(), "success") { + t.Error("expected 'success' in output") + } +} + +func TestApiCmd_RequestHeaderRejectsUnsupportedKey(t *testing.T) { + f, _, _, _ := cmdutil.TestFactory(t, &core.CliConfig{ + AppID: "test-app", AppSecret: "test-secret", Brand: core.BrandFeishu, + }) + + cmd := NewCmdApi(f, nil) + cmd.SetArgs([]string{"GET", "/open-apis/test", "--as", "user", "--request-header", "authorization=secret"}) + err := cmd.Execute() + if err == nil { + t.Fatal("expected unsupported request header error") + } + if !strings.Contains(err.Error(), "only x-tt-env is allowed") { + t.Fatalf("err = %v, want supported-header message", err) + } +} + func TestApiCmd_MissingArgs(t *testing.T) { f, _, _, _ := cmdutil.TestFactory(t, &core.CliConfig{ AppID: "test-app", AppSecret: "test-secret", Brand: core.BrandFeishu, diff --git a/cmd/skill/skill.go b/cmd/skill/skill.go index 351dda77..c9316fb4 100644 --- a/cmd/skill/skill.go +++ b/cmd/skill/skill.go @@ -50,7 +50,8 @@ func NewCmdSkill(f *cmdutil.Factory) *cobra.Command { Short: "Read embedded skill content (list / read)", Long: "Read agent-readable skill content (SKILL.md and reference files) embedded in " + "the CLI binary at build time, so it stays in sync with the CLI version. " + - "Machine resources such as assets/ and scripts/ are not embedded.", + "Selected lark-slides prompts, scripts, and artboard renderer package files are also embedded; " + + "runtime dependency folders such as node_modules/ and generated artifacts are not embedded.", } // Risk is set on each leaf (GetRisk does not walk parents); the group has none. cmdutil.DisableAuthCheck(cmd) diff --git a/shortcuts/common/runner.go b/shortcuts/common/runner.go index e3365d2f..0094c57c 100644 --- a/shortcuts/common/runner.go +++ b/shortcuts/common/runner.go @@ -251,6 +251,14 @@ func (ctx *RuntimeContext) CallAPI(method, url string, params map[string]interfa return HandleApiResult(result, err, "API call failed") } +// CallAPIWithHeaders is CallAPI plus request-scoped HTTP headers. Keep this for +// shortcuts that need a narrow, audited transport lane rather than global CLI +// header injection. +func (ctx *RuntimeContext) CallAPIWithHeaders(method, url string, params map[string]interface{}, data interface{}, headers http.Header) (map[string]interface{}, error) { + result, err := ctx.callRawWithHeaders(method, url, params, data, headers) + return HandleApiResult(result, err, "API call failed") +} + // CallAPITyped is the typed-only replacement for CallAPI: it performs the same // SDK request (buildRequest → APIClient.DoAPI → DoSDKRequest, identical // transport and query model to CallAPI) and returns the "data" object, but @@ -422,11 +430,19 @@ func (ctx *RuntimeContext) buildRequest(method, url string, params map[string]in } func (ctx *RuntimeContext) callRaw(method, url string, params map[string]interface{}, data interface{}) (interface{}, error) { + return ctx.callRawWithHeaders(method, url, params, data, nil) +} + +func (ctx *RuntimeContext) callRawWithHeaders(method, url string, params map[string]interface{}, data interface{}, headers http.Header) (interface{}, error) { ac, err := ctx.getAPIClient() if err != nil { return nil, err } - return ac.CallAPI(ctx.ctx, ctx.buildRequest(method, url, params, data)) + req := ctx.buildRequest(method, url, params, data) + if len(headers) > 0 { + req.ExtraOpts = append(req.ExtraOpts, larkcore.WithHeaders(headers)) + } + return ac.CallAPI(ctx.ctx, req) } // DoAPI executes a raw Lark SDK request with automatic auth handling. @@ -1000,6 +1016,10 @@ func runShortcut(cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut, botOnly bo } } + if wantDryRun, _ := cmd.Flags().GetBool("dry-run"); wantDryRun && s.DryRun != nil { + return runShortcutDryRunLocal(cmd, f, s, botOnly) + } + as, err := resolveShortcutIdentity(cmd, f, s) if err != nil { return err @@ -1050,6 +1070,49 @@ func runShortcut(cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut, botOnly bo return rctx.outputErr } +func runShortcutDryRunLocal(cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut, botOnly bool) error { + asFlag, _ := cmd.Flags().GetString("as") + as := core.Identity(strings.TrimSpace(asFlag)) + if as == "" || as == "auto" { + as = core.AsUser + } + if botOnly { + as = core.AsBot + } + if !shortcutSupportsIdentity(as, s.AuthTypes) { + return f.CheckIdentity(as, s.AuthTypes) + } + config := &core.CliConfig{} + rctx := newLocalDryRunRuntimeContext(cmd, f, s, config, as, botOnly) + if err := validateEnumFlags(rctx, s.Flags); err != nil { + return err + } + if err := resolveInputFlags(rctx, s.Flags); err != nil { + return err + } + if err := output.ValidateJqFlags(rctx.JqExpr, "", rctx.Format); err != nil { + return err + } + if s.Validate != nil { + if err := s.Validate(rctx.ctx, rctx); err != nil { + return err + } + } + return handleShortcutDryRun(f, rctx, s) +} + +func shortcutSupportsIdentity(as core.Identity, authTypes []string) bool { + if len(authTypes) == 0 { + return true + } + for _, raw := range authTypes { + if core.Identity(raw) == as { + return true + } + } + return false +} + func resolveShortcutIdentity(cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut) (core.Identity, error) { // Step 1: determine identity (--as > default-as > auto-detect). asFlag, _ := cmd.Flags().GetString("as") @@ -1104,6 +1167,21 @@ func newRuntimeContext(cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut, conf return rctx, nil } +func newLocalDryRunRuntimeContext(cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut, config *core.CliConfig, as core.Identity, botOnly bool) *RuntimeContext { + ctx := cmd.Context() + ctx = cmdutil.ContextWithShortcut(ctx, s.Service+":"+s.Command, uuid.New().String()) + rctx := &RuntimeContext{ctx: ctx, Config: config, Cmd: cmd, botOnly: botOnly, resolvedAs: as, Factory: f} + rctx.apiClientFunc = sync.OnceValues(func() (*client.APIClient, error) { + return nil, fmt.Errorf("API client is not available during local dry-run") + }) + rctx.botInfoFunc = sync.OnceValues(func() (*BotInfo, error) { + return nil, fmt.Errorf("bot info is not available during local dry-run") + }) + rctx.Format = rctx.Str("format") + rctx.JqExpr, _ = cmd.Flags().GetString("jq") + return rctx +} + // stripUTF8BOM removes a leading UTF-8 byte-order mark from content read from a // file or stdin. A BOM that survives into a CSV cell corrupts the first value // (e.g. "\ufeffNorth", which then makes a MAXIFS/lookup miss it), and a BOM at the diff --git a/shortcuts/slides/slides_create.go b/shortcuts/slides/slides_create.go index e809b204..badb8197 100644 --- a/shortcuts/slides/slides_create.go +++ b/shortcuts/slides/slides_create.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "net/http" "path/filepath" "strings" @@ -219,7 +220,11 @@ func buildPresentationXML(title string) string { } func createEmptyPresentation(runtime *common.RuntimeContext, title string) (string, int, error) { - data, err := runtime.CallAPI( + return createEmptyPresentationWithHeaders(runtime, title, nil) +} + +func createEmptyPresentationWithHeaders(runtime *common.RuntimeContext, title string, headers http.Header) (string, int, error) { + data, err := runtime.CallAPIWithHeaders( "POST", "/open-apis/slides_ai/v1/xml_presentations", nil, @@ -228,6 +233,7 @@ func createEmptyPresentation(runtime *common.RuntimeContext, title string) (stri "content": buildPresentationXML(title), }, }, + headers, ) if err != nil { return "", 0, err diff --git a/shortcuts/slides/slides_create_svg.go b/shortcuts/slides/slides_create_svg.go index 1a549a1c..1ff6080a 100644 --- a/shortcuts/slides/slides_create_svg.go +++ b/shortcuts/slides/slides_create_svg.go @@ -6,6 +6,7 @@ package slides import ( "context" "fmt" + "net/http" "strings" "github.com/larksuite/cli/internal/output" @@ -35,15 +36,24 @@ var SlidesCreateSVG = common.Shortcut{ Desc: "SVG file path; repeat for multiple pages", }, {Name: "assets", Desc: "optional assets.json path mapping SVG @path placeholders to uploaded file tokens"}, + {Name: "request-header", Type: "string_array", Desc: "internal request header for SVGlide live lanes; repeat key=value, currently only x-tt-env=ppe_pure_svg is allowed"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if err := validateSVGFileInputs(runtime, runtime.StrArray("file")); err != nil { return err } - return validateSVGAssetsPath(runtime, runtime.Str("assets")) + if err := validateSVGAssetsPath(runtime, runtime.Str("assets")); err != nil { + return err + } + _, err := parseSVGRequestHeaders(runtime.StrArray("request-header")) + return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { title := effectiveTitle(runtime.Str("title")) + requestHeaders, err := parseSVGRequestHeaders(runtime.StrArray("request-header")) + if err != nil { + return common.NewDryRunAPI().Set("error", err.Error()) + } svgs, err := readSVGFiles(runtime, runtime.StrArray("file")) if err != nil { return common.NewDryRunAPI().Set("error", err.Error()) @@ -86,10 +96,17 @@ var SlidesCreateSVG = common.Shortcut{ if runtime.IsBot() { dry.Desc("After creation succeeds in bot mode, the CLI will also try to grant the current CLI user full_access (可管理权限) on the new presentation.") } + if len(requestHeaders) > 0 { + dry.Set("request_headers", svgRequestHeadersForOutput(requestHeaders)) + } return dry.Set("title", title) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { title := effectiveTitle(runtime.Str("title")) + requestHeaders, err := parseSVGRequestHeaders(runtime.StrArray("request-header")) + if err != nil { + return err + } svgs, err := readSVGFiles(runtime, runtime.StrArray("file")) if err != nil { return err @@ -99,7 +116,7 @@ var SlidesCreateSVG = common.Shortcut{ return err } - presentationID, revisionID, err := createEmptyPresentation(runtime, title) + presentationID, revisionID, err := createEmptyPresentationWithHeaders(runtime, title, requestHeaders) if err != nil { return err } @@ -110,6 +127,9 @@ var SlidesCreateSVG = common.Shortcut{ if revisionID > 0 { result["revision_id"] = revisionID } + if len(requestHeaders) > 0 { + result["request_headers"] = svgRequestHeadersForOutput(requestHeaders) + } pages, uploaded, err := rewriteSVGImagePlaceholders(runtime, presentationID, svgs, assets) if err != nil { @@ -133,11 +153,12 @@ var SlidesCreateSVG = common.Shortcut{ "page %d/%d failed before API call: %v (presentation %s was created; %d slide(s) added; slide_ids=%s)", i+1, len(pages), err, presentationID, len(slideIDs), strings.Join(slideIDs, ",")) } - slideData, err := runtime.CallAPI( + slideData, err := runtime.CallAPIWithHeaders( "POST", slideURL, map[string]interface{}{"revision_id": -1}, buildCreateSVGBody(content), + requestHeaders, ) if err != nil { return output.Errorf(output.ExitAPI, "api_error", @@ -159,3 +180,35 @@ var SlidesCreateSVG = common.Shortcut{ return nil }, } + +func parseSVGRequestHeaders(values []string) (http.Header, error) { + headers := http.Header{} + for _, raw := range values { + item := strings.TrimSpace(raw) + if item == "" { + return nil, output.ErrValidation("--request-header cannot be empty") + } + key, value, ok := strings.Cut(item, "=") + if !ok { + return nil, output.ErrValidation("--request-header %q must use key=value", item) + } + key = strings.TrimSpace(key) + value = strings.TrimSpace(value) + if !strings.EqualFold(key, "x-tt-env") { + return nil, output.ErrValidation("--request-header %q is not supported; only x-tt-env is allowed", key) + } + if value != "ppe_pure_svg" { + return nil, output.ErrValidation("--request-header x-tt-env must be ppe_pure_svg") + } + headers.Set("x-tt-env", value) + } + return headers, nil +} + +func svgRequestHeadersForOutput(headers http.Header) map[string]string { + out := map[string]string{} + if value := headers.Get("x-tt-env"); value != "" { + out["x-tt-env"] = value + } + return out +} diff --git a/shortcuts/slides/slides_create_svg_test.go b/shortcuts/slides/slides_create_svg_test.go index 8e20d11b..f96972b8 100644 --- a/shortcuts/slides/slides_create_svg_test.go +++ b/shortcuts/slides/slides_create_svg_test.go @@ -145,6 +145,80 @@ func TestSlidesCreateSVGExecuteCreatesSlidesInFileOrder(t *testing.T) { assertSlideCreateBodyContains(t, slideStub2, ``) } +func TestSlidesCreateSVGRequestHeaderPassesToCreateAndSlideCalls(t *testing.T) { + dir := t.TempDir() + withSlidesTestWorkingDir(t, dir) + if err := os.WriteFile("page.svg", []byte(testSVGlidePage1), 0o644); err != nil { + t.Fatalf("write page.svg: %v", err) + } + + f, stdout, _, reg := cmdutil.TestFactory(t, slidesTestConfig(t, "")) + createStub := &httpmock.Stub{ + Method: "POST", + URL: "/open-apis/slides_ai/v1/xml_presentations", + Body: map[string]interface{}{ + "code": 0, + "data": map[string]interface{}{ + "xml_presentation_id": "pres_header", + "revision_id": 1, + }, + }, + } + slideStub := &httpmock.Stub{ + Method: "POST", + URL: "/open-apis/slides_ai/v1/xml_presentations/pres_header/slide", + Body: map[string]interface{}{"code": 0, "data": map[string]interface{}{"slide_id": "slide_header", "revision_id": 2}}, + } + reg.Register(createStub) + reg.Register(slideStub) + registerBatchQueryStub(reg, "pres_header", "https://x.feishu.cn/slides/pres_header") + + err := runSlidesCreateSVGShortcut(t, f, stdout, []string{ + "+create-svg", + "--file", "page.svg", + "--title", "SVG Header Deck", + "--request-header", "x-tt-env=ppe_pure_svg", + "--as", "user", + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if got := createStub.CapturedHeaders.Get("x-tt-env"); got != "ppe_pure_svg" { + t.Fatalf("create x-tt-env = %q, want ppe_pure_svg", got) + } + if got := slideStub.CapturedHeaders.Get("x-tt-env"); got != "ppe_pure_svg" { + t.Fatalf("slide x-tt-env = %q, want ppe_pure_svg", got) + } + data := decodeSlidesCreateEnvelope(t, stdout) + headers, _ := data["request_headers"].(map[string]interface{}) + if headers["x-tt-env"] != "ppe_pure_svg" { + t.Fatalf("request_headers = %#v, want x-tt-env", data["request_headers"]) + } +} + +func TestSlidesCreateSVGRejectsUnsupportedRequestHeader(t *testing.T) { + dir := t.TempDir() + withSlidesTestWorkingDir(t, dir) + if err := os.WriteFile("page.svg", []byte(testSVGlidePage1), 0o644); err != nil { + t.Fatalf("write page.svg: %v", err) + } + + f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, "")) + err := runSlidesCreateSVGShortcut(t, f, stdout, []string{ + "+create-svg", + "--file", "page.svg", + "--request-header", "authorization=secret", + "--as", "user", + }) + if err == nil { + t.Fatal("expected unsupported request header error") + } + if !strings.Contains(err.Error(), "only x-tt-env is allowed") { + t.Fatalf("err = %v, want supported-header message", err) + } +} + func TestSlidesCreateSVGChartMarkerPassesThroughSlideContent(t *testing.T) { dir := t.TempDir() withSlidesTestWorkingDir(t, dir) diff --git a/skills/lark-slides/prompts/svglide/canvas-planner.prompt.md b/skills/lark-slides/prompts/svglide/canvas-planner.prompt.md new file mode 100644 index 00000000..1df54387 --- /dev/null +++ b/skills/lark-slides/prompts/svglide/canvas-planner.prompt.md @@ -0,0 +1,30 @@ +# SVGlide Canvas Planner Prompt + +## Contract + +- Input bundle: `02-plan/slide-plan.json`, `svglide-template-registry.json`, `themes/registry.json`, `svglide-canvas-spec.schema.json`, golden CanvasSpec examples. +- Output schema: `skills/lark-slides/references/svglide-canvas-plan.schema.json`. +- Output path: `02-plan/slide_plan.json`. +- Validation command: `python3 skills/lark-slides/scripts/svglide_planner_contracts.py `. + +## Output Rules + +Return JSON only. Do not wrap the answer in Markdown fences. + +The Canvas Planner turns each slide plan into the final `generation_mode=artboard_satori` `slide_plan.json`. Every slide must include a full `canvas_spec` with: + +- `version` +- `canvas` +- `safe_area` +- `template_id` +- `theme_id` +- `theme` +- `content` +- `semantic_elements` +- `quality_constraints` + +The output must pass `svglide-plan.schema.json`, `svglide-canvas-plan.schema.json`, CanvasSpec validation, Template Registry binding, Theme Registry binding, and template text-budget/max-items checks before Satori is invoked. + +## Forbidden Outputs + +Do not output free HTML, CSS, SVG, JSX, TSX, Markdown prose, raw Satori SVG, foreignObject snippets, or arbitrary inline style. Use structured CanvasSpec JSON only. diff --git a/skills/lark-slides/prompts/svglide/deck-planner.prompt.md b/skills/lark-slides/prompts/svglide/deck-planner.prompt.md new file mode 100644 index 00000000..270cf781 --- /dev/null +++ b/skills/lark-slides/prompts/svglide/deck-planner.prompt.md @@ -0,0 +1,25 @@ +# SVGlide Deck Planner Prompt + +## Contract + +- Input bundle: `user_topic`, `audience`, `target_slide_count`, `source_policy`, `available_template_registry`, `available_theme_registry`. +- Output schema: `skills/lark-slides/references/svglide-deck-plan.schema.json`. +- Output path: `02-plan/deck-plan.json`. +- Validation command: `python3 skills/lark-slides/scripts/svglide_planner_contracts.py `. + +## Output Rules + +Return JSON only. Do not wrap the answer in Markdown fences. + +The Deck Planner defines the narrative system for the whole deck: + +- objective +- audience +- target slide count +- narrative arc +- theme direction +- per-slide role, key message, content goal, and visual goal + +## Forbidden Outputs + +Do not output free HTML, CSS, SVG, JSX, TSX, Markdown prose, base64 image data, or rendered visual markup. Do not create page geometry here. Do not invent numeric claims; mark missing facts as `pending_confirmation`. diff --git a/skills/lark-slides/prompts/svglide/repair-planner.prompt.md b/skills/lark-slides/prompts/svglide/repair-planner.prompt.md new file mode 100644 index 00000000..f52f3b64 --- /dev/null +++ b/skills/lark-slides/prompts/svglide/repair-planner.prompt.md @@ -0,0 +1,24 @@ +# SVGlide Repair Planner Prompt + +## Contract + +- Input bundle: validation receipt, target planner JSON, schema issue list, template fit issue list. +- Output schema: `skills/lark-slides/references/svglide-repair-plan.schema.json`. +- Output path: `02-plan/repair-plan.json`. +- Validation command: `python3 skills/lark-slides/scripts/svglide_planner_contracts.py `. + +## Output Rules + +Return JSON only. Do not wrap the answer in Markdown fences. + +The Repair Planner outputs scoped JSON Patch operations only. Each patch must target one precise field, such as: + +- `/slides/0/canvas_spec/content/title` +- `/slides/1/canvas_spec/content/right_points/2` +- `/slides/2/canvas_spec/semantic_elements/0/bbox/width` + +Every patch must include a short `reason` tied to a validation issue. + +## Forbidden Outputs + +Do not rewrite the full deck. Do not output `slides`, full `canvas_spec`, full `deck_plan`, free HTML, CSS, SVG, JSX, TSX, Markdown prose, or unscoped patch paths such as `/`, `/slides`, `/slides/0`, or `/slides/0/canvas_spec`. diff --git a/skills/lark-slides/prompts/svglide/slide-planner.prompt.md b/skills/lark-slides/prompts/svglide/slide-planner.prompt.md new file mode 100644 index 00000000..2c88d4f5 --- /dev/null +++ b/skills/lark-slides/prompts/svglide/slide-planner.prompt.md @@ -0,0 +1,20 @@ +# SVGlide Slide Planner Prompt + +## Contract + +- Input bundle: `02-plan/deck-plan.json`, `svglide-template-registry.json`, `themes/registry.json`, `svglide-layout-archetypes.json`, `svglide-component-registry.json`. +- Output schema: `skills/lark-slides/references/svglide-slide-plan.schema.json`. +- Output path: `02-plan/slide-plan.json`. +- Validation command: `python3 skills/lark-slides/scripts/svglide_planner_contracts.py `. + +## Output Rules + +Return JSON only. Do not wrap the answer in Markdown fences. + +The Slide Planner chooses registered templates and themes for each slide. It may define structured content requirements, but it must not write CanvasSpec yet. + +Every slide must choose a `template_id` and `theme_id` from the registries. Keep content short enough for the selected template budgets. + +## Forbidden Outputs + +Do not output free HTML, CSS, SVG, JSX, TSX, Markdown prose, raw Satori SVG, or unregistered template/theme IDs. Do not bypass Template Registry or Theme Registry. diff --git a/skills/lark-slides/references/ppe-pure-svg.whistle.js b/skills/lark-slides/references/ppe-pure-svg.whistle.js new file mode 100644 index 00000000..0083bacc --- /dev/null +++ b/skills/lark-slides/references/ppe-pure-svg.whistle.js @@ -0,0 +1,14 @@ +module.exports = function (callback) { + callback({ + name: 'lark-cli-openapi-ppe-pure-svg', + groupName: 'lark-cli', + rules: [ + '/^https:\\/\\/open\\.feishu\\.cn\\/(.*)$/ https://open.feishu-pre.cn/$1', + 'https://open.feishu.cn/ reqHeaders://Env=Pre_release', + 'https://open.feishu.cn/ reqHeaders://x-tt-env=ppe_pure_svg', + 'https://open.feishu-pre.cn/ reqHeaders://Env=Pre_release', + 'https://open.feishu-pre.cn/ reqHeaders://x-tt-env=ppe_pure_svg', + '/^https:\\/\\/accounts\\.feishu\\.cn\\/(.*)$/ https://accounts.feishu-pre.cn/$1', + ].join('\n'), + }); +}; diff --git a/skills/lark-slides/references/svg-private-manifest.json b/skills/lark-slides/references/svg-private-manifest.json index fd761c0e..f76188ec 100644 --- a/skills/lark-slides/references/svg-private-manifest.json +++ b/skills/lark-slides/references/svg-private-manifest.json @@ -15,6 +15,7 @@ "skills/lark-slides/references/svglide-lock.contract.md", "skills/lark-slides/references/svglide-assets.contract.md", "skills/lark-slides/references/svglide-generate-svg.contract.md", + "skills/lark-slides/references/svglide-artboard-satori.contract.md", "skills/lark-slides/references/svglide-preview.spec.md", "skills/lark-slides/references/svglide-checks.checklist.md", "skills/lark-slides/references/svglide-readback.contract.md", @@ -31,6 +32,10 @@ "skills/lark-slides/references/svglide-asset-planning.md", "skills/lark-slides/references/safe-native-v1.profile.json", "skills/lark-slides/references/svglide-plan.schema.json", + "skills/lark-slides/references/svglide-canvas-spec.schema.json", + "skills/lark-slides/references/svglide-semantic-map.schema.json", + "skills/lark-slides/references/svglide-node-layout-map.schema.json", + "skills/lark-slides/references/svglide-artboard-receipt.schema.json", "skills/lark-slides/references/svglide-strategy-review.schema.json", "skills/lark-slides/references/svglide-ppt-master-asset-map.schema.json", "skills/lark-slides/references/svglide-template-admission.schema.json", @@ -53,6 +58,12 @@ "skills/lark-slides/scripts/svg_preview_lint.py", "skills/lark-slides/scripts/svglide_source.py", "skills/lark-slides/scripts/svglide_assets.py", + "skills/lark-slides/scripts/svglide_artboard_renderer.py", + "skills/lark-slides/scripts/svglide_template_fit_check.py", + "skills/lark-slides/scripts/artboard_renderer/package.json", + "skills/lark-slides/scripts/artboard_renderer/render.mjs", + "skills/lark-slides/scripts/artboard_renderer/templates/README.md", + "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json", "skills/lark-slides/scripts/svglide_project_runner.py", "skills/lark-slides/scripts/svglide_strategy_review.py", "skills/lark-slides/scripts/svglide_prepare.py", @@ -84,6 +95,8 @@ "skills/lark-slides/references/svg-protocol.md", "skills/lark-slides/scripts/svglide_source.py", "skills/lark-slides/scripts/svglide_assets.py", + "skills/lark-slides/scripts/svglide_artboard_renderer.py", + "skills/lark-slides/scripts/svglide_template_fit_check.py", "skills/lark-slides/scripts/svglide_project_runner.py", "skills/lark-slides/scripts/svglide_strategy_review.py", "skills/lark-slides/scripts/svglide_prepare.py", diff --git a/skills/lark-slides/references/svglide-artboard-full-plan-action.md b/skills/lark-slides/references/svglide-artboard-full-plan-action.md new file mode 100644 index 00000000..611b4952 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-full-plan-action.md @@ -0,0 +1,920 @@ +# SVGlide Artboard/Satori Full Plan Action And Supervision Guide + +Last updated: 2026-06-21 + +## 0. Strict Full-Plan Mandate + +This file is the local action plan for completing the entire `/Users/bytedance/Downloads/PLAN.md`, not only P0, not only a demo deck, and not only the currently unblocked local vertical slice. + +Executor and reviewer agents must treat completion as an evidence-based state: + +```text +all gates 0-12b DONE +all reviewer verdicts PASS +PLAN.md completion status updated +real live_create/readback evidence present +legacy direct_svg regression still passing +instruction/plan/output adherence evidence present +packaging/distribution decision closed or explicitly rescoped in PLAN.md +``` + +Anything less than that is `IN_PROGRESS` or `BLOCKED`. + +The current execution cursor is: + +```text +Gate 12b: Final Full-Plan Acceptance +Status: DONE +Current issue: Gate 12b final reviewer verdict is PASS; current P0/P1 milestone is complete +Next allowed executor task: stop or start a new explicitly scoped follow-up +Next forbidden executor task: claiming P2/future scope as complete +``` + +Supervisor rule: + +- The reviewer subagent must reject any attempt to skip the current cursor. +- The reviewer subagent must reject "looks good" visual evidence unless it is tied to receipts, hashes, commands, and runner output. +- The reviewer subagent must reject fake dry-run, handwritten `.tmp` state, system screenshots, or direct Satori SVG as completion evidence. +- The executor must update this file after every gate status change. +- The executor must send this file, the relevant evidence file, changed file list, and validation commands to the reviewer before claiming a gate is done. + +## 1. Goal + +The goal is to complete the full plan in: + +```text +/Users/bytedance/Downloads/PLAN.md +``` + +This file is the local execution and supervision contract for executor agents and reviewer subagents. It exists to prevent partial vertical slices, visual demos, or smoke tests from being mistaken for completion of the full plan. + +Completion means: + +```text +Deck/Slide planning contract +-> CanvasSpec as page source of truth +-> Template Registry / Theme Token / Component Library +-> Satori renderer +-> resvg preview and raster fallback path +-> SatoriToSVGlide compiler +-> existing SVGlide prepare / preview / preflight / reviews / quality_gate +-> dry_run +-> ppe_proof +-> live_create +-> readback +-> instruction / plan / output adherence +-> regression and packaging decision evidence +``` + +The current state is not full completion. As of 2026-06-21: + +```text +Gates 0-8: complete with reviewer PASS +Gate 9: complete with reviewer PASS +Gate 10: complete with reviewer PASS +Gate 11: complete with reviewer PASS +Gate 12a: complete with reviewer PASS +Gate 12b: complete with reviewer PASS +P1: asset system scale-out, prompt/planning, and packaging decision complete with reviewer PASS +P2: not started +``` + +Current active cursor: + +```text +Gate 8 is now DONE/PASS. It was previously blocked because +`xml_presentations.get` returned 5090000 for presentations containing +`svglide-chart-spec-v1` chart markers. + +The slide-side fix has now been committed, pushed, built, and deployed to +`creation.slide.nodeserver_pre_release` in `ppe_pure_svg`: + +slide branch: feat/svglide-chart-direct-snapshot +slide commit: 8f682ab082f7d86ade966eb2ffc5849827b17dc5 +ENV ticket: 2068537756495360000 +TCE deployment: 362509781 +TCE service: 208677037 +deployed main repo: ee/slide/server@1.0.0.1184 +service status: running + +Fresh Gate 8 readback now passes: + +chart-only deck: C5fxszdjrlftMedvShmcOWtinqe +chart-only slide: pvv +combined deck: J35tspvJgltBnsdJpL7chnv6n2f +combined slides: pdd, pdu, pdR +combined checks: page_count, slide_order, blank_page, text_fit, bounds, + chart_markers, image_assets, core_visible_text all passed. + +Reviewer verdict: PASS. + +Next cursor: current P0/P1 milestone closed with Gate 12b reviewer PASS. + +The full PLAN is not complete until Gate 12b also passes review. +``` + +## 2. Source Of Truth + +Primary source: + +```text +/Users/bytedance/Downloads/PLAN.md +``` + +Branch and worktree: + +```text +worktree: /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private +branch: feat/svglide-artboard-satori +``` + +Important local references: + +```text +skills/lark-slides/references/svglide-artboard-p0-goal-lock.md +skills/lark-slides/references/svglide-artboard-satori.contract.md +skills/lark-slides/references/svglide-canvas-spec.schema.json +skills/lark-slides/references/svglide-semantic-map.schema.json +skills/lark-slides/references/svglide-node-layout-map.schema.json +skills/lark-slides/references/svglide-artboard-receipt.schema.json +skills/lark-slides/references/svglide-template-registry.json +skills/lark-slides/scripts/svglide_project_runner.py +skills/lark-slides/scripts/svglide_artboard_renderer.py +skills/lark-slides/scripts/svglide_template_fit_check.py +skills/lark-slides/scripts/svglide_quality_gate.py +skills/lark-slides/scripts/artboard_renderer/ +``` + +If this guide conflicts with `PLAN.md`, `PLAN.md` wins. If `PLAN.md` is too vague for implementation, update the plan or a referenced contract first, then implement. + +## 3. Roles + +Executor: + +- Implements only the next unblocked task. +- Updates receipts, tests, fixtures, and docs together with code changes. +- Reports exact files changed, exact validations run, and remaining gaps. +- Must not mark a gate complete without reviewer evidence. + +Reviewer subagent: + +- Audits against this guide and `PLAN.md`. +- Challenges missing evidence, vague claims, or shortcut paths. +- Returns `PASS` only when all blocking checks for the current gate pass. +- Returns `BLOCKED` if any required artifact, receipt, freshness check, or verification is missing. +- Owns final gate verdicts. The executor cannot self-promote a gate to reviewer `PASS`. +- Must inspect repo files and receipts, not only executor prose. + +Main agent: + +- Maintains the plan and task status. +- Dispatches implementation and review. +- Resolves conflicts between executor and reviewer using repo evidence. +- Keeps this file updated whenever a gate changes state. +- Re-routes work back to the executor when the reviewer returns `BLOCKED`. + +## 4. Hard Rules + +Do not use these shortcuts as completion evidence: + +```text +direct_svg demo replacing artboard_satori evidence +handwritten .tmp generator replacing runner integration +QuickLook / qlmanage / system screenshot replacing resvg main path +manual state.json patch replacing runner behavior +relaxed production gate replacing correct receipts +Satori SVG copied directly to live SVG +unregistered template or theme treated as valid +external HTML/CSS library directly used as runtime renderer +quality_gate checking only file existence without hash/freshness +fake lark-cli dry_run treated as live_create/readback evidence +executor-written claim treated as reviewer evidence +``` + +Temporary diagnostics are allowed, but diagnostic outputs cannot be counted as plan completion. + +## 4.1 Supervision Loop + +For every gate from Gate 4 onward, use this loop: + +```text +1. Executor selects exactly one gate or one blocker inside a gate. +2. Executor updates code/docs/fixtures/receipts for that scope only. +3. Executor runs the minimum validation commands listed in this file. +4. Executor writes or updates a gate evidence file. +5. Main agent sends the exact scope, changed files, commands, and evidence paths to the reviewer subagent. +6. Reviewer returns PASS or BLOCKED using the required format. +7. Main agent updates the status board. +8. If BLOCKED, the next executor task must address the reviewer blocker before moving to another gate. +``` + +The executor must not skip from a blocked gate to a later gate unless this file records an explicit scope deferral approved by the reviewer. + +## 5. Required Reporting Format + +Every executor update must use this shape: + +```text +Scope: +- ... + +Files changed: +- ... + +Validation run: +- command: +- result: +- evidence path: + +Plan items advanced: +- PLAN.md section: +- previous status: +- new status: + +Remaining blockers: +- ... +``` + +Every reviewer subagent response must use this shape: + +```text +Verdict: PASS / BLOCKED + +Blocking issues: +- ... + +Non-blocking risks: +- ... + +Evidence checked: +- file / command / receipt: + +Next required action: +- ... +``` + +## 6. Execution Gates + +The executor must proceed in order. A later gate cannot be marked complete while an earlier gate is blocked. + +### Gate 0: Baseline And Branch Discipline + +Purpose: prove the work starts from the right branch and does not break legacy `direct_svg`. + +Required actions: + +- Confirm current branch is `feat/svglide-artboard-satori`. +- Confirm worktree is `/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private`. +- Record dirty files before starting each chunk. +- Run legacy direct SVG regression. +- Confirm no `slide_engine` or `slide` changes are required for P0. + +Required evidence: + +```text +git status --short --branch +legacy direct_svg runner receipt +legacy quality_gate result +``` + +Reviewer blocking checks: + +- Block if branch/worktree is wrong. +- Block if direct SVG is broken without an explicit PLAN-approved migration. +- Block if unrelated repo changes are included. + +### Gate 1: Contract Layer Completion + +Purpose: close the engineering contract before adding more visual features. + +Required actions: + +- Finalize `generation_mode=direct_svg|artboard_satori`. +- Keep existing `generator_mode=script|external` semantics unchanged. +- Make `CanvasSpec` the page source of truth for artboard mode. +- Make `semantic-map/v1` an explicit compiler IR. +- Make `node-layout-map/v1` an explicit Satori/Yoga observation artifact or document the accepted substitute. +- Update generator receipt schema to include artboard receipts and hashes. +- Ensure `artboard_satori` requires legal `canvas_spec` per page. + +Required artifacts: + +```text +skills/lark-slides/references/svglide-plan.schema.json +skills/lark-slides/references/svglide-canvas-spec.schema.json +skills/lark-slides/references/svglide-semantic-map.schema.json +skills/lark-slides/references/svglide-node-layout-map.schema.json +skills/lark-slides/references/svglide-generator-receipt.schema.json +skills/lark-slides/references/svglide-artboard-receipt.schema.json +skills/lark-slides/references/svglide-artboard-satori.contract.md +``` + +Reviewer blocking checks: + +- Block if `CanvasSpec` is optional in `artboard_satori` mode. +- Block if semantic map and node layout map boundaries are unclear. +- Block if receipt schema does not support hash/freshness verification. +- Block if legacy `direct_svg` plans no longer validate. + +### Gate 2: Template, Theme, Component, And Input Quality System + +Purpose: prevent Satori from rendering arbitrary low-quality input. + +Required actions: + +- Provide at least 3 P0 templates: + - `cover-hero` + - `comparison-cards` + - `summary-final` +- Provide at least 3 formal theme tokens in the renderer registry or theme registry. +- Provide a Satori-compatible component layer: + - `Title` + - `Subtitle` + - `Chip` + - `StatCard` + - `ImageFrame` +- Ensure all templates and components use only the approved Satori CSS subset. +- Implement template fit checks for: + - unknown `template_id` + - unknown `theme_id` + - overlong title + - missing required content + - card count overflow + - unsafe text budget + - unsafe bbox or safe-area violation +- Add golden fixtures for each P0 template. + +Required artifacts: + +```text +skills/lark-slides/references/svglide-template-registry.json +skills/lark-slides/scripts/artboard_renderer/templates/ +skills/lark-slides/scripts/artboard_renderer/themes/ +skills/lark-slides/scripts/artboard_renderer/components/ +skills/lark-slides/scripts/svglide_template_fit_check.py +skills/lark-slides/scripts/fixtures/svglide_artboard/ +receipts/template-fit-check.json in fixture runs +``` + +Reviewer blocking checks: + +- Block if only one formal theme exists. +- Block if demo-local themes are not registered. +- Block if templates are hardcoded without registry hashes. +- Block if unknown template/theme does not fail fast. +- Block if fixtures are too narrow to prove 3-template behavior. + +### Gate 3: Satori Renderer And resvg Preview + +Purpose: prove CanvasSpec can render into preview artifacts through the intended renderer. + +Required actions: + +- Keep `artboard_renderer` as an isolated Node package for P0. +- Declare `satori` and `@resvg/resvg-js` in `package.json`. +- Keep `pnpm-lock.yaml` committed for the subpackage. +- Render each page to raw Satori SVG. +- Render each page to PNG using `@resvg/resvg-js`. +- Generate contact sheet from PNG artifacts. +- Record renderer runtime details. +- Record font hashes. +- Record input and output hashes. + +Required artifacts: + +```text +skills/lark-slides/scripts/artboard_renderer/package.json +skills/lark-slides/scripts/artboard_renderer/pnpm-lock.yaml +04-svg/artboard/raw/page-###.satori.svg +04-svg/artboard/page-###.png +05-preview/contact-sheet.png +receipts/artboard-render.json +``` + +Required validation commands: + +```bash +node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +``` + +Reviewer blocking checks: + +- Block if `resvg_version` is missing. +- Block if PNGs are produced by QuickLook/system screenshot as the main path. +- Block if Satori SVG is empty or copied directly to live output. +- Block if font hashes are not recorded. +- Block if renderer artifacts are not hash-bound to CanvasSpec/theme/template inputs. + +### Gate 4: SatoriToSVGlide Compiler + +Purpose: produce editable SVGlide protocol SVG from CanvasSpec and renderer outputs. + +Current reviewer verdict: `PASS`. + +Resolved blocking issues: + +```text +1. The current compiler path still treats raw Satori SVG as the conversion input. + The receipt may say `CanvasSpec`, but the implementation must prove the + generated SVGlide protocol SVG is derived from CanvasSpec / semantic-map / + owned template IR. + +2. P0 Gate 4 scope must be explicit. If image/chart mapping is not implemented + in Gate 4, the plan must state that P0 Gate 4 only certifies text/shape + mapping, while image/chart/readback remain Gate 8/P0c work. +``` + +Required actions: + +- Convert using CanvasSpec/semantic map, not by trusting raw Satori SVG as semantic truth. +- Keep raw Satori SVG as preview/layout evidence only. +- Record the actual compiler input artifact and hash, for example: + - `04-svg/artboard/page-###.canvas-template.svg` + - `canvas_template_svg_sha256` + - `compiler_input=CanvasSpecTemplateSVG` + - `satori_svg_usage=preview_only` +- Output current SVGlide protocol: + - `xmlns:slide="https://slides.bytedance.com/ns"` + - valid `slide:role` + - valid text/shape mapping for P0 Gate 4 +- Ensure final live SVG does not contain unsupported raw Satori constructs. +- Implement fail-fast or decorative raster fallback for unsupported Satori features. +- Write bridge receipt with input and output hashes. +- If image/chart mapping is deferred, explicitly document the deferral and keep Gate 8 as the owner for chart/image proof. + +Required artifacts: + +```text +04-svg/page-###.svg +04-svg/artboard/page-###.canvas-template.svg +receipts/satori-bridge.json +``` + +Reviewer blocking checks: + +- Block if final SVG is just raw Satori SVG. +- Block if final SVG is compiled from raw Satori SVG while receipts imply CanvasSpec. +- Block if the compiler input artifact and hash are missing. +- Block if text is not mapped to the current SVGlide text protocol. +- Block if unsupported `filter/mask/clipPath/pattern/foreignObject` paths enter live without explicit fallback policy. +- Block if image/chart mapping is claimed but no fixture/readback evidence exists. +- Block if bridge receipt cannot prove current input/output freshness. + +### Gate 5: Runner And Quality Gate Integration + +Purpose: make the new path part of the existing stage graph, not a side script. + +Required actions: + +- `generate_svg` dispatches on `generation_mode`. +- `direct_svg` legacy path remains compatible. +- `artboard_satori` runs CanvasSpec validation, template fit, render, bridge, and generate receipt. +- Per-page render/compile supports bounded concurrency: + - default `max_workers = min(4, page_count)` + - final generated file order must remain page-number stable +- `quality_gate` validates all artboard receipts and hashes. +- `quality_gate` fails on stale prepared SVG. +- `quality_gate` fails when required artboard receipts are missing. + +Required artifacts: + +```text +skills/lark-slides/scripts/svglide_project_runner.py +skills/lark-slides/scripts/svglide_quality_gate.py +receipts/generate_svg.json +06-check/quality-gate.json +``` + +Required validation: + +```bash +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Reviewer blocking checks: + +- Block if `artboard_satori` can pass with missing receipts. +- Block if hash/freshness checks only check file existence. +- Block if prepared SVG can be modified after bridge and still pass. +- Block if page order is nondeterministic under concurrency. +- Block if direct SVG regression tests are not run. + +### Gate 6: P0a And P0b Local E2E + +Purpose: prove 1-page and 3-page local vertical slices are repeatable. + +Required actions: + +- Add a complete P0a fixture bundle. +- Add a complete P0b fixture bundle. +- P0a runs to `dry_run`. +- P0b runs at least to `quality_gate`, preferably to `dry_run`. +- Fixture bundles include required project files, not just `slide_plan.json`. +- Evidence must prove: + - artboard mode was used + - all 3 P0 templates were hit + - resvg was used + - quality gate passed + - dry run passed when required + +Required fixture paths: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/ +``` + +Required output evidence: + +```text +02-plan/slide_plan.json +04-svg/artboard/raw/page-###.satori.svg +04-svg/artboard/page-###.png +04-svg/page-###.svg +05-preview/contact-sheet.png +06-check/quality-gate.json +07-create/dry-run.json +receipts/canvas-spec-validate.json +receipts/template-fit-check.json +receipts/artboard-render.json +receipts/satori-bridge.json +receipts/generate_svg.json +``` + +Reviewer blocking checks: + +- Block if demo is generated outside the fixture path and cannot be repeated. +- Block if P0b does not hit all required P0 templates. +- Block if P0b uses only one formal theme while plan requires three. +- Block if fixture bypasses runner stages. + +### Gate 7: P0c Live Closure + +Purpose: prove the new path can create and read back real Slides. + +Required actions: + +- Prepare a P0c fixture with `07-create/ppe-proof.input.json`. +- Run `dry_run`. +- Run `ppe_proof`. +- Run `live_create`. +- Run `readback`. +- Verify created slide count, page order, core visible text, and no blank pages. +- Verify the online result does not have obvious overflow or missing images. + +Required artifacts: + +```text +07-create/dry-run.json +07-create/ppe-proof.json +07-create/live-create.json +08-readback/readback-check.json +``` + +Reviewer blocking checks: + +- Block if fake lark-cli is used as live evidence. +- Block if `ppe_proof` inputs do not match current quality gate and dry-run outputs. +- Block if readback is not performed. +- Block if created pages are blank, missing expected text, or out of order. + +### Gate 8: Special Cases And Fallback Coverage + +Purpose: cover features likely to break the renderer/compiler boundary. + +Required actions: + +- Add unsupported Satori feature fixture. +- Add chart marker fixture for `svglide-chart-spec-v1`. +- Add image asset fixture with binding and readback. +- Add local raster fallback fixture for an isolated unsupported decoration. +- Prove fail-fast behavior before live for unsupported editable features. + +Reviewer blocking checks: + +- Block if unsupported features silently pass. +- Block if raster fallback lacks bbox/island receipt. +- Block if chart marker or image asset is not verified through readback. + +### Gate 9: P1 Asset System Scale-Out + +Purpose: move from P0 templates to a reusable visual system. + +Required actions: + +- Convert external visual references into owned Satori-compatible templates. +- Use external repos only as inspiration/input data, not runtime dependency. +- Build a source intake inventory before converting assets. Each source family must record: + - `source_path` + - `source_type` + - `extract_fields` + - `conversion_target` + - `acceptance_rule` + - `forbidden_usage` +- Target P1 asset counts from `PLAN.md`: + - 15-25 Canvas Templates + - 10-18 Theme Tokens + - 20-40 Component Variants + - 6-10 Layout Archetypes +- Add abstraction guide evidence for each source family. +- Add template quality fixtures and regression previews. + +Allowed reference sources and intake rules: + +```text +1. /Users/bytedance/bd-projects/open-design/design-templates/* + Read: template.json, example.html, preview screenshots when available. + Extract: palette, typography, mood, density, occasion, layout skeleton, component combinations. + Convert to: Theme Token, Canvas Template brief, Satori component candidates. + Forbidden: do not run arbitrary HTML/CSS as the SVGlide runtime renderer. + +2. /Users/bytedance/bd-projects/open-design/design-templates/html-ppt-zhangzara-* + Read: template.json, example.html, style sample images. + Extract: high-aesthetic style DNA, color hierarchy, display/body type pairing, spacing, poster/editorial/grid traits. + Convert to: deduped strong-style Theme Tokens and a small number of strong-style Canvas Templates. + Forbidden: do not copy all style packs blindly; dedupe and keep only reusable Satori-compatible patterns. + +3. /Users/bytedance/bd-projects/ppt-master/examples/examples.json + plus /Users/bytedance/bd-projects/ppt-master/examples/*/{design_spec.md,spec_lock.md,svg_final/*.svg} + Extract: deck rhythm, page archetypes, visual style rules, palette rules, element density, bbox/font-size facts, golden page examples. + Convert to: golden fixtures, style/quality rules, deck rhythm patterns, Canvas Template candidates. + Forbidden: do not use ppt-master SVG as final SVGlide output and do not add ppt-master as a CLI runtime dependency. + +4. /Users/bytedance/bd-projects/workspaces/SVGlide/PosterGen + Read: config/poster_config.yaml, config/prompts/*.txt, src/agents/*, resource/*, data/*. + Extract: research-poster layout heuristics, section balancing, color extraction, text-height/min-font rules, logo/affiliation/image placement patterns. + Convert to: special research-poster templates, quality rules, layout heuristics, asset-binding cases. + Forbidden: do not integrate PosterGen's LangGraph/Python generation workflow as the SVGlide runtime. + +5. html-ppt-skill + Status: allowed only after the real local path is documented. + Extract: full-deck rhythm, single-page layouts, theme tokens. + Convert to: deck rhythm patterns, Canvas Templates, Theme Tokens. + Forbidden: do not count this source as verified until the source path and inventory evidence exist. + +6. Other poster / OG / example sources + Status: allowed only when documented in PLAN.md with source path, license/provenance, intake fields, and forbidden usage. +``` + +Reviewer blocking checks: + +- Block if an external HTML/CSS/SVG library is directly used as runtime renderer. +- Block if a source family has no intake inventory entry. +- Block if a source path is unverified but counted toward P1 asset targets. +- Block if conversion evidence does not show source example -> abstraction record -> CanvasSpec fixture -> registry/theme/component output. +- Block if templates do not map to CanvasSpec schema. +- Block if visual assets do not have golden fixtures. +- Block if generated pages degrade to title-plus-bullets. + +### Gate 10: Prompt And Planning Layer + +Purpose: turn a user topic into structured data without letting LLM write arbitrary HTML/CSS/SVG. + +Required actions: + +- Add prompt contracts for: + - Deck Planner + - Slide Planner + - Canvas Planner + - Repair Planner +- Each prompt must declare: + - input bundle + - output schema + - output path + - validation command + - forbidden outputs +- LLM output must be structured JSON, not free HTML/CSS/SVG. +- Planner outputs must pass schema and template fit before Satori. + +Reviewer blocking checks: + +- Block if prompts produce unrestricted HTML/CSS/SVG. +- Block if planner output bypasses CanvasSpec validation. +- Block if repair prompt rewrites the whole deck instead of scoped JSON Patch where PLAN requires patch behavior. + +### Gate 11: Packaging And Distribution Decision + +Purpose: settle how Satori/resvg enters CLI distribution. + +Required actions: + +- Decide whether `artboard_renderer` stays as a skill subpackage, is embedded, or is packaged separately. +- Decide how native `@resvg/resvg-js` dependency is installed for CLI users. +- Decide whether `skills_embed.go` changes are required. +- Add CI or local install validation for macOS arm64/x64. +- Document fallback when Node dependencies are missing. + +Reviewer blocking checks: + +- Block if the CLI release story requires users to manually clone Satori source. +- Block if native dependency install is undocumented. +- Block if package output cannot reproduce `render.mjs --check-runtime`. + +### Gate 12a: Instruction / Plan / Output Adherence + +Purpose: ensure explicit user instructions, planner outputs, generated CanvasSpec, and final readback remain consistent before final acceptance. + +Required actions: + +- Add or verify a durable user instruction source, preferably `00-input/instruction.json`, with at least topic, target slide count, language, audience, must-include, must-avoid, and output requirements. +- Add or verify an adherence checker such as `svglide_instruction_adherence.py`. +- Check that `target_slide_count` is consistent across instruction, `deck-plan.json`, `slide-plan.json`, final `slide_plan.json`, and readback. +- Check that slide count, page order, title/key-message propagation, chosen template/theme, language, required sections, and forbidden items match the instruction and planner chain. +- On failure, require scoped repair output rather than full regeneration by default: + - append missing deck/slide/canvas entries when pages are missing + - patch only affected slide fields or CanvasSpec leaves when localized + - rerun from the smallest affected stage + - allow full replan only when the deck narrative itself is invalid +- Write `06-check/instruction-adherence.json` and `receipts/instruction-adherence.json`. +- Include tests for count mismatch, missing slide-plan/canvas pages, forbidden content, and a passing fixture. + +Reviewer blocking checks: + +- Block if user instructions are not recorded in a durable project artifact. +- Block if `target_slide_count` can drift from actual planner/readback page count. +- Block if missing pages trigger full regeneration instead of scoped append/patch when localized repair is possible. +- Block if `quality_gate` or final acceptance can pass without instruction adherence evidence. +- Block if final readback does not check page count, order, title/key text, and explicit must-include/must-avoid constraints where applicable. + +### Gate 12b: Final Full-Plan Acceptance + +Purpose: certify that the whole plan, not only P0, is complete. + +Required evidence: + +```text +P0a/P0b/P0c passed with receipts +legacy direct_svg regression passed +chart marker readback passed +image asset readback passed +unsupported feature fail-fast passed +template/theme/component scale targets met or PLAN.md explicitly revised +prompt contracts implemented and validated +packaging/distribution decision implemented or explicitly scoped with owner/date +instruction/plan/output adherence receipt passed +all unit/integration/regression tests passed +PLAN.md completion table updated +reviewer subagent final PASS recorded +``` + +Reviewer final blocking checks: + +- Block if any `PLAN.md` P0 success standard is incomplete. +- Block if P1/P2 scope was silently dropped without updating `PLAN.md`. +- Block if live/readback evidence is missing. +- Block if instruction/plan/output adherence evidence is missing or stale. +- Block if final claim depends on fake local dry-run only. + +## 7. Status Board + +Update this board after each meaningful implementation chunk. + +| Gate | Status | Owner | Reviewer verdict | Evidence | +|---:|---|---|---|---| +| 0 Baseline and branch discipline | DONE | executor | PASS | Branch/worktree verified; legacy `direct_svg` baseline ran to `quality_gate` at `/private/tmp/svglide-direct-gate0-9Wl2gp` with `generation_mode=direct_svg` and no `artboard_receipts`; evidence recorded in `svglide-artboard-gate0-gate1-evidence.md` | +| 1 Contract layer completion | DONE | executor | PASS | Plan schema now rejects `artboard_satori` slides without `canvas_spec`; semantic map now emits `elements[]`; PLAN/contract receipt wording aligned to per-page `artboard_receipts` + aggregate `artboard_additional_receipts`; reviewer PASS recorded in `svglide-artboard-gate0-gate1-evidence.md` | +| 2 Template/theme/component/input quality | DONE | executor | PASS | 3 templates + 3 registered themes + component module + `templates/p0-templates.mjs` exist; registry text budgets, golden CanvasSpec fixtures, and safe-area/semantic bbox admission checks added; P0b `/private/tmp/svglide-p0b-gate2-safe-YVT67C` passed template-fit/quality-gate/dry-run; evidence recorded in `svglide-artboard-gate2-evidence.md` | +| 3 Satori renderer and resvg preview | DONE | executor | PASS | `node render.mjs --check-runtime` and `node dist/render.mjs --check-runtime` passed with Satori 0.26.0 / resvg 2.6.2; P0b raw SVG/PNG/contact sheet and receipts verified; evidence recorded in `svglide-artboard-gate3-evidence.md` | +| 4 SatoriToSVGlide compiler | DONE | executor | PASS | Main artboard path now writes `04-svg/artboard/page-###.canvas-template.svg` and compiles final SVGlide SVG from `CanvasSpecTemplateSVG`; raw Satori SVG is `preview_only`; quality gate rejects RawSatori compiler metadata; P0b `/private/tmp/svglide-p0b-gate4-641DXp` passed quality_gate/dry_run; evidence recorded in `svglide-artboard-gate4-evidence.md` | +| 5 Runner and quality gate integration | DONE | executor | PASS | Page jobs now run with bounded `max_workers=min(4,page_count)` and stable sorted receipts; full test suite passed 254 tests; direct_svg `/private/tmp/svglide-direct-gate5-iYPBBA` passed quality_gate; artboard P0b `/private/tmp/svglide-p0b-gate5-qg7PC6` passed dry_run; evidence recorded in `svglide-artboard-gate5-evidence.md` | +| 6 P0a/P0b local E2E | DONE | executor | PASS | P0a `/private/tmp/svglide-p0a-gate6-zNSbw5` ran to dry_run; P0b `/private/tmp/svglide-p0b-gate5-qg7PC6` ran to dry_run; P0b hits `cover-hero/dark-clarity`, `comparison-cards/forest-signal`, `summary-final/warm-editorial`; evidence recorded in `svglide-artboard-gate6-evidence.md` | +| 7 P0c live closure | DONE | executor | PASS | Reviewer PASS: strengthened PPE proof validates Whistle capture/proxy/rule hash/injected headers; fresh P0c `.tmp/svglide-p0c-gate7-live6` ran `dry_run -> ppe_proof -> live_create -> readback`; live deck `MPcnsjAH5l5r2edcpWYcNhFVnVd` created 3 slides `["pbb","pbu","pbe"]`; readback passed page count, slide order, nonblank, text-fit/bounds marker scan, and 22 CanvasSpec visible text fragments; evidence recorded in `svglide-artboard-gate7-evidence.md` | +| 8 Special cases and fallback coverage | DONE | executor | PASS | Reviewer PASS: local Gate 8 evidence `.tmp/svglide-gate8-special-cases-r4/gate8-special-cases.json` passed all 4 cases; image-only and raster-only live/readback pass; previous chart-only and combined readback failures were traced to `creation.slide.nodeserver_pre_release::GetSXSDXml` on stale lane package `ee/slide/server@1.0.0.1149`; slide-side fix is committed and pushed at `8f682ab082f7d86ade966eb2ffc5849827b17dc5`; focused Jest passes 6/6 and strict single-file TypeScript passes; deploy-lane ticket `2068537756495360000` succeeded, TCE deployment `362509781` finished, service `208677037` is running `ee/slide/server@1.0.0.1184`; fresh chart-only readback `.tmp/svglide-gate8-live-chart/08-readback/readback-check.json` passed for deck `C5fxszdjrlftMedvShmcOWtinqe` / slide `pvv`; fresh combined readback `.tmp/svglide-gate8-live/08-readback/readback-check.json` passed for deck `J35tspvJgltBnsdJpL7chnv6n2f` / slides `pdd,pdu,pdR` with chart marker and 2 image assets verified; evidence recorded in `svglide-artboard-gate8-evidence.md`; current P0/P1 milestone is closed by Gate 12b reviewer PASS | +| 9 P1 asset system scale-out | DONE | executor | PASS | Source intake inventory, 15 active templates, 10 themes, 23 component variants, 10 layout archetypes, 15 golden CanvasSpec fixtures, Node/Satori renderer support, Python fallback, and tests are implemented; source intake has required fields plus per-source conversion traceability; `ppt-master` provenance records MIT and is test-guarded; `node --check`, Python compile, JSON parse, renderer source/dist runtime checks, `pnpm --dir ... run build`, focused artboard tests 15/15, scripts regression 265/265, and `git diff --check` all pass; reviewer PASS recorded in `svglide-artboard-gate9-evidence.md` | +| 10 Prompt and planning layer | DONE | executor | PASS | Prompt contracts for Deck/Slide/Canvas/Repair planners, four output schemas, `svglide_planner_contracts.py`, Gate 10 fixture, and tests are implemented; planner contract check validates JSON-only outputs, schema conformance, Slide Planner registry binding, CanvasSpec + registry binding before Satori, and scoped leaf-level repair JSON Patch; focused planner tests 5/5, scripts regression 270/270, renderer source/dist runtime checks, artboard continuation proof, and `git diff --check` pass; reviewer PASS recorded in `svglide-artboard-gate10-evidence.md` | +| 11 Packaging and distribution decision | DONE | executor | PASS | Reviewer PASS: `artboard_renderer` stays as a skill-local Node subpackage; Satori is bundled into `dist/render.mjs`; `@resvg/resvg-js` remains a pinned native runtime dependency; `skills_embed.go` embeds prompts, flat Python scripts, and renderer package resources with a whitelist excluding `node_modules`; package check `status=passed`, source/dist runtime checks pass, package-check tests 4/4 pass, `go test .` passes, embedded skills listing includes renderer and planner prompt resources; reviewer noted macOS x64 is structurally validated through lockfile optional native package coverage, not executed on x64 host; evidence recorded in `svglide-artboard-gate11-evidence.md` | +| 12a Instruction / plan / output adherence | DONE | executor | PASS | Reviewer PASS: durable `.tmp/svglide-p0c-gate7-live6/00-input/instruction.json` and `svglide_instruction_adherence.py` validate instruction -> deck-plan -> slide-plan -> final `slide_plan` -> SVG output -> readback for target slide count, actual `slides[]` count, page order, exact title/key_message, template/theme, language, must_include/must_avoid, explicit constraint surfaces, and readback hash bindings; scoped leaf repair aligned final `slide_plan` key_message fields, then readback was refreshed with current branch CLI and matched the new plan hash; instruction adherence receipt `status=passed`; focused tests 9/9 pass; evidence recorded in `svglide-artboard-gate12a-evidence.md` | +| 12b Final full-plan acceptance | DONE | executor | PASS | Reviewer PASS: final acceptance checker requires Gate 0-12a DONE/PASS, Gate 12a instruction-adherence check/receipt status passed, current instruction/deck-plan/slide-plan/final-plan/output/readback hashes matched, readback binding checks matched, package receipt passed, PLAN scope caveat present, and Gate 12 scope deferrals with owner/date; final acceptance receipt `status=passed`; package check passed; focused final tests 3/3, scripts regression 286/286, `go test .`, and `git diff --check` pass; evidence recorded in `svglide-artboard-gate12-evidence.md` | + +Status values: + +```text +TODO +IN_PROGRESS +PARTIAL +BLOCKED +DONE +``` + +Only a reviewer subagent can move `Reviewer verdict` to `PASS`. + +## 7.1 Current Next Task + +Gate 12b now has reviewer PASS: + +```text +Completed Gate 12a outcome: +- Durable instruction capture exists at 00-input/instruction.json in the live/readback project. +- Instruction / plan / output adherence receipt is passed. +- Target slide count, actual slides[] count, page order, exact key_message, language, must_include/must_avoid, explicit constraints, and readback hash bindings are checked. +- Scoped repair was used for final slide_plan key_message drift; full regeneration was not used. +- Reviewer verdict: PASS. + +Next required action: +- Stop here unless the user starts a new follow-up scope. +- Do not claim P2/future scope as complete. +``` + +Gate 8 closure evidence: + +```text +1. Service fix deployed: + ENV ticket `2068537756495360000`, TCE deployment `362509781`, + service `208677037`, `ee/slide/server@1.0.0.1184`. +2. Chart-only readback passed: + `.tmp/svglide-gate8-live-chart/08-readback/readback-check.json`. +3. Combined chart + image + raster readback passed: + `.tmp/svglide-gate8-live/08-readback/readback-check.json`. +4. Gate 8 evidence file updated: + `skills/lark-slides/references/svglide-artboard-gate8-evidence.md`. +``` + +## 8. Subagent Review Prompt + +Use this prompt when creating a reviewer subagent: + +```text +You are the independent reviewer for SVGlide Artboard/Satori full-plan execution. + +Primary source of truth: +/Users/bytedance/Downloads/PLAN.md + +Supervision guide: +skills/lark-slides/references/svglide-artboard-full-plan-action.md + +Your job is to challenge the executor and prevent partial completion from being called done. + +You must inspect actual repo files, changed files, runner outputs, receipts, +schemas, fixtures, and test results. Do not accept executor prose as evidence. +Do not accept demos, screenshots, fake dry-runs, stale receipts, missing hashes, +or direct Satori SVG as completion evidence. + +Current execution cursor: +Gate 12b Final Full-Plan Acceptance is DONE/PASS for the current P0/P1 milestone. +P2/future scope remains explicitly not claimed unless PLAN.md and this supervision guide record a new reviewer-approved follow-up completion. + +For every review, answer these checks: + +1. Is the executor working on the current allowed gate or blocker? +2. Are the claimed changed files in the correct repo/worktree? +3. Are receipts fresh and hash-bound to current inputs? +4. Did the executor run the required validation commands for this gate? +5. Does evidence include real runner/live/readback output where required? +6. Did legacy direct_svg remain compatible where the gate can affect it? +7. Did the executor update this supervision guide and gate evidence file? +8. Is any PLAN.md requirement silently dropped, weakened, or moved later? + +Return: + +Verdict: PASS / BLOCKED + +Blocking issues: +- ... + +Non-blocking risks: +- ... + +Evidence checked: +- ... + +Next required action: +- ... +``` + +## 9. Minimum Validation Command Set + +Run these when relevant to the current gate: + +```bash +git status --short --branch +``` + +```bash +node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime +``` + +```bash +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +``` + +```bash +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +For fixture E2E, use the runner with an explicit fixture path and stage target. Do not count ad hoc `.tmp` projects as the only evidence for a gate. + +## 10. Completion Rule + +The full plan is not complete until all of the following are true: + +```text +all gates 0-12 are DONE +all reviewer verdicts are PASS +PLAN.md completion table is updated +real live_create/readback evidence exists for P0c +legacy direct_svg remains compatible +artboard_satori path has reproducible fixtures and tests +packaging/distribution path is explicit enough for CLI users +``` + +If any of these are false, the correct status is still `IN_PROGRESS` or `BLOCKED`, not complete. diff --git a/skills/lark-slides/references/svglide-artboard-gate0-gate1-evidence.md b/skills/lark-slides/references/svglide-artboard-gate0-gate1-evidence.md new file mode 100644 index 00000000..cd564215 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate0-gate1-evidence.md @@ -0,0 +1,164 @@ +# SVGlide Artboard/Satori Gate 0-1 Evidence + +Last updated: 2026-06-21 + +This file records the reviewer-checked evidence for Gate 0 and Gate 1 in `svglide-artboard-full-plan-action.md`. + +## Gate 0: Baseline And Branch Discipline + +Reviewer verdict: PASS + +Direct SVG baseline project: + +```text +/private/tmp/svglide-direct-gate0-9Wl2gp +``` + +Validation command: + +```bash +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + /private/tmp/svglide-direct-gate0-9Wl2gp \ + --until quality_gate \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Checked evidence: + +```text +receipts/generate_svg.json: + status = passed + generator_mode = script + generation_mode = direct_svg + artboard_receipts absent + +06-check/preflight.json: + summary.error_count = 0 + +06-check/runtime-review.json: + status = passed + summary.error_count = 0 + +06-check/quality-gate.json: + status = passed + failed_check_count = 0 +``` + +Interpretation: + +```text +The legacy direct_svg path still reaches quality_gate and does not depend on artboard_satori receipts. +``` + +## Gate 1: Contract Layer Completion + +Reviewer verdict: PASS + +Validated contract changes: + +```text +skills/lark-slides/scripts/svglide_schema.py + supports allOf / if / then + +skills/lark-slides/references/svglide-plan.schema.json + generation_mode=artboard_satori requires slides[].canvas_spec + +skills/lark-slides/references/svglide-semantic-map.schema.json + requires semantic_source and element-level elements[] + +skills/lark-slides/scripts/svglide_artboard_renderer.py + writes semantic_map.elements[] from CanvasSpec/template layout nodes + +/Users/bytedance/Downloads/PLAN.md +skills/lark-slides/references/svglide-artboard-satori.contract.md +skills/lark-slides/references/svglide-generator-receipt.schema.json + aligned on per-page artboard_receipts plus aggregate artboard_additional_receipts +``` + +Schema negative probe: + +```text +Input: P0b artboard_satori slide_plan with all slides[].canvas_spec removed. +Result: + issue_count = 3 + $.slides[0].canvas_spec required property is missing + $.slides[1].canvas_spec required property is missing + $.slides[2].canvas_spec required property is missing +``` + +P0b evidence project: + +```text +/private/tmp/svglide-p0b-gate1-u1w9i4 +``` + +P0b validation command: + +```bash +SVGLIDE_LARK_CLI_CMD="python3 /private/tmp/svglide_fake_lark_cli.py" \ +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + /private/tmp/svglide-p0b-gate1-u1w9i4 \ + --until dry_run \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Checked evidence: + +```text +receipts/generate_svg.json: + status = passed + generator_mode = script + generation_mode = artboard_satori + artboard_receipts length = 3 + artboard_additional_receipts length = 3 + +06-check/quality-gate.json: + status = passed + +07-create/dry-run.json: + status = passed + +04-svg/artboard/page-001.semantic-map.json: + semantic_source = CanvasSpec + elements length = 9 + +04-svg/artboard/page-002.semantic-map.json: + semantic_source = CanvasSpec + elements length = 13 + +04-svg/artboard/page-003.semantic-map.json: + semantic_source = CanvasSpec + elements length = 16 +``` + +Test command: + +```bash +PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +247 tests passed +``` + +## Reviewer Result + +Reviewer output summary: + +```text +Verdict: PASS + +Blocking issues: +- None. Previous Gate 0/1 blocking issues are resolved. + +Non-blocking risks: +- Worktree is still dirty and must be organized before merge. +- Some runtime evidence is still in /private/tmp; future gates should prefer stable fixtures or evidence docs. +- This PASS only covers Gate 0/1, not P0c live/readback or Gate 2+. +``` diff --git a/skills/lark-slides/references/svglide-artboard-gate10-evidence.md b/skills/lark-slides/references/svglide-artboard-gate10-evidence.md new file mode 100644 index 00000000..bd5b0b31 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate10-evidence.md @@ -0,0 +1,290 @@ +# SVGlide Artboard Gate 10 Evidence + +Gate: `Gate 10: Prompt And Planning Layer` + +Status: `PASS` + +Date: 2026-06-21 + +Reviewer history: + +```text +Initial review: BLOCKED +Blockers: + 1. Slide Planner output did not validate template_id/theme_id against Template/Theme Registry. + 2. Repair Planner allowed broad object patch paths such as /slides/0/canvas_spec/content. +Fixes: + 1. svglide_planner_contracts.py now validates Slide Planner template/theme IDs against active registries and template supported_theme_ids. + 2. Repair Planner validation now rejects broad paths for whole content/theme/semantic_elements/quality_constraints/content_requirements objects. + 3. Repair Planner validation rejects object/list patch values for add/replace; patches must target scalar leaf values. + 4. Focused tests now include negative coverage for unregistered slide-plan template and broad repair object patch. +Current state: re-review requested after fresh validation. +Final reviewer verdict: PASS. +``` + +## Scope + +Gate 10 turns a user topic into structured planner JSON without letting the LLM output arbitrary HTML/CSS/SVG. + +Implemented planner chain: + +```text +Deck Planner +-> Slide Planner +-> Canvas Planner +-> Repair Planner +``` + +The planner chain is validated before Satori is invoked: + +```text +prompt contracts +-> planner output schemas +-> forbidden free markup scan +-> CanvasSpec validation +-> Template Registry / Theme Registry binding +-> template text_budget / max_items admission +-> scoped JSON Patch repair validation +``` + +## Prompt Contracts + +Contract registry: + +```text +skills/lark-slides/references/svglide-planner-prompt-contracts.json +``` + +Prompt files: + +```text +skills/lark-slides/prompts/svglide/deck-planner.prompt.md +skills/lark-slides/prompts/svglide/slide-planner.prompt.md +skills/lark-slides/prompts/svglide/canvas-planner.prompt.md +skills/lark-slides/prompts/svglide/repair-planner.prompt.md +``` + +Every prompt declares: + +```text +input_bundle +output_schema +output_path +validation_command +forbidden_outputs +``` + +All prompts require JSON-only output and forbid free HTML, free CSS, free SVG, JSX/TSX, Markdown fences, raw Satori SVG, and base64 image data. + +## Output Schemas + +Schemas: + +```text +skills/lark-slides/references/svglide-deck-plan.schema.json +skills/lark-slides/references/svglide-slide-plan.schema.json +skills/lark-slides/references/svglide-canvas-plan.schema.json +skills/lark-slides/references/svglide-repair-plan.schema.json +``` + +Schema roles: + +```text +svglide-deck-plan.schema.json: narrative and deck-level intent only +svglide-slide-plan.schema.json: registered template_id/theme_id selection +svglide-canvas-plan.schema.json: final artboard_satori slide_plan.json with CanvasSpec +svglide-repair-plan.schema.json: scoped JSON Patch operations only +``` + +## Validation Script + +Implemented: + +```text +skills/lark-slides/scripts/svglide_planner_contracts.py +``` + +The script validates: + +```text +1. Required four prompt contracts exist. +2. Prompt files exist and declare schema/path/validation command. +3. Prompt contracts include forbidden outputs. +4. Planner outputs exist at contract-declared paths. +5. Planner outputs pass their schemas. +6. Planner outputs do not contain free HTML/CSS/SVG markup patterns. +7. Canvas Planner output also passes existing svglide-plan.schema.json. +8. Every CanvasSpec passes CanvasSpec validation. +9. Every CanvasSpec passes Template Registry / Theme Registry binding. +10. Template required_content, max_items, and text_budget are enforced before Satori. +11. Repair Planner output cannot contain full deck/slides/canvas_spec rewrites. +12. Repair patch paths must be scoped to slides/style_system/art_direction fields. +13. Slide Planner template_id and theme_id must be active registry entries. +14. Slide Planner theme_id must be allowed by the selected template. +15. Repair patch paths must target leaf fields, not whole CanvasSpec content/theme/semantic objects. +``` + +Output receipts: + +```text +06-check/planner-contract-check.json +receipts/planner-contract-check.json +``` + +## Fixture + +Gate 10 fixture: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/ +``` + +Planner outputs: + +```text +02-plan/deck-plan.json +02-plan/slide-plan.json +02-plan/slide_plan.json +02-plan/repair-plan.json +``` + +Topic: + +```text +冰岛火山研究 +``` + +The fixture proves: + +```text +Deck Planner output stays narrative-only. +Slide Planner chooses registered templates/themes. +Canvas Planner emits full artboard_satori slide_plan.json with CanvasSpec. +Repair Planner emits one scoped JSON Patch, not a full-deck rewrite. +``` + +## Validation Commands + +JSON parse: + +```bash +python3 -c 'import json, pathlib; paths=list(pathlib.Path("skills/lark-slides/references").glob("svglide-*.json"))+list(pathlib.Path("skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan").glob("*.json")); [json.load(p.open(encoding="utf-8")) for p in paths]; print(len(paths))' +``` + +Result: + +```text +37 JSON files parsed +``` + +Python compile: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m py_compile \ + skills/lark-slides/scripts/svglide_planner_contracts.py \ + skills/lark-slides/scripts/svglide_planner_contracts_test.py +``` + +Result: + +```text +PASS +``` + +Planner contract fixture: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 skills/lark-slides/scripts/svglide_planner_contracts.py \ + skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner \ + --pretty +``` + +Result: + +```text +status: passed +prompt_contract_count: 4 +planner_output_count: 4 +error_count: 0 +``` + +Artboard continuation proof: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 skills/lark-slides/scripts/svglide_artboard_renderer.py \ + skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner \ + --pretty +``` + +Result: + +```text +status: passed +page_count: 3 +max_workers: 3 +contact_sheet: 05-preview/contact-sheet.png +``` + +Focused tests: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest skills/lark-slides/scripts/svglide_planner_contracts_test.py +``` + +Result: + +```text +Ran 5 tests in 0.204s +OK +``` + +Full scripts regression: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +Ran 270 tests in 15.565s +OK +``` + +Renderer runtime checks: + +```bash +node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +``` + +Result: + +```text +PASS +satori_version: 0.26.0 +resvg_version: 2.6.2 +``` + +Diff check: + +```bash +git diff --check +``` + +Result: + +```text +PASS +``` + +## Boundary Note + +`svglide_template_fit_check.py` is a runner post-generate check that requires `receipts/generate_svg.json`. Gate 10 does not use it directly as the planner gate. Instead, `svglide_planner_contracts.py` performs pre-Satori planner admission by calling CanvasSpec validation and Template/Theme Registry binding, including template required content, `max_items`, and `text_budget`. + +The artboard renderer command above is included only to prove that the validated Canvas Planner output can continue into Satori/resvg; it is not a substitute for planner contract validation. diff --git a/skills/lark-slides/references/svglide-artboard-gate11-evidence.md b/skills/lark-slides/references/svglide-artboard-gate11-evidence.md new file mode 100644 index 00000000..ed2eeae2 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate11-evidence.md @@ -0,0 +1,240 @@ +# SVGlide Artboard Gate 11 Evidence + +Status: implemented, pending reviewer verdict + +Gate: Packaging and distribution decision + +## Scope + +Gate 11 closes the distribution question for the Artboard/Satori lane: + +- whether `artboard_renderer` stays as a skill subpackage, is embedded, or is packaged separately +- how `@resvg/resvg-js` native dependency is installed +- whether `skills_embed.go` needs changes +- how to validate macOS arm64/x64 install/runtime readiness +- what happens when Node dependencies are missing + +## Decision + +`artboard_renderer` remains a `lark-slides` skill-local Node subpackage. It is not moved into the root Go CLI package and does not require users to clone Satori source. + +Satori is bundled into `dist/render.mjs`. `@resvg/resvg-js` remains a package-managed native runtime dependency pinned in the subpackage lockfile. + +`skills_embed.go` is changed to embed a whitelist of prompts, flat Python scripts, and artboard renderer package resources, while excluding `node_modules`, fixtures, and generated project artifacts. + +Full decision document: + +```text +skills/lark-slides/references/svglide-artboard-packaging-decision.md +``` + +## Files Changed For Gate 11 + +```text +skills_embed.go +cmd/skill/skill.go +skills/lark-slides/scripts/artboard_renderer/package.json +skills/lark-slides/scripts/artboard_renderer/render.mjs +skills/lark-slides/scripts/artboard_renderer/dist/render.mjs +skills/lark-slides/scripts/artboard_renderer/templates/README.md +skills/lark-slides/references/svglide-artboard-satori.contract.md +skills/lark-slides/references/svglide-artboard-packaging-decision.md +skills/lark-slides/scripts/svglide_artboard_package_check.py +skills/lark-slides/scripts/svglide_artboard_package_check_test.py +skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/06-check/artboard-package-check.json +skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json +``` + +## Package Check Result + +Command: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 skills/lark-slides/scripts/svglide_artboard_package_check.py \ + --output-dir skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package \ + --pretty +``` + +Result: + +```text +status: passed +issues: [] +host: Darwin arm64 +satori: 0.26.0 +@resvg/resvg-js: 2.6.2 +manual_satori_source_checkout_required: false +node_modules_embedded_in_go_binary: false +runtime_requires_native_resvg_install: true +``` + +Receipt paths: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/06-check/artboard-package-check.json +skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json +``` + +## Runtime Checks + +Source entry: + +```bash +node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime +``` + +Result: + +```text +ok: true +renderer: satori-resvg +satori_version: 0.26.0 +resvg_version: 2.6.2 +font_path: /System/Library/Fonts/Supplemental/Arial Unicode.ttf +``` + +Published dist entry: + +```bash +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +``` + +Result: + +```text +ok: true +renderer: satori-resvg +satori_version: 0.26.0 +resvg_version: 2.6.2 +font_path: /System/Library/Fonts/Supplemental/Arial Unicode.ttf +``` + +## Build Check + +Command: + +```bash +pnpm --dir skills/lark-slides/scripts/artboard_renderer run build +``` + +Result: + +```text +passed +dist/render.mjs rebuilt +``` + +`dist/render.mjs` no longer requires `satori/package.json`. It uses version constants and dynamically loads `@resvg/resvg-js`, so missing native resvg can be caught with a clear install instruction. + +## Go Embed Check + +Command: + +```bash +go test . +``` + +Result: + +```text +passed +``` + +The first sandboxed run was blocked by Go build cache writes under `~/Library/Caches/go-build`; the verified run used approved escalated execution for the cache write. + +Embedded artboard renderer listing: + +```bash +env GOCACHE=/private/tmp/svglide-gocache \ + go run . skills list lark-slides/scripts/artboard_renderer +``` + +Result includes: + +```text +components/ +dist/ +package.json +pnpm-lock.yaml +render.mjs +templates/ +themes/ +``` + +Embedded prompt listing: + +```bash +env GOCACHE=/private/tmp/svglide-gocache \ + go run . skills list lark-slides/prompts/svglide +``` + +Result includes: + +```text +canvas-planner.prompt.md +deck-planner.prompt.md +repair-planner.prompt.md +slide-planner.prompt.md +``` + +## Unit Tests + +Command: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest skills/lark-slides/scripts/svglide_artboard_package_check_test.py +``` + +Result: + +```text +4 tests passed +``` + +Full scripts regression: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +274 tests passed +``` + +Whitespace check: + +```bash +git diff --check +``` + +Result: + +```text +passed +``` + +## Fallback Policy + +If Node, Satori source dependencies, or native resvg dependencies are missing, generation must fail before live create. + +Repair command: + +```bash +pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +``` + +No-network environments must use a prewarmed pnpm store, CI-installed dependencies, or a packaged platform dependency layer. Generation must not auto-fetch packages while producing a deck. + +## Reviewer Checklist + +- Confirm no manual Satori source checkout is required. +- Confirm native `@resvg/resvg-js` install is documented and pinned. +- Confirm package output reproduces `render.mjs --check-runtime` for source and dist. +- Confirm `skills_embed.go` includes the required skill resources but not `node_modules`. +- Confirm `slides +create-svg` boundary remains final-SVG only. diff --git a/skills/lark-slides/references/svglide-artboard-gate12-evidence.md b/skills/lark-slides/references/svglide-artboard-gate12-evidence.md new file mode 100644 index 00000000..d7a718b9 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate12-evidence.md @@ -0,0 +1,216 @@ +# SVGlide Artboard Gate 12b Evidence + +Status: DONE / reviewer PASS + +Gate: Final Full-Plan Acceptance + +Reviewer verdict: + +```text +Verdict: PASS +Blocking issues: none +Non-blocking risk: Gate 12b PASS only covers the current P0/P1 implemented milestone through Gate 12a reviewer PASS. +``` + +## Scope + +Gate 12b accepts the current implemented milestone: + +```text +P0 technical vertical slice +P0 live/readback closure +Gate 8 special cases +P1 asset scale-out +P1 planner prompt contracts +P1 packaging/distribution decision +Gate 12a instruction / plan / output / readback adherence +``` + +It does not claim the complete high-quality PPT generation system with actual model-driven topic-to-deck automation. That follow-up scope is explicit in: + +```text +skills/lark-slides/references/svglide-artboard-gate12-scope.md +``` + +## Gate Status + +The supervision board records all prerequisite gates as DONE/PASS: + +```text +Gate 0: DONE/PASS +Gate 1: DONE/PASS +Gate 2: DONE/PASS +Gate 3: DONE/PASS +Gate 4: DONE/PASS +Gate 5: DONE/PASS +Gate 6: DONE/PASS +Gate 7: DONE/PASS +Gate 8: DONE/PASS +Gate 9: DONE/PASS +Gate 10: DONE/PASS +Gate 11: DONE/PASS +Gate 12a: DONE/PASS +``` + +Source: + +```text +skills/lark-slides/references/svglide-artboard-full-plan-action.md +``` + +## Final Acceptance Check + +Command: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 skills/lark-slides/scripts/svglide_artboard_final_acceptance.py \ + --output-dir skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final \ + --pretty +``` + +Result: + +```text +status: passed +issues: [] +accepted_milestone: P0/P1 artboard_satori implementation through Gate 12a reviewer PASS +not_claimed: complete high-quality PPT generation system with actual model-driven topic-to-deck loop +``` + +Receipt paths: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final/06-check/final-acceptance-check.json +skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final/receipts/final-acceptance-check.json +``` + +## Gate 12a Binding + +Final acceptance now verifies Gate 12a explicitly: + +```text +.tmp/svglide-p0c-gate7-live6/06-check/instruction-adherence.json +.tmp/svglide-p0c-gate7-live6/receipts/instruction-adherence.json +status: passed +issues: [] +``` + +Freshness checks: + +```text +instruction hash: matched +deck-plan hash: matched +slide-plan hash: matched +final slide_plan hash: matched +output SVG page hashes: matched +readback-check hash: matched +xml-presentations-get raw readback hash: matched +``` + +Readback binding checks: + +```text +plan_sha256: matched +quality_gate_sha256: matched +dry_run_sha256: matched +ppe_proof_sha256: matched +live_create_sha256: matched +``` + +## Package Receipt + +Command: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 skills/lark-slides/scripts/svglide_artboard_package_check.py \ + --output-dir skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package \ + --pretty +``` + +Result: + +```text +status: passed +source runtime check: passed +dist runtime check: passed +Satori: 0.26.0 +resvg: 2.6.2 +node_modules embedded in Go binary: false +``` + +## Follow-Up Scope + +Explicit follow-up items are recorded in `svglide-artboard-gate12-scope.md`: + +```text +1. Real Topic Model Loop +2. Semantic Map Compiler IR +3. True Node Layout Observation +4. Real macOS x64 Runtime Validation +``` + +Each item has an owner, target date, and a "Not claimed" declaration. + +## Validation Commands + +Focused final acceptance tests: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest skills/lark-slides/scripts/svglide_artboard_final_acceptance_test.py +``` + +Result: + +```text +3 tests passed +``` + +Full scripts regression: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +286 tests passed +``` + +Go root tests: + +```bash +go test . +``` + +Result: + +```text +passed +``` + +Whitespace check: + +```bash +git diff --check +``` + +Result: + +```text +passed +``` + +## Reviewer Checklist + +- Confirm Gate 0-12a are DONE/PASS in the action guide. +- Confirm final acceptance check receipt has `status=passed`. +- Confirm final acceptance cannot pass without Gate 12a reviewer PASS and current instruction-adherence receipt. +- Confirm PLAN.md does not claim complete high-quality PPT generation. +- Confirm P2/future scope is explicit with owner/date. +- Confirm live/readback evidence remains tied to Gate 7/Gate 8 and Gate 12a readback binding, not a fake local dry-run. +- Confirm Gate 12b final PASS, if granted, is scoped to this implemented milestone. diff --git a/skills/lark-slides/references/svglide-artboard-gate12-scope.md b/skills/lark-slides/references/svglide-artboard-gate12-scope.md new file mode 100644 index 00000000..bc724f19 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate12-scope.md @@ -0,0 +1,130 @@ +# SVGlide Artboard Gate 12 Scope + +Status: final acceptance scope for current implementation milestone + +## Accepted Milestone + +Gate 12 acceptance covers the implemented SVGlide Artboard/Satori milestone through Gate 12a: + +```text +P0 technical vertical slice +P0 live/readback special cases +P1 template/theme/component scale-out +P1 planner prompt contracts +P1 packaging/distribution decision +Gate 12a instruction / plan / output / readback adherence +``` + +This milestone proves: + +- `generation_mode=artboard_satori` can run through the existing SVGlide control plane. +- Satori output is preview/layout evidence, not the direct live SVG semantic source. +- Final live input remains SVGlide protocol SVG. +- `ppe_pure_svg` readback covers chart marker and image asset cases. +- Template/theme/component assets meet the current P1 minimum scale. +- Planner prompts produce structured JSON contracts instead of free HTML/CSS/SVG. +- `artboard_renderer` has a releaseable skill-local packaging story. +- Instruction, planner output, generated SVG, and readback evidence are bound by Gate 12a adherence receipts. + +## Not Claimed + +Not claimed: complete high-quality PPT generation system with actual model-driven topic-to-deck loop. + +Not claimed in this milestone: + +- actual model invocation from arbitrary user topic to Deck Plan / Slide Plan / CanvasSpec +- automated visual repair loop that calls a model and writes scoped JSON Patch +- full semantic-map-as-compiler-IR implementation for every element type +- true Satori `onNodeDetected` layout observation wired as `node-layout-map/v1` +- production CI coverage on a real macOS x64 host + +These are not silently dropped. They are explicit follow-up scope. + +## Follow-Up Items + +### 1. Real Topic Model Loop + +Owner: SVGlide artboard follow-up executor + +Target date: 2026-06-28 + +Scope: + +```text +user topic +-> model-generated deck-plan.json +-> model-generated slide-plan.json +-> model-generated canvas-plan.json +-> planner contract validation +-> template/theme binding +-> artboard_satori render +-> quality_gate +-> preview repair loop if needed +``` + +Acceptance: + +- At least one real topic deck runs from model output, not handwritten fixtures. +- All planner outputs pass schema and registry binding. +- Repair prompt is used only as scoped JSON Patch. + +### 2. Semantic Map Compiler IR + +Owner: SVGlide artboard follow-up executor + +Target date: 2026-06-28 + +Scope: + +```text +CanvasSpec.semantic_elements +-> semantic-map/v1 elements with bbox/role/source_ref/style +-> compiler consumes semantic-map/v1 as element-level IR +-> final SVGlide text/image/chart/shape roles are generated from semantic-map +``` + +Acceptance: + +- `semantic-map/v1` is no longer only a summary receipt. +- compiler receipts include `input_semantic_hash`. +- semantic review can compare visible text/source refs against element-level semantic map. + +### 3. True Node Layout Observation + +Owner: SVGlide artboard follow-up executor + +Target date: 2026-06-28 + +Scope: + +```text +Satori renderer layout observation +-> node-layout-map/v1 +-> element_id alignment +-> drift check against semantic-map bbox +``` + +Acceptance: + +- `node-layout-map/v1` records measured layout, not only static template geometry. +- quality gate blocks material layout drift before live create. + +### 4. Real macOS x64 Runtime Validation + +Owner: SVGlide release/CI owner + +Target date: 2026-06-28 + +Scope: + +```text +macOS x64 host +-> pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile +-> node dist/render.mjs --check-runtime +-> package check receipt +``` + +Acceptance: + +- x64 host runtime check confirms `@resvg/resvg-js-darwin-x64` loads and renders PNG. +- result is attached to package evidence or CI artifact. diff --git a/skills/lark-slides/references/svglide-artboard-gate12a-evidence.md b/skills/lark-slides/references/svglide-artboard-gate12a-evidence.md new file mode 100644 index 00000000..eb3db1b4 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate12a-evidence.md @@ -0,0 +1,238 @@ +# SVGlide Artboard Gate 12a Evidence + +Status: DONE / reviewer PASS + +Gate: Instruction / Plan / Output Adherence + +Reviewer verdict: + +```text +Verdict: PASS +Blocking issues: none +Non-blocking risk: Gate 12a PASS does not mean Gate 12b or full PLAN completion. +``` + +## Scope + +Gate 12a verifies that a durable user instruction remains consistent through: + +```text +00-input/instruction.json +-> 02-plan/deck-plan.json +-> 02-plan/slide-plan.json +-> 02-plan/slide_plan.json +-> 04-svg/page-###.svg +-> 08-readback/readback-check.json + xml-presentations-get.json +``` + +It does not enter Gate 12b final acceptance. + +## Files Added Or Updated + +```text +skills/lark-slides/scripts/svglide_instruction_adherence.py +skills/lark-slides/scripts/svglide_instruction_adherence_test.py +.tmp/svglide-p0c-gate7-live6/00-input/instruction.json +.tmp/svglide-p0c-gate7-live6/02-plan/deck-plan.json +.tmp/svglide-p0c-gate7-live6/02-plan/slide-plan.json +.tmp/svglide-p0c-gate7-live6/02-plan/slide_plan.json +.tmp/svglide-p0c-gate7-live6/08-readback/readback-check.json +.tmp/svglide-p0c-gate7-live6/08-readback/xml-presentations-get.json +.tmp/svglide-p0c-gate7-live6/06-check/instruction-adherence.json +.tmp/svglide-p0c-gate7-live6/receipts/instruction-adherence.json +``` + +Removed stale Gate10-only evidence: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/01-project/instruction-lock.json +skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/06-check/instruction-adherence.json +skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/instruction-adherence.json +``` + +## Durable Instruction Capture + +Project: + +```text +.tmp/svglide-p0c-gate7-live6 +``` + +Instruction artifact: + +```text +.tmp/svglide-p0c-gate7-live6/00-input/instruction.json +``` + +Captured requirements: + +```text +topic: SVGlide Artboard P0c live closure +language: zh-CN +audience: SVGlide engineers +target_slide_count: 3 +must_include: + - 画板链路 P0b + - 三页 fixture 验证模板系统、质量门禁和 dry-run 编排。 + - 新旧链路差异 + - 核心变化是把美学标准从自由生成迁移到受控设计系统。 + - 下一步进入真实 Satori + - 先稳定多页画板系统,再替换底层 renderer 并推进 live/readback。 +must_avoid: + - 自由 HTML + - 自由 CSS + - 自由 SVG + - 系统截图 + - 编造具体数值 +output_requirements: + - generation_mode=artboard_satori + - final_format=SVGlide protocol SVG + - live_create_required=true + - readback_required=true + - page_order_locked=true +repair_policy: + - default_on_missing_page=scoped_append_page + - default_on_local_mismatch=scoped_leaf_patch +``` + +Explicit constraints are surface-aware: + +```text +plan evidence: + - artboard_satori + - SVGlide protocol SVG + - 不输出自由 HTML/CSS/SVG + - 不使用系统截图 + - readback / 页数 / 顺序 / 关键文本 +output/readback evidence: + - Satori + - resvg + - readback +``` + +## Adherence Check + +Scoped repair performed before the final check: + +```text +target: .tmp/svglide-p0c-gate7-live6/02-plan/slide_plan.json +patch type: leaf-only key_message repair +patched fields: + - /slides/0/key_message + - /slides/1/key_message + - /slides/2/key_message +reason: align final slide_plan key_message with instruction, deck-plan, slide-plan, SVG output, and readback visible key text +full regeneration: not used +``` + +Readback was then refreshed with the current branch CLI so the readback binding hashes the scoped-repaired plan: + +```bash +env SVGLIDE_LARK_CLI_CMD='go run .' PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 skills/lark-slides/scripts/svglide_readback.py \ + .tmp/svglide-p0c-gate7-live6 \ + --pretty +``` + +Result: + +```text +status: passed +input_binding.plan_sha256: 3aa06d0877a587f8b62d0c8934fcc03ec8e34823c00c725f4e16c3d44ceb3bb7 +readback page_count / slide_order / core_visible_text: passed +``` + +Command: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 skills/lark-slides/scripts/svglide_instruction_adherence.py \ + .tmp/svglide-p0c-gate7-live6 \ + --pretty +``` + +Result: + +```text +status: passed +issues: [] +target_slide_count: 3 +deck_plan.slide_count: 3 +slide_plan.slide_count: 3 +final slide_plan.slide_count: 3 +output_pages: 3 +readback.slide_count: 3 +readback.slide_ids: pbb, pbu, pbe +must_include_missing: [] +must_avoid_present: [] +constraint_checks: all missing_text={} +repair_recommendations: no_repair_needed +``` + +Readback binding freshness: + +```text +plan_sha256 matched current 02-plan/slide_plan.json +quality_gate_sha256 matched current 06-check/quality-gate.json +dry_run_sha256 matched current 07-create/dry-run.json +ppe_proof_sha256 matched current 07-create/ppe-proof.json +live_create_sha256 matched current 07-create/live-create.json +``` + +Receipt paths: + +```text +.tmp/svglide-p0c-gate7-live6/06-check/instruction-adherence.json +.tmp/svglide-p0c-gate7-live6/receipts/instruction-adherence.json +``` + +## What The Checker Enforces + +- `00-input/instruction.json` exists and uses `svglide-instruction/v1`. +- `target_slide_count` matches deck-plan, slide-plan, final `slide_plan`, SVG output page count, readback page count, and readback slide id count. +- Actual `slides[]` length in deck-plan, slide-plan, and final `slide_plan` matches the instruction, so a stale target field cannot hide missing pages. +- Page order matches instruction, deck-plan, slide-plan, and final `slide_plan`. +- Per-page title, exact `key_message`, required text, template id, and theme id propagate through planner outputs, final `slide_plan`, SVG output, and readback visible text. +- `zh-CN` output/readback pages contain CJK visible text. +- `must_include` is present in readback. +- `must_avoid` is absent from SVG output and readback. +- Explicit constraints are checked on their declared evidence surfaces: plan, output, and/or readback. +- Readback hash bindings match current plan, quality gate, dry-run, PPE proof, and live-create artifacts when those bindings are present. +- Repair plans must target scoped `/slides/...` leaf paths; broad deck rewrites or object/list replacement values fail. + +## Focused Tests + +Command: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest skills/lark-slides/scripts/svglide_instruction_adherence_test.py +``` + +Result: + +```text +9 tests passed +``` + +Covered cases: + +```text +1. text matching ignores spacing. +2. broad object/list repair patch is rejected. +3. happy path writes both check and receipt. +4. deck-plan slides[] count drift fails with scoped append/delete recommendation. +5. missing final CanvasSpec page fails. +6. missing slide-plan page fails with scoped append/delete recommendation. +7. final slide_plan key_message drift fails even when readback binding is fresh. +8. forbidden text in SVG output/readback fails. +9. stale readback input_binding plan hash fails. +``` + +## Reviewer Checklist + +- Confirm `00-input/instruction.json` is the durable instruction artifact for Gate 12a. +- Confirm the receipt hashes instruction, deck-plan, slide-plan, final `slide_plan`, SVG pages, and readback artifacts. +- Confirm readback checks page count, order, title/key text, language, must_include, and must_avoid. +- Confirm missing pages/local mismatches are mapped to scoped repair recommendations, not full regeneration. +- Confirm Gate 12a reviewer PASS is recorded and is not treated as Gate 12b final acceptance. diff --git a/skills/lark-slides/references/svglide-artboard-gate2-evidence.md b/skills/lark-slides/references/svglide-artboard-gate2-evidence.md new file mode 100644 index 00000000..34d04370 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate2-evidence.md @@ -0,0 +1,126 @@ +# SVGlide Artboard/Satori Gate 2 Evidence + +Last updated: 2026-06-21 + +Gate: Template, Theme, Component, And Input Quality System + +Reviewer verdict: PASS + +## Artifacts + +Templates: + +```text +skills/lark-slides/references/svglide-template-registry.json +skills/lark-slides/scripts/artboard_renderer/templates/p0-templates.mjs +``` + +Themes: + +```text +skills/lark-slides/scripts/artboard_renderer/themes/registry.json +skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json +skills/lark-slides/scripts/artboard_renderer/themes/forest-signal.json +skills/lark-slides/scripts/artboard_renderer/themes/warm-editorial.json +``` + +Components: + +```text +skills/lark-slides/scripts/artboard_renderer/components/primitives.mjs +``` + +Golden CanvasSpec fixtures: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/golden/cover-hero.canvas-spec.json +skills/lark-slides/scripts/fixtures/svglide_artboard/golden/comparison-cards.canvas-spec.json +skills/lark-slides/scripts/fixtures/svglide_artboard/golden/summary-final.canvas-spec.json +``` + +## Checked Requirements + +```text +3 active templates: + cover-hero + comparison-cards + summary-final + +3 active formal themes: + dark-clarity + forest-signal + warm-editorial + +Component exports: + Title + Subtitle + Chip + StatCard + ImageFrame + +Input quality fail-fast: + unknown template_id + unknown theme_id + missing required content + card count overflow + text budget exceeded + semantic bbox outside safe_area + semantic bbox outside canvas +``` + +## Runtime Evidence + +P0b evidence project: + +```text +/private/tmp/svglide-p0b-gate2-safe-YVT67C +``` + +Validation command: + +```bash +SVGLIDE_LARK_CLI_CMD="python3 /private/tmp/svglide_fake_lark_cli.py" \ +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + /private/tmp/svglide-p0b-gate2-safe-YVT67C \ + --until dry_run \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Result: + +```text +template-fit = passed +quality_gate = passed +dry_run = passed + +page 1 = cover-hero / dark-clarity +page 2 = comparison-cards / forest-signal +page 3 = summary-final / warm-editorial +``` + +Test command: + +```bash +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +251 tests passed +``` + +## Reviewer Result + +```text +Verdict: PASS + +Blocking issues: +- None. The previous safe bbox / safe-area admission blocker is resolved. + +Non-blocking risks: +- Worktree is still dirty. +- P0b evidence is under /private/tmp; keep this evidence doc for stable audit. +``` diff --git a/skills/lark-slides/references/svglide-artboard-gate3-evidence.md b/skills/lark-slides/references/svglide-artboard-gate3-evidence.md new file mode 100644 index 00000000..2906e718 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate3-evidence.md @@ -0,0 +1,103 @@ +# SVGlide Artboard/Satori Gate 3 Evidence + +Last updated: 2026-06-21 + +Gate: Satori Renderer And resvg Preview + +Reviewer verdict: PASS + +## Runtime Package + +```text +skills/lark-slides/scripts/artboard_renderer/package.json +skills/lark-slides/scripts/artboard_renderer/pnpm-lock.yaml +skills/lark-slides/scripts/artboard_renderer/render.mjs +skills/lark-slides/scripts/artboard_renderer/dist/render.mjs +``` + +Declared runtime dependencies: + +```text +satori = 0.26.0 +@resvg/resvg-js = 2.6.2 +``` + +Runtime checks: + +```bash +node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +``` + +Result: + +```text +satori_version = 0.26.0 +resvg_version = 2.6.2 +renderer = satori-resvg +``` + +## P0b Evidence + +Evidence project: + +```text +/private/tmp/svglide-p0b-gate2-safe-YVT67C +``` + +Artifacts: + +```text +04-svg/artboard/raw/page-001.satori.svg +04-svg/artboard/raw/page-002.satori.svg +04-svg/artboard/raw/page-003.satori.svg +04-svg/artboard/page-001.png +04-svg/artboard/page-002.png +04-svg/artboard/page-003.png +05-preview/contact-sheet.png +receipts/artboard-render.json +04-svg/artboard/page-001.receipt.json +04-svg/artboard/page-002.receipt.json +04-svg/artboard/page-003.receipt.json +``` + +Reviewer-checked facts: + +```text +raw Satori SVGs exist and are non-empty +page PNGs exist and are non-empty +contact sheet exists +artboard-render receipt status = passed +per-page receipts include template_id/theme_id +per-page receipts include satori_version/resvg_version +per-page receipts include font_hashes +per-page receipts include Satori SVG, PNG, metadata, node-layout, and SVGlide SVG hashes +raw Satori SVG is not copied directly to final live SVG +no QuickLook/system screenshot/Playwright/Chromium/Puppeteer path is used in renderer code +``` + +Test command: + +```bash +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +251 tests passed +``` + +## Reviewer Result + +```text +Verdict: PASS + +Blocking issues: +- None. + +Non-blocking risks: +- Worktree is dirty and renderer files are untracked. +- dist/render.mjs externalizes @resvg/resvg-js; packaging belongs to a later gate. +- Runtime evidence is under /private/tmp; keep this evidence doc for audit. +``` diff --git a/skills/lark-slides/references/svglide-artboard-gate4-evidence.md b/skills/lark-slides/references/svglide-artboard-gate4-evidence.md new file mode 100644 index 00000000..de16cc3f --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate4-evidence.md @@ -0,0 +1,226 @@ +# SVGlide Artboard Gate 4 Evidence + +Date: 2026-06-21 + +Gate: `Gate 4: SatoriToSVGlide Compiler` + +Reviewer status before this evidence: `BLOCKED` + +Reviewer verdict after re-review: `PASS` + +## Scope + +This evidence addresses the reviewer blocker that the final SVGlide protocol SVG +was compiled from raw Satori SVG while receipt metadata claimed `CanvasSpec`. + +The implementation now keeps raw Satori SVG as preview/layout evidence only. The +final `04-svg/page-###.svg` is compiled from a CanvasSpec-owned template artifact: + +```text +04-svg/artboard/page-###.canvas-template.svg +``` + +P0 Gate 4 scope is explicitly text/shape only. Image asset binding and +`svglide-chart-spec-v1` chart marker proof remain Gate 8/P0c work. + +## Code And Contract Changes + +Changed files: + +```text +skills/lark-slides/scripts/svglide_artboard_renderer.py +skills/lark-slides/scripts/svglide_artboard_renderer_test.py +skills/lark-slides/scripts/svglide_quality_gate.py +skills/lark-slides/scripts/svglide_quality_gate_test.py +skills/lark-slides/references/svglide-artboard-receipt.schema.json +skills/lark-slides/references/svglide-artboard-satori.contract.md +skills/lark-slides/references/svglide-artboard-full-plan-action.md +/Users/bytedance/Downloads/PLAN.md +``` + +Key implementation points: + +```text +compile_satori_svg_to_svglide(...) + remains available for raw Satori SVG fail-fast tests and reports: + semantic_source = SatoriSVG + compiler_input = RawSatoriSVG + satori_svg_usage = compiler_input + +compile_canvas_template_svg_to_svglide(...) + is the artboard_satori main path and reports: + semantic_source = CanvasSpec + compiler_input = CanvasSpecTemplateSVG + satori_svg_usage = preview_only +``` + +`render_project(...)` now writes and compiles from: + +```text +04-svg/artboard/page-###.canvas-template.svg +``` + +Per-page artboard receipts now bind: + +```text +canvas_template_svg +canvas_template_svg_sha256 +compiler_input +compiler_input_sha256 +compiler.semantic_source = CanvasSpec +compiler.compiler_input = CanvasSpecTemplateSVG +compiler.satori_svg_usage = preview_only +``` + +`receipts/satori-bridge.json` page entries now bind: + +```text +canvas_template_svg +canvas_template_svg_sha256 +compiler_input +compiler_input_sha256 +compiler_input_type = CanvasSpecTemplateSVG +satori_svg_usage = preview_only +``` + +`quality_gate` now rejects: + +```text +missing or stale canvas_template_svg +missing or stale compiler_input +compiler_input_type != CanvasSpecTemplateSVG +satori_svg_usage != preview_only +artboard receipt compiler_input != CanvasSpecTemplateSVG +artboard receipt compiler_input path not equal to canvas_template_svg +``` + +## Validation + +Targeted unit tests: + +```bash +python3 -m unittest \ + skills/lark-slides/scripts/svglide_artboard_renderer_test.py \ + skills/lark-slides/scripts/svglide_quality_gate_test.py +``` + +Result: + +```text +Ran 35 tests in 1.708s +OK +``` + +Full unit test suite: + +```bash +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +Ran 252 tests in 9.955s +OK +``` + +Whitespace check: + +```bash +git diff --check +``` + +Result: + +```text +passed with no output +``` + +## P0b Fixture Evidence + +Fresh P0b project: + +```text +/private/tmp/svglide-p0b-gate4-641DXp +``` + +Command: + +```bash +SVGLIDE_LARK_CLI_CMD="python3 /private/tmp/svglide_fake_lark_cli.py" \ +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + /private/tmp/svglide-p0b-gate4-641DXp \ + --until dry_run \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Result: + +```text +current_stage = dry_run +generate_svg = passed +prepare = passed +preflight = passed +quality_gate = passed +dry_run = passed +``` + +Critical receipt check: + +```text +page-001: cover-hero / dark-clarity + compiler.semantic_source = CanvasSpec + compiler.compiler_input = CanvasSpecTemplateSVG + compiler.satori_svg_usage = preview_only + compiler_input = 04-svg/artboard/page-001.canvas-template.svg + +page-002: comparison-cards / forest-signal + compiler.semantic_source = CanvasSpec + compiler.compiler_input = CanvasSpecTemplateSVG + compiler.satori_svg_usage = preview_only + compiler_input = 04-svg/artboard/page-002.canvas-template.svg + +page-003: summary-final / warm-editorial + compiler.semantic_source = CanvasSpec + compiler.compiler_input = CanvasSpecTemplateSVG + compiler.satori_svg_usage = preview_only + compiler_input = 04-svg/artboard/page-003.canvas-template.svg +``` + +Bridge receipt check: + +```text +receipts/satori-bridge.json pages[0..2]: + compiler_input_type = CanvasSpecTemplateSVG + satori_svg_usage = preview_only +``` + +Artifacts present: + +```text +04-svg/artboard/page-001.canvas-template.svg +04-svg/artboard/page-002.canvas-template.svg +04-svg/artboard/page-003.canvas-template.svg +04-svg/artboard/raw/page-001.satori.svg +04-svg/artboard/raw/page-002.satori.svg +04-svg/artboard/raw/page-003.satori.svg +04-svg/page-001.svg +04-svg/page-002.svg +04-svg/page-003.svg +06-check/quality-gate.json +07-create/dry-run.json +``` + +## Remaining Non-Gate-4 Scope + +This evidence does not claim completion of: + +```text +Gate 7 P0c live_create/readback +Gate 8 image asset binding/readback +Gate 8 chart marker/readback +Gate 8 raster fallback fixture +Gate 9+ P1/P2 scale-out work +``` diff --git a/skills/lark-slides/references/svglide-artboard-gate5-evidence.md b/skills/lark-slides/references/svglide-artboard-gate5-evidence.md new file mode 100644 index 00000000..b9321f12 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate5-evidence.md @@ -0,0 +1,209 @@ +# SVGlide Artboard Gate 5 Evidence + +Date: 2026-06-21 + +Gate: `Gate 5: Runner And Quality Gate Integration` + +Reviewer status before this evidence: `PENDING` + +Reviewer verdict after re-review: `PASS` + +## Scope + +This evidence covers runner integration, artboard receipt freshness checks, +bounded page concurrency, stable page ordering, and legacy `direct_svg` +compatibility. + +## Code Changes + +Changed files: + +```text +skills/lark-slides/scripts/svglide_artboard_renderer.py +skills/lark-slides/scripts/svglide_artboard_renderer_test.py +skills/lark-slides/scripts/svglide_quality_gate.py +skills/lark-slides/scripts/svglide_quality_gate_test.py +``` + +Key implementation points: + +```text +svglide_project_runner.py + already dispatches `generate_svg` by `generation_mode`. + `direct_svg` keeps the existing generator script/external path. + `artboard_satori` calls `svglide_artboard_renderer.py` and then template-fit. + +svglide_artboard_renderer.py + now runs page jobs with max_workers = min(4, page_count). + page results are sorted by page number before aggregate receipts/contact sheet. + `artboard-render` and `satori-bridge` summaries record max_workers. + +svglide_quality_gate.py + validates aggregate artboard receipts. + validates per-page artboard receipts against schema. + validates raw Satori preview, PNG, render metadata, canvas-template compiler input, + semantic-map, node-layout-map, and final SVGlide hashes. + rejects RawSatoriSVG compiler metadata and stale compiler-input artifacts. +``` + +## Validation + +Full unit test suite: + +```bash +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +Ran 254 tests in 10.234s +OK +``` + +Focused tests: + +```bash +python3 -m unittest skills/lark-slides/scripts/svglide_artboard_renderer_test.py +``` + +Result: + +```text +Ran 14 tests in 1.722s +OK +``` + +```bash +python3 -m unittest skills/lark-slides/scripts/svglide_quality_gate_test.py +``` + +Result: + +```text +Ran 23 tests in 0.441s +OK +``` + +```bash +python3 -m unittest skills/lark-slides/scripts/svglide_project_runner_test.py +``` + +Result: + +```text +Ran 35 tests in 3.917s +OK +``` + +Whitespace check: + +```bash +git diff --check +``` + +Result: + +```text +passed with no output +``` + +## Legacy direct_svg Regression + +Fresh direct_svg evidence project: + +```text +/private/tmp/svglide-direct-gate5-iYPBBA +``` + +Command: + +```bash +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + /private/tmp/svglide-direct-gate5-iYPBBA \ + --until quality_gate \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Result: + +```text +generation_mode = direct_svg +generator_mode = script +artboard_receipts absent +quality_gate = passed +failed_check_count = 0 +``` + +## artboard_satori P0b Regression + +Fresh P0b evidence project: + +```text +/private/tmp/svglide-p0b-gate5-qg7PC6 +``` + +Command: + +```bash +SVGLIDE_LARK_CLI_CMD="python3 /private/tmp/svglide_fake_lark_cli.py" \ +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + /private/tmp/svglide-p0b-gate5-qg7PC6 \ + --until dry_run \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Result: + +```text +current_stage = dry_run +generate_svg = passed +prepare = passed +preflight = passed +quality_gate = passed +dry_run = passed +``` + +Concurrency and ordering evidence: + +```text +receipts/artboard-render.json: + summary.max_workers = 3 + pages = [1, 2, 3] + +receipts/satori-bridge.json: + summary.max_workers = 3 + pages = [1, 2, 3] + +receipts/generate_svg.json: + artboard_receipts = [ + 04-svg/artboard/page-001.receipt.json, + 04-svg/artboard/page-002.receipt.json, + 04-svg/artboard/page-003.receipt.json + ] +``` + +Freshness and negative tests now cover: + +```text +missing artboard receipts +stale raw Satori SVG +stale canvas-template compiler input +invalid RawSatoriSVG compiler metadata +artboard receipt schema failure +stale prepared SVG before dry_run +``` + +## Remaining Scope + +This evidence does not claim: + +```text +P0c live_create/readback +Gate 8 chart/image/raster fallback proof +Gate 9+ P1/P2 asset and planning scale-out +``` diff --git a/skills/lark-slides/references/svglide-artboard-gate6-evidence.md b/skills/lark-slides/references/svglide-artboard-gate6-evidence.md new file mode 100644 index 00000000..500722d8 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate6-evidence.md @@ -0,0 +1,172 @@ +# SVGlide Artboard Gate 6 Evidence + +Date: 2026-06-21 + +Gate: `Gate 6: P0a And P0b Local E2E` + +Reviewer status before this evidence: `PENDING` + +Reviewer verdict after re-review: `PASS` + +## Scope + +This evidence proves the checked-in P0a and P0b artboard fixtures are repeatable +through the real runner path, not ad hoc scripts. + +Gate 6 does not claim `live_create` or `readback`; those remain Gate 7. + +## P0a Fresh Run + +Fixture source: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover +``` + +Fresh project: + +```text +/private/tmp/svglide-p0a-gate6-zNSbw5 +``` + +Command: + +```bash +SVGLIDE_LARK_CLI_CMD="python3 /private/tmp/svglide_fake_lark_cli.py" \ +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + /private/tmp/svglide-p0a-gate6-zNSbw5 \ + --until dry_run \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Result: + +```text +current_stage = dry_run +generate_svg = passed +prepare = passed +preflight = passed +quality_gate = passed +dry_run = passed +``` + +Template/theme hit: + +```text +page 1 = cover-hero / dark-clarity +``` + +Required artifacts present: + +```text +04-svg/artboard/raw/page-001.satori.svg +04-svg/artboard/page-001.png +04-svg/artboard/page-001.canvas-template.svg +04-svg/artboard/page-001.semantic-map.json +04-svg/artboard/page-001.node-layout-map.json +04-svg/artboard/page-001.receipt.json +04-svg/page-001.svg +05-preview/contact-sheet.png +06-check/quality-gate.json +07-create/dry-run.json +receipts/generate_svg.json +``` + +## P0b Fresh Run + +Fixture source: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page +``` + +Fresh project: + +```text +/private/tmp/svglide-p0b-gate5-qg7PC6 +``` + +Command: + +```bash +SVGLIDE_LARK_CLI_CMD="python3 /private/tmp/svglide_fake_lark_cli.py" \ +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + /private/tmp/svglide-p0b-gate5-qg7PC6 \ + --until dry_run \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Result: + +```text +current_stage = dry_run +generate_svg = passed +prepare = passed +preflight = passed +quality_gate = passed +dry_run = passed +``` + +Template/theme hit list: + +```text +page 1 = cover-hero / dark-clarity +page 2 = comparison-cards / forest-signal +page 3 = summary-final / warm-editorial +``` + +Runner and receipt evidence: + +```text +receipts/generate_svg.json: + generation_mode = artboard_satori + artboard_receipts = [ + 04-svg/artboard/page-001.receipt.json, + 04-svg/artboard/page-002.receipt.json, + 04-svg/artboard/page-003.receipt.json + ] + +receipts/artboard-render.json: + summary.max_workers = 3 + pages = [1, 2, 3] + +receipts/satori-bridge.json: + summary.max_workers = 3 + pages = [1, 2, 3] + +06-check/quality-gate.json: + status = passed + failed_check_count = 0 + +07-create/dry-run.json: + status = passed +``` + +## Validation Context + +The same code state also passed: + +```text +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' + Ran 254 tests + OK + +git diff --check + passed with no output +``` + +## Remaining Scope + +This evidence does not claim: + +```text +Gate 7 P0c live_create/readback +Gate 8 chart marker readback +Gate 8 image asset readback +Gate 8 raster fallback fixture +Gate 9+ P1/P2 scale-out work +``` diff --git a/skills/lark-slides/references/svglide-artboard-gate7-evidence.md b/skills/lark-slides/references/svglide-artboard-gate7-evidence.md new file mode 100644 index 00000000..5cbfecda --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate7-evidence.md @@ -0,0 +1,287 @@ +# SVGlide Artboard Gate 7 Evidence + +Gate: `Gate 7: P0c Live Closure` + +Status: `PASS` + +Date: 2026-06-21 + +## Scope + +This gate closes the P0c live path with the real `slides +create-svg` command: + +```text +dry_run -> ppe_proof -> live_create -> readback +``` + +The fresh evidence project is: + +```text +.tmp/svglide-p0c-gate7-live6 +``` + +## Root Cause From Earlier Blocker + +Earlier Gate 7 attempts failed with: + +```text +[4001000] nodeServer invalid param +slides_added: 0 +``` + +The failed receipts are kept for audit: + +```text +.tmp/svglide-p0c-gate7-live4/07-create/live-create.json +.tmp/svglide-live-probes/probe-e2e-with-header.json +.tmp/svglide-live-probes/probe-e2e-no-header.json +``` + +The cause was not the artboard SVG output itself. The local process was not reaching the required PPE pure-SVG service lane. The successful lane requires: + +```text +Whistle capture mode +open.feishu.cn -> open.feishu-pre.cn +Env=Pre_release +x-tt-env=ppe_pure_svg +HTTP_PROXY/HTTPS_PROXY=http://127.0.0.1:8899 +``` + +The Whistle rule is now tracked in the CLI worktree: + +```text +skills/lark-slides/references/ppe-pure-svg.whistle.js +sha256: 641d01be2b2ea6b7a57a21302ee45cf10cc60d247132f50681966b88481a8487 +``` + +## Code Changes For Gate 7 + +Request-scoped PPE header propagation: + +```text +shortcuts/common/runner.go +shortcuts/slides/slides_create.go +shortcuts/slides/slides_create_svg.go +shortcuts/slides/slides_create_svg_test.go +``` + +Runner and proof integration: + +```text +skills/lark-slides/scripts/svglide_project_runner.py +skills/lark-slides/scripts/svglide_project_runner_test.py +skills/lark-slides/scripts/svglide_ppe_proof.py +skills/lark-slides/scripts/svglide_ppe_proof_test.py +skills/lark-slides/scripts/svglide_readback.py +skills/lark-slides/scripts/svglide_readback_test.py +skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/07-create/ppe-proof.input.json +``` + +Implemented behavior: + +```text +ppe-proof.input.json + -> validates quality_gate and dry_run hashes + -> validates Whistle proxy/capture/rule hash/injected headers + -> ppe-proof.json + -> runner live_create command + -> slides +create-svg --request-header x-tt-env=ppe_pure_svg + -> RuntimeContext.CallAPIWithHeaders + -> presentation create and slide add API calls +``` + +Readback now checks: + +```text +created slide count +actual readback page count from xml_presentation.content +created slide order +blank page markers +text-fit and bounds markers +CanvasSpec visible text fragments +``` + +## Fresh P0c Command + +```bash +SVGLIDE_LARK_CLI_CMD="env GOCACHE=/private/tmp/svglide-gocache HTTP_PROXY=http://127.0.0.1:8899 HTTPS_PROXY=http://127.0.0.1:8899 http_proxy=http://127.0.0.1:8899 https_proxy=http://127.0.0.1:8899 go run ." \ +python3 skills/lark-slides/scripts/svglide_project_runner.py run \ + .tmp/svglide-p0c-gate7-live6 \ + --until readback \ + --network-policy fixture \ + --asset-provider none \ + --image-backend none +``` + +Result: + +```text +current_stage: readback +dry_run: passed +ppe_proof: passed +live_create: passed +readback: passed +``` + +## PPE Proof Receipt + +Receipt: + +```text +.tmp/svglide-p0c-gate7-live6/07-create/ppe-proof.json +``` + +Key evidence: + +```text +status: passed +quality_gate_sha256: 072996e2b8a2c84d6d0efae0e0e23659438d7b8d1d129de3d520b40c13511844 +dry_run_sha256: 9886b4fbc0d039827eca6bca99d5e8f7ddfe69ac20c78f9a8c30afc9cb713bd0 +proxy.mode: whistle +proxy.capture: true +proxy.http_proxy: http://127.0.0.1:8899 +proxy.https_proxy: http://127.0.0.1:8899 +proxy.rewrite_host: open.feishu-pre.cn +proxy.rule_file: skills/lark-slides/references/ppe-pure-svg.whistle.js +proxy.rule_sha256: 641d01be2b2ea6b7a57a21302ee45cf10cc60d247132f50681966b88481a8487 +proxy.inject_headers.Env: Pre_release +proxy.inject_headers.x-tt-env: ppe_pure_svg +headers.x-tt-env: ppe_pure_svg +summary.error_count: 0 +``` + +## Live Create Receipt + +Receipt: + +```text +.tmp/svglide-p0c-gate7-live6/07-create/live-create.json +``` + +Key evidence: + +```text +status: passed +returncode: 0 +xml_presentation_id: MPcnsjAH5l5r2edcpWYcNhFVnVd +slides_added: 3 +slide_ids: ["pbb", "pbu", "pbe"] +revision_id: 4 +request_headers.x-tt-env: ppe_pure_svg +url: https://www.feishu.cn/slides/MPcnsjAH5l5r2edcpWYcNhFVnVd +``` + +Prepared SVG hashes were recorded in the live receipt: + +```text +04-svg/prepared/page-001.svg: 6d35ebfe7ec500472a9fd5ab69af230d1a73aea40b2f5f6bc6f02f5a58a0900f +04-svg/prepared/page-002.svg: 9c623b3138cab6f0dcf68fb3cdfc68e9747f11ef5a8ba9f048a7a2360524e799 +04-svg/prepared/page-003.svg: 13d7fc19310ac89a699f1fdf85597628b1a8428aa55468262c9a920644bf4e24 +``` + +## Readback Receipt + +Receipt: + +```text +.tmp/svglide-p0c-gate7-live6/08-readback/readback-check.json +``` + +Key evidence: + +```text +status: passed +xml_presentation_id: MPcnsjAH5l5r2edcpWYcNhFVnVd +expected_slide_count: 3 +created_slide_count: 3 +page_count: passed, expected 3, actual 3 +slide_ids: passed, actual 3 +slide_order: passed, expected ["pbb", "pbu", "pbe"], actual ["pbb", "pbu", "pbe"] +blank_page: passed +text_fit: passed +bounds: passed +core_visible_text: passed, expected 22, missing [] +failed_checks: [] +``` + +Raw readback content was saved at: + +```text +.tmp/svglide-p0c-gate7-live6/08-readback/xml-presentations-get.json +``` + +## Validation Passed + +```bash +python3 -m unittest skills/lark-slides/scripts/svglide_ppe_proof_test.py +``` + +Result: + +```text +Ran 4 tests +OK +``` + +```bash +python3 -m unittest skills/lark-slides/scripts/svglide_readback_test.py +``` + +Result: + +```text +Ran 9 tests +OK +``` + +```bash +python3 -m unittest skills/lark-slides/scripts/svglide_project_runner_test.py +``` + +Result: + +```text +Ran 36 tests +OK +``` + +```bash +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +Ran 259 tests +OK +``` + +```bash +env GOCACHE=/private/tmp/svglide-gocache go test ./shortcuts/common ./shortcuts/slides +``` + +Result: + +```text +ok github.com/larksuite/cli/shortcuts/common +ok github.com/larksuite/cli/shortcuts/slides +``` + +## Reviewer Decision Needed + +Reviewer verdict: + +```text +Verdict: PASS +Blocking issues: None. +``` + +Gate 7 is accepted because the reviewer confirmed: + +```text +1. The PPE lane proof is explicit enough and hash-bound. +2. The fresh P0c run used real lark-cli/go run, not fake dry-run. +3. live_create created 3 pages. +4. readback proves page count, page order, nonblank pages, and core CanvasSpec visible text. +5. The earlier blocker is resolved by the documented Whistle/PPE lane setup. +``` diff --git a/skills/lark-slides/references/svglide-artboard-gate8-evidence.md b/skills/lark-slides/references/svglide-artboard-gate8-evidence.md new file mode 100644 index 00000000..082d7fd7 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate8-evidence.md @@ -0,0 +1,1159 @@ +# SVGlide Artboard Gate 8 Evidence + +Gate: `Gate 8: Special Cases And Fallback Coverage` + +Status: `PASS` + +Date: 2026-06-21 + +## Scope + +Gate 8 covers special cases that can break the renderer/compiler boundary: + +```text +unsupported Satori feature fail-fast +svglide-chart-spec-v1 chart marker +image asset binding and readback +local raster fallback island +``` + +## Implemented Local Evidence + +New fixtures: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/gate8_special_cases/unsupported-filter.canvas-spec.json +skills/lark-slides/scripts/fixtures/svglide_artboard/gate8_special_cases/chart-spec.json +``` + +New evidence runner: + +```text +skills/lark-slides/scripts/svglide_gate8_special_cases.py +skills/lark-slides/scripts/svglide_gate8_special_cases_test.py +``` + +Readback was strengthened to check source image nodes: + +```text +skills/lark-slides/scripts/svglide_readback.py +skills/lark-slides/scripts/svglide_readback_test.py +``` + +Readback was also strengthened to reuse PPE request headers from live-create: + +```text +cmd/api/api.go +cmd/api/api_test.go +skills/lark-slides/scripts/svglide_readback.py +skills/lark-slides/scripts/svglide_readback_test.py +``` + +Behavior: + +```text +live-create request_headers.x-tt-env=ppe_pure_svg + -> svglide_readback.py calls: + lark-cli api GET /open-apis/slides_ai/v1/xml_presentations/{id} + --request-header x-tt-env=ppe_pure_svg + +no live-create request_headers + -> svglide_readback.py keeps the legacy: + lark-cli slides xml_presentations get --params ... +``` + +Local evidence command: + +```bash +python3 skills/lark-slides/scripts/svglide_gate8_special_cases.py \ + .tmp/svglide-gate8-special-cases-r4 \ + --pretty +``` + +Local evidence receipt: + +```text +.tmp/svglide-gate8-special-cases-r4/gate8-special-cases.json +``` + +Result: + +```text +status: passed +case_count: 4 +passed_count: 4 +failed_count: 0 +``` + +Local cases: + +```text +unsupported_feature_fail_fast: + render_failed_before_live: true + bridge_failed_before_live: true + +chart_marker_svglide_chart_spec_v1: + preflight error_count: 0 + chart_verify_status: passed + local readback chart_markers: passed + +image_asset_binding_readback: + injection used_count: 1 + prepare asset ref status: mapped + local readback asset_tokens: passed + local readback image_assets: passed + +local_raster_fallback_island: + preflight error_count: 0 + raster_fallback status: passed + fallback bbox: 128 x 128 + file_exists: true +``` + +## Real Live Evidence + +### PPE Deployment Used For Fresh Readback + +Slide branch: + +```text +worktree: /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/slide-svglide-chart-direct-snapshot +branch: feat/svglide-chart-direct-snapshot +commit: 8f682ab082f7d86ade966eb2ffc5849827b17dc5 +message: fix: guard svglide chart snapshot options +``` + +Validation before deploy: + +```text +focused Jest: + env API=true GULUX_ENV=test ROUTE_IP=common-consul-boe.bytedance.net \ + pnpm --dir .../apps/server exec jest --config jest.config.js \ + test/svg-parser/svglide-chart-spec-export.test.ts --runInBand --silent + result: PASS, 6 tests passed + +strict single-file TypeScript: + pnpm --dir .../apps/server exec tsc --noEmit --strict --skipLibCheck \ + --target ES2020 --module commonjs service/svglide-chart-spec.ts + result: PASS + +diff check: + git diff --check + result: PASS +``` + +PPE lane deploy: + +```text +command: + bytedcli --json tce deploy-lane \ + --env ppe_pure_svg \ + --standard-env online_cn \ + --psm creation.slide.nodeserver_pre_release \ + --flow-base prod \ + --branch feat/svglide-chart-direct-snapshot \ + --action upgrade \ + --hot-deploy + +ENV ticket: 2068537756495360000 +ENV status: success +ENV log_id: 20260621113326B21024A54E2E8A51E9BA +SCM version: 1.0.0.1184 +SCM base commit: 8f682ab082f7d86ade966eb2ffc5849827b17dc5 +TCE deployment: 362509781 +TCE service id: 208677037 +TCE service status: running +image_version: 1.0.0.480 +main repo after deploy: ee/slide/server@1.0.0.1184 +dependency conf after deploy: ee/apacana/conf@1.0.9.148 +``` + +Deploy notes: + +```text +The first corrected deploy ticket 2068535054574407680 built SCM version +1.0.0.1183 and failed during SCM compile because `viewModel` could be null in +`svglide-chart-spec.ts`. + +The follow-up fix was committed as 8f682ab082f7d86ade966eb2ffc5849827b17dc5. +The second deploy built SCM version 1.0.0.1184 successfully. Hot deploy fell +back to a normal TCE deploy because the lane also had an ee/conf version diff: +1.0.8.9733 -> 1.0.9.148. +``` + +### Combined Live Probe + +Project: + +```text +.tmp/svglide-gate8-live +``` + +Scope: + +```text +page 1: svglide-chart-spec-v1 chart marker +page 2: uploaded image asset +page 3: local raster fallback image island +``` + +Live receipt: + +```text +.tmp/svglide-gate8-live/07-create/live-create.json +``` + +Result: + +```text +status: passed +xml_presentation_id: J35tspvJgltBnsdJpL7chnv6n2f +slides_added: 3 +slide_ids: ["pdd", "pdu", "pdR"] +images_uploaded: 2 +request_headers.x-tt-env: ppe_pure_svg +url: https://www.feishu.cn/slides/J35tspvJgltBnsdJpL7chnv6n2f +``` + +Readback receipt: + +```text +.tmp/svglide-gate8-live/08-readback/readback-check.json +``` + +Historical pre-deploy result: + +```text +status: failed +failed_checks: ["readback_command"] +error.code: 5090000 +error.message: nodeServer internal error +log_id: 20260621042348E90BA595E19D631C3BD0 +``` + +Fresh header-aware retry: + +```text +command: + env SVGLIDE_LARK_CLI_CMD='env GOCACHE=/private/tmp/svglide-gocache HTTP_PROXY=http://127.0.0.1:8899 HTTPS_PROXY=http://127.0.0.1:8899 http_proxy=http://127.0.0.1:8899 https_proxy=http://127.0.0.1:8899 go run .' \ + python3 skills/lark-slides/scripts/svglide_readback.py \ + .tmp/svglide-gate8-live \ + --pretty + +raw command inside receipt: + env ... go run . api GET /open-apis/slides_ai/v1/xml_presentations/J35tspvJgltBnsdJpL7chnv6n2f + --as user + --request-header x-tt-env=ppe_pure_svg + +request_headers: + x-tt-env: ppe_pure_svg + +status: failed +failed_checks: ["readback_command"] +error.code: 5090000 +error.message: nodeServer internal error +log_id: 202606210441215C24A4F7B9A4D30D0AB4 +revision_id in input_binding: 4 +``` + +Fresh post-deploy readback result: + +```text +command: + env SVGLIDE_LARK_CLI_CMD='env GOCACHE=/private/tmp/svglide-gocache HTTP_PROXY=http://127.0.0.1:8899 HTTPS_PROXY=http://127.0.0.1:8899 http_proxy=http://127.0.0.1:8899 https_proxy=http://127.0.0.1:8899 go run .' \ + python3 skills/lark-slides/scripts/svglide_readback.py \ + .tmp/svglide-gate8-live \ + --pretty + +status: passed +xml_presentation_id: J35tspvJgltBnsdJpL7chnv6n2f +revision_id in input_binding: 4 +expected_slide_count: 3 +created_slide_count: 3 +page_count: passed, expected 3, actual 3 +slide_order: passed, ["pdd", "pdu", "pdR"] +blank_page: passed +text_fit: passed +bounds: passed +chart_markers: passed, expected 1 +image_assets: passed, expected 2 +core_visible_text: passed, expected 3, missing [] +failed_checks: [] +``` + +### Chart-Only Isolation + +Project: + +```text +.tmp/svglide-gate8-live-chart +``` + +Live receipt: + +```text +.tmp/svglide-gate8-live-chart/07-create/live-create.json +``` + +Result: + +```text +status: passed +xml_presentation_id: C5fxszdjrlftMedvShmcOWtinqe +slides_added: 1 +slide_ids: ["pvv"] +request_headers.x-tt-env: ppe_pure_svg +url: https://www.feishu.cn/slides/C5fxszdjrlftMedvShmcOWtinqe +``` + +Readback receipt: + +```text +.tmp/svglide-gate8-live-chart/08-readback/readback-check.json +``` + +Historical pre-deploy result: + +```text +status: failed +failed_checks: ["readback_command"] +error.code: 5090000 +error.message: nodeServer internal error +log_id: 202606210425412E6648986B9EBE0CC571 +``` + +Fresh header-aware retry: + +```text +command: + env SVGLIDE_LARK_CLI_CMD='env GOCACHE=/private/tmp/svglide-gocache HTTP_PROXY=http://127.0.0.1:8899 HTTPS_PROXY=http://127.0.0.1:8899 http_proxy=http://127.0.0.1:8899 https_proxy=http://127.0.0.1:8899 go run .' \ + python3 skills/lark-slides/scripts/svglide_readback.py \ + .tmp/svglide-gate8-live-chart \ + --pretty + +raw command inside receipt: + env ... go run . api GET /open-apis/slides_ai/v1/xml_presentations/C5fxszdjrlftMedvShmcOWtinqe + --as user + --request-header x-tt-env=ppe_pure_svg + +request_headers: + x-tt-env: ppe_pure_svg + +status: failed +failed_checks: ["readback_command"] +error.code: 5090000 +error.message: nodeServer internal error +log_id: 2026062104391986A0240903F18411513C +revision_id in input_binding: 2 +``` + +Fresh post-deploy readback result: + +```text +command: + env SVGLIDE_LARK_CLI_CMD='env GOCACHE=/private/tmp/svglide-gocache HTTP_PROXY=http://127.0.0.1:8899 HTTPS_PROXY=http://127.0.0.1:8899 http_proxy=http://127.0.0.1:8899 https_proxy=http://127.0.0.1:8899 go run .' \ + python3 skills/lark-slides/scripts/svglide_readback.py \ + .tmp/svglide-gate8-live-chart \ + --pretty + +status: passed +xml_presentation_id: C5fxszdjrlftMedvShmcOWtinqe +revision_id in input_binding: 2 +expected_slide_count: 1 +created_slide_count: 1 +page_count: passed, expected 1, actual 1 +slide_order: passed, ["pvv"] +blank_page: passed +text_fit: passed +bounds: passed +chart_markers: passed, expected 1 +core_visible_text: passed, expected 1, missing [] +failed_checks: [] +``` + +### Image-Only Isolation + +Project: + +```text +.tmp/svglide-gate8-live-image +``` + +Live receipt: + +```text +.tmp/svglide-gate8-live-image/07-create/live-create.json +``` + +Readback receipt: + +```text +.tmp/svglide-gate8-live-image/08-readback/readback-check.json +``` + +Result: + +```text +live_create: passed +xml_presentation_id: JAtBshUhElvhUxdb3RFc18Nknye +slides_added: 1 +images_uploaded: 1 +readback: passed after header-aware raw API retry +page_count: passed +slide_order: passed +image_assets: passed, expected 1 +core_visible_text: passed +revision_id in input_binding: 2 +``` + +### Raster-Fallback-Only Isolation + +Project: + +```text +.tmp/svglide-gate8-live-raster +``` + +Live receipt: + +```text +.tmp/svglide-gate8-live-raster/07-create/live-create.json +``` + +Readback receipt: + +```text +.tmp/svglide-gate8-live-raster/08-readback/readback-check.json +``` + +Result: + +```text +live_create: passed +xml_presentation_id: Px22s6J7VlBWlcd9AXRcI0emnmh +slides_added: 1 +images_uploaded: 1 +readback: passed after header-aware raw API retry +page_count: passed +slide_order: passed +image_assets: passed, expected 1 +core_visible_text: passed +revision_id in input_binding: 2 +``` + +## Validation Passed + +```bash +python3 -m unittest skills/lark-slides/scripts/svglide_readback_test.py +``` + +Result: + +```text +Ran 13 tests +OK +``` + +```bash +python3 -m unittest skills/lark-slides/scripts/svglide_gate8_special_cases_test.py +``` + +Result: + +```text +Ran 1 test +OK +``` + +```bash +python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +Ran 264 tests +OK +``` + +```bash +env GOCACHE=/private/tmp/svglide-gocache go test ./shortcuts/common ./shortcuts/slides ./cmd/api +``` + +Result: + +```text +ok github.com/larksuite/cli/shortcuts/common +ok github.com/larksuite/cli/shortcuts/slides +ok github.com/larksuite/cli/cmd/api +``` + +## Current Gate 8 State + +Gate 8 has reviewer PASS after the PPE deployment and fresh readback reruns. + +Current closure facts: + +```text +local special-case runner: passed, 4/4 +image-only live/readback: passed +raster-fallback-only live/readback: passed +chart-only live/readback after ppe_pure_svg deploy: passed +combined chart + image + raster live/readback after ppe_pure_svg deploy: passed +deployed lane main repo: ee/slide/server@1.0.0.1184 +``` + +Gate 9 can start next. This does not mean the full PLAN is complete. + +Next required action: + +```text +Move execution cursor to Gate 9. +Do not claim full-plan completion until Gates 9-12 also pass review. +``` + +## Historical Reviewer Verdict Before Service Deployment + +Reviewer status: + +```text +BLOCKED +``` + +Blocking issues: + +```text +1. Gate 8 and PLAN.md require svglide-chart-spec-v1 chart marker readback. + Chart-only live_create succeeded, but readback failed at `readback_command` + because `xml_presentations.get` returned 5090000 nodeServer internal error. + +2. Combined chart + image + raster live_create also succeeded, but combined + readback failed with the same 5090000 error. Therefore combined Gate 8 + live/readback evidence is incomplete. + +3. At review time, no already-implemented repo-local readback fix was found. + The readback implementation only used `lark-cli slides xml_presentations get`; + that path passed image-only and raster-only decks, but failed chart-only. + +4. Post-review, a header-aware raw API readback path was added and tested. It + still fails chart-only with 5090000 while image-only and raster-only pass. +``` + +Non-blocking risks: + +```text +1. Image-only live readback proves an image asset exists. Exact asset token + binding is still mainly covered by the local special-case fake readback. + +2. Real readback receipt currently records numeric revision ids as null in + `input_binding.revision_id`, which weakens traceability but is not the + active Gate 8 blocker. +``` + +Reviewer-required next action: + +```text +At that time, keep Gate 8 BLOCKED. Escalate or fix chart-marker readback for deck +`C5fxszdjrlftMedvShmcOWtinqe` with log_id +`202606210425412E6648986B9EBE0CC571`, or implement a supported chart-specific +readback path. Then rerun chart-only and combined Gate 8 live/readback before +proceeding. +``` + +## Historical Reviewer Re-Review After Header-Aware Readback + +Reviewer status: + +```text +BLOCKED +``` + +Blocking issues: + +```text +1. Header-aware readback did not unblock Gate 8. Chart-only live_create passed, + but `.tmp/svglide-gate8-live-chart/08-readback/readback-check.json` still + fails `readback_command` with 5090000, log_id + `2026062104391986A0240903F18411513C`. + +2. Combined chart + image + raster live_create passed, but + `.tmp/svglide-gate8-live/08-readback/readback-check.json` still fails with + 5090000, log_id `202606210441215C24A4F7B9A4D30D0AB4`. + +3. Gate 8 / PLAN.md still require svglide-chart-spec-v1 chart marker readback. + Image-only and raster-only pass through the same header-aware raw API path, + so the remaining blocker is native chart marker readback / service behavior, + not missing PPE header propagation. +``` + +Reviewer-required next action: + +```text +At that time, keep Gate 8 BLOCKED. Escalate or fix readback for presentations containing +`svglide-chart-spec-v1`, or add a supported chart-specific readback path, then +rerun chart-only and combined Gate 8 live/readback before proceeding to Gate 9. +``` + +## Service-Side Candidate Fix Investigation + +Date: 2026-06-21 + +Slide worktree: + +```text +/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/slide-svglide-chart-direct-snapshot +branch: feat/svglide-chart-direct-snapshot +``` + +Read code path: + +```text +GetSXSDXml / GetSlidesSXSDXml +-> tasks/get-xml.ts builds SXSD from current blocks +-> chartService.handleXMLData(...) +-> chartService.writeChartData(...) +-> chartService.extractChartXML(...) +-> sheetSupportBlockService.GetChartLatestSnapshot(...) +-> sheetSupportBlockService.GetChartData(...) +-> ThreadTaskName.Chart2XML +``` + +Finding: + +```text +SVGlide chart markers are created successfully as ChartEmbedBlock objects, but +readback/export expands chartToken through the generic Chart2XML worker. The +existing tests proved only chart creation and insertion; they did not prove that +the custom `svglide-chart-spec/v1` snapshot/staticData shape can be exported +back through `xml_presentations.get`. +``` + +## Actual PPE Readback Lane Discovery + +Date: 2026-06-21 + +Trace command: + +```bash +bytedcli log trace-tree \ + --log-id 2026062104391986A0240903F18411513C \ + --region China-North +``` + +Result: + +```text +Full trace saved to: +/var/folders/gh/pcgh9htj0ynfb6mbvy38rtpc0000gn/T/trace-tree-2026062104391986A0240903F18411513C.json + +sampling_source.psm: creation.slide.nodeserver_pre_release +sampling_source.method: GetSXSDXml +``` + +TCE env discovery: + +```bash +bytedcli --json tce env-cascader \ + --psm creation.slide.nodeserver_pre_release +``` + +Relevant result: + +```text +partition: CN +env: ppe +lane: ppe_pure_svg +rid: 208677037 +``` + +Service detail: + +```bash +bytedcli --json tce service get 208677037 +``` + +Relevant result: + +```text +psm: creation.slide.nodeserver_pre_release +env: ppe_pure_svg +status: running +owner: songtianyi.theo +main repo: ee/slide/server +current main repo version: 1.0.0.1149 +service url: https://cloud.bytedance.net/tce/services/208677037 +last deploy ticket: 360169625 +``` + +Available main repo package check: + +```bash +bytedcli --json tce service repo-info-list --service-id 208677037 +``` + +Relevant result: + +```text +latest listed ee/slide/server package: 1.0.0.1180 +latest songtianyi.theo listed package: 1.0.0.1172 +current ppe_pure_svg deployed package: 1.0.0.1149 +no listed package contains the current local chart readback candidate fix +``` + +Implication: + +```text +The remaining Gate 8 closure requires a real remote state change: + +1. Commit/push the slide-side candidate fix. Done. +2. Build or deploy the fixed branch/package for `ee/slide/server`. +3. Upgrade TCE service `208677037` + (`creation.slide.nodeserver_pre_release`, env/lane `ppe_pure_svg`). +4. Rerun chart-only and combined Gate 8 live/readback. +``` + +Slide-side deployable branch state: + +```text +worktree: /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/slide-svglide-chart-direct-snapshot +branch: feat/svglide-chart-direct-snapshot +commit: 90dbcdf8e779c17ab3617868578d5566b3739dd1 +message: fix: export svglide chart specs for readback +remote ref: origin/feat/svglide-chart-direct-snapshot +status: clean after push +``` + +Remote verification: + +```bash +git -C /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/slide-svglide-chart-direct-snapshot \ + ls-remote origin refs/heads/feat/svglide-chart-direct-snapshot +``` + +Result: + +```text +90dbcdf8e779c17ab3617868578d5566b3739dd1 refs/heads/feat/svglide-chart-direct-snapshot +``` + +PPE service pre-deploy check: + +```bash +bytedcli --json tce service get 208677037 +``` + +Result: + +```text +service: creation.slide.nodeserver_pre_release_cn +env/lane: ppe_pure_svg +status: running +current main repo package: ee/slide/server@1.0.0.1149 +candidate branch head: 90dbcdf8e779c17ab3617868578d5566b3739dd1 +deploy status: not deployed yet +``` + +Deploy command preflight check: + +```bash +bytedcli tce deploy-lane --help --debug +bytedcli env service --help --debug +bytedcli env ticket --help --debug +bytedcli --json tce deployment list --service-id 208677037 --page 1 +``` + +Findings: + +```text +`bytedcli tce deploy-lane` has no dry-run/preflight flag. Its public command +surface performs a real deploy into the target env lane. + +`bytedcli env service` exposes deploy/upgrade commands, but the visible help +does not expose a no-side-effect preview for the required TCE lane upgrade. + +Recent deployment history for service `208677037` shows previous `ppe_pure_svg` +updates were real finished deployments/hot deploys by `songtianyi.theo`, +including: + +- deployment 360169625, 2026-06-15, update, note `[v1.0.0.1172] [Hot Deploy]` +- deployment 359608396, 2026-06-12, update, note `[v1.0.0.1157] [Hot Deploy]` +- deployment 358896617, 2026-06-10, upgrade to `ee/slide/server@1.0.0.1149` +``` + +Historical pre-deploy conclusion: + +```text +There was no confirmed no-op deploy preflight path. At that point, the next +Gate 8 action was a real shared PPE lane change and required explicit execution +approval. +``` + +Historical initial deployment sequence: + +```bash +bytedcli tce deploy-lane \ + --psm creation.slide.nodeserver_pre_release \ + --standard-env ppe \ + --env ppe_pure_svg \ + --branch feat/svglide-chart-direct-snapshot \ + --flow-base prod \ + --action upgrade +``` + +Note: + +```text +This initial sequence was later corrected because `--standard-env ppe` is +invalid for this service. The successful deploy used `--standard-env online_cn` +and is recorded in the PPE deployment section above. +``` + +Remote-state caution: + +```text +The deploy-lane command changes the shared PPE lane `ppe_pure_svg`. It should be +run only as an explicit Gate 8 verification step, then immediately followed by +fresh chart-only and combined readback receipts. +``` + +Candidate fix added in the slide worktree: + +```text +apps/server/service/svglide-chart-spec.ts + - added buildSVGlideChartSpecXML(snapshot, staticData) + - converts controlled svglide-chart-spec/v1 static data into standard SXSD + + +apps/server/service/chart.service.ts + - in extractChartXML static-data branch, tries buildSVGlideChartSpecXML(...) + before falling back to generic ThreadTaskName.Chart2XML + +apps/server/test/svg-parser/create-by-xml-svg-dispatch.test.ts + - keeps the existing SVG dispatch / preprocessing coverage + +apps/server/test/svg-parser/svglide-chart-spec-export.test.ts + - added a pure export test for SVGlide chart payload -> SXSD chart XML + - added a ChartService prepareSVGlideChartSpec test proving chart specs are + created directly through SheetSupportBlock.CreateChart + - added a ChartService extractChartXML test proving Chart2XML worker is not + called for SVGlide chart spec export + - added non-SVGlide static chart coverage to ensure ordinary chart exports + still fall back to generic Chart2XML + - added XML/CSV escaping and snapshot-option coverage for labels, legend, + and stacked settings +``` + +Validation attempted: + +```bash +git -C /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/slide-svglide-chart-direct-snapshot diff --check +``` + +Result: + +```text +passed +``` + +Test attempted: + +```bash +pnpm --dir /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/slide-svglide-chart-direct-snapshot/apps/server test -- test/svg-parser/create-by-xml-svg-dispatch.test.ts +``` + +Result: + +```text +failed before running tests: `jest: command not found` +reason: apps/server has package.json, but node_modules is missing in this worktree +``` + +Dependency bootstrap: + +```text +eden-mono --help +``` + +Result: + +```text +installed @ies/eden-monorepo@3.3.0 and pnpm 8.12.1 into the slide worktree +apps/server/node_modules/.bin/jest now exists +``` + +Targeted chart export Jest: + +```bash +env API=true GULUX_ENV=test ROUTE_IP=common-consul-boe.bytedance.net \ + pnpm --dir /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/slide-svglide-chart-direct-snapshot/apps/server \ + exec jest --config jest.config.js test/svg-parser/svglide-chart-spec-export.test.ts \ + --runInBand --silent +``` + +Result: + +```text +PASS test/svg-parser/svglide-chart-spec-export.test.ts +Tests: 5 passed, 5 total +Test Suites: 1 passed, 1 total +``` + +Existing SVG dispatch integration file after test isolation: + +```bash +env API=true GULUX_ENV=test ROUTE_IP=common-consul-boe.bytedance.net \ + pnpm --dir /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/slide-svglide-chart-direct-snapshot/apps/server \ + exec jest --config jest.config.js test/svg-parser/create-by-xml-svg-dispatch.test.ts \ + --runInBand --silent +``` + +Result: + +```text +FAIL test/svg-parser/create-by-xml-svg-dispatch.test.ts +Tests: 12 failed, 22 passed, 34 total +``` + +Failure character: + +```text +The remaining failures are in the existing SVG dispatch integration environment: +imageBlockService.insertImage / chartBlockService.insertEmbedChart DI shape, +viewBox/cascade/style/shadow/rgba assertions. They are not counted as Gate 8 +closure evidence and do not prove the PPE readback blocker is fixed. +``` + +Historical status before PPE deployment: + +```text +At that time, Gate 8 remained BLOCKED. + +The local service-side candidate fix is now proven by the focused chart export +Jest test, but it is not deployed to the PPE lane and has not been validated by +fresh chart-only/combined live_create/readback receipts. +``` + +Historical next required action before PPE deployment: + +```text +1. Deploy or route the candidate service-side fix to the PPE readback lane. +2. Rerun chart-only Gate 8 live/readback. +3. Rerun combined chart + image + raster Gate 8 live/readback. +4. Ask reviewer subagent for a new Gate 8 verdict. +``` + +## Historical Reviewer Re-Review After Service-Side Candidate Fix + +Reviewer status: + +```text +BLOCKED +``` + +Blocking issues: + +```text +1. Gate 8 still requires real svglide-chart-spec-v1 chart marker readback. + There is no fresh PPE/live/readback receipt after the candidate slide-side + fix. + +2. The latest chart-only receipt still fails: + `.tmp/svglide-gate8-live-chart/08-readback/readback-check.json` + status failed, 5090000, log_id `2026062104391986A0240903F18411513C`. + +3. The latest combined chart + image + raster receipt still fails: + `.tmp/svglide-gate8-live/08-readback/readback-check.json` + status failed, 5090000, log_id `202606210441215C24A4F7B9A4D30D0AB4`. + +4. The focused chart export Jest now passes locally, but no fresh PPE/readback + receipt proves that the candidate fix is active in the readback lane. +``` + +Reviewer evidence checked: + +```text +/Users/bytedance/Downloads/PLAN.md +skills/lark-slides/references/svglide-artboard-full-plan-action.md +skills/lark-slides/references/svglide-artboard-gate8-evidence.md +slide apps/server/service/svglide-chart-spec.ts +slide apps/server/service/chart.service.ts +slide apps/server/test/svg-parser/create-by-xml-svg-dispatch.test.ts +git status / git diff --stat / git diff --check +Gate 8 readback receipts +``` + +Reviewer-required next action: + +```text +1. Deploy or route the slide-side fix into the PPE readback lane. +2. Rerun chart-only and combined Gate 8 live/readback. +3. Request review again. +``` + +## Historical Reviewer Re-Review After Focused Jest And Lane Discovery + +Reviewer status: + +```text +BLOCKED +``` + +Blocking issues: + +```text +1. Gate 8 still requires real svglide-chart-spec-v1 chart marker readback. + There is no fresh chart-only or combined live/readback receipt after the + slide-side candidate fix is deployed or routed to the PPE readback lane. + +2. The latest chart-only receipt is still the old failure: + `.tmp/svglide-gate8-live-chart/08-readback/readback-check.json` + status failed, 5090000, log_id `2026062104391986A0240903F18411513C`. + +3. The latest combined chart + image + raster receipt is still the old failure: + `.tmp/svglide-gate8-live/08-readback/readback-check.json` + status failed, 5090000, log_id `202606210441215C24A4F7B9A4D30D0AB4`. + +4. Focused Jest proves only the local `buildSVGlideChartSpecXML` / + `ChartService.extractChartXML` candidate path. It does not replace Gate 8 + PPE live/readback evidence. +``` + +Non-blocking risks: + +```text +1. `create-by-xml-svg-dispatch.test.ts` still has 12 failures, currently + classified as existing SVG dispatch integration environment / DI / style + assertion failures, not the active Gate 8 closure blocker. + +2. Image-only and raster-only readback pass, so the active risk remains native + chart marker readback/export. +``` + +Reviewer-required next action: + +```text +Deploy or route the slide-side fix to service `208677037` +(`creation.slide.nodeserver_pre_release`, CN/ppe/ppe_pure_svg), rerun +chart-only and combined Gate 8 live/readback, then request PASS review again. +``` + +## Historical Reviewer Re-Review After Commit And Push + +Reviewer status: + +```text +BLOCKED +``` + +Blocking issues: + +```text +1. Gate 8 boundary is clear: the slide-side fix is committed and pushed to + `90dbcdf8e779c17ab3617868578d5566b3739dd1`, and focused Jest passes, but + the PPE lane has not deployed that fix. + +2. TCE service `208677037` / `creation.slide.nodeserver_pre_release` / + `ppe_pure_svg` still runs `ee/slide/server@1.0.0.1149`; deploy status is + explicitly `not deployed yet`. + +3. There is no post-deploy fresh chart-only or combined live/readback receipt. + Existing chart-only and combined receipts remain the old 5090000 failures. +``` + +Reviewer-required next action: + +```text +Deploy or route commit `90dbcdf8e779c17ab3617868578d5566b3739dd1` to TCE +service `208677037` on lane `ppe_pure_svg`, rerun chart-only and combined Gate +8 live/readback, then request PASS review only after fresh receipts prove chart +marker readback passes. +``` + +## Executor Re-Review Request After PPE Deployment + +Reviewer status: + +```text +PASS +``` + +Executor evidence ready for review: + +```text +1. Gate 8 local runner still has passing special-case evidence: + `.tmp/svglide-gate8-special-cases-r4/gate8-special-cases.json` + status passed, 4/4 cases. + +2. The slide-side fix is deployed to the actual readback lane: + ENV ticket `2068537756495360000` status success. + TCE deployment `362509781` status finished. + TCE service `208677037` status running. + Deployed main repo `ee/slide/server@1.0.0.1184`. + +3. Fresh chart-only readback now passes: + `.tmp/svglide-gate8-live-chart/08-readback/readback-check.json` + deck `C5fxszdjrlftMedvShmcOWtinqe`, slide `pvv`, + `chart_markers` passed, `failed_checks: []`. + +4. Fresh combined readback now passes: + `.tmp/svglide-gate8-live/08-readback/readback-check.json` + deck `J35tspvJgltBnsdJpL7chnv6n2f`, slides `pdd`, `pdu`, `pdR`, + `chart_markers` passed, `image_assets` passed with expected 2, + `failed_checks: []`. +``` + +Reviewer-required action: + +```text +Inspect the updated evidence, receipts, deployment facts, changed files, and +validation commands. Return PASS only if Gate 8 special-case and fallback +coverage is complete under PLAN.md; otherwise return BLOCKED with the exact +missing evidence. +``` + +## Reviewer Re-Review After PPE Deployment + +Reviewer status: + +```text +PASS +``` + +Blocking issues: + +```text +None for Gate 8. The prior chart-marker readback blocker is cleared by fresh +successful chart-only and combined readback receipts. +``` + +Non-blocking risks: + +```text +1. This PASS applies only to Gate 8. It does not mean the full PLAN is complete; + Gates 9-12 are still pending. + +2. `create-by-xml-svg-dispatch.test.ts` still has unrelated historical failures + per evidence, but focused chart export Jest and strict single-file + TypeScript checks pass. +``` + +Evidence checked: + +```text +skills/lark-slides/references/svglide-artboard-full-plan-action.md +skills/lark-slides/references/svglide-artboard-gate8-evidence.md +.tmp/svglide-gate8-special-cases-r4/gate8-special-cases.json +.tmp/svglide-gate8-live-chart/08-readback/readback-check.json +.tmp/svglide-gate8-live/08-readback/readback-check.json +raw readback receipts with PPE header-aware API command +focused Jest: svglide-chart-spec-export.test.ts passed 6/6 +strict single-file TypeScript check passed +slide worktree HEAD: 8f682ab082f7d86ade966eb2ffc5849827b17dc5 +``` + +Next required action: + +```text +Mark Gate 8 as DONE/PASS in the action plan, then move the execution cursor to +Gate 9. Do not claim full-plan completion until Gates 9-12 pass. +``` diff --git a/skills/lark-slides/references/svglide-artboard-gate9-evidence.md b/skills/lark-slides/references/svglide-artboard-gate9-evidence.md new file mode 100644 index 00000000..9501bf40 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-gate9-evidence.md @@ -0,0 +1,422 @@ +# SVGlide Artboard Gate 9 Evidence + +Gate: `Gate 9: P1 Asset System Scale-Out` + +Status: `PASS` + +Date: 2026-06-21 + +Reviewer history: + +```text +Initial review: BLOCKED +Blocker: source intake lacked required Gate 9 fields and per-source conversion traceability. +Fix: source intake now includes required fields and conversion_records[] for every source family; tests now assert these fields. +Second review: BLOCKED +Blocker: ppt-master provenance incorrectly said no local LICENSE found. +Fix: ppt-master provenance now records local MIT LICENSE, and tests assert that it does not regress. +Current state: narrow re-review requested after fresh validation. +Final reviewer verdict: PASS. +``` + +## Scope + +Gate 9 moves the P0 artboard path from a minimal 3-template vertical slice to a reusable P1 visual asset system: + +```text +external examples as inspiration only +-> source intake inventory +-> layout archetypes +-> component variants +-> template registry +-> theme registry +-> golden CanvasSpec fixtures +-> Satori/resvg render regression +``` + +No external HTML/CSS/SVG library is used as a runtime renderer. + +## Source Intake Inventory + +Implemented: + +```text +skills/lark-slides/references/svglide-p1-source-intake.json +``` + +Reviewer blocker fix: + +```text +The intake file now records the required Gate 9 fields for every source family: + +source_path +source_type +extract_fields +conversion_target +acceptance_rule +forbidden_usage +source_hash_or_version +license_or_provenance +``` + +Covered source families: + +```text +open-design html-ppt examples +open-design retro quarterly review +open-design zhangzara design templates +ppt-master examples +PosterGen poster-generation rules +``` + +Policy recorded in the inventory: + +```text +runtime_import: forbidden +usage: inspiration source only +conversion_path: source example -> visual archetype -> CanvasSpec schema fields -> Template Registry -> Theme Token +``` + +Verified source versions/provenance: + +```text +open-design: git 2aadac07c, origin https://github.com/nexu-io/open-design.git, Apache-2.0 +ppt-master: git 45d9a79, origin https://github.com/hugohe3/ppt-master.git, MIT, inspiration-only +PosterGen: git 8a54325, origin https://github.com/Y-Research-SBU/PosterGen.git, MIT +``` + +Per-source conversion traceability is recorded in `conversion_records[]`. +Each record links: + +```text +source_examples +-> abstraction_record +-> canvas_spec_fields +-> registry_output.templates/themes/components/layouts/golden_fixtures +-> acceptance_rule +``` + +Examples: + +```text +open-design html-ppt-weekly-report +-> dense report pages +-> agenda-list / metric-dashboard +-> agenda-list.canvas-spec.json / metric-dashboard.canvas-spec.json + +open-design zhangzara editorial/cartesian examples +-> editorial tri-tone and grid discipline +-> section-title / image-feature / roadmap-lanes / architecture-blueprint +-> editorial-tritone / swiss-red / blueprint-technical + +ppt-master attention/kubernetes/swiss/glass/newspaper examples +-> technical narrative, blueprint, comparison, data, quote abstractions +-> process-flow / architecture-blueprint / comparison-cards / data-story / quote-focus + +PosterGen config and prompt rules +-> research poster three-column layout and bounded section rules +-> research-poster / paper-research / PosterSection +``` + +## P1 Asset Counts + +Implemented assets: + +```text +Canvas Templates: 15 active templates +Theme Tokens: 10 active themes +Component Variants: 23 active variants +Layout Archetypes: 10 active archetypes +Golden CanvasSpec fixtures: 15, one per active template +``` + +Template registry: + +```text +skills/lark-slides/references/svglide-template-registry.json +``` + +Active template IDs: + +```text +cover-hero +comparison-cards +summary-final +section-title +agenda-list +timeline-steps +process-flow +metric-dashboard +quote-focus +image-feature +research-poster +data-story +risk-alert +roadmap-lanes +architecture-blueprint +``` + +Each template declares: + +```text +renderer_id +layout_family +required_content +optional_content +max_items +text_budget +supported_theme_ids +``` + +Theme registry: + +```text +skills/lark-slides/scripts/artboard_renderer/themes/registry.json +``` + +Active theme IDs: + +```text +dark-clarity +forest-signal +warm-editorial +blueprint-technical +editorial-tritone +cobalt-grid +finance-dark +swiss-red +glass-neon +paper-research +``` + +Layout archetype registry: + +```text +skills/lark-slides/references/svglide-layout-archetypes.json +``` + +Component registry: + +```text +skills/lark-slides/references/svglide-component-registry.json +``` + +## Renderer Changes + +Node/Satori renderer: + +```text +skills/lark-slides/scripts/artboard_renderer/templates/p0-templates.mjs +skills/lark-slides/scripts/artboard_renderer/dist/render.mjs +``` + +The renderer now supports all 15 active templates. + +Python fallback and admission control: + +```text +skills/lark-slides/scripts/svglide_artboard_renderer.py +``` + +Changes: + +```text +SUPPORTED_TEMPLATES expanded from 3 to 15 +unsupported template message no longer says P0-only +generic P1 local fallback added for Node/Satori-disabled execution +``` + +## Golden Fixtures + +Golden fixture directory: + +```text +skills/lark-slides/scripts/fixtures/svglide_artboard/golden/ +``` + +New P1 fixtures: + +```text +section-title.canvas-spec.json +agenda-list.canvas-spec.json +timeline-steps.canvas-spec.json +process-flow.canvas-spec.json +metric-dashboard.canvas-spec.json +quote-focus.canvas-spec.json +image-feature.canvas-spec.json +research-poster.canvas-spec.json +data-story.canvas-spec.json +risk-alert.canvas-spec.json +roadmap-lanes.canvas-spec.json +architecture-blueprint.canvas-spec.json +``` + +Existing P0 fixtures retained: + +```text +cover-hero.canvas-spec.json +comparison-cards.canvas-spec.json +summary-final.canvas-spec.json +``` + +Every active template has one golden CanvasSpec fixture. Each fixture includes: + +```text +version +canvas +safe_area +template_id +theme_id +theme +content +semantic_elements +quality_constraints +``` + +## Tests Added Or Updated + +Updated: + +```text +skills/lark-slides/scripts/svglide_artboard_renderer_test.py +``` + +Coverage: + +```text +active theme count >= 10 +required P1 themes registered +active template count >= 15 +required P1 templates registered +all active templates allow all active themes +component registry active variants >= 20 +layout archetypes >= 8 +source intake runtime_import == forbidden +source intake required fields are present for every source +source intake conversion_records link examples to registry outputs and golden fixtures +every active template has a valid golden CanvasSpec +all active golden fixtures render through Satori/resvg into PNG and SVGlide SVG +bounded max_workers remains 4 for 15-page render +``` + +## Validation Commands + +JSON parse: + +```bash +python3 -c 'import json, pathlib; paths=list(pathlib.Path("skills/lark-slides/references").glob("svglide-*.json"))+list(pathlib.Path("skills/lark-slides/scripts/artboard_renderer/themes").glob("*.json"))+list(pathlib.Path("skills/lark-slides/scripts/fixtures/svglide_artboard/golden").glob("*.json")); [json.load(p.open(encoding="utf-8")) for p in paths]; print(len(paths))' +``` + +Result: + +```text +54 JSON files parsed +``` + +Node template syntax: + +```bash +node --check skills/lark-slides/scripts/artboard_renderer/templates/p0-templates.mjs +``` + +Result: + +```text +PASS +``` + +Python syntax: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m py_compile \ + skills/lark-slides/scripts/svglide_artboard_renderer.py \ + skills/lark-slides/scripts/svglide_artboard_renderer_test.py +``` + +Result: + +```text +PASS +``` + +Renderer build: + +```bash +pnpm --dir skills/lark-slides/scripts/artboard_renderer run build +``` + +Result: + +```text +PASS +dist/render.mjs 925.5kb +``` + +Renderer runtime checks: + +```bash +node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +``` + +Result: + +```text +PASS +satori_version: 0.26.0 +resvg_version: 2.6.2 +font_path: /System/Library/Fonts/Supplemental/Arial Unicode.ttf +``` + +Focused artboard tests: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest skills/lark-slides/scripts/svglide_artboard_renderer_test.py +``` + +Result: + +```text +Ran 15 tests in 5.301s after provenance blocker fix. +OK +``` + +Scripts regression: + +```bash +env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \ + python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py' +``` + +Result: + +```text +Ran 265 tests in 15.270s after provenance blocker fix. +OK +``` + +Diff check: + +```bash +git diff --check +``` + +Result: + +```text +PASS +``` + +## Known Remaining Scope + +Gate 9 does not complete: + +```text +Gate 10 prompt/planning layer +Gate 11 packaging/distribution decision +Gate 12 final full-plan acceptance +``` + +Gate 9 reviewer verdict is `PASS`. diff --git a/skills/lark-slides/references/svglide-artboard-p0-goal-lock.md b/skills/lark-slides/references/svglide-artboard-p0-goal-lock.md new file mode 100644 index 00000000..8a1534f3 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-p0-goal-lock.md @@ -0,0 +1,278 @@ +# SVGlide Artboard/Satori P0 Goal Lock + +## 1. Current Objective + +当前唯一目标是按 `/Users/bytedance/Downloads/PLAN.md` 跑通一个可重复的 `artboard_satori` 竖切,而不是继续做新的 `direct_svg` 视觉 demo。 + +目标链路: + +```text +3 页 artboard_satori fixture +-> 命中 3 个 Template Registry 模板 +-> Satori 渲染 +-> resvg 输出 PNG/contact sheet +-> compiler 输出 SVGlide protocol SVG +-> quality_gate + dry_run 通过 +``` + +## 2. Source Of Truth + +实现和审查必须优先对齐以下文件: + +```text +/Users/bytedance/Downloads/PLAN.md +skills/lark-slides/references/svglide-artboard-satori.contract.md +skills/lark-slides/references/svglide-canvas-spec.schema.json +skills/lark-slides/references/svglide-renderer-registry.json +skills/lark-slides/scripts/svglide_project_runner.py +skills/lark-slides/scripts/svglide_artboard_renderer.py +skills/lark-slides/scripts/artboard_renderer/package.json +``` + +如果本文件与 `PLAN.md` 冲突,以 `PLAN.md` 为主;如果 `PLAN.md` 表述仍不够工程化,先补本文件或相关 contract,再进入实现。 + +## 3. Hard No-Go Rules + +接下来不允许用以下方式制造“看起来完成”: + +```text +不再用 direct_svg 做新的主验证 +不再手写 .tmp/.../generate_svg.py 作为主路径 +不再用 QuickLook / qlmanage 作为主截图路径 +不先放松 production 门禁来制造通过 +不降低或删除任何既有 production gate;artboard 只能增加检查 +不新增无法被 Template Registry / renderer registry 追踪的模板或 renderer +不手工 patch state.json 作为常规 runner 操作 +不把 Satori 输出 SVG 当作 SVGlide 语义真源 +不把外部 HTML/CSS 库直接接入运行时 +``` + +允许临时诊断,但诊断结果不能作为 P0 验收证据。 + +## 4. Required Vertical Slice + +P0 竖切必须至少包含 3 页 fixture: + +| Page | Required template | Purpose | +| --- | --- | --- | +| 1 | `cover-hero` | 证明封面模板和主题 token 生效 | +| 2 | `comparison-cards` | 证明多内容块模板、文本预算和 template fit 生效 | +| 3 | `summary-final` | 证明收束页模板、可见文本追溯和多页一致性生效 | + +每页必须满足: + +```text +generation_mode = artboard_satori +canvas_spec.version 存在 +canvas_spec.template_id 来自 Template Registry +canvas_spec.theme 或 theme_id 存在 +canvas_spec.content.title 存在 +visible_text / semantic elements 可追溯 +artboard receipt 存在 +``` + +P0 当前只验收到 `quality_gate + dry_run`。`ppe_proof / live_create / readback` 属于 P0c 后续闭环,不是本竖切的通过条件;但本竖切不能做任何会阻碍 P0c 的协议或 receipt 设计。 + +## 5. Dependency Lock + +`skills/lark-slides/scripts/artboard_renderer/package.json` 必须显式声明: + +```text +satori +@resvg/resvg-js +``` + +P0 主路径: + +```text +Satori SVG -> @resvg/resvg-js -> PNG preview / contact sheet +``` + +QuickLook、Chromium、Playwright 只能作为 fallback 或调试工具,不能作为 P0 默认路径。 + +receipt 必须记录: + +```text +node_version +satori_version +resvg_version +font_hashes +template_id +theme_id +input_canvas_spec_hash +output_satori_svg_hash +output_svglide_svg_hash +output_png_hash +``` + +## 6. Template Registry And Theme Lock + +模板和主题不能用“等价来源”模糊带过。P0 必须有明确 registry/theme 证据: + +```text +skills/lark-slides/references/svglide-template-registry.json +skills/lark-slides/scripts/artboard_renderer/templates/*.mjs +skills/lark-slides/scripts/artboard_renderer/themes/*.mjs +``` + +如果 P0 选择项目级 registry,也必须落盘为: + +```text +02-plan/template-registry.json +02-plan/theme-registry.json +``` + +并且必须满足: + +```text +未知 template_id fail-fast +未知 theme_id fail-fast +template_id 必须来自 registry +theme_id 必须来自 registry +template registry hash 写入 template-fit receipt +theme registry hash 写入 artboard-render receipt +generate_svg receipt 汇总 registry/theme hash +quality_gate 校验 registry/theme hash fresh +``` + +禁止临时在 renderer 代码里硬编码一个未登记模板并把它算作“命中模板”。 + +## 7. Acceptance Evidence + +P0 通过时必须能提供以下证据: + +```text +02-plan/slide_plan.json +02-plan/template-registry.json 或 skills/lark-slides/references/svglide-template-registry.json +02-plan/theme-registry.json 或 skills/lark-slides/scripts/artboard_renderer/themes/*.mjs +04-svg/artboard/raw/page-###.satori.svg +04-svg/artboard/page-###.png +04-svg/page-###.svg +05-preview/contact-sheet.png +06-check/canvas-spec-validate.json +06-check/template-fit.json +06-check/quality-gate.json +07-create/dry-run.json +receipts/canvas-spec-validate.json +receipts/template-fit-check.json +receipts/artboard-render.json +receipts/satori-bridge.json +receipts/generate_svg.json +receipts/dry_run.json +``` + +并且报告中必须能证明: + +```text +真的走 artboard_satori +真的命中 3 个模板 +真的使用 @resvg/resvg-js +quality_gate passed +dry_run passed +没有依赖 direct_svg 旁路 +没有依赖 QuickLook 作为主路径 +``` + +## 8. Quality Gate Hard Requirements + +`quality_gate` 通过必须证明以下 hash/freshness 关系,不允许只检查文件存在: + +```text +generate_svg.receipt.generation_mode == artboard_satori +generate_svg.receipt.artboard_receipts 非空且页数匹配 +canvas-spec-validate receipt hash == 当前 slide_plan canvas_spec hash +template-fit receipt hash == 当前 CanvasSpec + Template Registry hash +artboard-render receipt hash == 当前 CanvasSpec + Theme Registry + font inputs hash +satori-bridge receipt hash == 当前 CanvasSpec + semantic-map + node-layout-map + raw Satori SVG hash +prepared SVG hash == satori-bridge 输出 SVGlide SVG hash +PNG/contact-sheet hash == artboard-render 或 preview artifact 记录值 +dry_run 输入 prepared files hash == quality_gate prepared_files hash +``` + +`quality_gate` 还必须保证: + +```text +direct_svg receipt 不能作为 artboard_satori 验收证据 +缺少 resvg_version 时失败 +缺少 template_id/theme_id 时失败 +缺少 template registry/theme registry hash 时失败 +缺少 node-layout-map 或 drift 超阈值时失败 +``` + +任何为了让 P0 通过而删除、降级或绕过既有 production 检查的改动,都视为偏离目标。 + +## 9. Stage Discipline + +每次阶段汇报只回答四件事: + +```text +做了什么 +改了哪些文件 +跑了哪些验证 +离 P0 竖切还差什么 +``` + +不在 P0 竖切完成前扩展到: + +```text +完整 Deck Planner +外部美学资产大规模抽取 +自动 repair loop +线上 live_create +CLI binary/npm 分发方案 +复杂图表 +真实图片资产 +``` + +## 10. Subagent Supervisor Checklist + +独立监督者每次审查必须检查: + +```text +1. 是否偏离当前唯一目标 +2. 是否偷偷回到 direct_svg +3. 是否绕开 Template Registry +4. 是否缺少 CanvasSpec / template_id / theme_id +5. 是否没有安装或调用 @resvg/resvg-js +6. 是否用 QuickLook / browser 截图冒充主路径 +7. 是否为了过门禁而放松 production gate +8. 是否手工 patch state.json 代替 runner 能力 +9. 是否有可重复 fixture 和测试证据 +10. 是否能映射回 PLAN.md 的 P0/P1 条目 +11. 是否具备 canvas-spec/template-fit/artboard-render/satori-bridge 的具体 receipt +12. quality_gate 是否检查 hash/freshness,而不是只检查文件存在 +13. registry/theme 是否 hash-bound 且未知 id fail-fast +``` + +审查输出格式: + +```text +Verdict: PASS / BLOCKED + +Blocking issues: +- ... + +Non-blocking risks: +- ... + +Evidence checked: +- ... + +Required next action: +- ... +``` + +## 11. First Implementation Steps + +推荐执行顺序: + +```text +1. 给 artboard_renderer 加 @resvg/resvg-js,并生成 PNG/contact sheet +2. 补最小 Template Registry,包含 cover-hero / comparison-cards / summary-final +3. 补 3 页 artboard_satori fixture 的 CanvasSpec +4. 让 generate_svg receipt 汇总 artboard receipts 和 PNG 输出 +5. quality_gate 检查 artboard receipts fresh +6. 跑到 dry_run +``` + +任何下一步如果不能落到以上 6 项之一,默认先不做。 diff --git a/skills/lark-slides/references/svglide-artboard-packaging-decision.md b/skills/lark-slides/references/svglide-artboard-packaging-decision.md new file mode 100644 index 00000000..8d7f2657 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-packaging-decision.md @@ -0,0 +1,125 @@ +# SVGlide Artboard Packaging Decision + +Status: Gate 11 implemented, pending reviewer verdict + +## Decision + +`artboard_renderer` stays as a `lark-slides` skill-local Node subpackage: + +```text +skills/lark-slides/scripts/artboard_renderer/ + package.json + pnpm-lock.yaml + render.mjs + dist/render.mjs + templates/ + themes/ + components/ +``` + +The root CLI remains a Go binary. Satori/resvg are not added as Go modules and are not installed in the root CLI package. + +## What Is Embedded + +`skills_embed.go` now embeds a whitelist: + +```text +skills/*/SKILL.md +skills/*/references +skills/*/routes +skills/*/scenes +skills/*/prompts +skills/*/scripts/*.py +skills/*/scripts/artboard_renderer/*.mjs +skills/*/scripts/artboard_renderer/package.json +skills/*/scripts/artboard_renderer/pnpm-lock.yaml +skills/*/scripts/artboard_renderer/components +skills/*/scripts/artboard_renderer/dist +skills/*/scripts/artboard_renderer/templates +skills/*/scripts/artboard_renderer/themes +``` + +This makes `lark-cli skills read/list` version-match the binary for prompts, Python scripts, and renderer package resources. + +## What Is Not Embedded + +The Go binary must not embed: + +```text +node_modules/ +fixtures/ +runtime project artifacts +assets/ +.tmp/ +``` + +`@resvg/resvg-js` is native and platform-specific. It must be installed by the package manager or supplied by a platform dependency layer, not committed or broadly embedded. + +## Dependency Installation + +Required local or CI install command: + +```bash +pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile +``` + +Required runtime checks: + +```bash +node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +python3 skills/lark-slides/scripts/svglide_artboard_package_check.py --pretty +``` + +The subpackage pins: + +```text +satori: 0.26.0 +@resvg/resvg-js: 2.6.2 +``` + +The lockfile must include macOS arm64 and x64 optional native packages: + +```text +@resvg/resvg-js-darwin-arm64 +@resvg/resvg-js-darwin-x64 +``` + +## Runtime Contract + +Satori is bundled into `dist/render.mjs`. The release path must not require users to clone a sibling Satori source repository. + +`@resvg/resvg-js` remains the required native runtime dependency. Missing Node or resvg dependencies must fail before live create. + +Operator repair command: + +```bash +pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile +node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime +``` + +No-network environments must not auto-fetch packages during generation. They need a prewarmed pnpm store, CI-installed dependencies, or a packaged platform dependency layer. + +## CLI Boundary + +`slides +create-svg` still consumes final SVGlide SVG files only. It does not run Satori, resvg, Python generation, or planner prompts. + +The artboard generation path remains an agent/skill pipeline: + +```text +Planner JSON + -> CanvasSpec + -> artboard_renderer Satori/resvg preview + -> SatoriToSVGlide compiler + -> existing SVGlide prepare/quality_gate/dry_run/live_create/readback +``` + +## Validation Evidence + +Gate 11 evidence is recorded in: + +```text +skills/lark-slides/references/svglide-artboard-gate11-evidence.md +skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/06-check/artboard-package-check.json +skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json +``` diff --git a/skills/lark-slides/references/svglide-artboard-receipt.schema.json b/skills/lark-slides/references/svglide-artboard-receipt.schema.json new file mode 100644 index 00000000..311808d4 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-receipt.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-artboard-receipt.schema.json", + "title": "SVGlide artboard render receipt", + "type": "object", + "required": [ + "version", + "stage", + "status", + "page", + "canvas_spec_path", + "canvas_spec_sha256", + "template_id", + "theme_id", + "template_registry", + "template_registry_sha256", + "theme_registry", + "theme_registry_sha256", + "satori_version", + "resvg_version", + "font_hashes", + "satori_svg", + "satori_svg_sha256", + "png", + "png_sha256", + "render_metadata", + "render_metadata_sha256", + "canvas_template_svg", + "canvas_template_svg_sha256", + "compiler_input", + "compiler_input_sha256", + "semantic_map", + "semantic_map_sha256", + "node_layout_map", + "node_layout_map_sha256", + "svglide_svg", + "svglide_svg_sha256" + ], + "additionalProperties": true, + "properties": { + "version": {"const": "svglide-artboard-receipt/v1"}, + "stage": {"const": "generate_svg"}, + "status": {"enum": ["passed", "failed"]}, + "page": {"type": "integer", "minimum": 1}, + "canvas_spec_path": {"type": "string", "minLength": 1}, + "canvas_spec_sha256": {"type": "string", "minLength": 1}, + "template_id": {"type": "string", "minLength": 1}, + "theme_id": {"type": "string", "minLength": 1}, + "template_registry": {"type": "string", "minLength": 1}, + "template_registry_sha256": {"type": "string", "minLength": 1}, + "theme_registry": {"type": "string", "minLength": 1}, + "theme_registry_sha256": {"type": "string", "minLength": 1}, + "satori_version": {"type": "string", "minLength": 1}, + "resvg_version": {"type": "string", "minLength": 1}, + "font_hashes": {"type": "array", "minItems": 1}, + "satori_svg": {"type": "string", "minLength": 1}, + "satori_svg_sha256": {"type": "string", "minLength": 1}, + "png": {"type": "string", "minLength": 1}, + "png_sha256": {"type": "string", "minLength": 1}, + "render_metadata": {"type": "string", "minLength": 1}, + "render_metadata_sha256": {"type": "string", "minLength": 1}, + "canvas_template_svg": {"type": "string", "minLength": 1}, + "canvas_template_svg_sha256": {"type": "string", "minLength": 1}, + "compiler_input": {"type": "string", "minLength": 1}, + "compiler_input_sha256": {"type": "string", "minLength": 1}, + "semantic_map": {"type": "string", "minLength": 1}, + "semantic_map_sha256": {"type": "string", "minLength": 1}, + "node_layout_map": {"type": "string", "minLength": 1}, + "node_layout_map_sha256": {"type": "string", "minLength": 1}, + "svglide_svg": {"type": "string", "minLength": 1}, + "svglide_svg_sha256": {"type": "string", "minLength": 1} + } +} diff --git a/skills/lark-slides/references/svglide-artboard-satori.contract.md b/skills/lark-slides/references/svglide-artboard-satori.contract.md new file mode 100644 index 00000000..65d43d16 --- /dev/null +++ b/skills/lark-slides/references/svglide-artboard-satori.contract.md @@ -0,0 +1,153 @@ +# SVGlide Artboard / Satori Contract + +P0a adds an `artboard_satori` generation mode inside `generate_svg`. + +This mode is intentionally narrow: + +- `CanvasSpec` is the semantic source of truth. +- A CanvasSpec-owned template artifact / semantic map is the direct compiler input for SVGlide private SVG. +- Raw Satori SVG is preview/layout evidence only; it is not the semantic source for final live SVG. +- `SVGLIDE_ARTBOARD_USE_NODE_SATORI=1` enables the Node adapter. Published skills must use the bundled adapter at `skills/lark-slides/scripts/artboard_renderer/dist/render.mjs`. +- Published CLI/skill resources must not require a sibling Satori source checkout; Satori is bundled into `dist/render.mjs`. +- The native `@resvg/resvg-js` package is still a runtime dependency and must be installed from the locked skill subpackage before `artboard_satori` runs. +- Live input remains SVGlide protocol SVG. +- The downstream chain stays unchanged: `prepare -> preview -> preflight -> quality_gate -> dry_run`. + +## Boundary + +Do not pass arbitrary Satori SVG to `slides +create-svg`. + +The compiler must receive CanvasSpec-derived template IR, then produce SVGlide protocol SVG with private `slide:*` markers. P0 records this with per-page artboard receipts in `artboard_receipts` and aggregate child receipts in `artboard_additional_receipts`. + +Default offline chain: + +```text +CanvasSpec + -> Satori-compatible template renderer + -> 04-svg/artboard/page-xxx.canvas-template.svg + -> compile_canvas_template_svg_to_svglide + -> 04-svg/page-xxx.svg +``` + +Optional true Satori chain: + +```text +CanvasSpec + -> skills/lark-slides/scripts/artboard_renderer/dist/render.mjs + -> bundled Satori runtime + -> 04-svg/artboard/raw/page-xxx.satori.svg + -> resvg PNG preview + -> CanvasSpec template artifact remains compiler input + -> compile_canvas_template_svg_to_svglide + -> 04-svg/page-xxx.svg +``` + +Local development may run the unbundled source entry +`skills/lark-slides/scripts/artboard_renderer/render.mjs` after +`pnpm install --frozen-lockfile`. That path is not the release contract. + +## P0 Supported Surface + +P0 supports three templates: + +- `template_id=cover-hero` +- `template_id=comparison-cards` +- `template_id=summary-final` + +P0 has two Satori source modes: + +- default offline mode: Python emits deterministic Satori-compatible SVG from the controlled template renderer +- real Satori mode: `skills/lark-slides/scripts/artboard_renderer/dist/render.mjs` renders CanvasSpec through bundled Satori runtime. It must not require a sibling Satori source repository, but it does require package-managed `@resvg/resvg-js` native dependencies. + +## Adapter Packaging + +The CLI repository is a Go binary plus separately installed skills. Satori is a +Node dependency and must not be added as a Go module. + +Release shape: + +```text +skills/lark-slides/scripts/artboard_renderer/ + package.json # dependency declaration; satori and @resvg/resvg-js are exact registry versions + pnpm-lock.yaml # pinned build input + render.mjs # source entry for local development + dist/render.mjs # published runtime entry; Satori is bundled, resvg stays native + templates/ + themes/ + components/ +``` + +Build command: + +```bash +cd skills/lark-slides/scripts/artboard_renderer +pnpm install --frozen-lockfile +pnpm run build +node dist/render.mjs --check-runtime +``` + +Gate 11 packaging validation: + +```bash +python3 skills/lark-slides/scripts/svglide_artboard_package_check.py --pretty +``` + +Runtime rules: + +- Published skills call `dist/render.mjs`. +- The adapter may fall back to source `render.mjs` only in local development. +- `satori` must be a fixed npm version in `package.json` and lockfile, not a `file:` dependency. +- `@resvg/resvg-js` must be a fixed npm version in `package.json` and lockfile; macOS arm64/x64 optional native packages must be present in `pnpm-lock.yaml`. +- `node_modules/` must not be committed or embedded into the Go binary. Release packaging must install dependencies for the target host or provide a preinstalled platform dependency layer. +- If Node or resvg dependencies are missing, fail before live create and run `pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile`, then rerun `node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime`. +- If no usable system font exists, set `SVGLIDE_SATORI_FONT_PATH` to a local `.ttf` or `.otf` font. + +P0 native output surface: + +- `rect slide:role="shape"` +- `circle slide:role="shape"` +- `foreignObject slide:role="shape" slide:shape-type="text"` + +P0 Gate 4 certifies text/shape mapping only. Image asset binding and +`svglide-chart-spec-v1` chart markers remain separate Gate 8/P0c proof items and +must not be claimed as complete from Gate 4 evidence. + +P0 fail-fast surface for final SVGlide output: + +- remote assets +- remote fonts +- WOFF2 fonts +- `filter` / `fe*` +- `mask` +- `clipPath` +- `pattern` +- CSS animation / transition +- `%`, `em`, `rem`, or `calc(...)` geometry +- root-level SVG text in final SVGlide output + +## Required Receipts + +For `generation_mode=artboard_satori`, `receipts/generate_svg.json` must include: + +- existing generator receipt fields: `generated_files`, `page_receipts`, `plan_sha256`, `evidence_sha256`, `asset_manifest_sha256`, `source_receipt_sha256` +- `generation_mode: "artboard_satori"` +- `artboard_receipts`: ordered per-page receipt paths, for example `04-svg/artboard/page-001.receipt.json` +- `artboard_additional_receipts`: aggregate receipt paths, currently `receipts/canvas-spec-validate.json`, `receipts/artboard-render.json`, and `receipts/satori-bridge.json` +- `template_fit_check`: `06-check/template-fit.json` +- `canvas_spec_validate`: `06-check/canvas-spec-validate.json` +- `artboard_render_receipt`: `receipts/artboard-render.json` +- `satori_bridge_receipt`: `receipts/satori-bridge.json` + +Each per-page artboard receipt must bind: + +- CanvasSpec hash +- raw Satori preview SVG hash +- CanvasSpec template SVG hash +- compiler input path and hash +- renderer mode (`local-static` or `satori-node`) +- compiler mode (`CanvasSpecTemplateSVG` / `preview_only`) +- semantic map hash +- node layout map hash +- final SVGlide SVG hash + +`quality_gate` rejects missing, failed, or stale artboard receipts. diff --git a/skills/lark-slides/references/svglide-canvas-plan.schema.json b/skills/lark-slides/references/svglide-canvas-plan.schema.json new file mode 100644 index 00000000..231ecf63 --- /dev/null +++ b/skills/lark-slides/references/svglide-canvas-plan.schema.json @@ -0,0 +1,72 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-canvas-plan.schema.json", + "title": "SVGlide Canvas Planner Output", + "type": "object", + "additionalProperties": true, + "required": ["schema_version", "route", "generation_mode", "plan_path", "quality_gates", "art_direction", "style_preset", "style_selection_reason", "style_system", "loaded_rule_set", "slides"], + "properties": { + "schema_version": {"const": "svglide-canvas-plan/v1"}, + "route": {"const": "svglide-svg"}, + "generation_mode": {"const": "artboard_satori"}, + "plan_path": {"type": "string", "minLength": 1}, + "style_preset": {"type": "string", "minLength": 1}, + "style_selection_reason": {"type": "string", "minLength": 1}, + "style_system": {"type": "object"}, + "loaded_rule_set": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, + "quality_gates": { + "type": "object", + "required": ["no_text_overflow", "no_debug_guides", "no_xml_like_pages"], + "properties": { + "no_text_overflow": {"const": true}, + "no_debug_guides": {"const": true}, + "no_xml_like_pages": {"const": true} + }, + "additionalProperties": true + }, + "art_direction": { + "type": "object", + "required": ["cover_treatment", "section_divider_treatment", "closing_treatment", "deck_motif", "svg_native_moments"], + "properties": { + "cover_treatment": {"type": "string", "minLength": 1}, + "section_divider_treatment": {"type": "string", "minLength": 1}, + "closing_treatment": {"type": "string", "minLength": 1}, + "deck_motif": {"type": "string", "minLength": 1}, + "svg_native_moments": {"type": "array", "minItems": 3, "items": {"type": "string", "minLength": 1}} + }, + "additionalProperties": true + }, + "slides": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["page", "title", "key_message", "renderer_id", "layout_family", "visual_recipe", "visual_intent", "visual_focal_point", "visual_signature", "svg_effects", "required_primitives", "svg_primitives", "xml_like_risk", "content_density_contract", "risk_flags", "source_policy", "canvas_spec"], + "properties": { + "page": {"type": "integer", "minimum": 1}, + "title": {"type": "string", "minLength": 1}, + "key_message": {"type": "string", "minLength": 1}, + "renderer_id": {"type": "string", "minLength": 1}, + "layout_family": {"type": "string", "minLength": 1}, + "visual_recipe": {"type": "string", "minLength": 1}, + "visual_intent": {"type": "string", "minLength": 1}, + "visual_focal_point": {"type": "string", "minLength": 1}, + "visual_signature": {"type": "string", "minLength": 1}, + "svg_effects": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "required_primitives": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "svg_primitives": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "xml_like_risk": {"type": "string", "minLength": 1}, + "content_density_contract": {"type": "string", "minLength": 1}, + "risk_flags": {"type": "array", "items": {"type": "string"}}, + "source_policy": {"type": "string", "minLength": 1}, + "canvas_spec": { + "type": "object", + "required": ["version", "canvas", "safe_area", "template_id", "theme_id", "theme", "content", "semantic_elements", "quality_constraints"], + "additionalProperties": true + } + }, + "additionalProperties": true + } + } + } +} diff --git a/skills/lark-slides/references/svglide-canvas-spec.schema.json b/skills/lark-slides/references/svglide-canvas-spec.schema.json new file mode 100644 index 00000000..1cff2c72 --- /dev/null +++ b/skills/lark-slides/references/svglide-canvas-spec.schema.json @@ -0,0 +1,82 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-canvas-spec.schema.json", + "title": "SVGlide CanvasSpec", + "type": "object", + "required": ["version", "canvas", "safe_area", "template_id", "theme_id", "theme", "content", "semantic_elements", "quality_constraints"], + "additionalProperties": true, + "properties": { + "version": {"const": "svglide-canvas-spec/v1"}, + "canvas": { + "type": "object", + "required": ["width", "height", "viewBox"], + "additionalProperties": true, + "properties": { + "width": {"const": 960}, + "height": {"const": 540}, + "viewBox": {"const": "0 0 960 540"} + } + }, + "template_id": {"type": "string", "minLength": 1}, + "theme_id": {"type": "string", "minLength": 1}, + "safe_area": { + "type": "object", + "required": ["x", "y", "width", "height"], + "additionalProperties": true, + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + } + }, + "theme": {"type": "object", "additionalProperties": true}, + "content": {"type": "object", "additionalProperties": true}, + "semantic_elements": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["element_id", "kind", "role", "source_ref", "bbox"], + "additionalProperties": true, + "properties": { + "element_id": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "role": {"type": "string", "minLength": 1}, + "source_ref": {"type": "string", "minLength": 1}, + "bbox": { + "type": "object", + "required": ["x", "y", "width", "height"], + "additionalProperties": true, + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + } + } + } + } + }, + "quality_constraints": { + "type": "object", + "required": ["min_font_size", "safe_area"], + "additionalProperties": true, + "properties": { + "min_font_size": {"type": "number"}, + "max_title_lines": {"type": "integer", "minimum": 1}, + "safe_area": { + "type": "object", + "required": ["x", "y", "width", "height"], + "additionalProperties": true, + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + } + } + } + } + } +} diff --git a/skills/lark-slides/references/svglide-component-registry.json b/skills/lark-slides/references/svglide-component-registry.json new file mode 100644 index 00000000..9ad5630b --- /dev/null +++ b/skills/lark-slides/references/svglide-component-registry.json @@ -0,0 +1,33 @@ +{ + "schema_version": "svglide-component-registry/v1", + "runtime_contract": { + "format": "Satori-compatible JSX/render tree", + "allowed_styles": ["static flex layout", "absolute positioning", "solid backgrounds", "font tokens", "bounded text boxes"], + "forbidden_styles": ["CSS animation", "filter effects", "external stylesheets", "arbitrary HTML passthrough"] + }, + "components": [ + {"id": "Title", "status": "active", "role": "primary text hierarchy", "source": "components/primitives.mjs"}, + {"id": "Subtitle", "status": "active", "role": "secondary text hierarchy", "source": "components/primitives.mjs"}, + {"id": "TextBlock", "status": "active", "role": "bounded text surface", "source": "components/primitives.mjs"}, + {"id": "Badge", "status": "active", "role": "eyebrow or metadata label", "source": "components/primitives.mjs"}, + {"id": "Chip", "status": "active", "role": "short categorical tag", "source": "components/primitives.mjs"}, + {"id": "StatCard", "status": "active", "role": "numbered takeaway card", "source": "components/primitives.mjs"}, + {"id": "ImageFrame", "status": "active", "role": "bounded visual placeholder", "source": "components/primitives.mjs"}, + {"id": "MetricTile", "status": "active", "role": "dashboard metric block", "source": "templates/p0-templates.mjs"}, + {"id": "TimelineNode", "status": "active", "role": "milestone marker", "source": "templates/p0-templates.mjs"}, + {"id": "ProcessStep", "status": "active", "role": "pipeline step card", "source": "templates/p0-templates.mjs"}, + {"id": "QuoteBlock", "status": "active", "role": "large quotation layout", "source": "templates/p0-templates.mjs"}, + {"id": "RiskBanner", "status": "active", "role": "severity and risk grouping", "source": "templates/p0-templates.mjs"}, + {"id": "RoadmapLane", "status": "active", "role": "horizontal roadmap row", "source": "templates/p0-templates.mjs"}, + {"id": "ArchitectureNode", "status": "active", "role": "system node block", "source": "templates/p0-templates.mjs"}, + {"id": "Callout", "status": "active", "role": "emphasis panel", "source": "templates/p0-templates.mjs"}, + {"id": "SectionHeader", "status": "active", "role": "compact page heading cluster", "source": "templates/p0-templates.mjs"}, + {"id": "FigurePlaceholder", "status": "active", "role": "image or diagram placeholder", "source": "templates/p0-templates.mjs"}, + {"id": "LegendDot", "status": "active", "role": "comparison and chart marker", "source": "templates/p0-templates.mjs"}, + {"id": "IndexNumber", "status": "active", "role": "ordered list number", "source": "templates/p0-templates.mjs"}, + {"id": "DividerRule", "status": "active", "role": "section or timeline divider", "source": "templates/p0-templates.mjs"}, + {"id": "PosterSection", "status": "active", "role": "research poster content block", "source": "templates/p0-templates.mjs"}, + {"id": "BarMark", "status": "active", "role": "simple data story bar", "source": "templates/p0-templates.mjs"}, + {"id": "AccentShape", "status": "active", "role": "bounded decorative visual signal", "source": "templates/p0-templates.mjs"} + ] +} diff --git a/skills/lark-slides/references/svglide-deck-plan.schema.json b/skills/lark-slides/references/svglide-deck-plan.schema.json new file mode 100644 index 00000000..a3843a50 --- /dev/null +++ b/skills/lark-slides/references/svglide-deck-plan.schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-deck-plan.schema.json", + "title": "SVGlide Deck Planner Output", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "topic", "audience", "objective", "target_slide_count", "narrative_arc", "theme_direction", "constraints", "slides"], + "properties": { + "schema_version": {"const": "svglide-deck-plan/v1"}, + "topic": {"type": "string", "minLength": 1}, + "audience": {"type": "string", "minLength": 1}, + "objective": {"type": "string", "minLength": 1}, + "target_slide_count": {"type": "integer", "minimum": 1}, + "narrative_arc": {"type": "array", "minItems": 3, "items": {"type": "string", "minLength": 1}}, + "theme_direction": { + "type": "object", + "additionalProperties": false, + "required": ["preferred_theme_ids", "visual_identity", "tone"], + "properties": { + "preferred_theme_ids": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, + "visual_identity": {"type": "string", "minLength": 1}, + "tone": {"type": "string", "minLength": 1} + } + }, + "constraints": { + "type": "object", + "additionalProperties": false, + "required": ["generation_mode", "source_policy", "forbidden_outputs"], + "properties": { + "generation_mode": {"const": "artboard_satori"}, + "source_policy": {"type": "string", "minLength": 1}, + "forbidden_outputs": {"type": "array", "minItems": 4, "items": {"type": "string", "minLength": 1}} + } + }, + "slides": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["page", "title", "role", "key_message", "content_goal", "visual_goal", "allowed_template_ids"], + "properties": { + "page": {"type": "integer", "minimum": 1}, + "title": {"type": "string", "minLength": 1}, + "role": {"type": "string", "minLength": 1}, + "key_message": {"type": "string", "minLength": 1}, + "content_goal": {"type": "string", "minLength": 1}, + "visual_goal": {"type": "string", "minLength": 1}, + "allowed_template_ids": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}} + } + } + } + } +} diff --git a/skills/lark-slides/references/svglide-generator-receipt.schema.json b/skills/lark-slides/references/svglide-generator-receipt.schema.json index 564d358c..c99c508d 100644 --- a/skills/lark-slides/references/svglide-generator-receipt.schema.json +++ b/skills/lark-slides/references/svglide-generator-receipt.schema.json @@ -19,6 +19,7 @@ "stage": {"const": "generate_svg"}, "status": {"enum": ["passed", "failed"]}, "generator_mode": {"enum": ["script", "external"]}, + "generation_mode": {"enum": ["direct_svg", "artboard_satori"]}, "generated_files": { "type": "array", "minItems": 1, @@ -33,11 +34,47 @@ } }, "page_receipts": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, + "artboard_receipts": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, + "artboard_additional_receipts": {"type": "array", "minItems": 3, "items": {"type": "string", "minLength": 1}}, + "canvas_spec_validate": {"type": "string", "minLength": 1}, + "artboard_render_receipt": {"type": "string", "minLength": 1}, + "satori_bridge_receipt": {"type": "string", "minLength": 1}, + "template_fit_check": {"type": "string", "minLength": 1}, + "contact_sheet": { + "type": "object", + "required": ["path", "sha256"], + "additionalProperties": true, + "properties": { + "path": {"type": "string", "minLength": 1}, + "sha256": {"type": "string", "minLength": 1} + } + }, "plan_sha256": {"type": "string", "minLength": 1}, "evidence_sha256": {"type": "string", "minLength": 1}, "asset_manifest_sha256": {"type": "string", "minLength": 1}, "source_receipt_sha256": {"type": "string", "minLength": 1}, "lock_sha256": {"type": ["string", "null"]}, "generator_script_sha256": {"type": ["string", "null"]} - } + }, + "allOf": [ + { + "if": { + "properties": { + "generation_mode": {"const": "artboard_satori"} + }, + "required": ["generation_mode"] + }, + "then": { + "required": [ + "artboard_receipts", + "artboard_additional_receipts", + "canvas_spec_validate", + "artboard_render_receipt", + "satori_bridge_receipt", + "template_fit_check", + "contact_sheet" + ] + } + } + ] } diff --git a/skills/lark-slides/references/svglide-layout-archetypes.json b/skills/lark-slides/references/svglide-layout-archetypes.json new file mode 100644 index 00000000..3f3b3b54 --- /dev/null +++ b/skills/lark-slides/references/svglide-layout-archetypes.json @@ -0,0 +1,15 @@ +{ + "schema_version": "svglide-layout-archetypes/v1", + "archetypes": [ + {"id": "hero-cover", "status": "active", "templates": ["cover-hero"], "purpose": "deck opening page with one dominant message and controlled chips"}, + {"id": "section-divider", "status": "active", "templates": ["section-title"], "purpose": "chapter transition and narrative pacing"}, + {"id": "agenda-list", "status": "active", "templates": ["agenda-list"], "purpose": "ordered content map for multi-page decks"}, + {"id": "two-column-comparison", "status": "active", "templates": ["comparison-cards"], "purpose": "before-after or option comparison"}, + {"id": "timeline-horizontal", "status": "active", "templates": ["timeline-steps"], "purpose": "time or milestone sequence"}, + {"id": "process-flow", "status": "active", "templates": ["process-flow"], "purpose": "pipeline and dependency explanation"}, + {"id": "metric-dashboard", "status": "active", "templates": ["metric-dashboard", "data-story"], "purpose": "small set of quantitative highlights"}, + {"id": "image-text-split", "status": "active", "templates": ["image-feature"], "purpose": "one visual anchor with supporting interpretation"}, + {"id": "research-poster-3col", "status": "active", "templates": ["research-poster"], "purpose": "dense research summary with balanced sections"}, + {"id": "architecture-blueprint", "status": "active", "templates": ["architecture-blueprint", "roadmap-lanes", "risk-alert", "quote-focus", "summary-final"], "purpose": "systems, plans, risks, quotes, and closing pages"} + ] +} diff --git a/skills/lark-slides/references/svglide-node-layout-map.schema.json b/skills/lark-slides/references/svglide-node-layout-map.schema.json new file mode 100644 index 00000000..01721297 --- /dev/null +++ b/skills/lark-slides/references/svglide-node-layout-map.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-node-layout-map.schema.json", + "title": "SVGlide node layout map", + "type": "object", + "required": ["version", "page", "source", "drift", "nodes"], + "additionalProperties": true, + "properties": { + "version": {"const": "svglide-node-layout-map/v1"}, + "page": {"type": "integer", "minimum": 1}, + "source": {"type": "string", "minLength": 1}, + "drift": { + "type": "object", + "required": ["status", "max_px"], + "additionalProperties": true, + "properties": { + "status": {"type": "string", "minLength": 1}, + "max_px": {"type": "number"} + } + }, + "nodes": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "kind", "x", "y", "width", "height"], + "additionalProperties": true, + "properties": { + "id": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "x": {"type": "number"}, + "y": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + } + } + } + } +} diff --git a/skills/lark-slides/references/svglide-nz-preview-retro.md b/skills/lark-slides/references/svglide-nz-preview-retro.md new file mode 100644 index 00000000..d989cf87 --- /dev/null +++ b/skills/lark-slides/references/svglide-nz-preview-retro.md @@ -0,0 +1,516 @@ +# SVGlide 新西兰风光本地预览复盘与改进计划 + +## 背景 + +本次验证目标是基于主题“新西兰风光”生成一份可本地预览的 SVGlide deck,不做简单冒烟,而是尽量产出一套能体现真实视觉水平的多页结果。 + +最终产物为 5 页 SVG-native deck: + +1. 封面:新西兰风光 +2. 南岛:雪山、峡湾与冰川湖 +3. 北岛:火山、地热与海岸 +4. 7 日风光路线节奏 +5. 四个风景关键词收束 + +最终链路通过到 `dry_run`: + +```text +plan + -> assets + -> generate_svg + -> prepare + -> preview + -> preflight + -> preview_lint + -> aesthetic_review + -> chart_verify + -> semantic_review + -> runtime_review + -> visual_distinctness_review + -> quality_gate + -> dry_run +``` + +本地项目路径: + +```text +.tmp/nz-scenery-svglide +``` + +关键报告: + +```text +.tmp/nz-scenery-svglide/06-check/preflight.json +.tmp/nz-scenery-svglide/06-check/preview-lint.json +.tmp/nz-scenery-svglide/06-check/semantic-review.json +.tmp/nz-scenery-svglide/06-check/runtime-review.json +.tmp/nz-scenery-svglide/06-check/quality-gate.json +.tmp/nz-scenery-svglide/receipts/dry_run.json +``` + +## 结论 + +这次耗时偏长的主因不是 runner 执行慢,而是: + +```text +缺少模板化生成能力 ++ 缺少自动契约补齐 ++ 缺少门禁自动修复 ++ 在本地探索阶段跑了接近 production 的严格门禁 +``` + +最终 runner 实际执行耗时是秒级到几十秒级;主要时间花在人工写 SVG、人工调版式、人工补 `slide_plan.json`、人工补项目级 `renderer-registry.json`、手工处理状态重跑。 + +## 时间拆解 + +| 阶段 | 现象 | 结论 | +| --- | --- | --- | +| 项目初始化 | `init` 在 20:54:19 完成 | 很快,不是瓶颈 | +| source 准备 | `source` 在 20:57:14 完成 | 主要是人工写 source notes/evidence | +| SVG 生成脚本编写 | 手写 5 页 direct SVG generator | 最大耗时来源 | +| preview/preflight 修复 | 被文本高度、卡片容器、文本重叠打回 | 模板没有文本预算和布局约束 | +| semantic review 修复 | 可见文字没有完整追溯到 plan/source | 计划层和产物层没有自动对齐 | +| runtime review 修复 | 自定义 renderer_id 未登记 | 缺少项目级 renderer registry 自动生成 | +| final dry_run | `quality_gate` 和 `dry_run` 最终 passed | 链路可以跑通,但前置人工摩擦太多 | +| 本地视觉截图 | 环境缺 Playwright/resvg/CairoSVG,最后用 QuickLook | 预览渲染工具链不稳定 | + +## 具体卡点 + +### 1. `preview_lint` 对文本框高度敏感 + +失败表现: + +```text +preview_text_overflow_risk +preview_big_number_box_tight +``` + +触发页面: + +- 第 1 页:`南岛`、`北岛`、`路线` +- 第 4 页:标题和阶段标签 +- 第 5 页:标题、关键词、说明文本 + +根因: + +手写 SVG 时没有统一的文本预算函数,只是凭视觉估计设置 `foreignObject` 的 `width/height`。门禁按保守行高判断后,短文本也可能被判为有溢出风险。 + +判断: + +规则本身不应删除,因为 PPT 导出后文本被裁切是严重问题。但探索阶段不应要求人工逐个调框。 + +改进: + +- 模板层内置 `fitTextBox()` 预算。 +- 所有 text component 默认给足 `line-height * lines + padding`。 +- `preview_lint` 在 `draft_preview` profile 下将轻微高度不足降级为 warning。 +- runner 增加自动修复建议或自动扩高能力。 + +### 2. `preflight` 拦截了第 5 页卡片文本重叠 + +失败表现: + +```text +text_bbox_overlap +text_container_overflow +``` + +根因: + +为了修 `preview_lint`,人工增大了关键词卡片内文本框高度,但没有同步增大卡片容器,也没有重新计算卡片内部 y 坐标。 + +判断: + +这类门禁不算太紧。文本重叠和容器越界在真实 PPT 中会直接翻车,应该阻断。 + +改进: + +- 卡片模板必须用布局函数生成,而不是人工坐标。 +- 卡片容器高度由内容反推。 +- 文本变更后自动触发布局重算。 +- `preflight` 输出更精确的元素位置和最近容器,减少人工定位时间。 + +### 3. `semantic_review` 要求可见文字追溯到 plan/source + +失败表现: + +```text +visible_text_not_in_plan_or_source +body_point_not_chinese +content_body_points_too_many +``` + +根因: + +SVG 中出现了大量设计文案和英文页签: + +```text +SCENIC ATLAS +SOUTH +NORTH +ROUTE +TAKEAWAY +Day 1 +Tekapo 蓝 +SVGlide 本地预览... +``` + +这些文案没有在 `slide_plan.json` 或 source evidence 中完整登记。 + +判断: + +规则方向是对的。PPT 生成链路需要保证“计划层负责内容,渲染层不私自发明业务文案”。但当前执行方式太依赖人工。 + +改进: + +- Slide Planner 需要输出 `visible_text` 字段。 +- Template Renderer 需要把实际渲染文本反写为 text inventory。 +- semantic review 应比较 `planned_visible_text` 和 `rendered_visible_text`,而不是让人手工补。 +- 英文装饰标签应有明确字段,例如 `decorative_labels`,并允许白名单。 +- 对纯设计辅助文案区分 `content_text` 和 `decorative_text`。 + +### 4. 修改 plan 后 receipt/hash 级联失效 + +失败表现: + +```text +plan confirmation plan_sha256 does not match current plan +assets manifest plan_sha256 does not match current project files +``` + +根因: + +为了修 semantic review 修改了 `slide_plan.json`,但 `plan-confirmation.json`、assets manifest、generate receipt 等下游文件仍指向旧 hash。 + +判断: + +hash 校验是必要的,但当前恢复方式太手工。 + +改进: + +- runner 增加 `reset --from `。 +- runner 增加 `rerun --from --until `。 +- 当 plan hash 改变时,自动标记下游 stage stale。 +- 对本地预览提供 `--auto-confirm-plan`,仅限非 live create。 +- state 文件不要靠人工 patch。 + +### 5. `runtime_review` 要求 renderer_id 登记 + +失败表现: + +```text +renderer_unknown +``` + +触发 renderer: + +```text +nz_scenic_cover +nz_south_island_panels +nz_north_island_map +nz_route_timeline +nz_closing_keywords +``` + +根因: + +为了让这套 deck 的页面类型更语义化,使用了项目自定义 `renderer_id`,但一开始没有生成项目级: + +```text +02-plan/renderer-registry.json +``` + +判断: + +这个规则也不应删除。renderer registry 是后续运行时可追踪、模板资产可治理的基础。 + +改进: + +- Slide Planner 选模板时自动生成项目级 renderer registry。 +- Template Registry 中的模板 id 自动映射到 renderer id。 +- 对 direct SVG prototype 支持临时 renderer registry。 +- `runtime_review` 在报错时输出建议 registry stub。 + +### 6. 本地预览渲染工具链不稳定 + +失败表现: + +- node_repl 的 Playwright 调用失败。 +- 本地没有 `playwright`。 +- 本地没有 `rsvg-convert`。 +- 本地没有 `cairosvg`。 +- `sips` 不能直接转换当前 SVG。 +- 最后使用 macOS QuickLook `qlmanage` 生成缩略图。 + +判断: + +这不是主链路的核心问题,但严重影响“让我看看实际效果”的体验。 + +改进: + +- CLI/skill 内置稳定预览截图工具。 +- 优先选项:Playwright 渲染 `preview.html`。 +- SVG 单页截图选项:resvg。 +- 产物应自动生成: + +```text +05-preview/preview.html +05-preview/contact-sheet.png +05-preview/page-001.png +... +``` + +## 门禁是否太紧 + +不是简单的“太紧”。 + +更准确的判断: + +```text +production 门禁是合理的 +但不应该直接用于 draft preview 的人工探索阶段 +``` + +建议分三档: + +### `draft_preview` + +用于本地快速看效果。 + +只阻断: + +- SVG 无法解析 +- 页面空白 +- 文本严重重叠 +- 关键元素明显出画布 +- preview.html 无法生成 + +降级为 warning: + +- 轻微文本框高度不足 +- 装饰元素轻微越过 safe area +- 文案未完全补齐 text inventory +- renderer registry 缺失但可推断 + +### `review_preview` + +用于准备给人评审。 + +阻断: + +- 文本溢出风险 +- 容器越界 +- 页面视觉重复度过高 +- 语义和计划明显不一致 +- renderer_id 未登记 + +允许 warning: + +- 非核心装饰标签未完全追溯 +- 少量安全区装饰 warning + +### `production_live` + +用于真实创建线上 Slides。 + +阻断全部关键门禁: + +- generator receipt +- preflight +- preview_lint +- aesthetic_review +- semantic_review +- runtime_review +- visual_distinctness_review +- quality_gate +- dry_run + +## 根因归纳 + +本次慢点可以归纳为四类: + +### 1. 设计生成没有模板系统 + +现在实际是“人工画 5 页”,不是“主题输入后模板系统生成 5 页”。 + +缺失能力: + +- Template Registry +- Theme Token +- CanvasSpec +- Satori-compatible / SVG-native component library +- 文本自动布局 +- 页面类型自动映射 + +### 2. 计划层和渲染层没有自动闭环 + +渲染层实际出现的可见文本、renderer id、layout family、装饰标签,都需要回到计划层或 registry 中。但现在是人工补。 + +缺失能力: + +- visible_text 自动生成 +- text inventory 自动比对 +- renderer registry 自动生成 +- plan hash 级联失效管理 + +### 3. 门禁只有拦截,缺少自动修复 + +当前门禁能发现问题,但不会给 runner 一个可执行修复动作。 + +缺失能力: + +- 自动扩高 text box +- 自动增大 card container +- 自动移动重叠文本 +- 自动生成 registry stub +- 自动重跑 stale stages + +### 4. 本地预览体验没有产品化 + +产物存在,但“快速看效果”还依赖手工打开 HTML、手工截图、手工拼图。 + +缺失能力: + +- 自动打开预览 +- 自动生成 contact sheet +- 自动截图 +- preview report 中显示封面/缩略图 + +## 改进计划 + +### P0:减少同类返工 + +目标:下一次同类 5 页本地预览不再因为文本高度、registry、hash 手工处理卡住。 + +任务: + +1. runner 增加 `reset --from `。 +2. runner 增加 `--auto-confirm-plan`,仅允许非 live 阶段使用。 +3. direct_svg/artboard 生成时自动写 `visible_text` 或 text inventory。 +4. 生成项目级 `02-plan/renderer-registry.json`。 +5. preview_lint 对 `draft_preview` 支持轻微文本高度 warning。 +6. preflight 报告补充 bbox 坐标和最近容器信息。 + +验收: + +```text +主题输入后,人工只改一次 generator 或模板,runner 能自动从 stale stage 续跑到 dry_run。 +``` + +### P1:模板化常见页面 + +目标:不要再手写 5 页 direct SVG。 + +先做 5 个模板: + +```text +cover_hero +three_scenic_cards +map_and_notes +route_timeline +keyword_wall +``` + +每个模板必须内置: + +- schema 输入 +- theme token +- text budget +- safe area +- visible_text 输出 +- renderer registry metadata +- preview fixture + +验收: + +```text +输入主题“新西兰风光”后,5 页 deck 由模板组合生成,人工不需要逐页调坐标。 +``` + +### P2:质量门禁 profile 化 + +目标:本地探索和线上创建使用不同严格度。 + +新增或明确三档: + +```text +draft_preview +review_preview +production_live +``` + +验收: + +```text +draft_preview 能在不牺牲严重质量底线的前提下快速给出可看预览; +production_live 仍保持严格创建标准。 +``` + +### P3:自动修复闭环 + +目标:门禁发现的问题可以被 runner 自动修复一部分。 + +优先自动修: + +- text box height +- card container height +- renderer registry stub +- visible_text 补全建议 +- stale receipt reset + +验收: + +```text +preview_lint/preflight 常见问题不需要人工 patch SVG 坐标。 +``` + +### P4:本地预览产品化 + +目标:跑完后自动给用户可视化结果。 + +产物: + +```text +05-preview/preview.html +05-preview/contact-sheet.png +05-preview/page-001.png +05-preview/page-002.png +... +``` + +实现建议: + +- HTML 截图:Playwright +- SVG 单页截图:resvg +- fallback:QuickLook 仅作为 macOS fallback,不作为主链路 + +验收: + +```text +runner dry_run 后自动输出 contact sheet,用户无需手工打开浏览器也能先看一眼。 +``` + +## 建议的目标耗时 + +| 阶段 | 当前体感 | 改进后目标 | +| --- | --- | --- | +| 主题到 draft preview | 10-20 分钟 | 1-3 分钟 | +| draft preview 到 review preview | 多轮人工修 | 3-5 分钟 | +| review preview 到 production dry_run | 手工补契约 | 1 分钟以内 | +| 单次 runner 重跑 | 秒级到几十秒 | 秒级到几十秒 | + +## 下一步建议 + +优先级最高的不是放松所有门禁,而是把“探索生成”和“线上创建”分层。 + +建议先做: + +```text +1. runner reset/rerun 能力 +2. draft_preview profile +3. 自动 visible_text +4. 自动项目级 renderer-registry +5. 5 个基础模板 +``` + +做完这 5 件事后,同样的“新西兰风光 5 页预览”应该可以从人工 10-20 分钟降到 1-3 分钟,并且不会牺牲最终 production_live 的质量门禁。 diff --git a/skills/lark-slides/references/svglide-p1-source-intake.json b/skills/lark-slides/references/svglide-p1-source-intake.json new file mode 100644 index 00000000..82cd5d38 --- /dev/null +++ b/skills/lark-slides/references/svglide-p1-source-intake.json @@ -0,0 +1,225 @@ +{ + "schema_version": "svglide-source-intake/v1", + "policy": { + "runtime_import": "forbidden", + "usage": "external repositories are inspiration and measurement sources only; runtime output must be CanvasSpec plus in-repo Satori-compatible templates", + "conversion_path": "source example -> visual archetype -> CanvasSpec schema fields -> Template Registry -> Theme Token", + "acceptance_gate": "a source family is counted only when it has required intake fields and at least one conversion record pointing to owned registry output and golden CanvasSpec fixtures" + }, + "sources": [ + { + "id": "open-design-html-ppt", + "source_path": "/Users/bytedance/bd-projects/open-design/design-templates", + "source_type": "html_css_examples", + "source_hash_or_version": "git:2aadac07c", + "license_or_provenance": "origin https://github.com/nexu-io/open-design.git; local LICENSE is Apache-2.0; used as inspiration only", + "extract_fields": ["palette", "typography", "mood", "density", "occasion", "layout skeleton", "component combinations"], + "conversion_target": ["Theme Token", "Canvas Template", "Satori component candidate", "Layout Archetype"], + "acceptance_rule": "no source HTML/CSS enters runtime; every adopted pattern must map to a registry id and at least one golden CanvasSpec fixture", + "forbidden_usage": ["direct HTML runtime import", "arbitrary CSS passthrough", "client-side animation", "external stylesheet dependency"], + "verified_examples": [ + "/Users/bytedance/bd-projects/open-design/design-templates/html-ppt-weekly-report", + "/Users/bytedance/bd-projects/open-design/design-templates/html-ppt-product-launch" + ], + "conversion_records": [ + { + "source_examples": ["html-ppt-weekly-report"], + "abstraction_record": "dense report pages with compact headers, numbered rows, and metric grouping", + "canvas_spec_fields": ["content.items", "content.metrics", "semantic_elements.title", "quality_constraints.safe_area"], + "registry_output": { + "templates": ["agenda-list", "metric-dashboard"], + "themes": ["finance-dark", "swiss-red"], + "components": ["IndexNumber", "MetricTile", "SectionHeader"], + "layouts": ["agenda-list", "metric-dashboard"], + "golden_fixtures": ["agenda-list.canvas-spec.json", "metric-dashboard.canvas-spec.json"] + }, + "acceptance_rule": "fixture validates required content, max_items, text_budget, safe_area, and renders through Satori/resvg" + }, + { + "source_examples": ["html-ppt-product-launch"], + "abstraction_record": "launch narrative pages with hero, image-text split, and short supporting points", + "canvas_spec_fields": ["content.title", "content.subtitle", "content.points", "content.caption"], + "registry_output": { + "templates": ["cover-hero", "image-feature", "summary-final"], + "themes": ["glass-neon", "dark-clarity"], + "components": ["Chip", "ImageFrame", "FigurePlaceholder", "Callout"], + "layouts": ["hero-cover", "image-text-split"], + "golden_fixtures": ["cover-hero.canvas-spec.json", "image-feature.canvas-spec.json", "summary-final.canvas-spec.json"] + }, + "acceptance_rule": "image is represented as a bounded placeholder in CanvasSpec; no source DOM or CSS is copied" + } + ] + }, + { + "id": "open-design-retro-quarterly-review", + "source_path": "/Users/bytedance/bd-projects/open-design/skills/html-ppt-retro-quarterly-review", + "source_type": "html_css_case_study", + "source_hash_or_version": "git:2aadac07c", + "license_or_provenance": "origin https://github.com/nexu-io/open-design.git; local LICENSE is Apache-2.0; used as inspiration only", + "extract_fields": ["report structure", "metric hierarchy", "risk sectioning", "timeline rhythm", "executive density"], + "conversion_target": ["Canvas Template", "Component Variant", "Layout Archetype"], + "acceptance_rule": "patterns must become bounded CanvasSpec arrays with max_items/text_budget checks and golden fixtures", + "forbidden_usage": ["runtime dependency", "unbounded responsive layout", "copied JS interaction", "copied CSS animation"], + "verified_examples": [ + "/Users/bytedance/bd-projects/open-design/skills/html-ppt-retro-quarterly-review" + ], + "conversion_records": [ + { + "source_examples": ["html-ppt-retro-quarterly-review"], + "abstraction_record": "quarterly review flow with metric overview, timeline, risk callout, and closing summary", + "canvas_spec_fields": ["content.metrics", "content.events", "content.risks", "content.takeaways"], + "registry_output": { + "templates": ["metric-dashboard", "timeline-steps", "risk-alert", "summary-final"], + "themes": ["finance-dark", "warm-editorial"], + "components": ["MetricTile", "TimelineNode", "RiskBanner", "StatCard"], + "layouts": ["metric-dashboard", "timeline-horizontal", "architecture-blueprint"], + "golden_fixtures": ["metric-dashboard.canvas-spec.json", "timeline-steps.canvas-spec.json", "risk-alert.canvas-spec.json", "summary-final.canvas-spec.json"] + }, + "acceptance_rule": "all sections use arrays with explicit max_items and render in the 15-page golden regression" + } + ] + }, + { + "id": "open-design-zhangzara", + "source_path": "/Users/bytedance/bd-projects/open-design/design-templates/html-ppt-zhangzara-*", + "source_type": "design_templates", + "source_hash_or_version": "git:2aadac07c", + "license_or_provenance": "origin https://github.com/nexu-io/open-design.git; local LICENSE is Apache-2.0; used as inspiration only", + "extract_fields": ["grid system", "editorial color balance", "paper texture metaphor", "display/body contrast", "card spacing"], + "conversion_target": ["Theme Token", "Layout Archetype", "Canvas Template"], + "acceptance_rule": "dedupe strong-style packs into reusable Satori-compatible tokens and fixtures; do not count duplicate style variants", + "forbidden_usage": ["copying generated markup", "uncontrolled visual effects", "blindly copying all style packs", "texture/image runtime dependency"], + "verified_examples": [ + "/Users/bytedance/bd-projects/open-design/design-templates/html-ppt-zhangzara-editorial-tri-tone", + "/Users/bytedance/bd-projects/open-design/design-templates/html-ppt-zhangzara-cartesian", + "/Users/bytedance/bd-projects/open-design/design-templates/html-ppt-zhangzara-pin-and-paper" + ], + "conversion_records": [ + { + "source_examples": ["html-ppt-zhangzara-editorial-tri-tone"], + "abstraction_record": "editorial tri-tone palette and strong asymmetrical section page", + "canvas_spec_fields": ["content.eyebrow", "content.title", "content.subtitle"], + "registry_output": { + "templates": ["section-title", "image-feature"], + "themes": ["editorial-tritone", "paper-research"], + "components": ["SectionHeader", "DividerRule", "FigurePlaceholder"], + "layouts": ["section-divider", "image-text-split"], + "golden_fixtures": ["section-title.canvas-spec.json", "image-feature.canvas-spec.json"] + }, + "acceptance_rule": "theme colors are explicit hex tokens and fixture text remains within text_budget" + }, + { + "source_examples": ["html-ppt-zhangzara-cartesian", "html-ppt-zhangzara-pin-and-paper"], + "abstraction_record": "grid and paper-note discipline converted to bounded rows, lanes, and blueprint nodes", + "canvas_spec_fields": ["content.lanes", "content.nodes", "semantic_elements.title"], + "registry_output": { + "templates": ["roadmap-lanes", "architecture-blueprint"], + "themes": ["swiss-red", "blueprint-technical"], + "components": ["RoadmapLane", "ArchitectureNode", "DividerRule"], + "layouts": ["architecture-blueprint"], + "golden_fixtures": ["roadmap-lanes.canvas-spec.json", "architecture-blueprint.canvas-spec.json"] + }, + "acceptance_rule": "roadmap and blueprint output remains editable SVG shapes/text, not rasterized source art" + } + ] + }, + { + "id": "ppt-master-examples", + "source_path": "/Users/bytedance/bd-projects/workspaces/SVGlide/ppt-master/examples", + "source_type": "pptx_svg_examples", + "source_hash_or_version": "git:45d9a79", + "license_or_provenance": "origin https://github.com/hugohe3/ppt-master.git; local LICENSE is MIT; inspiration-only with no copied SVG/runtime dependency", + "extract_fields": ["deck rhythm", "page archetypes", "visual style rules", "palette rules", "element density", "bbox/font-size facts"], + "conversion_target": ["Canvas Template", "Theme Token", "Golden Fixture", "Quality Rule"], + "acceptance_rule": "ppt-master SVG may inform measurements but must not be emitted as final SVGlide output or imported at runtime", + "forbidden_usage": ["direct SVG runtime import", "PPT generation workflow import", "copying svg_final as live SVG", "adding ppt-master as CLI dependency"], + "verified_examples": [ + "/Users/bytedance/bd-projects/workspaces/SVGlide/ppt-master/examples/ppt169_attention_is_all_you_need/design_spec.md", + "/Users/bytedance/bd-projects/workspaces/SVGlide/ppt-master/examples/ppt169_swiss_grid_systems/design_spec.md", + "/Users/bytedance/bd-projects/workspaces/SVGlide/ppt-master/examples/ppt169_glassmorphism_demo", + "/Users/bytedance/bd-projects/workspaces/SVGlide/ppt-master/examples/ppt169_kubernetes_blueprint_2026/design_spec.md", + "/Users/bytedance/bd-projects/workspaces/SVGlide/ppt-master/examples/ppt169_brutalist_ai_newspaper_2026/design_spec.md" + ], + "conversion_records": [ + { + "source_examples": ["ppt169_attention_is_all_you_need", "ppt169_kubernetes_blueprint_2026"], + "abstraction_record": "technical narrative pages with architecture nodes, process stages, and research framing", + "canvas_spec_fields": ["content.steps", "content.nodes", "content.sections", "semantic_elements.title"], + "registry_output": { + "templates": ["process-flow", "architecture-blueprint", "research-poster"], + "themes": ["blueprint-technical", "cobalt-grid"], + "components": ["ProcessStep", "ArchitectureNode", "PosterSection"], + "layouts": ["process-flow", "architecture-blueprint", "research-poster-3col"], + "golden_fixtures": ["process-flow.canvas-spec.json", "architecture-blueprint.canvas-spec.json", "research-poster.canvas-spec.json"] + }, + "acceptance_rule": "all outputs are owned templates and CanvasSpec fixtures; raw ppt-master SVG is preview reference only" + }, + { + "source_examples": ["ppt169_swiss_grid_systems", "ppt169_glassmorphism_demo", "ppt169_brutalist_ai_newspaper_2026"], + "abstraction_record": "Swiss grid, glass neon contrast, and editorial newspaper density distilled into reusable theme tokens and comparison/data layouts", + "canvas_spec_fields": ["content.left_points", "content.right_points", "content.metrics", "content.callout"], + "registry_output": { + "templates": ["comparison-cards", "data-story", "quote-focus"], + "themes": ["swiss-red", "glass-neon", "editorial-tritone"], + "components": ["LegendDot", "BarMark", "QuoteBlock", "Callout"], + "layouts": ["two-column-comparison", "metric-dashboard"], + "golden_fixtures": ["comparison-cards.canvas-spec.json", "data-story.canvas-spec.json", "quote-focus.canvas-spec.json"] + }, + "acceptance_rule": "fixture content is short enough for text_budget and renderer uses Satori-compatible static style only" + } + ] + }, + { + "id": "postergen", + "source_path": "/Users/bytedance/bd-projects/workspaces/SVGlide/PosterGen", + "source_type": "poster_generation_rules", + "source_hash_or_version": "git:8a54325", + "license_or_provenance": "origin https://github.com/Y-Research-SBU/PosterGen.git; local LICENSE is MIT; used as inspiration only", + "extract_fields": ["three-column poster", "title area ratio", "section balance", "logo-aware palette", "font-size constraints", "text-height measurement"], + "conversion_target": ["Research Poster Template", "Quality Rule", "Layout Archetype", "Theme Token"], + "acceptance_rule": "poster heuristics must be encoded as bounded sections, max_items, text_budget, and golden fixture render checks", + "forbidden_usage": ["LangGraph workflow runtime", "Python poster agents as production dependency", "GPT image generation workflow import", "web UI runtime import"], + "verified_examples": [ + "/Users/bytedance/bd-projects/workspaces/SVGlide/PosterGen/config/poster_config.yaml", + "/Users/bytedance/bd-projects/workspaces/SVGlide/PosterGen/config/prompts/layout_balancer.txt", + "/Users/bytedance/bd-projects/workspaces/SVGlide/PosterGen/config/prompts/extract_color_from_figure.txt", + "/Users/bytedance/bd-projects/workspaces/SVGlide/PosterGen/config/prompts/spatial_content_planner.txt" + ], + "conversion_records": [ + { + "source_examples": ["config/poster_config.yaml", "layout_balancer.txt", "spatial_content_planner.txt"], + "abstraction_record": "research poster with three bounded columns, title/authors area, balanced sections, and explicit section max count", + "canvas_spec_fields": ["content.title", "content.authors", "content.sections", "content.key_visual"], + "registry_output": { + "templates": ["research-poster"], + "themes": ["paper-research"], + "components": ["PosterSection", "FigurePlaceholder", "SectionHeader"], + "layouts": ["research-poster-3col"], + "golden_fixtures": ["research-poster.canvas-spec.json"] + }, + "acceptance_rule": "sections max at 6, title/authors are explicit fields, and the fixture renders through the golden regression" + }, + { + "source_examples": ["extract_color_from_figure.txt"], + "abstraction_record": "color extraction idea converted to fixed theme-token selection rather than arbitrary color generation", + "canvas_spec_fields": ["theme_id", "theme.colors"], + "registry_output": { + "templates": ["image-feature", "data-story"], + "themes": ["paper-research", "glass-neon"], + "components": ["FigurePlaceholder", "BarMark"], + "layouts": ["image-text-split", "metric-dashboard"], + "golden_fixtures": ["image-feature.canvas-spec.json", "data-story.canvas-spec.json"] + }, + "acceptance_rule": "LLM/planner must choose a registered theme_id; arbitrary CSS colors are not accepted as runtime style" + } + ] + } + ], + "p1_abstractions": { + "template_count": 15, + "theme_count": 10, + "layout_archetype_count": 10, + "component_variant_count": 23, + "golden_fixture_count": 15 + } +} diff --git a/skills/lark-slides/references/svglide-plan.schema.json b/skills/lark-slides/references/svglide-plan.schema.json index ece01571..e747174f 100644 --- a/skills/lark-slides/references/svglide-plan.schema.json +++ b/skills/lark-slides/references/svglide-plan.schema.json @@ -7,6 +7,7 @@ "properties": { "route": {"const": "svglide-svg"}, "output_mode": {"const": "svglide-svg"}, + "generation_mode": {"enum": ["direct_svg", "artboard_satori"]}, "page_count": {"type": "integer", "minimum": 1}, "target_slide_count": {"type": "integer", "minimum": 1}, "fallback_policy": {"enum": ["strict-native", "auto"]}, @@ -144,6 +145,10 @@ "content_density_contract": {"type": "string", "minLength": 1}, "risk_flags": {"type": "array", "items": {"type": "string"}}, "source_policy": {"type": "string", "minLength": 1}, + "canvas_spec": { + "type": "object", + "additionalProperties": true + }, "asset_contract": {} }, "additionalProperties": true @@ -163,5 +168,112 @@ "anyOf": [ {"required": ["route"]}, {"required": ["output_mode"]} + ], + "allOf": [ + { + "if": { + "properties": { + "generation_mode": {"const": "artboard_satori"} + }, + "required": ["generation_mode"] + }, + "then": { + "properties": { + "slides": { + "items": { + "required": ["canvas_spec"], + "properties": { + "canvas_spec": { + "type": "object", + "required": ["version", "canvas", "safe_area", "template_id", "theme_id", "theme", "content", "semantic_elements", "quality_constraints"], + "properties": { + "version": {"const": "svglide-canvas-spec/v1"}, + "canvas": { + "type": "object", + "required": ["width", "height", "viewBox"], + "properties": { + "width": {"const": 960}, + "height": {"const": 540}, + "viewBox": {"const": "0 0 960 540"} + }, + "additionalProperties": true + }, + "template_id": {"type": "string", "minLength": 1}, + "theme_id": {"type": "string", "minLength": 1}, + "safe_area": { + "type": "object", + "required": ["x", "y", "width", "height"], + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + }, + "additionalProperties": true + }, + "theme": {"type": "object"}, + "content": { + "type": "object", + "required": ["title"], + "properties": { + "title": {"type": "string", "minLength": 1} + }, + "additionalProperties": true + }, + "semantic_elements": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["element_id", "kind", "role", "source_ref", "bbox"], + "properties": { + "element_id": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "role": {"type": "string", "minLength": 1}, + "source_ref": {"type": "string", "minLength": 1}, + "bbox": { + "type": "object", + "required": ["x", "y", "width", "height"], + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "quality_constraints": { + "type": "object", + "required": ["min_font_size", "safe_area"], + "properties": { + "min_font_size": {"type": "number"}, + "max_title_lines": {"type": "integer", "minimum": 1}, + "safe_area": { + "type": "object", + "required": ["x", "y", "width", "height"], + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + } + } + } + } + } + } ] } diff --git a/skills/lark-slides/references/svglide-planner-prompt-contracts.json b/skills/lark-slides/references/svglide-planner-prompt-contracts.json new file mode 100644 index 00000000..5919188d --- /dev/null +++ b/skills/lark-slides/references/svglide-planner-prompt-contracts.json @@ -0,0 +1,45 @@ +{ + "schema_version": "svglide-planner-prompt-contracts/v1", + "contracts": [ + { + "id": "deck-planner", + "stage": "deck_planner", + "prompt_path": "skills/lark-slides/prompts/svglide/deck-planner.prompt.md", + "input_bundle": ["user_topic", "audience", "target_slide_count", "source_policy", "available_template_registry", "available_theme_registry"], + "output_schema": "skills/lark-slides/references/svglide-deck-plan.schema.json", + "output_path": "02-plan/deck-plan.json", + "validation_command": "python3 skills/lark-slides/scripts/svglide_planner_contracts.py ", + "forbidden_outputs": ["free_html", "free_css", "free_svg", "jsx_or_tsx", "markdown_fence", "base64_image_data"] + }, + { + "id": "slide-planner", + "stage": "slide_planner", + "prompt_path": "skills/lark-slides/prompts/svglide/slide-planner.prompt.md", + "input_bundle": ["deck-plan.json", "svglide-template-registry.json", "themes/registry.json", "svglide-layout-archetypes.json", "svglide-component-registry.json"], + "output_schema": "skills/lark-slides/references/svglide-slide-plan.schema.json", + "output_path": "02-plan/slide-plan.json", + "validation_command": "python3 skills/lark-slides/scripts/svglide_planner_contracts.py ", + "forbidden_outputs": ["free_html", "free_css", "free_svg", "jsx_or_tsx", "markdown_fence", "unregistered_template_id"] + }, + { + "id": "canvas-planner", + "stage": "canvas_planner", + "prompt_path": "skills/lark-slides/prompts/svglide/canvas-planner.prompt.md", + "input_bundle": ["slide-plan.json", "svglide-template-registry.json", "themes/registry.json", "svglide-canvas-spec.schema.json", "golden CanvasSpec examples"], + "output_schema": "skills/lark-slides/references/svglide-canvas-plan.schema.json", + "output_path": "02-plan/slide_plan.json", + "validation_command": "python3 skills/lark-slides/scripts/svglide_planner_contracts.py ", + "forbidden_outputs": ["free_html", "free_css", "free_svg", "jsx_or_tsx", "markdown_fence", "raw_satori_svg"] + }, + { + "id": "repair-planner", + "stage": "repair_planner", + "prompt_path": "skills/lark-slides/prompts/svglide/repair-planner.prompt.md", + "input_bundle": ["validation receipt", "target planner JSON", "schema issue list", "template fit issue list"], + "output_schema": "skills/lark-slides/references/svglide-repair-plan.schema.json", + "output_path": "02-plan/repair-plan.json", + "validation_command": "python3 skills/lark-slides/scripts/svglide_planner_contracts.py ", + "forbidden_outputs": ["full_deck_rewrite", "free_html", "free_css", "free_svg", "markdown_fence", "unscoped_patch"] + } + ] +} diff --git a/skills/lark-slides/references/svglide-renderer-registry.json b/skills/lark-slides/references/svglide-renderer-registry.json index a0b0201a..9f321a94 100644 --- a/skills/lark-slides/references/svglide-renderer-registry.json +++ b/skills/lark-slides/references/svglide-renderer-registry.json @@ -15,6 +15,111 @@ "runtime_module": "svglide_gen_runtime.py", "allowed_style_presets": ["*"] }, + { + "id": "artboard_cover_hero", + "status": "active", + "family": "cover", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_comparison", + "status": "active", + "family": "comparison", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_section_title", + "status": "active", + "family": "closing", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.cover-hero", + "status": "active", + "family": "cover", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.comparison-cards", + "status": "active", + "family": "comparison", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.image-feature", + "status": "active", + "family": "image_text", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.data-story", + "status": "active", + "family": "data_story", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.summary-final", + "status": "active", + "family": "closing", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.agenda-list", + "status": "active", + "family": "agenda", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.timeline-steps", + "status": "active", + "family": "timeline", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.architecture-blueprint", + "status": "active", + "family": "architecture", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.process-flow", + "status": "active", + "family": "process", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.metric-dashboard", + "status": "active", + "family": "dashboard", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.risk-alert", + "status": "active", + "family": "risk", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, + { + "id": "artboard_satori.roadmap-lanes", + "status": "active", + "family": "roadmap", + "runtime_module": "svglide_artboard_renderer.py", + "allowed_style_presets": ["*"] + }, { "id": "chart", "status": "active", diff --git a/skills/lark-slides/references/svglide-repair-plan.schema.json b/skills/lark-slides/references/svglide-repair-plan.schema.json new file mode 100644 index 00000000..4cb42413 --- /dev/null +++ b/skills/lark-slides/references/svglide-repair-plan.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-repair-plan.schema.json", + "title": "SVGlide Repair Planner Output", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "target_plan_path", "change_reason", "patches"], + "properties": { + "schema_version": {"const": "svglide-repair-plan/v1"}, + "target_plan_path": {"type": "string", "minLength": 1}, + "change_reason": {"type": "string", "minLength": 1}, + "patches": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["op", "path", "reason"], + "properties": { + "op": {"enum": ["add", "replace", "remove", "test"]}, + "path": {"type": "string", "minLength": 1}, + "value": {}, + "reason": {"type": "string", "minLength": 1} + } + } + } + } +} diff --git a/skills/lark-slides/references/svglide-semantic-map.schema.json b/skills/lark-slides/references/svglide-semantic-map.schema.json new file mode 100644 index 00000000..3a1e6e7e --- /dev/null +++ b/skills/lark-slides/references/svglide-semantic-map.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-semantic-map.schema.json", + "title": "SVGlide semantic map", + "type": "object", + "required": ["version", "page", "template_id", "semantic_source", "elements"], + "additionalProperties": true, + "properties": { + "version": {"const": "svglide-semantic-map/v1"}, + "page": {"type": "integer", "minimum": 1}, + "template_id": {"type": "string", "minLength": 1}, + "theme_id": {"type": ["string", "null"]}, + "semantic_source": {"type": "string", "minLength": 1}, + "content_keys": {"type": "array", "items": {"type": "string"}}, + "elements": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["element_id", "kind", "role", "bbox"], + "additionalProperties": true, + "properties": { + "element_id": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "role": {"type": "string", "minLength": 1}, + "source_ref": {"type": ["string", "null"]}, + "text": {"type": ["string", "null"]}, + "bbox": { + "type": "object", + "required": ["x", "y", "width", "height"], + "additionalProperties": true, + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "width": {"type": "number"}, + "height": {"type": "number"} + } + } + } + } + } + } +} diff --git a/skills/lark-slides/references/svglide-slide-plan.schema.json b/skills/lark-slides/references/svglide-slide-plan.schema.json new file mode 100644 index 00000000..1c5d4611 --- /dev/null +++ b/skills/lark-slides/references/svglide-slide-plan.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-slide-plan.schema.json", + "title": "SVGlide Slide Planner Output", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "deck_plan_ref", "generation_mode", "slides"], + "properties": { + "schema_version": {"const": "svglide-slide-plan/v1"}, + "deck_plan_ref": { + "type": "object", + "additionalProperties": false, + "required": ["path"], + "properties": { + "path": {"type": "string", "minLength": 1}, + "sha256": {"type": "string", "minLength": 1} + } + }, + "generation_mode": {"const": "artboard_satori"}, + "slides": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["page", "title", "key_message", "template_id", "theme_id", "content_requirements", "visual_role", "source_policy"], + "properties": { + "page": {"type": "integer", "minimum": 1}, + "title": {"type": "string", "minLength": 1}, + "key_message": {"type": "string", "minLength": 1}, + "template_id": {"type": "string", "minLength": 1}, + "theme_id": {"type": "string", "minLength": 1}, + "content_requirements": {"type": "object", "additionalProperties": true}, + "visual_role": {"type": "string", "minLength": 1}, + "source_policy": {"type": "string", "minLength": 1} + } + } + } + } +} diff --git a/skills/lark-slides/references/svglide-svg-private.rules.json b/skills/lark-slides/references/svglide-svg-private.rules.json index cc515cd7..2abd31a7 100644 --- a/skills/lark-slides/references/svglide-svg-private.rules.json +++ b/skills/lark-slides/references/svglide-svg-private.rules.json @@ -14,6 +14,7 @@ "skills/lark-slides/references/svglide-lock.contract.md", "skills/lark-slides/references/svglide-assets.contract.md", "skills/lark-slides/references/svglide-generate-svg.contract.md", + "skills/lark-slides/references/svglide-artboard-satori.contract.md", "skills/lark-slides/references/svglide-preview.spec.md", "skills/lark-slides/references/svglide-checks.checklist.md", "skills/lark-slides/references/svglide-readback.contract.md", @@ -30,6 +31,10 @@ "skills/lark-slides/references/svglide-asset-planning.md", "skills/lark-slides/references/safe-native-v1.profile.json", "skills/lark-slides/references/svglide-plan.schema.json", + "skills/lark-slides/references/svglide-canvas-spec.schema.json", + "skills/lark-slides/references/svglide-semantic-map.schema.json", + "skills/lark-slides/references/svglide-node-layout-map.schema.json", + "skills/lark-slides/references/svglide-artboard-receipt.schema.json", "skills/lark-slides/references/svglide-strategy-review.schema.json", "skills/lark-slides/references/svglide-ppt-master-asset-map.schema.json", "skills/lark-slides/references/svglide-template-admission.schema.json", @@ -52,6 +57,12 @@ "skills/lark-slides/scripts/svg_preview_lint.py", "skills/lark-slides/scripts/svglide_source.py", "skills/lark-slides/scripts/svglide_assets.py", + "skills/lark-slides/scripts/svglide_artboard_renderer.py", + "skills/lark-slides/scripts/svglide_template_fit_check.py", + "skills/lark-slides/scripts/artboard_renderer/package.json", + "skills/lark-slides/scripts/artboard_renderer/render.mjs", + "skills/lark-slides/scripts/artboard_renderer/templates/README.md", + "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json", "skills/lark-slides/scripts/svglide_project_runner.py", "skills/lark-slides/scripts/svglide_strategy_review.py", "skills/lark-slides/scripts/svglide_prepare.py", @@ -80,6 +91,8 @@ "skills/lark-slides/references/svg-protocol.md", "skills/lark-slides/scripts/svglide_source.py", "skills/lark-slides/scripts/svglide_assets.py", + "skills/lark-slides/scripts/svglide_artboard_renderer.py", + "skills/lark-slides/scripts/svglide_template_fit_check.py", "skills/lark-slides/scripts/svglide_project_runner.py", "skills/lark-slides/scripts/svglide_strategy_review.py", "skills/lark-slides/scripts/svglide_prepare.py", @@ -111,6 +124,7 @@ "skills/lark-slides/references/svglide-lock.contract.md", "skills/lark-slides/references/svglide-assets.contract.md", "skills/lark-slides/references/svglide-generate-svg.contract.md", + "skills/lark-slides/references/svglide-artboard-satori.contract.md", "skills/lark-slides/references/svglide-preview.spec.md", "skills/lark-slides/references/svglide-checks.checklist.md", "skills/lark-slides/references/svglide-readback.contract.md", @@ -120,6 +134,10 @@ "skills/lark-slides/references/svglide-strategy-review.schema.json", "skills/lark-slides/references/svglide-ppt-master-asset-map.schema.json", "skills/lark-slides/references/svglide-template-admission.schema.json", + "skills/lark-slides/references/svglide-canvas-spec.schema.json", + "skills/lark-slides/references/svglide-semantic-map.schema.json", + "skills/lark-slides/references/svglide-node-layout-map.schema.json", + "skills/lark-slides/references/svglide-artboard-receipt.schema.json", "skills/lark-slides/references/svglide-evidence.schema.json", "skills/lark-slides/references/svglide-source-receipt.schema.json", "skills/lark-slides/references/svglide-generator-receipt.schema.json", @@ -137,6 +155,12 @@ "skills/lark-slides/references/svglide-quality-gate.schema.json", "skills/lark-slides/scripts/svglide_source.py", "skills/lark-slides/scripts/svglide_assets.py", + "skills/lark-slides/scripts/svglide_artboard_renderer.py", + "skills/lark-slides/scripts/svglide_template_fit_check.py", + "skills/lark-slides/scripts/artboard_renderer/package.json", + "skills/lark-slides/scripts/artboard_renderer/render.mjs", + "skills/lark-slides/scripts/artboard_renderer/templates/README.md", + "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json", "skills/lark-slides/scripts/svglide_project_runner.py", "skills/lark-slides/scripts/svglide_strategy_review.py", "skills/lark-slides/scripts/svglide_prepare.py", diff --git a/skills/lark-slides/references/svglide-template-registry.json b/skills/lark-slides/references/svglide-template-registry.json new file mode 100644 index 00000000..9a884d13 --- /dev/null +++ b/skills/lark-slides/references/svglide-template-registry.json @@ -0,0 +1,170 @@ +{ + "schema_version": "svglide-template-registry/v1", + "templates": [ + { + "id": "cover-hero", + "status": "active", + "renderer_id": "artboard_satori.cover-hero", + "layout_family": "cover", + "required_content": ["title"], + "optional_content": ["eyebrow", "subtitle", "chips"], + "max_items": {"chips": 4}, + "text_budget": {"eyebrow": 28, "title": 32, "subtitle": 80, "chips": 16}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "comparison-cards", + "status": "active", + "renderer_id": "artboard_satori.comparison-cards", + "layout_family": "comparison", + "required_content": ["title", "left_title", "right_title", "left_points", "right_points"], + "optional_content": ["conclusion"], + "max_items": {"left_points": 3, "right_points": 3}, + "text_budget": {"title": 36, "left_title": 16, "right_title": 16, "left_points": 22, "right_points": 22, "conclusion": 52}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "summary-final", + "status": "active", + "renderer_id": "artboard_satori.summary-final", + "layout_family": "closing", + "required_content": ["title"], + "optional_content": ["eyebrow", "subtitle", "takeaways"], + "max_items": {"takeaways": 3}, + "text_budget": {"eyebrow": 20, "title": 34, "subtitle": 76, "takeaways": 24}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "section-title", + "status": "active", + "renderer_id": "artboard_satori.section-title", + "layout_family": "section", + "required_content": ["title"], + "optional_content": ["eyebrow", "subtitle"], + "max_items": {}, + "text_budget": {"eyebrow": 24, "title": 34, "subtitle": 72}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "agenda-list", + "status": "active", + "renderer_id": "artboard_satori.agenda-list", + "layout_family": "agenda", + "required_content": ["title", "items"], + "optional_content": ["eyebrow", "subtitle"], + "max_items": {"items": 6}, + "text_budget": {"eyebrow": 24, "title": 36, "subtitle": 72, "items": 28}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "timeline-steps", + "status": "active", + "renderer_id": "artboard_satori.timeline-steps", + "layout_family": "timeline", + "required_content": ["title", "events"], + "optional_content": ["eyebrow", "subtitle"], + "max_items": {"events": 5}, + "text_budget": {"eyebrow": 24, "title": 36, "subtitle": 72, "events": 22}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "process-flow", + "status": "active", + "renderer_id": "artboard_satori.process-flow", + "layout_family": "process", + "required_content": ["title", "steps"], + "optional_content": ["eyebrow", "subtitle", "conclusion"], + "max_items": {"steps": 5}, + "text_budget": {"eyebrow": 24, "title": 36, "subtitle": 72, "steps": 24, "conclusion": 56}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "metric-dashboard", + "status": "active", + "renderer_id": "artboard_satori.metric-dashboard", + "layout_family": "dashboard", + "required_content": ["title", "metrics"], + "optional_content": ["eyebrow", "subtitle"], + "max_items": {"metrics": 6}, + "text_budget": {"eyebrow": 24, "title": 34, "subtitle": 68, "metrics": 20}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "quote-focus", + "status": "active", + "renderer_id": "artboard_satori.quote-focus", + "layout_family": "quote", + "required_content": ["title", "quote"], + "optional_content": ["attribution"], + "max_items": {}, + "text_budget": {"title": 36, "quote": 88, "attribution": 36}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "image-feature", + "status": "active", + "renderer_id": "artboard_satori.image-feature", + "layout_family": "image_text", + "required_content": ["title", "points"], + "optional_content": ["eyebrow", "subtitle", "image_label", "caption"], + "max_items": {"points": 3}, + "text_budget": {"eyebrow": 24, "title": 34, "subtitle": 64, "points": 24, "caption": 52, "image_label": 18}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "research-poster", + "status": "active", + "renderer_id": "artboard_satori.research-poster", + "layout_family": "poster", + "required_content": ["title", "sections"], + "optional_content": ["eyebrow", "authors", "key_visual"], + "max_items": {"sections": 6}, + "text_budget": {"eyebrow": 24, "title": 42, "authors": 48, "sections": 22, "key_visual": 28}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "data-story", + "status": "active", + "renderer_id": "artboard_satori.data-story", + "layout_family": "data_story", + "required_content": ["title", "metrics"], + "optional_content": ["eyebrow", "subtitle", "callout"], + "max_items": {"metrics": 4}, + "text_budget": {"eyebrow": 24, "title": 34, "subtitle": 64, "metrics": 18, "callout": 48}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "risk-alert", + "status": "active", + "renderer_id": "artboard_satori.risk-alert", + "layout_family": "risk", + "required_content": ["title", "risks"], + "optional_content": ["eyebrow", "subtitle", "summary", "severity"], + "max_items": {"risks": 4}, + "text_budget": {"eyebrow": 24, "title": 36, "subtitle": 64, "risks": 28, "summary": 52, "severity": 8}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "roadmap-lanes", + "status": "active", + "renderer_id": "artboard_satori.roadmap-lanes", + "layout_family": "roadmap", + "required_content": ["title", "lanes"], + "optional_content": ["eyebrow", "subtitle"], + "max_items": {"lanes": 4}, + "text_budget": {"eyebrow": 24, "title": 36, "subtitle": 64, "lanes": 18}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + }, + { + "id": "architecture-blueprint", + "status": "active", + "renderer_id": "artboard_satori.architecture-blueprint", + "layout_family": "architecture", + "required_content": ["title", "nodes"], + "optional_content": ["eyebrow", "subtitle"], + "max_items": {"nodes": 6}, + "text_budget": {"eyebrow": 24, "title": 38, "subtitle": 64, "nodes": 22}, + "supported_theme_ids": ["dark-clarity", "forest-signal", "warm-editorial", "blueprint-technical", "editorial-tritone", "cobalt-grid", "finance-dark", "swiss-red", "glass-neon", "paper-research"] + } + ] +} diff --git a/skills/lark-slides/references/svglide-theme-spec.schema.json b/skills/lark-slides/references/svglide-theme-spec.schema.json new file mode 100644 index 00000000..4be9e3aa --- /dev/null +++ b/skills/lark-slides/references/svglide-theme-spec.schema.json @@ -0,0 +1,113 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://larksuite.local/svglide-theme-spec.schema.json", + "title": "SVGlide ThemeSpec", + "type": "object", + "required": [ + "schema_version", + "theme_id", + "mode", + "colors", + "semantic_colors", + "tokens", + "contrast", + "allowed_color_roles" + ], + "additionalProperties": true, + "properties": { + "schema_version": {"const": "svglide-theme/v1"}, + "theme_id": {"type": "string", "minLength": 1}, + "mode": {"enum": ["light", "dark"]}, + "colors": { + "type": "object", + "required": [ + "background", + "surface", + "text", + "muted", + "primary", + "accent", + "success", + "warning", + "danger" + ], + "additionalProperties": {"$ref": "#/$defs/hexColor"}, + "properties": { + "background": {"$ref": "#/$defs/hexColor"}, + "surface": {"$ref": "#/$defs/hexColor"}, + "text": {"$ref": "#/$defs/hexColor"}, + "muted": {"$ref": "#/$defs/hexColor"}, + "primary": {"$ref": "#/$defs/hexColor"}, + "accent": {"$ref": "#/$defs/hexColor"}, + "success": {"$ref": "#/$defs/hexColor"}, + "warning": {"$ref": "#/$defs/hexColor"}, + "danger": {"$ref": "#/$defs/hexColor"} + } + }, + "semantic_colors": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "anyOf": [ + {"$ref": "#/$defs/hexColor"}, + {"type": "string", "minLength": 1} + ] + } + }, + "tokens": { + "type": "object", + "required": [ + "color.background", + "color.surface", + "color.text", + "color.muted", + "color.primary", + "color.accent", + "color.success", + "color.warning", + "color.danger" + ], + "additionalProperties": { + "anyOf": [ + {"$ref": "#/$defs/hexColor"}, + {"type": "string", "minLength": 1} + ] + }, + "properties": { + "color.background": {"$ref": "#/$defs/hexColor"}, + "color.surface": {"$ref": "#/$defs/hexColor"}, + "color.text": {"$ref": "#/$defs/hexColor"}, + "color.muted": {"$ref": "#/$defs/hexColor"}, + "color.primary": {"$ref": "#/$defs/hexColor"}, + "color.accent": {"$ref": "#/$defs/hexColor"}, + "color.success": {"$ref": "#/$defs/hexColor"}, + "color.warning": {"$ref": "#/$defs/hexColor"}, + "color.danger": {"$ref": "#/$defs/hexColor"} + } + }, + "contrast": { + "type": "object", + "required": ["min_text_contrast"], + "additionalProperties": true, + "properties": { + "min_text_contrast": {"type": "number"} + } + }, + "allowed_color_roles": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"type": "string", "minLength": 1} + }, + "data_series": { + "type": "array", + "items": {"$ref": "#/$defs/hexColor"} + } + }, + "$defs": { + "hexColor": { + "type": "string", + "pattern": "^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$" + } + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/components/primitives.mjs b/skills/lark-slides/scripts/artboard_renderer/components/primitives.mjs new file mode 100644 index 00000000..b96ac2dd --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/components/primitives.mjs @@ -0,0 +1,96 @@ +export function node(type, style, children) { + return { type, props: { style, children } } +} + +export function box(style, children = []) { + return node('div', { display: 'flex', boxSizing: 'border-box', ...style }, children) +} + +export function TextBlock(value, style = {}) { + return node( + 'div', + { + display: 'flex', + boxSizing: 'border-box', + whiteSpace: 'normal', + ...style + }, + value + ) +} + +export function Title(value, style = {}) { + return TextBlock(value, { + fontSize: 58, + fontWeight: 800, + lineHeight: 1.05, + ...style + }) +} + +export function Subtitle(value, style = {}) { + return TextBlock(value, { + fontSize: 24, + fontWeight: 500, + lineHeight: 1.25, + ...style + }) +} + +export function Badge(value, style = {}) { + return TextBlock(value, { + fontSize: 18, + fontWeight: 700, + ...style + }) +} + +export function Chip(value, style = {}) { + return TextBlock(value, { + minWidth: 92, + height: 40, + padding: '8px 15px', + fontSize: 17, + fontWeight: 600, + ...style + }) +} + +export function StatCard({ index, label, color, textColor, panelColor, style = {} }) { + return box( + { + width: 250, + minHeight: 126, + flexDirection: 'column', + backgroundColor: panelColor, + padding: 22, + ...style + }, + [ + TextBlock(String(index).padStart(2, '0'), { + color, + fontSize: 18, + fontWeight: 800, + marginBottom: 12 + }), + TextBlock(label, { + color: textColor, + fontSize: 21, + fontWeight: 700, + lineHeight: 1.18 + }) + ] + ) +} + +export function ImageFrame({ children = [], style = {} }) { + return box( + { + position: 'relative', + overflow: 'hidden', + backgroundColor: 'rgba(255,255,255,0.08)', + ...style + }, + children + ) +} diff --git a/skills/lark-slides/scripts/artboard_renderer/dist/render.mjs b/skills/lark-slides/scripts/artboard_renderer/dist/render.mjs new file mode 100644 index 00000000..b1b8e4e5 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/dist/render.mjs @@ -0,0 +1,20727 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn2, res, err) => function __init() { + if (err) throw err[0]; + try { + return fn2 && (res = (0, fn2[__getOwnPropNames(fn2)[0]])(fn2 = 0)), res; + } catch (e) { + throw err = [e], e; + } +}; +var __commonJS = (cb, mod) => function __require() { + try { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; + } catch (e) { + throw mod = 0, e; + } +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// node_modules/.pnpm/tiny-inflate@1.0.3/node_modules/tiny-inflate/index.js +var require_tiny_inflate = __commonJS({ + "node_modules/.pnpm/tiny-inflate@1.0.3/node_modules/tiny-inflate/index.js"(exports, module) { + var TINF_OK = 0; + var TINF_DATA_ERROR = -3; + function Tree() { + this.table = new Uint16Array(16); + this.trans = new Uint16Array(288); + } + function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + this.dest = dest; + this.destLen = 0; + this.ltree = new Tree(); + this.dtree = new Tree(); + } + var sltree = new Tree(); + var sdtree = new Tree(); + var length_bits = new Uint8Array(30); + var length_base = new Uint16Array(30); + var dist_bits = new Uint8Array(30); + var dist_base = new Uint16Array(30); + var clcidx = new Uint8Array([ + 16, + 17, + 18, + 0, + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15 + ]); + var code_tree = new Tree(); + var lengths = new Uint8Array(288 + 32); + function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + for (i = 0; i < delta; ++i) bits[i] = 0; + for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0; + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } + } + function tinf_build_fixed_trees(lt, dt) { + var i; + for (i = 0; i < 7; ++i) lt.table[i] = 0; + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i; + for (i = 0; i < 144; ++i) lt.trans[24 + i] = i; + for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i; + for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i; + for (i = 0; i < 5; ++i) dt.table[i] = 0; + dt.table[5] = 32; + for (i = 0; i < 32; ++i) dt.trans[i] = i; + } + var offs = new Uint16Array(16); + function tinf_build_tree(t, lengths2, off, num) { + var i, sum; + for (i = 0; i < 16; ++i) t.table[i] = 0; + for (i = 0; i < num; ++i) t.table[lengths2[off + i]]++; + t.table[0] = 0; + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + for (i = 0; i < num; ++i) { + if (lengths2[off + i]) t.trans[offs[lengths2[off + i]]++] = i; + } + } + function tinf_getbit(d2) { + if (!d2.bitcount--) { + d2.tag = d2.source[d2.sourceIndex++]; + d2.bitcount = 7; + } + var bit = d2.tag & 1; + d2.tag >>>= 1; + return bit; + } + function tinf_read_bits(d2, num, base) { + if (!num) + return base; + while (d2.bitcount < 24) { + d2.tag |= d2.source[d2.sourceIndex++] << d2.bitcount; + d2.bitcount += 8; + } + var val = d2.tag & 65535 >>> 16 - num; + d2.tag >>>= num; + d2.bitcount -= num; + return val + base; + } + function tinf_decode_symbol(d2, t) { + while (d2.bitcount < 24) { + d2.tag |= d2.source[d2.sourceIndex++] << d2.bitcount; + d2.bitcount += 8; + } + var sum = 0, cur = 0, len = 0; + var tag = d2.tag; + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + d2.tag = tag; + d2.bitcount -= len; + return t.trans[sum + cur]; + } + function tinf_decode_trees(d2, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + hlit = tinf_read_bits(d2, 5, 257); + hdist = tinf_read_bits(d2, 5, 1); + hclen = tinf_read_bits(d2, 4, 4); + for (i = 0; i < 19; ++i) lengths[i] = 0; + for (i = 0; i < hclen; ++i) { + var clen = tinf_read_bits(d2, 3, 0); + lengths[clcidx[i]] = clen; + } + tinf_build_tree(code_tree, lengths, 0, 19); + for (num = 0; num < hlit + hdist; ) { + var sym = tinf_decode_symbol(d2, code_tree); + switch (sym) { + case 16: + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d2, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + for (length = tinf_read_bits(d2, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + for (length = tinf_read_bits(d2, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + lengths[num++] = sym; + break; + } + } + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); + } + function tinf_inflate_block_data(d2, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d2, lt); + if (sym === 256) { + return TINF_OK; + } + if (sym < 256) { + d2.dest[d2.destLen++] = sym; + } else { + var length, dist, offs2; + var i; + sym -= 257; + length = tinf_read_bits(d2, length_bits[sym], length_base[sym]); + dist = tinf_decode_symbol(d2, dt); + offs2 = d2.destLen - tinf_read_bits(d2, dist_bits[dist], dist_base[dist]); + for (i = offs2; i < offs2 + length; ++i) { + d2.dest[d2.destLen++] = d2.dest[i]; + } + } + } + } + function tinf_inflate_uncompressed_block(d2) { + var length, invlength; + var i; + while (d2.bitcount > 8) { + d2.sourceIndex--; + d2.bitcount -= 8; + } + length = d2.source[d2.sourceIndex + 1]; + length = 256 * length + d2.source[d2.sourceIndex]; + invlength = d2.source[d2.sourceIndex + 3]; + invlength = 256 * invlength + d2.source[d2.sourceIndex + 2]; + if (length !== (~invlength & 65535)) + return TINF_DATA_ERROR; + d2.sourceIndex += 4; + for (i = length; i; --i) + d2.dest[d2.destLen++] = d2.source[d2.sourceIndex++]; + d2.bitcount = 0; + return TINF_OK; + } + function tinf_uncompress(source, dest) { + var d2 = new Data(source, dest); + var bfinal, btype, res; + do { + bfinal = tinf_getbit(d2); + btype = tinf_read_bits(d2, 2, 0); + switch (btype) { + case 0: + res = tinf_inflate_uncompressed_block(d2); + break; + case 1: + res = tinf_inflate_block_data(d2, sltree, sdtree); + break; + case 2: + tinf_decode_trees(d2, d2.ltree, d2.dtree); + res = tinf_inflate_block_data(d2, d2.ltree, d2.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + if (res !== TINF_OK) + throw new Error("Data error"); + } while (!bfinal); + if (d2.destLen < d2.dest.length) { + if (typeof d2.dest.slice === "function") + return d2.dest.slice(0, d2.destLen); + else + return d2.dest.subarray(0, d2.destLen); + } + return d2.dest; + } + tinf_build_fixed_trees(sltree, sdtree); + tinf_build_bits_base(length_bits, length_base, 4, 3); + tinf_build_bits_base(dist_bits, dist_base, 2, 1); + length_bits[28] = 0; + length_base[28] = 258; + module.exports = tinf_uncompress; + } +}); + +// node_modules/.pnpm/unicode-trie@2.0.0/node_modules/unicode-trie/swap.js +var require_swap = __commonJS({ + "node_modules/.pnpm/unicode-trie@2.0.0/node_modules/unicode-trie/swap.js"(exports, module) { + var isBigEndian = new Uint8Array(new Uint32Array([305419896]).buffer)[0] === 18; + var swap = (b, n, m2) => { + let i = b[n]; + b[n] = b[m2]; + b[m2] = i; + }; + var swap32 = (array) => { + const len = array.length; + for (let i = 0; i < len; i += 4) { + swap(array, i, i + 3); + swap(array, i + 1, i + 2); + } + }; + var swap32LE = (array) => { + if (isBigEndian) { + swap32(array); + } + }; + module.exports = { + swap32LE + }; + } +}); + +// node_modules/.pnpm/unicode-trie@2.0.0/node_modules/unicode-trie/index.js +var require_unicode_trie = __commonJS({ + "node_modules/.pnpm/unicode-trie@2.0.0/node_modules/unicode-trie/index.js"(exports, module) { + var inflate = require_tiny_inflate(); + var { swap32LE } = require_swap(); + var SHIFT_1 = 6 + 5; + var SHIFT_2 = 5; + var SHIFT_1_2 = SHIFT_1 - SHIFT_2; + var OMITTED_BMP_INDEX_1_LENGTH = 65536 >> SHIFT_1; + var INDEX_2_BLOCK_LENGTH = 1 << SHIFT_1_2; + var INDEX_2_MASK = INDEX_2_BLOCK_LENGTH - 1; + var INDEX_SHIFT = 2; + var DATA_BLOCK_LENGTH = 1 << SHIFT_2; + var DATA_MASK = DATA_BLOCK_LENGTH - 1; + var LSCP_INDEX_2_OFFSET = 65536 >> SHIFT_2; + var LSCP_INDEX_2_LENGTH = 1024 >> SHIFT_2; + var INDEX_2_BMP_LENGTH = LSCP_INDEX_2_OFFSET + LSCP_INDEX_2_LENGTH; + var UTF8_2B_INDEX_2_OFFSET = INDEX_2_BMP_LENGTH; + var UTF8_2B_INDEX_2_LENGTH = 2048 >> 6; + var INDEX_1_OFFSET = UTF8_2B_INDEX_2_OFFSET + UTF8_2B_INDEX_2_LENGTH; + var DATA_GRANULARITY = 1 << INDEX_SHIFT; + var UnicodeTrie = class { + constructor(data) { + const isBuffer = typeof data.readUInt32BE === "function" && typeof data.slice === "function"; + if (isBuffer || data instanceof Uint8Array) { + let uncompressedLength; + if (isBuffer) { + this.highStart = data.readUInt32LE(0); + this.errorValue = data.readUInt32LE(4); + uncompressedLength = data.readUInt32LE(8); + data = data.slice(12); + } else { + const view = new DataView(data.buffer); + this.highStart = view.getUint32(0, true); + this.errorValue = view.getUint32(4, true); + uncompressedLength = view.getUint32(8, true); + data = data.subarray(12); + } + data = inflate(data, new Uint8Array(uncompressedLength)); + data = inflate(data, new Uint8Array(uncompressedLength)); + swap32LE(data); + this.data = new Uint32Array(data.buffer); + } else { + ({ data: this.data, highStart: this.highStart, errorValue: this.errorValue } = data); + } + } + get(codePoint) { + let index; + if (codePoint < 0 || codePoint > 1114111) { + return this.errorValue; + } + if (codePoint < 55296 || codePoint > 56319 && codePoint <= 65535) { + index = (this.data[codePoint >> SHIFT_2] << INDEX_SHIFT) + (codePoint & DATA_MASK); + return this.data[index]; + } + if (codePoint <= 65535) { + index = (this.data[LSCP_INDEX_2_OFFSET + (codePoint - 55296 >> SHIFT_2)] << INDEX_SHIFT) + (codePoint & DATA_MASK); + return this.data[index]; + } + if (codePoint < this.highStart) { + index = this.data[INDEX_1_OFFSET - OMITTED_BMP_INDEX_1_LENGTH + (codePoint >> SHIFT_1)]; + index = this.data[index + (codePoint >> SHIFT_2 & INDEX_2_MASK)]; + index = (index << INDEX_SHIFT) + (codePoint & DATA_MASK); + return this.data[index]; + } + return this.data[this.data.length - DATA_GRANULARITY]; + } + }; + module.exports = UnicodeTrie; + } +}); + +// node_modules/.pnpm/base64-js@0.0.8/node_modules/base64-js/lib/b64.js +var require_b64 = __commonJS({ + "node_modules/.pnpm/base64-js@0.0.8/node_modules/base64-js/lib/b64.js"(exports) { + var lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + (function(exports2) { + "use strict"; + var Arr = typeof Uint8Array !== "undefined" ? Uint8Array : Array; + var PLUS = "+".charCodeAt(0); + var SLASH = "/".charCodeAt(0); + var NUMBER = "0".charCodeAt(0); + var LOWER = "a".charCodeAt(0); + var UPPER = "A".charCodeAt(0); + var PLUS_URL_SAFE = "-".charCodeAt(0); + var SLASH_URL_SAFE = "_".charCodeAt(0); + function decode(elt) { + var code = elt.charCodeAt(0); + if (code === PLUS || code === PLUS_URL_SAFE) + return 62; + if (code === SLASH || code === SLASH_URL_SAFE) + return 63; + if (code < NUMBER) + return -1; + if (code < NUMBER + 10) + return code - NUMBER + 26 + 26; + if (code < UPPER + 26) + return code - UPPER; + if (code < LOWER + 26) + return code - LOWER + 26; + } + function b64ToByteArray(b64) { + var i, j, l2, tmp, placeHolders, arr; + if (b64.length % 4 > 0) { + throw new Error("Invalid string. Length must be a multiple of 4"); + } + var len = b64.length; + placeHolders = "=" === b64.charAt(len - 2) ? 2 : "=" === b64.charAt(len - 1) ? 1 : 0; + arr = new Arr(b64.length * 3 / 4 - placeHolders); + l2 = placeHolders > 0 ? b64.length - 4 : b64.length; + var L = 0; + function push(v2) { + arr[L++] = v2; + } + for (i = 0, j = 0; i < l2; i += 4, j += 3) { + tmp = decode(b64.charAt(i)) << 18 | decode(b64.charAt(i + 1)) << 12 | decode(b64.charAt(i + 2)) << 6 | decode(b64.charAt(i + 3)); + push((tmp & 16711680) >> 16); + push((tmp & 65280) >> 8); + push(tmp & 255); + } + if (placeHolders === 2) { + tmp = decode(b64.charAt(i)) << 2 | decode(b64.charAt(i + 1)) >> 4; + push(tmp & 255); + } else if (placeHolders === 1) { + tmp = decode(b64.charAt(i)) << 10 | decode(b64.charAt(i + 1)) << 4 | decode(b64.charAt(i + 2)) >> 2; + push(tmp >> 8 & 255); + push(tmp & 255); + } + return arr; + } + function uint8ToBase64(uint8) { + var i, extraBytes = uint8.length % 3, output = "", temp, length; + function encode(num) { + return lookup.charAt(num); + } + function tripletToBase64(num) { + return encode(num >> 18 & 63) + encode(num >> 12 & 63) + encode(num >> 6 & 63) + encode(num & 63); + } + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + uint8[i + 2]; + output += tripletToBase64(temp); + } + switch (extraBytes) { + case 1: + temp = uint8[uint8.length - 1]; + output += encode(temp >> 2); + output += encode(temp << 4 & 63); + output += "=="; + break; + case 2: + temp = (uint8[uint8.length - 2] << 8) + uint8[uint8.length - 1]; + output += encode(temp >> 10); + output += encode(temp >> 4 & 63); + output += encode(temp << 2 & 63); + output += "="; + break; + } + return output; + } + exports2.toByteArray = b64ToByteArray; + exports2.fromByteArray = uint8ToBase64; + })(typeof exports === "undefined" ? exports.base64js = {} : exports); + } +}); + +// node_modules/.pnpm/linebreak@1.1.0/node_modules/linebreak/dist/module.mjs +var import_unicode_trie, import_base64_js, $557adaaeb0c7885f$exports, $1627905f8be2ef3f$export$fb4028874a74450, $1627905f8be2ef3f$export$1bb1140fe1358b00, $1627905f8be2ef3f$export$f3e416a182673355, $1627905f8be2ef3f$export$24aa617c849a894a, $1627905f8be2ef3f$export$a73c4d14459b698d, $1627905f8be2ef3f$export$9e5d732f3676a9ba, $1627905f8be2ef3f$export$1dff41d5c0caca01, $1627905f8be2ef3f$export$30a74a373318dec6, $1627905f8be2ef3f$export$d710c5f50fc7496a, $1627905f8be2ef3f$export$66498d28055820a9, $1627905f8be2ef3f$export$eb6c6d0b7c8826f2, $1627905f8be2ef3f$export$de92be486109a1df, $1627905f8be2ef3f$export$606cfc2a8896c91f, $1627905f8be2ef3f$export$e51d3c675bb0140d, $1627905f8be2ef3f$export$da51c6332ad11d7b, $1627905f8be2ef3f$export$bea437c40441867d, $1627905f8be2ef3f$export$c4c7eecbfed13dc9, $1627905f8be2ef3f$export$98e1f8a379849661, $32627af916ac1b00$export$98f50d781a474745, $32627af916ac1b00$export$12ee1f8f5315ca7e, $32627af916ac1b00$export$e4965ce242860454, $32627af916ac1b00$export$8f14048969dcd45e, $32627af916ac1b00$export$133eb141bf58aff4, $32627af916ac1b00$export$5bdb8ccbf5c57afc, $557adaaeb0c7885f$var$data, $557adaaeb0c7885f$var$classTrie, $557adaaeb0c7885f$var$mapClass, $557adaaeb0c7885f$var$mapFirst, $557adaaeb0c7885f$var$Break, $557adaaeb0c7885f$var$LineBreaker; +var init_module = __esm({ + "node_modules/.pnpm/linebreak@1.1.0/node_modules/linebreak/dist/module.mjs"() { + import_unicode_trie = __toESM(require_unicode_trie(), 1); + import_base64_js = __toESM(require_b64(), 1); + $557adaaeb0c7885f$exports = {}; + $1627905f8be2ef3f$export$fb4028874a74450 = 5; + $1627905f8be2ef3f$export$1bb1140fe1358b00 = 12; + $1627905f8be2ef3f$export$f3e416a182673355 = 13; + $1627905f8be2ef3f$export$24aa617c849a894a = 16; + $1627905f8be2ef3f$export$a73c4d14459b698d = 17; + $1627905f8be2ef3f$export$9e5d732f3676a9ba = 22; + $1627905f8be2ef3f$export$1dff41d5c0caca01 = 28; + $1627905f8be2ef3f$export$30a74a373318dec6 = 31; + $1627905f8be2ef3f$export$d710c5f50fc7496a = 33; + $1627905f8be2ef3f$export$66498d28055820a9 = 34; + $1627905f8be2ef3f$export$eb6c6d0b7c8826f2 = 35; + $1627905f8be2ef3f$export$de92be486109a1df = 36; + $1627905f8be2ef3f$export$606cfc2a8896c91f = 37; + $1627905f8be2ef3f$export$e51d3c675bb0140d = 38; + $1627905f8be2ef3f$export$da51c6332ad11d7b = 39; + $1627905f8be2ef3f$export$bea437c40441867d = 40; + $1627905f8be2ef3f$export$c4c7eecbfed13dc9 = 41; + $1627905f8be2ef3f$export$98e1f8a379849661 = 42; + $32627af916ac1b00$export$98f50d781a474745 = 0; + $32627af916ac1b00$export$12ee1f8f5315ca7e = 1; + $32627af916ac1b00$export$e4965ce242860454 = 2; + $32627af916ac1b00$export$8f14048969dcd45e = 3; + $32627af916ac1b00$export$133eb141bf58aff4 = 4; + $32627af916ac1b00$export$5bdb8ccbf5c57afc = [ + //OP , CL , CP , QU , GL , NS , EX , SY , IS , PR , PO , NU , AL , HL , ID , IN , HY , BA , BB , B2 , ZW , CM , WJ , H2 , H3 , JL , JV , JT , RI , EB , EM , ZWJ , CB + [ + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$8f14048969dcd45e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ], + [ + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$e4965ce242860454, + $32627af916ac1b00$export$133eb141bf58aff4, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$98f50d781a474745, + $32627af916ac1b00$export$12ee1f8f5315ca7e, + $32627af916ac1b00$export$98f50d781a474745 + ] + // CB + ]; + $557adaaeb0c7885f$var$data = import_base64_js.default.toByteArray("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"); + $557adaaeb0c7885f$var$classTrie = new import_unicode_trie.default($557adaaeb0c7885f$var$data); + $557adaaeb0c7885f$var$mapClass = function(c2) { + switch (c2) { + case $1627905f8be2ef3f$export$d710c5f50fc7496a: + return $1627905f8be2ef3f$export$1bb1140fe1358b00; + case $1627905f8be2ef3f$export$da51c6332ad11d7b: + case $1627905f8be2ef3f$export$bea437c40441867d: + case $1627905f8be2ef3f$export$98e1f8a379849661: + return $1627905f8be2ef3f$export$1bb1140fe1358b00; + case $1627905f8be2ef3f$export$eb6c6d0b7c8826f2: + return $1627905f8be2ef3f$export$fb4028874a74450; + default: + return c2; + } + }; + $557adaaeb0c7885f$var$mapFirst = function(c2) { + switch (c2) { + case $1627905f8be2ef3f$export$606cfc2a8896c91f: + case $1627905f8be2ef3f$export$e51d3c675bb0140d: + return $1627905f8be2ef3f$export$66498d28055820a9; + case $1627905f8be2ef3f$export$c4c7eecbfed13dc9: + return $1627905f8be2ef3f$export$9e5d732f3676a9ba; + default: + return c2; + } + }; + $557adaaeb0c7885f$var$Break = class { + constructor(position, required = false) { + this.position = position; + this.required = required; + } + }; + $557adaaeb0c7885f$var$LineBreaker = class { + nextCodePoint() { + const code = this.string.charCodeAt(this.pos++); + const next = this.string.charCodeAt(this.pos); + if (55296 <= code && code <= 56319 && 56320 <= next && next <= 57343) { + this.pos++; + return (code - 55296) * 1024 + (next - 56320) + 65536; + } + return code; + } + nextCharClass() { + return $557adaaeb0c7885f$var$mapClass($557adaaeb0c7885f$var$classTrie.get(this.nextCodePoint())); + } + getSimpleBreak() { + switch (this.nextClass) { + case $1627905f8be2ef3f$export$c4c7eecbfed13dc9: + return false; + case $1627905f8be2ef3f$export$66498d28055820a9: + case $1627905f8be2ef3f$export$606cfc2a8896c91f: + case $1627905f8be2ef3f$export$e51d3c675bb0140d: + this.curClass = $1627905f8be2ef3f$export$66498d28055820a9; + return false; + case $1627905f8be2ef3f$export$de92be486109a1df: + this.curClass = $1627905f8be2ef3f$export$de92be486109a1df; + return false; + } + return null; + } + getPairTableBreak(lastClass) { + let shouldBreak = false; + switch ($32627af916ac1b00$export$5bdb8ccbf5c57afc[this.curClass][this.nextClass]) { + case $32627af916ac1b00$export$98f50d781a474745: + shouldBreak = true; + break; + case $32627af916ac1b00$export$12ee1f8f5315ca7e: + shouldBreak = lastClass === $1627905f8be2ef3f$export$c4c7eecbfed13dc9; + break; + case $32627af916ac1b00$export$e4965ce242860454: + shouldBreak = lastClass === $1627905f8be2ef3f$export$c4c7eecbfed13dc9; + if (!shouldBreak) { + shouldBreak = false; + return shouldBreak; + } + break; + case $32627af916ac1b00$export$8f14048969dcd45e: + if (lastClass !== $1627905f8be2ef3f$export$c4c7eecbfed13dc9) return shouldBreak; + break; + case $32627af916ac1b00$export$133eb141bf58aff4: + break; + } + if (this.LB8a) shouldBreak = false; + if (this.LB21a && (this.curClass === $1627905f8be2ef3f$export$24aa617c849a894a || this.curClass === $1627905f8be2ef3f$export$a73c4d14459b698d)) { + shouldBreak = false; + this.LB21a = false; + } else this.LB21a = this.curClass === $1627905f8be2ef3f$export$f3e416a182673355; + if (this.curClass === $1627905f8be2ef3f$export$1dff41d5c0caca01) { + this.LB30a++; + if (this.LB30a == 2 && this.nextClass === $1627905f8be2ef3f$export$1dff41d5c0caca01) { + shouldBreak = true; + this.LB30a = 0; + } + } else this.LB30a = 0; + this.curClass = this.nextClass; + return shouldBreak; + } + nextBreak() { + if (this.curClass == null) { + let firstClass = this.nextCharClass(); + this.curClass = $557adaaeb0c7885f$var$mapFirst(firstClass); + this.nextClass = firstClass; + this.LB8a = firstClass === $1627905f8be2ef3f$export$30a74a373318dec6; + this.LB30a = 0; + } + while (this.pos < this.string.length) { + this.lastPos = this.pos; + const lastClass = this.nextClass; + this.nextClass = this.nextCharClass(); + if (this.curClass === $1627905f8be2ef3f$export$66498d28055820a9 || this.curClass === $1627905f8be2ef3f$export$de92be486109a1df && this.nextClass !== $1627905f8be2ef3f$export$606cfc2a8896c91f) { + this.curClass = $557adaaeb0c7885f$var$mapFirst($557adaaeb0c7885f$var$mapClass(this.nextClass)); + return new $557adaaeb0c7885f$var$Break(this.lastPos, true); + } + let shouldBreak = this.getSimpleBreak(); + if (shouldBreak === null) shouldBreak = this.getPairTableBreak(lastClass); + this.LB8a = this.nextClass === $1627905f8be2ef3f$export$30a74a373318dec6; + if (shouldBreak) return new $557adaaeb0c7885f$var$Break(this.lastPos); + } + if (this.lastPos < this.string.length) { + this.lastPos = this.string.length; + return new $557adaaeb0c7885f$var$Break(this.string.length); + } + return null; + } + constructor(string) { + this.string = string; + this.pos = 0; + this.lastPos = 0; + this.curClass = null; + this.nextClass = null; + this.LB8a = false; + this.LB21a = false; + this.LB30a = 0; + } + }; + $557adaaeb0c7885f$exports = $557adaaeb0c7885f$var$LineBreaker; + } +}); + +// node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/parse.js +var require_parse = __commonJS({ + "node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/parse.js"(exports, module) { + var openParentheses = "(".charCodeAt(0); + var closeParentheses = ")".charCodeAt(0); + var singleQuote = "'".charCodeAt(0); + var doubleQuote = '"'.charCodeAt(0); + var backslash = "\\".charCodeAt(0); + var slash = "/".charCodeAt(0); + var comma = ",".charCodeAt(0); + var colon = ":".charCodeAt(0); + var star = "*".charCodeAt(0); + var uLower = "u".charCodeAt(0); + var uUpper = "U".charCodeAt(0); + var plus = "+".charCodeAt(0); + var isUnicodeRange = /^[a-f0-9?-]+$/i; + module.exports = function(input) { + var tokens = []; + var value = input; + var next, quote, prev, token, escape, escapePos, whitespacePos, parenthesesOpenPos; + var pos = 0; + var code = value.charCodeAt(pos); + var max = value.length; + var stack = [{ nodes: tokens }]; + var balanced = 0; + var parent; + var name = ""; + var before = ""; + var after = ""; + while (pos < max) { + if (code <= 32) { + next = pos; + do { + next += 1; + code = value.charCodeAt(next); + } while (code <= 32); + token = value.slice(pos, next); + prev = tokens[tokens.length - 1]; + if (code === closeParentheses && balanced) { + after = token; + } else if (prev && prev.type === "div") { + prev.after = token; + prev.sourceEndIndex += token.length; + } else if (code === comma || code === colon || code === slash && value.charCodeAt(next + 1) !== star && (!parent || parent && parent.type === "function" && parent.value !== "calc")) { + before = token; + } else { + tokens.push({ + type: "space", + sourceIndex: pos, + sourceEndIndex: next, + value: token + }); + } + pos = next; + } else if (code === singleQuote || code === doubleQuote) { + next = pos; + quote = code === singleQuote ? "'" : '"'; + token = { + type: "string", + sourceIndex: pos, + quote + }; + do { + escape = false; + next = value.indexOf(quote, next + 1); + if (~next) { + escapePos = next; + while (value.charCodeAt(escapePos - 1) === backslash) { + escapePos -= 1; + escape = !escape; + } + } else { + value += quote; + next = value.length - 1; + token.unclosed = true; + } + } while (escape); + token.value = value.slice(pos + 1, next); + token.sourceEndIndex = token.unclosed ? next : next + 1; + tokens.push(token); + pos = next + 1; + code = value.charCodeAt(pos); + } else if (code === slash && value.charCodeAt(pos + 1) === star) { + next = value.indexOf("*/", pos); + token = { + type: "comment", + sourceIndex: pos, + sourceEndIndex: next + 2 + }; + if (next === -1) { + token.unclosed = true; + next = value.length; + token.sourceEndIndex = next; + } + token.value = value.slice(pos + 2, next); + tokens.push(token); + pos = next + 2; + code = value.charCodeAt(pos); + } else if ((code === slash || code === star) && parent && parent.type === "function" && parent.value === "calc") { + token = value[pos]; + tokens.push({ + type: "word", + sourceIndex: pos - before.length, + sourceEndIndex: pos + token.length, + value: token + }); + pos += 1; + code = value.charCodeAt(pos); + } else if (code === slash || code === comma || code === colon) { + token = value[pos]; + tokens.push({ + type: "div", + sourceIndex: pos - before.length, + sourceEndIndex: pos + token.length, + value: token, + before, + after: "" + }); + before = ""; + pos += 1; + code = value.charCodeAt(pos); + } else if (openParentheses === code) { + next = pos; + do { + next += 1; + code = value.charCodeAt(next); + } while (code <= 32); + parenthesesOpenPos = pos; + token = { + type: "function", + sourceIndex: pos - name.length, + value: name, + before: value.slice(parenthesesOpenPos + 1, next) + }; + pos = next; + if (name === "url" && code !== singleQuote && code !== doubleQuote) { + next -= 1; + do { + escape = false; + next = value.indexOf(")", next + 1); + if (~next) { + escapePos = next; + while (value.charCodeAt(escapePos - 1) === backslash) { + escapePos -= 1; + escape = !escape; + } + } else { + value += ")"; + next = value.length - 1; + token.unclosed = true; + } + } while (escape); + whitespacePos = next; + do { + whitespacePos -= 1; + code = value.charCodeAt(whitespacePos); + } while (code <= 32); + if (parenthesesOpenPos < whitespacePos) { + if (pos !== whitespacePos + 1) { + token.nodes = [ + { + type: "word", + sourceIndex: pos, + sourceEndIndex: whitespacePos + 1, + value: value.slice(pos, whitespacePos + 1) + } + ]; + } else { + token.nodes = []; + } + if (token.unclosed && whitespacePos + 1 !== next) { + token.after = ""; + token.nodes.push({ + type: "space", + sourceIndex: whitespacePos + 1, + sourceEndIndex: next, + value: value.slice(whitespacePos + 1, next) + }); + } else { + token.after = value.slice(whitespacePos + 1, next); + token.sourceEndIndex = next; + } + } else { + token.after = ""; + token.nodes = []; + } + pos = next + 1; + token.sourceEndIndex = token.unclosed ? next : pos; + code = value.charCodeAt(pos); + tokens.push(token); + } else { + balanced += 1; + token.after = ""; + token.sourceEndIndex = pos + 1; + tokens.push(token); + stack.push(token); + tokens = token.nodes = []; + parent = token; + } + name = ""; + } else if (closeParentheses === code && balanced) { + pos += 1; + code = value.charCodeAt(pos); + parent.after = after; + parent.sourceEndIndex += after.length; + after = ""; + balanced -= 1; + stack[stack.length - 1].sourceEndIndex = pos; + stack.pop(); + parent = stack[balanced]; + tokens = parent.nodes; + } else { + next = pos; + do { + if (code === backslash) { + next += 1; + } + next += 1; + code = value.charCodeAt(next); + } while (next < max && !(code <= 32 || code === singleQuote || code === doubleQuote || code === comma || code === colon || code === slash || code === openParentheses || code === star && parent && parent.type === "function" && parent.value === "calc" || code === slash && parent.type === "function" && parent.value === "calc" || code === closeParentheses && balanced)); + token = value.slice(pos, next); + if (openParentheses === code) { + name = token; + } else if ((uLower === token.charCodeAt(0) || uUpper === token.charCodeAt(0)) && plus === token.charCodeAt(1) && isUnicodeRange.test(token.slice(2))) { + tokens.push({ + type: "unicode-range", + sourceIndex: pos, + sourceEndIndex: next, + value: token + }); + } else { + tokens.push({ + type: "word", + sourceIndex: pos, + sourceEndIndex: next, + value: token + }); + } + pos = next; + } + } + for (pos = stack.length - 1; pos; pos -= 1) { + stack[pos].unclosed = true; + stack[pos].sourceEndIndex = value.length; + } + return stack[0].nodes; + }; + } +}); + +// node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/walk.js +var require_walk = __commonJS({ + "node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/walk.js"(exports, module) { + module.exports = function walk(nodes, cb, bubble) { + var i, max, node2, result; + for (i = 0, max = nodes.length; i < max; i += 1) { + node2 = nodes[i]; + if (!bubble) { + result = cb(node2, i, nodes); + } + if (result !== false && node2.type === "function" && Array.isArray(node2.nodes)) { + walk(node2.nodes, cb, bubble); + } + if (bubble) { + cb(node2, i, nodes); + } + } + }; + } +}); + +// node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/stringify.js +var require_stringify = __commonJS({ + "node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/stringify.js"(exports, module) { + function stringifyNode(node2, custom) { + var type = node2.type; + var value = node2.value; + var buf; + var customResult; + if (custom && (customResult = custom(node2)) !== void 0) { + return customResult; + } else if (type === "word" || type === "space") { + return value; + } else if (type === "string") { + buf = node2.quote || ""; + return buf + value + (node2.unclosed ? "" : buf); + } else if (type === "comment") { + return "/*" + value + (node2.unclosed ? "" : "*/"); + } else if (type === "div") { + return (node2.before || "") + value + (node2.after || ""); + } else if (Array.isArray(node2.nodes)) { + buf = stringify(node2.nodes, custom); + if (type !== "function") { + return buf; + } + return value + "(" + (node2.before || "") + buf + (node2.after || "") + (node2.unclosed ? "" : ")"); + } + return value; + } + function stringify(nodes, custom) { + var result, i; + if (Array.isArray(nodes)) { + result = ""; + for (i = nodes.length - 1; ~i; i -= 1) { + result = stringifyNode(nodes[i], custom) + result; + } + return result; + } + return stringifyNode(nodes, custom); + } + module.exports = stringify; + } +}); + +// node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/unit.js +var require_unit = __commonJS({ + "node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/unit.js"(exports, module) { + var minus = "-".charCodeAt(0); + var plus = "+".charCodeAt(0); + var dot = ".".charCodeAt(0); + var exp = "e".charCodeAt(0); + var EXP = "E".charCodeAt(0); + function likeNumber(value) { + var code = value.charCodeAt(0); + var nextCode; + if (code === plus || code === minus) { + nextCode = value.charCodeAt(1); + if (nextCode >= 48 && nextCode <= 57) { + return true; + } + var nextNextCode = value.charCodeAt(2); + if (nextCode === dot && nextNextCode >= 48 && nextNextCode <= 57) { + return true; + } + return false; + } + if (code === dot) { + nextCode = value.charCodeAt(1); + if (nextCode >= 48 && nextCode <= 57) { + return true; + } + return false; + } + if (code >= 48 && code <= 57) { + return true; + } + return false; + } + module.exports = function(value) { + var pos = 0; + var length = value.length; + var code; + var nextCode; + var nextNextCode; + if (length === 0 || !likeNumber(value)) { + return false; + } + code = value.charCodeAt(pos); + if (code === plus || code === minus) { + pos++; + } + while (pos < length) { + code = value.charCodeAt(pos); + if (code < 48 || code > 57) { + break; + } + pos += 1; + } + code = value.charCodeAt(pos); + nextCode = value.charCodeAt(pos + 1); + if (code === dot && nextCode >= 48 && nextCode <= 57) { + pos += 2; + while (pos < length) { + code = value.charCodeAt(pos); + if (code < 48 || code > 57) { + break; + } + pos += 1; + } + } + code = value.charCodeAt(pos); + nextCode = value.charCodeAt(pos + 1); + nextNextCode = value.charCodeAt(pos + 2); + if ((code === exp || code === EXP) && (nextCode >= 48 && nextCode <= 57 || (nextCode === plus || nextCode === minus) && nextNextCode >= 48 && nextNextCode <= 57)) { + pos += nextCode === plus || nextCode === minus ? 3 : 2; + while (pos < length) { + code = value.charCodeAt(pos); + if (code < 48 || code > 57) { + break; + } + pos += 1; + } + } + return { + number: value.slice(0, pos), + unit: value.slice(pos) + }; + }; + } +}); + +// node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/index.js +var require_lib = __commonJS({ + "node_modules/.pnpm/postcss-value-parser@4.2.0/node_modules/postcss-value-parser/lib/index.js"(exports, module) { + var parse = require_parse(); + var walk = require_walk(); + var stringify = require_stringify(); + function ValueParser(value) { + if (this instanceof ValueParser) { + this.nodes = parse(value); + return this; + } + return new ValueParser(value); + } + ValueParser.prototype.toString = function() { + return Array.isArray(this.nodes) ? stringify(this.nodes) : ""; + }; + ValueParser.prototype.walk = function(cb, bubble) { + walk(this.nodes, cb, bubble); + return this; + }; + ValueParser.unit = require_unit(); + ValueParser.walk = walk; + ValueParser.stringify = stringify; + module.exports = ValueParser; + } +}); + +// node_modules/.pnpm/camelize@1.0.1/node_modules/camelize/index.js +var require_camelize = __commonJS({ + "node_modules/.pnpm/camelize@1.0.1/node_modules/camelize/index.js"(exports, module) { + "use strict"; + module.exports = function(obj) { + if (typeof obj === "string") { + return camelCase(obj); + } + return walk(obj); + }; + function walk(obj) { + if (!obj || typeof obj !== "object") { + return obj; + } + if (isDate(obj) || isRegex(obj)) { + return obj; + } + if (isArray(obj)) { + return map(obj, walk); + } + return reduce(objectKeys(obj), function(acc, key) { + var camel = camelCase(key); + acc[camel] = walk(obj[key]); + return acc; + }, {}); + } + function camelCase(str) { + return str.replace(/[_.-](\w|$)/g, function(_, x2) { + return x2.toUpperCase(); + }); + } + var isArray = Array.isArray || function(obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + var isDate = function(obj) { + return Object.prototype.toString.call(obj) === "[object Date]"; + }; + var isRegex = function(obj) { + return Object.prototype.toString.call(obj) === "[object RegExp]"; + }; + var has = Object.prototype.hasOwnProperty; + var objectKeys = Object.keys || function(obj) { + var keys = []; + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + return keys; + }; + function map(xs2, f) { + if (xs2.map) { + return xs2.map(f); + } + var res = []; + for (var i = 0; i < xs2.length; i++) { + res.push(f(xs2[i], i)); + } + return res; + } + function reduce(xs2, f, acc) { + if (xs2.reduce) { + return xs2.reduce(f, acc); + } + for (var i = 0; i < xs2.length; i++) { + acc = f(acc, xs2[i], i); + } + return acc; + } + } +}); + +// node_modules/.pnpm/css-color-keywords@1.0.0/node_modules/css-color-keywords/colors.json +var require_colors = __commonJS({ + "node_modules/.pnpm/css-color-keywords@1.0.0/node_modules/css-color-keywords/colors.json"(exports, module) { + module.exports = { + black: "#000000", + silver: "#c0c0c0", + gray: "#808080", + white: "#ffffff", + maroon: "#800000", + red: "#ff0000", + purple: "#800080", + fuchsia: "#ff00ff", + green: "#008000", + lime: "#00ff00", + olive: "#808000", + yellow: "#ffff00", + navy: "#000080", + blue: "#0000ff", + teal: "#008080", + aqua: "#00ffff", + orange: "#ffa500", + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + blanchedalmond: "#ffebcd", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + limegreen: "#32cd32", + linen: "#faf0e6", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + oldlace: "#fdf5e6", + olivedrab: "#6b8e23", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + whitesmoke: "#f5f5f5", + yellowgreen: "#9acd32", + rebeccapurple: "#663399" + }; + } +}); + +// node_modules/.pnpm/css-color-keywords@1.0.0/node_modules/css-color-keywords/index.js +var require_css_color_keywords = __commonJS({ + "node_modules/.pnpm/css-color-keywords@1.0.0/node_modules/css-color-keywords/index.js"(exports, module) { + "use strict"; + module.exports = require_colors(); + } +}); + +// node_modules/.pnpm/css-to-react-native@3.2.0/node_modules/css-to-react-native/index.js +var require_css_to_react_native = __commonJS({ + "node_modules/.pnpm/css-to-react-native@3.2.0/node_modules/css-to-react-native/index.js"(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + function _interopDefault(ex) { + return ex && typeof ex === "object" && "default" in ex ? ex["default"] : ex; + } + var parse = require_lib(); + var parse__default = _interopDefault(parse); + var camelizeStyleName = _interopDefault(require_camelize()); + var cssColorKeywords = _interopDefault(require_css_color_keywords()); + var matchString = function matchString2(node2) { + if (node2.type !== "string") return null; + return node2.value.replace(/\\([0-9a-f]{1,6})(?:\s|$)/gi, function(match, charCode) { + return String.fromCharCode(parseInt(charCode, 16)); + }).replace(/\\/g, ""); + }; + var hexColorRe = /^(#(?:[0-9a-f]{3,4}){1,2})$/i; + var cssFunctionNameRe = /^(rgba?|hsla?|hwb|lab|lch|gray|color)$/; + var matchColor = function matchColor2(node2) { + if (node2.type === "word" && (hexColorRe.test(node2.value) || node2.value in cssColorKeywords || node2.value === "transparent")) { + return node2.value; + } else if (node2.type === "function" && cssFunctionNameRe.test(node2.value)) { + return parse.stringify(node2); + } + return null; + }; + var noneRe = /^(none)$/i; + var autoRe = /^(auto)$/i; + var identRe = /(^-?[_a-z][_a-z0-9-]*$)/i; + var numberRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)$/i; + var lengthRe = /^(0$|(?:[+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)(?=px$))/i; + var unsupportedUnitRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(ch|em|ex|rem|vh|vw|vmin|vmax|cm|mm|in|pc|pt))$/i; + var angleRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?(?:deg|rad))$/i; + var percentRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?%)$/i; + var noopToken = function noopToken2(predicate) { + return function(node2) { + return predicate(node2) ? "" : null; + }; + }; + var valueForTypeToken = function valueForTypeToken2(type) { + return function(node2) { + return node2.type === type ? node2.value : null; + }; + }; + var regExpToken = function regExpToken2(regExp, transform2) { + if (transform2 === void 0) { + transform2 = String; + } + return function(node2) { + if (node2.type !== "word") return null; + var match = node2.value.match(regExp); + if (match === null) return null; + var value = transform2(match[1]); + return value; + }; + }; + var SPACE = noopToken(function(node2) { + return node2.type === "space"; + }); + var SLASH = noopToken(function(node2) { + return node2.type === "div" && node2.value === "/"; + }); + var COMMA = noopToken(function(node2) { + return node2.type === "div" && node2.value === ","; + }); + var WORD = valueForTypeToken("word"); + var NONE = regExpToken(noneRe); + var AUTO = regExpToken(autoRe); + var NUMBER = regExpToken(numberRe, Number); + var LENGTH = regExpToken(lengthRe, Number); + var UNSUPPORTED_LENGTH_UNIT = regExpToken(unsupportedUnitRe); + var ANGLE = regExpToken(angleRe, function(angle) { + return angle.toLowerCase(); + }); + var PERCENT = regExpToken(percentRe); + var IDENT = regExpToken(identRe); + var STRING = matchString; + var COLOR = matchColor; + var LINE = regExpToken(/^(none|underline|line-through)$/i); + var aspectRatio = function aspectRatio2(tokenStream) { + var aspectRatio3 = tokenStream.expect(NUMBER); + if (tokenStream.hasTokens()) { + tokenStream.expect(SLASH); + aspectRatio3 /= tokenStream.expect(NUMBER); + } + return { + aspectRatio: aspectRatio3 + }; + }; + var BORDER_STYLE = regExpToken(/^(solid|dashed|dotted)$/); + var defaultBorderWidth = 1; + var defaultBorderColor = "black"; + var defaultBorderStyle = "solid"; + var border = function border2(tokenStream) { + var borderWidth2; + var borderColor2; + var borderStyle; + if (tokenStream.matches(NONE)) { + tokenStream.expectEmpty(); + return { + borderWidth: 0, + borderColor: "black", + borderStyle: "solid" + }; + } + var partsParsed = 0; + while (partsParsed < 3 && tokenStream.hasTokens()) { + if (partsParsed !== 0) tokenStream.expect(SPACE); + if (borderWidth2 === void 0 && tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT)) { + borderWidth2 = tokenStream.lastValue; + } else if (borderColor2 === void 0 && tokenStream.matches(COLOR)) { + borderColor2 = tokenStream.lastValue; + } else if (borderStyle === void 0 && tokenStream.matches(BORDER_STYLE)) { + borderStyle = tokenStream.lastValue; + } else { + tokenStream["throw"](); + } + partsParsed += 1; + } + tokenStream.expectEmpty(); + if (borderWidth2 === void 0) borderWidth2 = defaultBorderWidth; + if (borderColor2 === void 0) borderColor2 = defaultBorderColor; + if (borderStyle === void 0) borderStyle = defaultBorderStyle; + return { + borderWidth: borderWidth2, + borderColor: borderColor2, + borderStyle + }; + }; + var directionFactory = function directionFactory2(_ref) { + var _ref$types = _ref.types, types = _ref$types === void 0 ? [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT] : _ref$types, _ref$directions = _ref.directions, directions = _ref$directions === void 0 ? ["Top", "Right", "Bottom", "Left"] : _ref$directions, _ref$prefix = _ref.prefix, prefix = _ref$prefix === void 0 ? "" : _ref$prefix, _ref$suffix = _ref.suffix, suffix = _ref$suffix === void 0 ? "" : _ref$suffix; + return function(tokenStream) { + var _ref2; + var values = []; + values.push(tokenStream.expect.apply(tokenStream, types)); + while (values.length < 4 && tokenStream.hasTokens()) { + tokenStream.expect(SPACE); + values.push(tokenStream.expect.apply(tokenStream, types)); + } + tokenStream.expectEmpty(); + var top = values[0], _values$ = values[1], right = _values$ === void 0 ? top : _values$, _values$2 = values[2], bottom = _values$2 === void 0 ? top : _values$2, _values$3 = values[3], left = _values$3 === void 0 ? right : _values$3; + var keyFor = function keyFor2(n) { + return "" + prefix + directions[n] + suffix; + }; + return _ref2 = {}, _ref2[keyFor(0)] = top, _ref2[keyFor(1)] = right, _ref2[keyFor(2)] = bottom, _ref2[keyFor(3)] = left, _ref2; + }; + }; + var parseShadowOffset = function parseShadowOffset2(tokenStream) { + var width = tokenStream.expect(LENGTH); + var height = tokenStream.matches(SPACE) ? tokenStream.expect(LENGTH) : width; + tokenStream.expectEmpty(); + return { + width, + height + }; + }; + var parseShadow = function parseShadow2(tokenStream) { + var offsetX; + var offsetY; + var radius; + var color; + if (tokenStream.matches(NONE)) { + tokenStream.expectEmpty(); + return { + offset: { + width: 0, + height: 0 + }, + radius: 0, + color: "black" + }; + } + var didParseFirst = false; + while (tokenStream.hasTokens()) { + if (didParseFirst) tokenStream.expect(SPACE); + if (offsetX === void 0 && tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT)) { + offsetX = tokenStream.lastValue; + tokenStream.expect(SPACE); + offsetY = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT); + tokenStream.saveRewindPoint(); + if (tokenStream.matches(SPACE) && tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT)) { + radius = tokenStream.lastValue; + } else { + tokenStream.rewind(); + } + } else if (color === void 0 && tokenStream.matches(COLOR)) { + color = tokenStream.lastValue; + } else { + tokenStream["throw"](); + } + didParseFirst = true; + } + if (offsetX === void 0) tokenStream["throw"](); + return { + offset: { + width: offsetX, + height: offsetY + }, + radius: radius !== void 0 ? radius : 0, + color: color !== void 0 ? color : "black" + }; + }; + var boxShadow = function boxShadow2(tokenStream) { + var _parseShadow = parseShadow(tokenStream), offset = _parseShadow.offset, radius = _parseShadow.radius, color = _parseShadow.color; + return { + shadowOffset: offset, + shadowRadius: radius, + shadowColor: color, + shadowOpacity: 1 + }; + }; + var defaultFlexGrow = 1; + var defaultFlexShrink = 1; + var defaultFlexBasis = 0; + var flex = function flex2(tokenStream) { + var flexGrow; + var flexShrink; + var flexBasis; + if (tokenStream.matches(NONE)) { + tokenStream.expectEmpty(); + return { + flexGrow: 0, + flexShrink: 0, + flexBasis: "auto" + }; + } + tokenStream.saveRewindPoint(); + if (tokenStream.matches(AUTO) && !tokenStream.hasTokens()) { + return { + flexGrow: 1, + flexShrink: 1, + flexBasis: "auto" + }; + } + tokenStream.rewind(); + var partsParsed = 0; + while (partsParsed < 2 && tokenStream.hasTokens()) { + if (partsParsed !== 0) tokenStream.expect(SPACE); + if (flexGrow === void 0 && tokenStream.matches(NUMBER)) { + flexGrow = tokenStream.lastValue; + tokenStream.saveRewindPoint(); + if (tokenStream.matches(SPACE) && tokenStream.matches(NUMBER)) { + flexShrink = tokenStream.lastValue; + } else { + tokenStream.rewind(); + } + } else if (flexBasis === void 0 && tokenStream.matches(LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT)) { + flexBasis = tokenStream.lastValue; + } else if (flexBasis === void 0 && tokenStream.matches(AUTO)) { + flexBasis = "auto"; + } else { + tokenStream["throw"](); + } + partsParsed += 1; + } + tokenStream.expectEmpty(); + if (flexGrow === void 0) flexGrow = defaultFlexGrow; + if (flexShrink === void 0) flexShrink = defaultFlexShrink; + if (flexBasis === void 0) flexBasis = defaultFlexBasis; + return { + flexGrow, + flexShrink, + flexBasis + }; + }; + var FLEX_WRAP = regExpToken(/(nowrap|wrap|wrap-reverse)/); + var FLEX_DIRECTION = regExpToken(/(row|row-reverse|column|column-reverse)/); + var defaultFlexWrap = "nowrap"; + var defaultFlexDirection = "row"; + var flexFlow = function flexFlow2(tokenStream) { + var flexWrap; + var flexDirection; + var partsParsed = 0; + while (partsParsed < 2 && tokenStream.hasTokens()) { + if (partsParsed !== 0) tokenStream.expect(SPACE); + if (flexWrap === void 0 && tokenStream.matches(FLEX_WRAP)) { + flexWrap = tokenStream.lastValue; + } else if (flexDirection === void 0 && tokenStream.matches(FLEX_DIRECTION)) { + flexDirection = tokenStream.lastValue; + } else { + tokenStream["throw"](); + } + partsParsed += 1; + } + tokenStream.expectEmpty(); + if (flexWrap === void 0) flexWrap = defaultFlexWrap; + if (flexDirection === void 0) flexDirection = defaultFlexDirection; + return { + flexWrap, + flexDirection + }; + }; + var fontFamily = function fontFamily2(tokenStream) { + var fontFamily3; + if (tokenStream.matches(STRING)) { + fontFamily3 = tokenStream.lastValue; + } else { + fontFamily3 = tokenStream.expect(IDENT); + while (tokenStream.hasTokens()) { + tokenStream.expect(SPACE); + var nextIdent = tokenStream.expect(IDENT); + fontFamily3 += " " + nextIdent; + } + } + tokenStream.expectEmpty(); + return { + fontFamily: fontFamily3 + }; + }; + var NORMAL = regExpToken(/^(normal)$/); + var STYLE = regExpToken(/^(italic)$/); + var WEIGHT = regExpToken(/^([1-9]00|bold)$/); + var VARIANT = regExpToken(/^(small-caps)$/); + var defaultFontStyle = "normal"; + var defaultFontWeight = "normal"; + var defaultFontVariant = []; + var font = function font2(tokenStream) { + var fontStyle; + var fontWeight2; + var fontVariant2; + var lineHeight; + var numStyleWeightVariantMatched = 0; + while (numStyleWeightVariantMatched < 3 && tokenStream.hasTokens()) { + if (tokenStream.matches(NORMAL)) ; + else if (fontStyle === void 0 && tokenStream.matches(STYLE)) { + fontStyle = tokenStream.lastValue; + } else if (fontWeight2 === void 0 && tokenStream.matches(WEIGHT)) { + fontWeight2 = tokenStream.lastValue; + } else if (fontVariant2 === void 0 && tokenStream.matches(VARIANT)) { + fontVariant2 = [tokenStream.lastValue]; + } else { + break; + } + tokenStream.expect(SPACE); + numStyleWeightVariantMatched += 1; + } + var fontSize = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT); + if (tokenStream.matches(SLASH)) { + lineHeight = tokenStream.expect(LENGTH, UNSUPPORTED_LENGTH_UNIT); + } + tokenStream.expect(SPACE); + var _fontFamily = fontFamily(tokenStream), fontFamily$1 = _fontFamily.fontFamily; + if (fontStyle === void 0) fontStyle = defaultFontStyle; + if (fontWeight2 === void 0) fontWeight2 = defaultFontWeight; + if (fontVariant2 === void 0) fontVariant2 = defaultFontVariant; + var out = { + fontStyle, + fontWeight: fontWeight2, + fontVariant: fontVariant2, + fontSize, + fontFamily: fontFamily$1 + }; + if (lineHeight !== void 0) out.lineHeight = lineHeight; + return out; + }; + var fontVariant = function fontVariant2(tokenStream) { + var values = [tokenStream.expect(IDENT)]; + while (tokenStream.hasTokens()) { + tokenStream.expect(SPACE); + values.push(tokenStream.expect(IDENT)); + } + return { + fontVariant: values + }; + }; + var ALIGN_CONTENT = regExpToken(/(flex-(?:start|end)|center|stretch|space-(?:between|around))/); + var JUSTIFY_CONTENT = regExpToken(/(flex-(?:start|end)|center|space-(?:between|around|evenly))/); + var placeContent = function placeContent2(tokenStream) { + var alignContent = tokenStream.expect(ALIGN_CONTENT); + var justifyContent; + if (tokenStream.hasTokens()) { + tokenStream.expect(SPACE); + justifyContent = tokenStream.expect(JUSTIFY_CONTENT); + } else { + justifyContent = "stretch"; + } + tokenStream.expectEmpty(); + return { + alignContent, + justifyContent + }; + }; + var STYLE$1 = regExpToken(/^(solid|double|dotted|dashed)$/); + var defaultTextDecorationLine = "none"; + var defaultTextDecorationStyle = "solid"; + var defaultTextDecorationColor = "black"; + var textDecoration = function textDecoration2(tokenStream) { + var line; + var style; + var color; + var didParseFirst = false; + while (tokenStream.hasTokens()) { + if (didParseFirst) tokenStream.expect(SPACE); + if (line === void 0 && tokenStream.matches(LINE)) { + var lines = [tokenStream.lastValue.toLowerCase()]; + tokenStream.saveRewindPoint(); + if (lines[0] !== "none" && tokenStream.matches(SPACE) && tokenStream.matches(LINE)) { + lines.push(tokenStream.lastValue.toLowerCase()); + lines.sort().reverse(); + } else { + tokenStream.rewind(); + } + line = lines.join(" "); + } else if (style === void 0 && tokenStream.matches(STYLE$1)) { + style = tokenStream.lastValue; + } else if (color === void 0 && tokenStream.matches(COLOR)) { + color = tokenStream.lastValue; + } else { + tokenStream["throw"](); + } + didParseFirst = true; + } + return { + textDecorationLine: line !== void 0 ? line : defaultTextDecorationLine, + textDecorationColor: color !== void 0 ? color : defaultTextDecorationColor, + textDecorationStyle: style !== void 0 ? style : defaultTextDecorationStyle + }; + }; + var textDecorationLine = function textDecorationLine2(tokenStream) { + var lines = []; + var didParseFirst = false; + while (tokenStream.hasTokens()) { + if (didParseFirst) tokenStream.expect(SPACE); + lines.push(tokenStream.expect(LINE).toLowerCase()); + didParseFirst = true; + } + lines.sort().reverse(); + return { + textDecorationLine: lines.join(" ") + }; + }; + var textShadow = function textShadow2(tokenStream) { + var _parseShadow2 = parseShadow(tokenStream), offset = _parseShadow2.offset, radius = _parseShadow2.radius, color = _parseShadow2.color; + return { + textShadowOffset: offset, + textShadowRadius: radius, + textShadowColor: color + }; + }; + var oneOfType = function oneOfType2(tokenType) { + return function(functionStream) { + var value = functionStream.expect(tokenType); + functionStream.expectEmpty(); + return value; + }; + }; + var singleNumber = oneOfType(NUMBER); + var singleLength = oneOfType(LENGTH); + var singleAngle = oneOfType(ANGLE); + var xyTransformFactory = function xyTransformFactory2(tokenType) { + return function(key, valueIfOmitted) { + return function(functionStream) { + var _ref3, _ref4; + var x2 = functionStream.expect(tokenType); + var y; + if (functionStream.hasTokens()) { + functionStream.expect(COMMA); + y = functionStream.expect(tokenType); + } else if (valueIfOmitted !== void 0) { + y = valueIfOmitted; + } else { + return x2; + } + functionStream.expectEmpty(); + return [(_ref3 = {}, _ref3[key + "Y"] = y, _ref3), (_ref4 = {}, _ref4[key + "X"] = x2, _ref4)]; + }; + }; + }; + var xyNumber = xyTransformFactory(NUMBER); + var xyLength = xyTransformFactory(LENGTH); + var xyAngle = xyTransformFactory(ANGLE); + var partTransforms = { + perspective: singleNumber, + scale: xyNumber("scale"), + scaleX: singleNumber, + scaleY: singleNumber, + translate: xyLength("translate", 0), + translateX: singleLength, + translateY: singleLength, + rotate: singleAngle, + rotateX: singleAngle, + rotateY: singleAngle, + rotateZ: singleAngle, + skewX: singleAngle, + skewY: singleAngle, + skew: xyAngle("skew", "0deg") + }; + var transform = function transform2(tokenStream) { + var transforms2 = []; + var didParseFirst = false; + while (tokenStream.hasTokens()) { + if (didParseFirst) tokenStream.expect(SPACE); + var functionStream = tokenStream.expectFunction(); + var functionName = functionStream.functionName; + var transformedValues = partTransforms[functionName](functionStream); + if (!Array.isArray(transformedValues)) { + var _ref5; + transformedValues = [(_ref5 = {}, _ref5[functionName] = transformedValues, _ref5)]; + } + transforms2 = transformedValues.concat(transforms2); + didParseFirst = true; + } + return { + transform: transforms2 + }; + }; + var background = function background2(tokenStream) { + return { + backgroundColor: tokenStream.expect(COLOR) + }; + }; + var borderColor = directionFactory({ + types: [COLOR], + prefix: "border", + suffix: "Color" + }); + var borderRadius = directionFactory({ + directions: ["TopLeft", "TopRight", "BottomRight", "BottomLeft"], + prefix: "border", + suffix: "Radius" + }); + var borderWidth = directionFactory({ + prefix: "border", + suffix: "Width" + }); + var margin = directionFactory({ + types: [LENGTH, UNSUPPORTED_LENGTH_UNIT, PERCENT, AUTO], + prefix: "margin" + }); + var padding = directionFactory({ + prefix: "padding" + }); + var fontWeight = function fontWeight2(tokenStream) { + return { + fontWeight: tokenStream.expect(WORD) + // Also match numbers as strings + }; + }; + var shadowOffset = function shadowOffset2(tokenStream) { + return { + shadowOffset: parseShadowOffset(tokenStream) + }; + }; + var textShadowOffset = function textShadowOffset2(tokenStream) { + return { + textShadowOffset: parseShadowOffset(tokenStream) + }; + }; + var transforms = { + aspectRatio, + background, + border, + borderColor, + borderRadius, + borderWidth, + boxShadow, + flex, + flexFlow, + font, + fontFamily, + fontVariant, + fontWeight, + margin, + padding, + placeContent, + shadowOffset, + textShadow, + textShadowOffset, + textDecoration, + textDecorationLine, + transform + }; + var propertiesWithoutUnits; + if (process.env.NODE_ENV !== "production") { + propertiesWithoutUnits = ["aspectRatio", "elevation", "flexGrow", "flexShrink", "opacity", "shadowOpacity", "zIndex"]; + } + var devPropertiesWithUnitsRegExp = propertiesWithoutUnits != null ? new RegExp(propertiesWithoutUnits.join("|")) : null; + var SYMBOL_MATCH = "SYMBOL_MATCH"; + var TokenStream = /* @__PURE__ */ (function() { + function TokenStream2(nodes, parent) { + this.index = 0; + this.nodes = nodes; + this.functionName = parent != null ? parent.value : null; + this.lastValue = null; + this.rewindIndex = -1; + } + var _proto = TokenStream2.prototype; + _proto.hasTokens = function hasTokens() { + return this.index <= this.nodes.length - 1; + }; + _proto[SYMBOL_MATCH] = function() { + if (!this.hasTokens()) return null; + var node2 = this.nodes[this.index]; + for (var i = 0; i < arguments.length; i += 1) { + var tokenDescriptor = i < 0 || arguments.length <= i ? void 0 : arguments[i]; + var value = tokenDescriptor(node2); + if (value !== null) { + this.index += 1; + this.lastValue = value; + return value; + } + } + return null; + }; + _proto.matches = function matches() { + return this[SYMBOL_MATCH].apply(this, arguments) !== null; + }; + _proto.expect = function expect() { + var value = this[SYMBOL_MATCH].apply(this, arguments); + return value !== null ? value : this["throw"](); + }; + _proto.matchesFunction = function matchesFunction() { + var node2 = this.nodes[this.index]; + if (node2.type !== "function") return null; + var value = new TokenStream2(node2.nodes, node2); + this.index += 1; + this.lastValue = null; + return value; + }; + _proto.expectFunction = function expectFunction() { + var value = this.matchesFunction(); + return value !== null ? value : this["throw"](); + }; + _proto.expectEmpty = function expectEmpty() { + if (this.hasTokens()) this["throw"](); + }; + _proto["throw"] = function _throw() { + throw new Error("Unexpected token type: " + this.nodes[this.index].type); + }; + _proto.saveRewindPoint = function saveRewindPoint() { + this.rewindIndex = this.index; + }; + _proto.rewind = function rewind() { + if (this.rewindIndex === -1) throw new Error("Internal error"); + this.index = this.rewindIndex; + this.lastValue = null; + }; + return TokenStream2; + })(); + var numberOrLengthRe = /^([+-]?(?:\d*\.)?\d+(?:e[+-]?\d+)?)(?:px)?$/i; + var numberOnlyRe = /^[+-]?(?:\d*\.\d*|[1-9]\d*)(?:e[+-]?\d+)?$/i; + var boolRe = /^true|false$/i; + var nullRe = /^null$/i; + var undefinedRe = /^undefined$/i; + var transformRawValue = function transformRawValue2(propName, value) { + if (process.env.NODE_ENV !== "production") { + var needsUnit = !devPropertiesWithUnitsRegExp.test(propName); + var isNumberWithoutUnit = numberOnlyRe.test(value); + if (needsUnit && isNumberWithoutUnit) { + console.warn('Expected style "' + propName + ": " + value + '" to contain units'); + } + if (!needsUnit && value !== "0" && !isNumberWithoutUnit) { + console.warn('Expected style "' + propName + ": " + value + '" to be unitless'); + } + } + var numberMatch = value.match(numberOrLengthRe); + if (numberMatch !== null) return Number(numberMatch[1]); + var boolMatch = value.match(boolRe); + if (boolMatch !== null) return boolMatch[0].toLowerCase() === "true"; + var nullMatch = value.match(nullRe); + if (nullMatch !== null) return null; + var undefinedMatch = value.match(undefinedRe); + if (undefinedMatch !== null) return void 0; + return value; + }; + var baseTransformShorthandValue = function baseTransformShorthandValue2(propName, value) { + var ast = parse__default(value); + var tokenStream = new TokenStream(ast.nodes); + return transforms[propName](tokenStream); + }; + var transformShorthandValue = process.env.NODE_ENV === "production" ? baseTransformShorthandValue : function(propName, value) { + try { + return baseTransformShorthandValue(propName, value); + } catch (e) { + throw new Error('Failed to parse declaration "' + propName + ": " + value + '"'); + } + }; + var getStylesForProperty = function getStylesForProperty2(propName, inputValue, allowShorthand) { + var _ref6; + var isRawValue = allowShorthand === false || !(propName in transforms); + var value = inputValue.trim(); + var propValues = isRawValue ? (_ref6 = {}, _ref6[propName] = transformRawValue(propName, value), _ref6) : transformShorthandValue(propName, value); + return propValues; + }; + var getPropertyName = function getPropertyName2(propName) { + var isCustomProp = /^--\w+/.test(propName); + if (isCustomProp) { + return propName; + } + return camelizeStyleName(propName); + }; + var index = function index2(rules, shorthandBlacklist) { + if (shorthandBlacklist === void 0) { + shorthandBlacklist = []; + } + return rules.reduce(function(accum, rule) { + var propertyName = getPropertyName(rule[0]); + var value = rule[1]; + var allowShorthand = shorthandBlacklist.indexOf(propertyName) === -1; + return Object.assign(accum, getStylesForProperty(propertyName, value, allowShorthand)); + }, {}); + }; + exports["default"] = index; + exports.getPropertyName = getPropertyName; + exports.getStylesForProperty = getStylesForProperty; + exports.transformRawValue = transformRawValue; + } +}); + +// node_modules/.pnpm/css-background-parser@0.1.0/node_modules/css-background-parser/index.js +var require_css_background_parser = __commonJS({ + "node_modules/.pnpm/css-background-parser@0.1.0/node_modules/css-background-parser/index.js"(exports, module) { + (function(exports2) { + function BackgroundList(backgrounds) { + if (!(this instanceof BackgroundList)) { + return new BackgroundList(); + } + this.backgrounds = backgrounds || []; + } + BackgroundList.prototype.toString = function() { + return this.backgrounds.join(", "); + }; + function Background(props) { + if (!(this instanceof Background)) { + return new Background(props); + } + props = props || {}; + var bg = this; + function defprop(name, defaultValue) { + bg[name] = name in props ? props[name] : defaultValue; + } + defprop("color", ""); + defprop("image", "none"); + defprop("attachment", "scroll"); + defprop("clip", "border-box"); + defprop("origin", "padding-box"); + defprop("position", "0% 0%"); + defprop("repeat", "repeat"); + defprop("size", "auto"); + } + Background.prototype.toString = function() { + var list2 = [ + this.image, + this.repeat, + this.attachment, + this.position + " / " + this.size, + this.origin, + this.clip + ]; + if (this.color) { + list2.unshift(this.color); + } + return list2.join(" "); + }; + exports2.BackgroundList = BackgroundList; + exports2.Background = Background; + function parseImages(cssText) { + var images = []; + var tokens = /[,\(\)]/; + var parens = 0; + var buffer = ""; + if (cssText == null) { + return images; + } + while (cssText.length) { + var match = tokens.exec(cssText); + if (!match) { + break; + } + var char = match[0]; + var ignoreChar = false; + switch (char) { + case ",": + if (!parens) { + images.push(buffer.trim()); + buffer = ""; + ignoreChar = true; + } + break; + case "(": + parens++; + break; + case ")": + parens--; + break; + } + var index = match.index + 1; + buffer += cssText.slice(0, ignoreChar ? index - 1 : index); + cssText = cssText.slice(index); + } + if (buffer.length || cssText.length) { + images.push((buffer + cssText).trim()); + } + return images; + } + function trim(str) { + return str.trim(); + } + function parseSimpleList(cssText) { + return (cssText || "").split(",").map(trim); + } + exports2.parseElementStyle = function(styleObject) { + var list2 = new BackgroundList(); + if (styleObject == null) { + return list2; + } + var bgImage = parseImages(styleObject.backgroundImage); + var bgColor = styleObject.backgroundColor; + var bgAttachment = parseSimpleList(styleObject.backgroundAttachment); + var bgClip = parseSimpleList(styleObject.backgroundClip); + var bgOrigin = parseSimpleList(styleObject.backgroundOrigin); + var bgPosition = parseSimpleList(styleObject.backgroundPosition); + var bgRepeat = parseSimpleList(styleObject.backgroundRepeat); + var bgSize = parseSimpleList(styleObject.backgroundSize); + var background; + for (var i = 0, ii2 = bgImage.length; i < ii2; i++) { + background = new Background({ + image: bgImage[i], + attachment: bgAttachment[i % bgAttachment.length], + clip: bgClip[i % bgClip.length], + origin: bgOrigin[i % bgOrigin.length], + position: bgPosition[i % bgPosition.length], + repeat: bgRepeat[i % bgRepeat.length], + size: bgSize[i % bgSize.length] + }); + if (i === ii2 - 1) { + background.color = bgColor; + } + list2.backgrounds.push(background); + } + return list2; + }; + })((function(root) { + if (typeof module !== "undefined" && module.exports !== void 0) return module.exports; + return root.cssBgParser = {}; + })(exports)); + } +}); + +// node_modules/.pnpm/css-box-shadow@1.0.0-3/node_modules/css-box-shadow/index.js +var require_css_box_shadow = __commonJS({ + "node_modules/.pnpm/css-box-shadow@1.0.0-3/node_modules/css-box-shadow/index.js"(exports, module) { + var VALUES_REG = /,(?![^\(]*\))/; + var PARTS_REG = /\s(?![^(]*\))/; + var LENGTH_REG = /^[0-9]+[a-zA-Z%]+?$/; + var parseValue = (str) => { + const parts = str.split(PARTS_REG); + const inset = parts.includes("inset"); + const last = parts.slice(-1)[0]; + const color = !isLength(last) ? last : void 0; + const nums = parts.filter((n) => n !== "inset").filter((n) => n !== color).map(toNum); + const [offsetX, offsetY, blurRadius, spreadRadius] = nums; + return { + inset, + offsetX, + offsetY, + blurRadius, + spreadRadius, + color + }; + }; + var stringifyValue = (obj) => { + const { + inset, + offsetX = 0, + offsetY = 0, + blurRadius = 0, + spreadRadius, + color + } = obj || {}; + return [ + inset ? "inset" : null, + offsetX, + offsetY, + blurRadius, + spreadRadius, + color + ].filter((v2) => v2 !== null && v2 !== void 0).map(toPx).map((s) => ("" + s).trim()).join(" "); + }; + var isLength = (v2) => v2 === "0" || LENGTH_REG.test(v2); + var toNum = (v2) => { + if (!/px$/.test(v2) && v2 !== "0") return v2; + const n = parseFloat(v2); + return !isNaN(n) ? n : v2; + }; + var toPx = (n) => typeof n === "number" && n !== 0 ? n + "px" : n; + var parse = (str) => str.split(VALUES_REG).map((s) => s.trim()).map(parseValue); + var stringify = (arr) => arr.map(stringifyValue).join(", "); + module.exports = { + parse, + stringify + }; + } +}); + +// node_modules/.pnpm/color-name@1.1.4/node_modules/color-name/index.js +var require_color_name = __commonJS({ + "node_modules/.pnpm/color-name@1.1.4/node_modules/color-name/index.js"(exports, module) { + "use strict"; + module.exports = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] + }; + } +}); + +// node_modules/.pnpm/hex-rgb@4.3.0/node_modules/hex-rgb/index.js +var require_hex_rgb = __commonJS({ + "node_modules/.pnpm/hex-rgb@4.3.0/node_modules/hex-rgb/index.js"(exports, module) { + "use strict"; + var hexCharacters = "a-f\\d"; + var match3or4Hex = `#?[${hexCharacters}]{3}[${hexCharacters}]?`; + var match6or8Hex = `#?[${hexCharacters}]{6}([${hexCharacters}]{2})?`; + var nonHexChars = new RegExp(`[^#${hexCharacters}]`, "gi"); + var validHexSize = new RegExp(`^${match3or4Hex}$|^${match6or8Hex}$`, "i"); + module.exports = (hex, options = {}) => { + if (typeof hex !== "string" || nonHexChars.test(hex) || !validHexSize.test(hex)) { + throw new TypeError("Expected a valid hex string"); + } + hex = hex.replace(/^#/, ""); + let alphaFromHex = 1; + if (hex.length === 8) { + alphaFromHex = Number.parseInt(hex.slice(6, 8), 16) / 255; + hex = hex.slice(0, 6); + } + if (hex.length === 4) { + alphaFromHex = Number.parseInt(hex.slice(3, 4).repeat(2), 16) / 255; + hex = hex.slice(0, 3); + } + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + const number = Number.parseInt(hex, 16); + const red = number >> 16; + const green = number >> 8 & 255; + const blue = number & 255; + const alpha = typeof options.alpha === "number" ? options.alpha : alphaFromHex; + if (options.format === "array") { + return [red, green, blue, alpha]; + } + if (options.format === "css") { + const alphaString = alpha === 1 ? "" : ` / ${Number((alpha * 100).toFixed(2))}%`; + return `rgb(${red} ${green} ${blue}${alphaString})`; + } + return { red, green, blue, alpha }; + }; + } +}); + +// node_modules/.pnpm/parse-css-color@0.2.1/node_modules/parse-css-color/dist/index.cjs.js +var require_index_cjs = __commonJS({ + "node_modules/.pnpm/parse-css-color@0.2.1/node_modules/parse-css-color/dist/index.cjs.js"(exports, module) { + "use strict"; + function _interopDefault(ex) { + return ex && typeof ex === "object" && "default" in ex ? ex["default"] : ex; + } + var colorName = _interopDefault(require_color_name()); + var hex2Rgb = _interopDefault(require_hex_rgb()); + var pattern = /^#([a-f0-9]{3,4}|[a-f0-9]{4}(?:[a-f0-9]{2}){1,2})\b$/; + var hexRe = new RegExp(pattern, "i"); + var float = "-?\\d*(?:\\.\\d+)"; + var number = `(${float}?)`; + var percentage = `(${float}?%)`; + var numberOrPercentage = `(${float}?%?)`; + var pattern$1 = `^ + hsla?\\( + \\s*(-?\\d*(?:\\.\\d+)?(?:deg|rad|turn)?)\\s*, + \\s*${percentage}\\s*, + \\s*${percentage}\\s* + (?:,\\s*${numberOrPercentage}\\s*)? + \\) + $ +`.replace(/\n|\s/g, ""); + var hsl3Re = new RegExp(pattern$1); + var pattern$2 = `^ + hsla?\\( + \\s*(-?\\d*(?:\\.\\d+)?(?:deg|rad|turn)?)\\s* + \\s+${percentage} + \\s+${percentage} + \\s*(?:\\s*\\/\\s*${numberOrPercentage}\\s*)? + \\) + $ +`.replace(/\n|\s/g, ""); + var hsl4Re = new RegExp(pattern$2); + var pattern$3 = `^ + rgba?\\( + \\s*${number}\\s*, + \\s*${number}\\s*, + \\s*${number}\\s* + (?:,\\s*${numberOrPercentage}\\s*)? + \\) + $ +`.replace(/\n|\s/g, ""); + var rgb3NumberRe = new RegExp(pattern$3); + var pattern$4 = `^ + rgba?\\( + \\s*${percentage}\\s*, + \\s*${percentage}\\s*, + \\s*${percentage}\\s* + (?:,\\s*${numberOrPercentage}\\s*)? + \\) + $ +`.replace(/\n|\s/g, ""); + var rgb3PercentageRe = new RegExp(pattern$4); + var pattern$5 = `^ + rgba?\\( + \\s*${number} + \\s+${number} + \\s+${number} + \\s*(?:\\s*\\/\\s*${numberOrPercentage}\\s*)? + \\) +$ +`.replace(/\n|\s/g, ""); + var rgb4NumberRe = new RegExp(pattern$5); + var pattern$6 = `^ + rgba?\\( + \\s*${percentage} + \\s+${percentage} + \\s+${percentage} + \\s*(?:\\s*\\/\\s*${numberOrPercentage}\\s*)? + \\) +$ +`.replace(/\n|\s/g, ""); + var rgb4PercentageRe = new RegExp(pattern$6); + var pattern$7 = /^transparent$/; + var transparentRe = new RegExp(pattern$7, "i"); + var clamp = (num, min, max) => Math.min(Math.max(min, num), max); + var parseRGB = (num) => { + let n = num; + if (typeof n !== "number") n = n.endsWith("%") ? parseFloat(n) * 255 / 100 : parseFloat(n); + return clamp(Math.round(n), 0, 255); + }; + var parsePercentage = (percentage2) => clamp(parseFloat(percentage2), 0, 100); + function parseAlpha(alpha) { + let a = alpha; + if (typeof a !== "number") a = a.endsWith("%") ? parseFloat(a) / 100 : parseFloat(a); + return clamp(a, 0, 1); + } + function getHEX(hex) { + const [r, g2, b, a] = hex2Rgb(hex, { format: "array" }); + return getRGB([null, ...[r, g2, b, a]]); + } + function getHSL([, h2, s, l2, a = 1]) { + let hh = h2; + if (hh.endsWith("turn")) { + hh = parseFloat(hh) * 360 / 1; + } else if (hh.endsWith("rad")) { + hh = Math.round(parseFloat(hh) * 180 / Math.PI); + } else { + hh = parseFloat(hh); + } + return { + type: "hsl", + values: [hh, parsePercentage(s), parsePercentage(l2)], + alpha: parseAlpha(a === null ? 1 : a) + }; + } + function getRGB([, r, g2, b, a = 1]) { + return { + type: "rgb", + values: [r, g2, b].map(parseRGB), + alpha: parseAlpha(a === null ? 1 : a) + }; + } + var parseCSSColor = (str) => { + if (typeof str !== "string") return null; + const hex = hexRe.exec(str); + if (hex) return getHEX(hex[0]); + const hsl = hsl4Re.exec(str) || hsl3Re.exec(str); + if (hsl) return getHSL(hsl); + const rgb = rgb4NumberRe.exec(str) || rgb4PercentageRe.exec(str) || rgb3NumberRe.exec(str) || rgb3PercentageRe.exec(str); + if (rgb) return getRGB(rgb); + if (transparentRe.exec(str)) return getRGB([null, 0, 0, 0, 0]); + const cn2 = colorName[str.toLowerCase()]; + if (cn2) return getRGB([null, cn2[0], cn2[1], cn2[2], 1]); + return null; + }; + module.exports = parseCSSColor; + } +}); + +// node_modules/.pnpm/escape-html@1.0.3/node_modules/escape-html/index.js +var require_escape_html = __commonJS({ + "node_modules/.pnpm/escape-html@1.0.3/node_modules/escape-html/index.js"(exports, module) { + "use strict"; + var matchHtmlRegExp = /["'&<>]/; + module.exports = escapeHtml; + function escapeHtml(string) { + var str = "" + string; + var match = matchHtmlRegExp.exec(str); + if (!match) { + return str; + } + var escape; + var html = ""; + var index = 0; + var lastIndex = 0; + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: + escape = """; + break; + case 38: + escape = "&"; + break; + case 39: + escape = "'"; + break; + case 60: + escape = "<"; + break; + case 62: + escape = ">"; + break; + default: + continue; + } + if (lastIndex !== index) { + html += str.substring(lastIndex, index); + } + lastIndex = index + 1; + html += escape; + } + return lastIndex !== index ? html + str.substring(lastIndex, index) : html; + } + } +}); + +// node_modules/.pnpm/css-gradient-parser@0.0.17/node_modules/css-gradient-parser/dist/index.js +function c(e, o = ",") { + let t = [], n = 0, i = 0; + o = new RegExp(o); + for (let r = 0; r < e.length; r++) e[r] === "(" ? i++ : e[r] === ")" && i--, i === 0 && o.test(e[r]) && (t.push(e.slice(n, r).trim()), n = r + 1); + return t.push(e.slice(n).trim()), t; +} +function g(e) { + let o = []; + for (let t = 0, n = e.length; t < n; ) { + let [i, r] = c(e[t], /\s+/); + m(e[t + 1]) ? (o.push({ color: i, offset: l(r), hint: l(e[t + 1]) }), t += 2) : (o.push({ color: i, offset: l(r) }), t++); + } + return o; +} +function m(e) { + return u.test(e); +} +function l(e) { + if (!e) return; + let [, o, t] = e.trim().match(u) || []; + return { value: o, unit: t ?? "px" }; +} +function P(e) { + if (!/^(repeating-)?linear-gradient/.test(e)) throw new SyntaxError(`could not find syntax for this item: ${e}`); + let [, o, t] = e.match(/(repeating-)?linear-gradient\((.+)\)/), n = { orientation: { type: "directional", value: "bottom" }, repeating: !!o, stops: [] }, i = c(t), r = x(i[0]); + return r && (n.orientation = r, i.shift()), { ...n, stops: g(i) }; +} +function x(e) { + return e.startsWith("to ") ? { type: "directional", value: e.replace("to ", "") } : ["turn", "deg", "grad", "rad"].some((o) => e.endsWith(o)) ? { type: "angular", value: l(e) } : null; +} +function d(e) { + return v.has(e); +} +function h(e) { + return w.has(e); +} +function R(e) { + let o = Array(2).fill(""); + for (let t = 0; t < 2; t++) e[t] ? o[t] = e[t] : o[t] = "center"; + return o; +} +function K(e) { + if (!/(repeating-)?radial-gradient/.test(e)) throw new SyntaxError(`could not find syntax for this item: ${e}`); + let [, o, t] = e.match(/(repeating-)?radial-gradient\((.+)\)/), n = { shape: "ellipse", repeating: !!o, size: [{ type: "keyword", value: "farthest-corner" }], position: { x: { type: "keyword", value: "center" }, y: { type: "keyword", value: "center" } }, stops: [] }, i = c(t); + if (S(i[0])) return { ...n, stops: g(i) }; + let r = i[0].split("at").map((f) => f.trim()), p = ((r[0] || "").match(/(circle|ellipse)/) || [])[1], a = (r[0] || "").match(/(-?\d+\.?\d*(vw|vh|px|em|rem|%|rad|grad|turn|deg)?|closest-corner|closest-side|farthest-corner|farthest-side)/g) || [], s = R((r[1] || "").split(" ")); + return p ? n.shape = p : a.length === 1 && !d(a[0]) ? n.shape = "circle" : n.shape = "ellipse", a.length === 0 && a.push("farthest-corner"), n.size = a.map((f) => d(f) ? { type: "keyword", value: f } : { type: "length", value: l(f) }), n.position.x = h(s[0]) ? { type: "keyword", value: s[0] } : { type: "length", value: l(s[0]) }, n.position.y = h(s[1]) ? { type: "keyword", value: s[1] } : { type: "length", value: l(s[1]) }, (p || a.length > 0 || r[1]) && i.shift(), { ...n, stops: g(i) }; +} +function S(e) { + return /(circle|ellipse|at)/.test(e) ? false : /^(rgba?|hwb|hsl|lab|lch|oklab|color|#|[a-zA-Z]+)/.test(e); +} +var u, v, w; +var init_dist = __esm({ + "node_modules/.pnpm/css-gradient-parser@0.0.17/node_modules/css-gradient-parser/dist/index.js"() { + u = /^(-?\d+\.?\d*)(%|vw|vh|px|em|rem|deg|rad|grad|turn|ch|vmin|vmax)?$/; + v = /* @__PURE__ */ new Set(["closest-corner", "closest-side", "farthest-corner", "farthest-side"]); + w = /* @__PURE__ */ new Set(["center", "left", "top", "right", "bottom"]); + } +}); + +// node_modules/.pnpm/@shuding+opentype.js@1.4.0-beta.0/node_modules/@shuding/opentype.js/dist/opentype.js +var require_opentype = __commonJS({ + "node_modules/.pnpm/@shuding+opentype.js@1.4.0-beta.0/node_modules/@shuding/opentype.js/dist/opentype.js"(exports, module) { + (function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = global || self, factory(global.opentype = {})); + })(exports, (function(exports2) { + "use strict"; + var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array; + var fleb = new u8([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 0, + /* unused */ + 0, + 0, + /* impossible */ + 0 + ]); + var fdeb = new u8([ + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + /* unused */ + 0, + 0 + ]); + var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); + var freb = function(eb, start) { + var b = new u16(31); + for (var i2 = 0; i2 < 31; ++i2) { + b[i2] = start += 1 << eb[i2 - 1]; + } + var r = new u32(b[30]); + for (var i2 = 1; i2 < 30; ++i2) { + for (var j = b[i2]; j < b[i2 + 1]; ++j) { + r[j] = j - b[i2] << 5 | i2; + } + } + return [b, r]; + }; + var _a = freb(fleb, 2), fl2 = _a[0], revfl = _a[1]; + fl2[28] = 258, revfl[258] = 28; + var _b = freb(fdeb, 0), fd = _b[0]; + var rev = new u16(32768); + for (var i = 0; i < 32768; ++i) { + var x2 = (i & 43690) >>> 1 | (i & 21845) << 1; + x2 = (x2 & 52428) >>> 2 | (x2 & 13107) << 2; + x2 = (x2 & 61680) >>> 4 | (x2 & 3855) << 4; + rev[i] = ((x2 & 65280) >>> 8 | (x2 & 255) << 8) >>> 1; + } + var hMap = (function(cd, mb, r) { + var s = cd.length; + var i2 = 0; + var l2 = new u16(mb); + for (; i2 < s; ++i2) { + if (cd[i2]) { + ++l2[cd[i2] - 1]; + } + } + var le = new u16(mb); + for (i2 = 0; i2 < mb; ++i2) { + le[i2] = le[i2 - 1] + l2[i2 - 1] << 1; + } + var co2; + if (r) { + co2 = new u16(1 << mb); + var rvb = 15 - mb; + for (i2 = 0; i2 < s; ++i2) { + if (cd[i2]) { + var sv = i2 << 4 | cd[i2]; + var r_1 = mb - cd[i2]; + var v2 = le[cd[i2] - 1]++ << r_1; + for (var m2 = v2 | (1 << r_1) - 1; v2 <= m2; ++v2) { + co2[rev[v2] >>> rvb] = sv; + } + } + } + } else { + co2 = new u16(s); + for (i2 = 0; i2 < s; ++i2) { + if (cd[i2]) { + co2[i2] = rev[le[cd[i2] - 1]++] >>> 15 - cd[i2]; + } + } + } + return co2; + }); + var flt = new u8(288); + for (var i = 0; i < 144; ++i) { + flt[i] = 8; + } + for (var i = 144; i < 256; ++i) { + flt[i] = 9; + } + for (var i = 256; i < 280; ++i) { + flt[i] = 7; + } + for (var i = 280; i < 288; ++i) { + flt[i] = 8; + } + var fdt = new u8(32); + for (var i = 0; i < 32; ++i) { + fdt[i] = 5; + } + var flrm = /* @__PURE__ */ hMap(flt, 9, 1); + var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1); + var max = function(a) { + var m2 = a[0]; + for (var i2 = 1; i2 < a.length; ++i2) { + if (a[i2] > m2) { + m2 = a[i2]; + } + } + return m2; + }; + var bits = function(d2, p, m2) { + var o = p / 8 | 0; + return (d2[o] | d2[o + 1] << 8) >> (p & 7) & m2; + }; + var bits16 = function(d2, p) { + var o = p / 8 | 0; + return (d2[o] | d2[o + 1] << 8 | d2[o + 2] << 16) >> (p & 7); + }; + var shft = function(p) { + return (p + 7) / 8 | 0; + }; + var slc = function(v2, s, e) { + if (s == null || s < 0) { + s = 0; + } + if (e == null || e > v2.length) { + e = v2.length; + } + var n = new (v2.BYTES_PER_ELEMENT == 2 ? u16 : v2.BYTES_PER_ELEMENT == 4 ? u32 : u8)(e - s); + n.set(v2.subarray(s, e)); + return n; + }; + var ec = [ + "unexpected EOF", + "invalid block type", + "invalid length/literal", + "invalid distance", + "stream finished", + "no stream handler", + , + "no callback", + "invalid UTF-8 data", + "extra field too long", + "date not in range 1980-2099", + "filename too long", + "stream finishing", + "invalid zip data" + // determined by unknown compression method + ]; + var err = function(ind, msg, nt2) { + var e = new Error(msg || ec[ind]); + e.code = ind; + if (Error.captureStackTrace) { + Error.captureStackTrace(e, err); + } + if (!nt2) { + throw e; + } + return e; + }; + var inflt = function(dat, buf, st) { + var sl2 = dat.length; + if (!sl2 || st && st.f && !st.l) { + return buf || new u8(0); + } + var noBuf = !buf || st; + var noSt = !st || st.i; + if (!st) { + st = {}; + } + if (!buf) { + buf = new u8(sl2 * 3); + } + var cbuf = function(l3) { + var bl2 = buf.length; + if (l3 > bl2) { + var nbuf = new u8(Math.max(bl2 * 2, l3)); + nbuf.set(buf); + buf = nbuf; + } + }; + var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; + var tbts = sl2 * 8; + do { + if (!lm) { + final = bits(dat, pos, 1); + var type = bits(dat, pos + 1, 3); + pos += 3; + if (!type) { + var s = shft(pos) + 4, l2 = dat[s - 4] | dat[s - 3] << 8, t = s + l2; + if (t > sl2) { + if (noSt) { + err(0); + } + break; + } + if (noBuf) { + cbuf(bt + l2); + } + buf.set(dat.subarray(s, t), bt); + st.b = bt += l2, st.p = pos = t * 8, st.f = final; + continue; + } else if (type == 1) { + lm = flrm, dm = fdrm, lbt = 9, dbt = 5; + } else if (type == 2) { + var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; + var tl2 = hLit + bits(dat, pos + 5, 31) + 1; + pos += 14; + var ldt = new u8(tl2); + var clt = new u8(19); + for (var i2 = 0; i2 < hcLen; ++i2) { + clt[clim[i2]] = bits(dat, pos + i2 * 3, 7); + } + pos += hcLen * 3; + var clb = max(clt), clbmsk = (1 << clb) - 1; + var clm = hMap(clt, clb, 1); + for (var i2 = 0; i2 < tl2; ) { + var r = clm[bits(dat, pos, clbmsk)]; + pos += r & 15; + var s = r >>> 4; + if (s < 16) { + ldt[i2++] = s; + } else { + var c2 = 0, n = 0; + if (s == 16) { + n = 3 + bits(dat, pos, 3), pos += 2, c2 = ldt[i2 - 1]; + } else if (s == 17) { + n = 3 + bits(dat, pos, 7), pos += 3; + } else if (s == 18) { + n = 11 + bits(dat, pos, 127), pos += 7; + } + while (n--) { + ldt[i2++] = c2; + } + } + } + var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); + lbt = max(lt); + dbt = max(dt); + lm = hMap(lt, lbt, 1); + dm = hMap(dt, dbt, 1); + } else { + err(1); + } + if (pos > tbts) { + if (noSt) { + err(0); + } + break; + } + } + if (noBuf) { + cbuf(bt + 131072); + } + var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; + var lpos = pos; + for (; ; lpos = pos) { + var c2 = lm[bits16(dat, pos) & lms], sym = c2 >>> 4; + pos += c2 & 15; + if (pos > tbts) { + if (noSt) { + err(0); + } + break; + } + if (!c2) { + err(2); + } + if (sym < 256) { + buf[bt++] = sym; + } else if (sym == 256) { + lpos = pos, lm = null; + break; + } else { + var add = sym - 254; + if (sym > 264) { + var i2 = sym - 257, b = fleb[i2]; + add = bits(dat, pos, (1 << b) - 1) + fl2[i2]; + pos += b; + } + var d2 = dm[bits16(dat, pos) & dms], dsym = d2 >>> 4; + if (!d2) { + err(3); + } + pos += d2 & 15; + var dt = fd[dsym]; + if (dsym > 3) { + var b = fdeb[dsym]; + dt += bits16(dat, pos) & (1 << b) - 1, pos += b; + } + if (pos > tbts) { + if (noSt) { + err(0); + } + break; + } + if (noBuf) { + cbuf(bt + 131072); + } + var end = bt + add; + for (; bt < end; bt += 4) { + buf[bt] = buf[bt - dt]; + buf[bt + 1] = buf[bt + 1 - dt]; + buf[bt + 2] = buf[bt + 2 - dt]; + buf[bt + 3] = buf[bt + 3 - dt]; + } + bt = end; + } + } + st.l = lm, st.p = lpos, st.b = bt, st.f = final; + if (lm) { + final = 1, st.m = lbt, st.d = dm, st.n = dbt; + } + } while (!final); + return bt == buf.length ? buf : slc(buf, 0, bt); + }; + var et = /* @__PURE__ */ new u8(0); + function inflateSync(data, out) { + return inflt(data, out); + } + var td2 = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder(); + var tds = 0; + try { + td2.decode(et, { stream: true }); + tds = 1; + } catch (e) { + } + function Path() { + this.commands = []; + this.fill = "black"; + this.stroke = null; + this.strokeWidth = 1; + } + Path.prototype.moveTo = function(x3, y) { + this.commands.push({ + type: "M", + x: x3, + y + }); + }; + Path.prototype.lineTo = function(x3, y) { + this.commands.push({ + type: "L", + x: x3, + y + }); + }; + Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x22, y2, x3, y) { + this.commands.push({ + type: "C", + x1, + y1, + x2: x22, + y2, + x: x3, + y + }); + }; + Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x3, y) { + this.commands.push({ + type: "Q", + x1, + y1, + x: x3, + y + }); + }; + Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: "Z" + }); + }; + Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } + Array.prototype.push.apply(this.commands, pathOrCommands); + }; + Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== void 0 ? decimalPlaces : 2; + function floatToString(v2) { + if (Math.round(v2) === v2) { + return "" + Math.round(v2); + } else { + return v2.toFixed(decimalPlaces); + } + } + function packValues() { + var arguments$1 = arguments; + var s = ""; + for (var i3 = 0; i3 < arguments.length; i3 += 1) { + var v2 = arguments$1[i3]; + if (v2 >= 0 && i3 > 0) { + s += " "; + } + s += floatToString(v2); + } + return s; + } + var d2 = ""; + for (var i2 = 0; i2 < this.commands.length; i2 += 1) { + var cmd = this.commands[i2]; + if (cmd.type === "M") { + d2 += "M" + packValues(cmd.x, cmd.y); + } else if (cmd.type === "L") { + d2 += "L" + packValues(cmd.x, cmd.y); + } else if (cmd.type === "C") { + d2 += "C" + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === "Q") { + d2 += "Q" + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === "Z") { + d2 += "Z"; + } + } + return d2; + }; + var cffStandardStrings = [ + ".notdef", + "space", + "exclam", + "quotedbl", + "numbersign", + "dollar", + "percent", + "ampersand", + "quoteright", + "parenleft", + "parenright", + "asterisk", + "plus", + "comma", + "hyphen", + "period", + "slash", + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "colon", + "semicolon", + "less", + "equal", + "greater", + "question", + "at", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "bracketleft", + "backslash", + "bracketright", + "asciicircum", + "underscore", + "quoteleft", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "braceleft", + "bar", + "braceright", + "asciitilde", + "exclamdown", + "cent", + "sterling", + "fraction", + "yen", + "florin", + "section", + "currency", + "quotesingle", + "quotedblleft", + "guillemotleft", + "guilsinglleft", + "guilsinglright", + "fi", + "fl", + "endash", + "dagger", + "daggerdbl", + "periodcentered", + "paragraph", + "bullet", + "quotesinglbase", + "quotedblbase", + "quotedblright", + "guillemotright", + "ellipsis", + "perthousand", + "questiondown", + "grave", + "acute", + "circumflex", + "tilde", + "macron", + "breve", + "dotaccent", + "dieresis", + "ring", + "cedilla", + "hungarumlaut", + "ogonek", + "caron", + "emdash", + "AE", + "ordfeminine", + "Lslash", + "Oslash", + "OE", + "ordmasculine", + "ae", + "dotlessi", + "lslash", + "oslash", + "oe", + "germandbls", + "onesuperior", + "logicalnot", + "mu", + "trademark", + "Eth", + "onehalf", + "plusminus", + "Thorn", + "onequarter", + "divide", + "brokenbar", + "degree", + "thorn", + "threequarters", + "twosuperior", + "registered", + "minus", + "eth", + "multiply", + "threesuperior", + "copyright", + "Aacute", + "Acircumflex", + "Adieresis", + "Agrave", + "Aring", + "Atilde", + "Ccedilla", + "Eacute", + "Ecircumflex", + "Edieresis", + "Egrave", + "Iacute", + "Icircumflex", + "Idieresis", + "Igrave", + "Ntilde", + "Oacute", + "Ocircumflex", + "Odieresis", + "Ograve", + "Otilde", + "Scaron", + "Uacute", + "Ucircumflex", + "Udieresis", + "Ugrave", + "Yacute", + "Ydieresis", + "Zcaron", + "aacute", + "acircumflex", + "adieresis", + "agrave", + "aring", + "atilde", + "ccedilla", + "eacute", + "ecircumflex", + "edieresis", + "egrave", + "iacute", + "icircumflex", + "idieresis", + "igrave", + "ntilde", + "oacute", + "ocircumflex", + "odieresis", + "ograve", + "otilde", + "scaron", + "uacute", + "ucircumflex", + "udieresis", + "ugrave", + "yacute", + "ydieresis", + "zcaron", + "exclamsmall", + "Hungarumlautsmall", + "dollaroldstyle", + "dollarsuperior", + "ampersandsmall", + "Acutesmall", + "parenleftsuperior", + "parenrightsuperior", + "266 ff", + "onedotenleader", + "zerooldstyle", + "oneoldstyle", + "twooldstyle", + "threeoldstyle", + "fouroldstyle", + "fiveoldstyle", + "sixoldstyle", + "sevenoldstyle", + "eightoldstyle", + "nineoldstyle", + "commasuperior", + "threequartersemdash", + "periodsuperior", + "questionsmall", + "asuperior", + "bsuperior", + "centsuperior", + "dsuperior", + "esuperior", + "isuperior", + "lsuperior", + "msuperior", + "nsuperior", + "osuperior", + "rsuperior", + "ssuperior", + "tsuperior", + "ff", + "ffi", + "ffl", + "parenleftinferior", + "parenrightinferior", + "Circumflexsmall", + "hyphensuperior", + "Gravesmall", + "Asmall", + "Bsmall", + "Csmall", + "Dsmall", + "Esmall", + "Fsmall", + "Gsmall", + "Hsmall", + "Ismall", + "Jsmall", + "Ksmall", + "Lsmall", + "Msmall", + "Nsmall", + "Osmall", + "Psmall", + "Qsmall", + "Rsmall", + "Ssmall", + "Tsmall", + "Usmall", + "Vsmall", + "Wsmall", + "Xsmall", + "Ysmall", + "Zsmall", + "colonmonetary", + "onefitted", + "rupiah", + "Tildesmall", + "exclamdownsmall", + "centoldstyle", + "Lslashsmall", + "Scaronsmall", + "Zcaronsmall", + "Dieresissmall", + "Brevesmall", + "Caronsmall", + "Dotaccentsmall", + "Macronsmall", + "figuredash", + "hypheninferior", + "Ogoneksmall", + "Ringsmall", + "Cedillasmall", + "questiondownsmall", + "oneeighth", + "threeeighths", + "fiveeighths", + "seveneighths", + "onethird", + "twothirds", + "zerosuperior", + "foursuperior", + "fivesuperior", + "sixsuperior", + "sevensuperior", + "eightsuperior", + "ninesuperior", + "zeroinferior", + "oneinferior", + "twoinferior", + "threeinferior", + "fourinferior", + "fiveinferior", + "sixinferior", + "seveninferior", + "eightinferior", + "nineinferior", + "centinferior", + "dollarinferior", + "periodinferior", + "commainferior", + "Agravesmall", + "Aacutesmall", + "Acircumflexsmall", + "Atildesmall", + "Adieresissmall", + "Aringsmall", + "AEsmall", + "Ccedillasmall", + "Egravesmall", + "Eacutesmall", + "Ecircumflexsmall", + "Edieresissmall", + "Igravesmall", + "Iacutesmall", + "Icircumflexsmall", + "Idieresissmall", + "Ethsmall", + "Ntildesmall", + "Ogravesmall", + "Oacutesmall", + "Ocircumflexsmall", + "Otildesmall", + "Odieresissmall", + "OEsmall", + "Oslashsmall", + "Ugravesmall", + "Uacutesmall", + "Ucircumflexsmall", + "Udieresissmall", + "Yacutesmall", + "Thornsmall", + "Ydieresissmall", + "001.000", + "001.001", + "001.002", + "001.003", + "Black", + "Bold", + "Book", + "Light", + "Medium", + "Regular", + "Roman", + "Semibold" + ]; + var cffStandardEncoding = [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "space", + "exclam", + "quotedbl", + "numbersign", + "dollar", + "percent", + "ampersand", + "quoteright", + "parenleft", + "parenright", + "asterisk", + "plus", + "comma", + "hyphen", + "period", + "slash", + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "colon", + "semicolon", + "less", + "equal", + "greater", + "question", + "at", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "bracketleft", + "backslash", + "bracketright", + "asciicircum", + "underscore", + "quoteleft", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "braceleft", + "bar", + "braceright", + "asciitilde", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "exclamdown", + "cent", + "sterling", + "fraction", + "yen", + "florin", + "section", + "currency", + "quotesingle", + "quotedblleft", + "guillemotleft", + "guilsinglleft", + "guilsinglright", + "fi", + "fl", + "", + "endash", + "dagger", + "daggerdbl", + "periodcentered", + "", + "paragraph", + "bullet", + "quotesinglbase", + "quotedblbase", + "quotedblright", + "guillemotright", + "ellipsis", + "perthousand", + "", + "questiondown", + "", + "grave", + "acute", + "circumflex", + "tilde", + "macron", + "breve", + "dotaccent", + "dieresis", + "", + "ring", + "cedilla", + "", + "hungarumlaut", + "ogonek", + "caron", + "emdash", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "AE", + "", + "ordfeminine", + "", + "", + "", + "", + "Lslash", + "Oslash", + "OE", + "ordmasculine", + "", + "", + "", + "", + "", + "ae", + "", + "", + "", + "dotlessi", + "", + "", + "lslash", + "oslash", + "oe", + "germandbls" + ]; + var cffExpertEncoding = [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "space", + "exclamsmall", + "Hungarumlautsmall", + "", + "dollaroldstyle", + "dollarsuperior", + "ampersandsmall", + "Acutesmall", + "parenleftsuperior", + "parenrightsuperior", + "twodotenleader", + "onedotenleader", + "comma", + "hyphen", + "period", + "fraction", + "zerooldstyle", + "oneoldstyle", + "twooldstyle", + "threeoldstyle", + "fouroldstyle", + "fiveoldstyle", + "sixoldstyle", + "sevenoldstyle", + "eightoldstyle", + "nineoldstyle", + "colon", + "semicolon", + "commasuperior", + "threequartersemdash", + "periodsuperior", + "questionsmall", + "", + "asuperior", + "bsuperior", + "centsuperior", + "dsuperior", + "esuperior", + "", + "", + "isuperior", + "", + "", + "lsuperior", + "msuperior", + "nsuperior", + "osuperior", + "", + "", + "rsuperior", + "ssuperior", + "tsuperior", + "", + "ff", + "fi", + "fl", + "ffi", + "ffl", + "parenleftinferior", + "", + "parenrightinferior", + "Circumflexsmall", + "hyphensuperior", + "Gravesmall", + "Asmall", + "Bsmall", + "Csmall", + "Dsmall", + "Esmall", + "Fsmall", + "Gsmall", + "Hsmall", + "Ismall", + "Jsmall", + "Ksmall", + "Lsmall", + "Msmall", + "Nsmall", + "Osmall", + "Psmall", + "Qsmall", + "Rsmall", + "Ssmall", + "Tsmall", + "Usmall", + "Vsmall", + "Wsmall", + "Xsmall", + "Ysmall", + "Zsmall", + "colonmonetary", + "onefitted", + "rupiah", + "Tildesmall", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "exclamdownsmall", + "centoldstyle", + "Lslashsmall", + "", + "", + "Scaronsmall", + "Zcaronsmall", + "Dieresissmall", + "Brevesmall", + "Caronsmall", + "", + "Dotaccentsmall", + "", + "", + "Macronsmall", + "", + "", + "figuredash", + "hypheninferior", + "", + "", + "Ogoneksmall", + "Ringsmall", + "Cedillasmall", + "", + "", + "", + "onequarter", + "onehalf", + "threequarters", + "questiondownsmall", + "oneeighth", + "threeeighths", + "fiveeighths", + "seveneighths", + "onethird", + "twothirds", + "", + "", + "zerosuperior", + "onesuperior", + "twosuperior", + "threesuperior", + "foursuperior", + "fivesuperior", + "sixsuperior", + "sevensuperior", + "eightsuperior", + "ninesuperior", + "zeroinferior", + "oneinferior", + "twoinferior", + "threeinferior", + "fourinferior", + "fiveinferior", + "sixinferior", + "seveninferior", + "eightinferior", + "nineinferior", + "centinferior", + "dollarinferior", + "periodinferior", + "commainferior", + "Agravesmall", + "Aacutesmall", + "Acircumflexsmall", + "Atildesmall", + "Adieresissmall", + "Aringsmall", + "AEsmall", + "Ccedillasmall", + "Egravesmall", + "Eacutesmall", + "Ecircumflexsmall", + "Edieresissmall", + "Igravesmall", + "Iacutesmall", + "Icircumflexsmall", + "Idieresissmall", + "Ethsmall", + "Ntildesmall", + "Ogravesmall", + "Oacutesmall", + "Ocircumflexsmall", + "Otildesmall", + "Odieresissmall", + "OEsmall", + "Oslashsmall", + "Ugravesmall", + "Uacutesmall", + "Ucircumflexsmall", + "Udieresissmall", + "Yacutesmall", + "Thornsmall", + "Ydieresissmall" + ]; + function DefaultEncoding(font) { + this.font = font; + } + DefaultEncoding.prototype.charToGlyphIndex = function(c2) { + var code = c2.codePointAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i2 = 0; i2 < glyphs.length; i2 += 1) { + var glyph = glyphs.get(i2); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i2; + } + } + } + } + return null; + }; + function CmapEncoding(cmap2) { + this.cmap = cmap2; + } + CmapEncoding.prototype.charToGlyphIndex = function(c2) { + return this.cmap.glyphIndexMap[c2.codePointAt(0)] || 0; + }; + function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; + } + CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.codePointAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); + }; + function addGlyphNamesAll(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + for (var i2 = 0; i2 < charCodes.length; i2 += 1) { + var c2 = charCodes[i2]; + var glyphIndex = glyphIndexMap[c2]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c2)); + } + } + function addGlyphNamesToUnicodeMap(font) { + font._IndexToUnicodeMap = {}; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + for (var i2 = 0; i2 < charCodes.length; i2 += 1) { + var c2 = charCodes[i2]; + var glyphIndex = glyphIndexMap[c2]; + if (font._IndexToUnicodeMap[glyphIndex] === void 0) { + font._IndexToUnicodeMap[glyphIndex] = { + unicodes: [parseInt(c2)] + }; + } else { + font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c2)); + } + } + } + function addGlyphNames(font, opt) { + if (opt.lowMemory) { + addGlyphNamesToUnicodeMap(font); + } else { + addGlyphNamesAll(font); + } + } + function fail(message) { + throw new Error(message); + } + function argument(predicate, message) { + if (!predicate) { + fail(message); + } + } + var check = { fail, argument, assert: argument }; + function getPathDefinition(glyph, path2) { + var _path = path2 || new Path(); + return { + configurable: true, + get: function() { + if (typeof _path === "function") { + _path = _path(); + } + return _path; + }, + set: function(p) { + _path = p; + } + }; + } + function Glyph(options) { + this.bindConstructorValues(options); + } + Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; + this.name = options.name || null; + this.unicode = options.unicode || void 0; + this.unicodes = options.unicodes || options.unicode !== void 0 ? [options.unicode] : []; + if ("xMin" in options) { + this.xMin = options.xMin; + } + if ("yMin" in options) { + this.yMin = options.yMin; + } + if ("xMax" in options) { + this.xMax = options.xMax; + } + if ("yMax" in options) { + this.yMax = options.yMax; + } + if ("advanceWidth" in options) { + this.advanceWidth = options.advanceWidth; + } + Object.defineProperty(this, "path", getPathDefinition(this, options.path)); + }; + Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } + this.unicodes.push(unicode); + }; + Glyph.prototype.getPath = function(x3, y, fontSize, options, font) { + x3 = x3 !== void 0 ? x3 : 0; + y = y !== void 0 ? y : 0; + fontSize = fontSize !== void 0 ? fontSize : 72; + var commands; + var hPoints; + if (!options) { + options = {}; + } + var xScale = options.xScale; + var yScale = options.yScale; + if (options.hinting && font && font.hinting) { + hPoints = this.path && font.hinting.exec(this, fontSize); + } + if (hPoints) { + commands = font.hinting.getCommands(hPoints); + x3 = Math.round(x3); + y = Math.round(y); + xScale = yScale = 1; + } else { + commands = this.path.commands; + var scale = 1 / (this.path.unitsPerEm || 1e3) * fontSize; + if (xScale === void 0) { + xScale = scale; + } + if (yScale === void 0) { + yScale = scale; + } + } + var p = new Path(); + for (var i2 = 0; i2 < commands.length; i2 += 1) { + var cmd = commands[i2]; + if (cmd.type === "M") { + p.moveTo(x3 + cmd.x * xScale, y + -cmd.y * yScale); + } else if (cmd.type === "L") { + p.lineTo(x3 + cmd.x * xScale, y + -cmd.y * yScale); + } else if (cmd.type === "Q") { + p.quadraticCurveTo( + x3 + cmd.x1 * xScale, + y + -cmd.y1 * yScale, + x3 + cmd.x * xScale, + y + -cmd.y * yScale + ); + } else if (cmd.type === "C") { + p.curveTo( + x3 + cmd.x1 * xScale, + y + -cmd.y1 * yScale, + x3 + cmd.x2 * xScale, + y + -cmd.y2 * yScale, + x3 + cmd.x * xScale, + y + -cmd.y * yScale + ); + } else if (cmd.type === "Z") { + p.closePath(); + } + } + return p; + }; + Glyph.prototype.getContours = function() { + if (this.points === void 0) { + return []; + } + var contours = []; + var currentContour = []; + for (var i2 = 0; i2 < this.points.length; i2 += 1) { + var pt = this.points[i2]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + check.argument( + currentContour.length === 0, + "There are still points left in the current contour." + ); + return contours; + }; + Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i2 = 0; i2 < commands.length; i2 += 1) { + var cmd = commands[i2]; + if (cmd.type !== "Z") { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } + if (cmd.type === "Q" || cmd.type === "C") { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + if (cmd.type === "C") { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: this.leftSideBearing + }; + if (!isFinite(metrics.xMin)) { + metrics.xMin = 0; + } + if (!isFinite(metrics.xMax)) { + metrics.xMax = this.advanceWidth; + } + if (!isFinite(metrics.yMin)) { + metrics.yMin = 0; + } + if (!isFinite(metrics.yMax)) { + metrics.yMax = 0; + } + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; + }; + function defineDependentProperty(glyph, externalName, internalName) { + Object.defineProperty(glyph, externalName, { + get: function() { + glyph.path; + return glyph[internalName]; + }, + set: function(newValue) { + glyph[internalName] = newValue; + }, + enumerable: true, + configurable: true + }); + } + function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i2 = 0; i2 < glyphs.length; i2++) { + var glyph = glyphs[i2]; + glyph.path.unitsPerEm = font.unitsPerEm; + this.glyphs[i2] = glyph; + } + } + this.length = glyphs && glyphs.length || 0; + } + GlyphSet.prototype.get = function(index) { + if (this.glyphs[index] === void 0) { + this.font._push(index); + if (typeof this.glyphs[index] === "function") { + this.glyphs[index] = this.glyphs[index](); + } + var glyph = this.glyphs[index]; + var unicodeObj = this.font._IndexToUnicodeMap[index]; + if (unicodeObj) { + for (var j = 0; j < unicodeObj.unicodes.length; j++) { + glyph.addUnicode(unicodeObj.unicodes[j]); + } + } + this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; + this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; + } else { + if (typeof this.glyphs[index] === "function") { + this.glyphs[index] = this.glyphs[index](); + } + } + return this.glyphs[index]; + }; + GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; + }; + function glyphLoader(font, index) { + return new Glyph({ index, font }); + } + function ttfGlyphLoader(font, index, parseGlyph2, data, position, buildPath2) { + return function() { + var glyph = new Glyph({ index, font }); + glyph.path = function() { + parseGlyph2(glyph, data, position); + var path2 = buildPath2(font.glyphs, glyph); + path2.unitsPerEm = font.unitsPerEm; + return path2; + }; + defineDependentProperty(glyph, "xMin", "_xMin"); + defineDependentProperty(glyph, "xMax", "_xMax"); + defineDependentProperty(glyph, "yMin", "_yMin"); + defineDependentProperty(glyph, "yMax", "_yMax"); + return glyph; + }; + } + function cffGlyphLoader(font, index, parseCFFCharstring2, charstring) { + return function() { + var glyph = new Glyph({ index, font }); + glyph.path = function() { + var path2 = parseCFFCharstring2(font, glyph, charstring); + path2.unitsPerEm = font.unitsPerEm; + return path2; + }; + return glyph; + }; + } + var glyphset = { GlyphSet, glyphLoader, ttfGlyphLoader, cffGlyphLoader }; + function searchTag(arr, tag) { + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = imin + imax >>> 1; + var val = arr[imid].tag; + if (val === tag) { + return imid; + } else if (val < tag) { + imin = imid + 1; + } else { + imax = imid - 1; + } + } + return -imin - 1; + } + function binSearch(arr, value) { + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = imin + imax >>> 1; + var val = arr[imid]; + if (val === value) { + return imid; + } else if (val < value) { + imin = imid + 1; + } else { + imax = imid - 1; + } + } + return -imin - 1; + } + function searchRange(ranges, value) { + var range; + var imin = 0; + var imax = ranges.length - 1; + while (imin <= imax) { + var imid = imin + imax >>> 1; + range = ranges[imid]; + var start = range.start; + if (start === value) { + return range; + } else if (start < value) { + imin = imid + 1; + } else { + imax = imid - 1; + } + } + if (imin > 0) { + range = ranges[imin - 1]; + if (value > range.end) { + return 0; + } + return range; + } + } + function Layout(font, tableName) { + this.font = font; + this.tableName = tableName; + } + Layout.prototype = { + /** + * Binary search an object by "tag" property + * @instance + * @function searchTag + * @memberof opentype.Layout + * @param {Array} arr + * @param {string} tag + * @return {number} + */ + searchTag, + /** + * Binary search in a list of numbers + * @instance + * @function binSearch + * @memberof opentype.Layout + * @param {Array} arr + * @param {number} value + * @return {number} + */ + binSearch, + /** + * Get or create the Layout table (GSUB, GPOS etc). + * @param {boolean} create - Whether to create a new one. + * @return {Object} The GSUB or GPOS table. + */ + getTable: function(create) { + var layout = this.font.tables[this.tableName]; + if (!layout && create) { + layout = this.font.tables[this.tableName] = this.createDefaultTable(); + } + return layout; + }, + /** + * Returns the best bet for a script name. + * Returns 'DFLT' if it exists. + * If not, returns 'latn' if it exists. + * If neither exist, returns undefined. + */ + getDefaultScriptName: function() { + var layout = this.getTable(); + if (!layout) { + return; + } + var hasLatn = false; + for (var i2 = 0; i2 < layout.scripts.length; i2++) { + var name = layout.scripts[i2].tag; + if (name === "DFLT") { + return name; + } + if (name === "latn") { + hasLatn = true; + } + } + if (hasLatn) { + return "latn"; + } + }, + /** + * Returns all LangSysRecords in the given script. + * @instance + * @param {string} [script='DFLT'] + * @param {boolean} create - forces the creation of this script table if it doesn't exist. + * @return {Object} An object with tag and script properties. + */ + getScriptTable: function(script, create) { + var layout = this.getTable(create); + if (layout) { + script = script || "DFLT"; + var scripts = layout.scripts; + var pos = searchTag(layout.scripts, script); + if (pos >= 0) { + return scripts[pos].script; + } else if (create) { + var scr = { + tag: script, + script: { + defaultLangSys: { + reserved: 0, + reqFeatureIndex: 65535, + featureIndexes: [] + }, + langSysRecords: [] + } + }; + scripts.splice(-1 - pos, 0, scr); + return scr.script; + } + } + }, + /** + * Returns a language system table + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. + * @return {Object} + */ + getLangSysTable: function(script, language, create) { + var scriptTable = this.getScriptTable(script, create); + if (scriptTable) { + if (!language || language === "dflt" || language === "DFLT") { + return scriptTable.defaultLangSys; + } + var pos = searchTag(scriptTable.langSysRecords, language); + if (pos >= 0) { + return scriptTable.langSysRecords[pos].langSys; + } else if (create) { + var langSysRecord = { + tag: language, + langSys: { + reserved: 0, + reqFeatureIndex: 65535, + featureIndexes: [] + } + }; + scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); + return langSysRecord.langSys; + } + } + }, + /** + * Get a specific feature table. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm + * @param {boolean} create - forces the creation of the feature table if it doesn't exist. + * @return {Object} + */ + getFeatureTable: function(script, language, feature, create) { + var langSysTable2 = this.getLangSysTable(script, language, create); + if (langSysTable2) { + var featureRecord; + var featIndexes = langSysTable2.featureIndexes; + var allFeatures = this.font.tables[this.tableName].features; + for (var i2 = 0; i2 < featIndexes.length; i2++) { + featureRecord = allFeatures[featIndexes[i2]]; + if (featureRecord.tag === feature) { + return featureRecord.feature; + } + } + if (create) { + var index = allFeatures.length; + check.assert( + index === 0 || feature >= allFeatures[index - 1].tag, + "Features must be added in alphabetical order." + ); + featureRecord = { + tag: feature, + feature: { params: 0, lookupListIndexes: [] } + }; + allFeatures.push(featureRecord); + featIndexes.push(index); + return featureRecord.feature; + } + } + }, + /** + * Get the lookup tables of a given type for a script/language/feature. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - 4-letter feature code + * @param {number} lookupType - 1 to 9 + * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. + * @return {Object[]} + */ + getLookupTables: function(script, language, feature, lookupType, create) { + var featureTable = this.getFeatureTable( + script, + language, + feature, + create + ); + var tables = []; + if (featureTable) { + var lookupTable; + var lookupListIndexes = featureTable.lookupListIndexes; + var allLookups = this.font.tables[this.tableName].lookups; + for (var i2 = 0; i2 < lookupListIndexes.length; i2++) { + lookupTable = allLookups[lookupListIndexes[i2]]; + if (lookupTable.lookupType === lookupType) { + tables.push(lookupTable); + } + } + if (tables.length === 0 && create) { + lookupTable = { + lookupType, + lookupFlag: 0, + subtables: [], + markFilteringSet: void 0 + }; + var index = allLookups.length; + allLookups.push(lookupTable); + lookupListIndexes.push(index); + return [lookupTable]; + } + } + return tables; + }, + /** + * Find a glyph in a class definition table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table + * @param {object} classDefTable - an OpenType Layout class definition table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getGlyphClass: function(classDefTable, glyphIndex) { + switch (classDefTable.format) { + case 1: + if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { + return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; + } + return 0; + case 2: + var range = searchRange(classDefTable.ranges, glyphIndex); + return range ? range.classId : 0; + } + }, + /** + * Find a glyph in a coverage table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table + * @param {object} coverageTable - an OpenType Layout coverage table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getCoverageIndex: function(coverageTable, glyphIndex) { + switch (coverageTable.format) { + case 1: + var index = binSearch(coverageTable.glyphs, glyphIndex); + return index >= 0 ? index : -1; + case 2: + var range = searchRange(coverageTable.ranges, glyphIndex); + return range ? range.index + glyphIndex - range.start : -1; + } + }, + /** + * Returns the list of glyph indexes of a coverage table. + * Format 1: the list is stored raw + * Format 2: compact list as range records. + * @instance + * @param {Object} coverageTable + * @return {Array} + */ + expandCoverage: function(coverageTable) { + if (coverageTable.format === 1) { + return coverageTable.glyphs; + } else { + var glyphs = []; + var ranges = coverageTable.ranges; + for (var i2 = 0; i2 < ranges.length; i2++) { + var range = ranges[i2]; + var start = range.start; + var end = range.end; + for (var j = start; j <= end; j++) { + glyphs.push(j); + } + } + return glyphs; + } + } + }; + function Position(font) { + Layout.call(this, font, "gpos"); + } + Position.prototype = Layout.prototype; + Position.prototype.init = function() { + var script = this.getDefaultScriptName(); + this.defaultKerningTables = this.getKerningTables(script); + }; + Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { + for (var i2 = 0; i2 < kerningLookups.length; i2++) { + var subtables = kerningLookups[i2].subtables; + for (var j = 0; j < subtables.length; j++) { + var subtable = subtables[j]; + var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); + if (covIndex < 0) { + continue; + } + switch (subtable.posFormat) { + case 1: + var pairSet = subtable.pairSets[covIndex]; + for (var k = 0; k < pairSet.length; k++) { + var pair = pairSet[k]; + if (pair.secondGlyph === rightIndex) { + return pair.value1 && pair.value1.xAdvance || 0; + } + } + break; + // left glyph found, not right glyph - try next subtable + case 2: + var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); + var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); + var pair$1 = subtable.classRecords[class1][class2]; + return pair$1.value1 && pair$1.value1.xAdvance || 0; + } + } + } + return 0; + }; + Position.prototype.getKerningTables = function(script, language) { + if (this.font.tables.gpos) { + return this.getLookupTables(script, language, "kern", 2); + } + }; + function Substitution(font) { + Layout.call(this, font, "gsub"); + } + function arraysEqual(ar1, ar2) { + var n = ar1.length; + if (n !== ar2.length) { + return false; + } + for (var i2 = 0; i2 < n; i2++) { + if (ar1[i2] !== ar2[i2]) { + return false; + } + } + return true; + } + function getSubstFormat(lookupTable, format, defaultSubtable) { + var subtables = lookupTable.subtables; + for (var i2 = 0; i2 < subtables.length; i2++) { + var subtable = subtables[i2]; + if (subtable.substFormat === format) { + return subtable; + } + } + if (defaultSubtable) { + subtables.push(defaultSubtable); + return defaultSubtable; + } + return void 0; + } + Substitution.prototype = Layout.prototype; + Substitution.prototype.createDefaultTable = function() { + return { + version: 1, + scripts: [ + { + tag: "DFLT", + script: { + defaultLangSys: { + reserved: 0, + reqFeatureIndex: 65535, + featureIndexes: [] + }, + langSysRecords: [] + } + } + ], + features: [], + lookups: [] + }; + }; + Substitution.prototype.getSingle = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 1); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i2 = 0; i2 < subtables.length; i2++) { + var subtable = subtables[i2]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = void 0; + if (subtable.substFormat === 1) { + var delta = subtable.deltaGlyphId; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + substitutions.push({ sub: glyph, by: glyph + delta }); + } + } else { + var substitute = subtable.substitute; + for (j = 0; j < glyphs.length; j++) { + substitutions.push({ sub: glyphs[j], by: substitute[j] }); + } + } + } + } + return substitutions; + }; + Substitution.prototype.getMultiple = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 2); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i2 = 0; i2 < subtables.length; i2++) { + var subtable = subtables[i2]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = void 0; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + var replacements = subtable.sequences[j]; + substitutions.push({ sub: glyph, by: replacements }); + } + } + } + return substitutions; + }; + Substitution.prototype.getAlternates = function(feature, script, language) { + var alternates = []; + var lookupTables = this.getLookupTables(script, language, feature, 3); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i2 = 0; i2 < subtables.length; i2++) { + var subtable = subtables[i2]; + var glyphs = this.expandCoverage(subtable.coverage); + var alternateSets = subtable.alternateSets; + for (var j = 0; j < glyphs.length; j++) { + alternates.push({ sub: glyphs[j], by: alternateSets[j] }); + } + } + } + return alternates; + }; + Substitution.prototype.getLigatures = function(feature, script, language) { + var ligatures = []; + var lookupTables = this.getLookupTables(script, language, feature, 4); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i2 = 0; i2 < subtables.length; i2++) { + var subtable = subtables[i2]; + var glyphs = this.expandCoverage(subtable.coverage); + var ligatureSets = subtable.ligatureSets; + for (var j = 0; j < glyphs.length; j++) { + var startGlyph = glyphs[j]; + var ligSet = ligatureSets[j]; + for (var k = 0; k < ligSet.length; k++) { + var lig = ligSet[k]; + ligatures.push({ + sub: [startGlyph].concat(lig.components), + by: lig.ligGlyph + }); + } + } + } + } + return ligatures; + }; + Substitution.prototype.addSingle = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables( + script, + language, + feature, + 1, + true + )[0]; + var subtable = getSubstFormat(lookupTable, 2, { + // lookup type 1 subtable, format 2, coverage format 1 + substFormat: 2, + coverage: { format: 1, glyphs: [] }, + substitute: [] + }); + check.assert( + subtable.coverage.format === 1, + "Single: unable to modify coverage table format " + subtable.coverage.format + ); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.substitute.splice(pos, 0, 0); + } + subtable.substitute[pos] = substitution.by; + }; + Substitution.prototype.addMultiple = function(feature, substitution, script, language) { + check.assert( + substitution.by instanceof Array && substitution.by.length > 1, + 'Multiple: "by" must be an array of two or more ids' + ); + var lookupTable = this.getLookupTables( + script, + language, + feature, + 2, + true + )[0]; + var subtable = getSubstFormat(lookupTable, 1, { + // lookup type 2 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + sequences: [] + }); + check.assert( + subtable.coverage.format === 1, + "Multiple: unable to modify coverage table format " + subtable.coverage.format + ); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.sequences.splice(pos, 0, 0); + } + subtable.sequences[pos] = substitution.by; + }; + Substitution.prototype.addAlternate = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables( + script, + language, + feature, + 3, + true + )[0]; + var subtable = getSubstFormat(lookupTable, 1, { + // lookup type 3 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + alternateSets: [] + }); + check.assert( + subtable.coverage.format === 1, + "Alternate: unable to modify coverage table format " + subtable.coverage.format + ); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.alternateSets.splice(pos, 0, 0); + } + subtable.alternateSets[pos] = substitution.by; + }; + Substitution.prototype.addLigature = function(feature, ligature, script, language) { + var lookupTable = this.getLookupTables( + script, + language, + feature, + 4, + true + )[0]; + var subtable = lookupTable.subtables[0]; + if (!subtable) { + subtable = { + // lookup type 4 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + ligatureSets: [] + }; + lookupTable.subtables[0] = subtable; + } + check.assert( + subtable.coverage.format === 1, + "Ligature: unable to modify coverage table format " + subtable.coverage.format + ); + var coverageGlyph = ligature.sub[0]; + var ligComponents = ligature.sub.slice(1); + var ligatureTable = { + ligGlyph: ligature.by, + components: ligComponents + }; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos >= 0) { + var ligatureSet = subtable.ligatureSets[pos]; + for (var i2 = 0; i2 < ligatureSet.length; i2++) { + if (arraysEqual(ligatureSet[i2].components, ligComponents)) { + return; + } + } + ligatureSet.push(ligatureTable); + } else { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.ligatureSets.splice(pos, 0, [ligatureTable]); + } + }; + Substitution.prototype.getFeature = function(feature, script, language) { + if (/ss\d\d/.test(feature)) { + return this.getSingle(feature, script, language); + } + switch (feature) { + case "aalt": + case "salt": + return this.getSingle(feature, script, language).concat( + this.getAlternates(feature, script, language) + ); + case "dlig": + case "liga": + case "rlig": + return this.getLigatures(feature, script, language); + case "ccmp": + return this.getMultiple(feature, script, language).concat( + this.getLigatures(feature, script, language) + ); + case "stch": + return this.getMultiple(feature, script, language); + } + return void 0; + }; + Substitution.prototype.add = function(feature, sub, script, language) { + if (/ss\d\d/.test(feature)) { + return this.addSingle(feature, sub, script, language); + } + switch (feature) { + case "aalt": + case "salt": + if (typeof sub.by === "number") { + return this.addSingle(feature, sub, script, language); + } + return this.addAlternate(feature, sub, script, language); + case "dlig": + case "liga": + case "rlig": + return this.addLigature(feature, sub, script, language); + case "ccmp": + if (sub.by instanceof Array) { + return this.addMultiple(feature, sub, script, language); + } + return this.addLigature(feature, sub, script, language); + } + return void 0; + }; + function checkArgument(expression, message) { + if (!expression) { + throw message; + } + } + function getByte(dataView, offset) { + return dataView.getUint8(offset); + } + function getUShort(dataView, offset) { + return dataView.getUint16(offset, false); + } + function getShort(dataView, offset) { + return dataView.getInt16(offset, false); + } + function getULong(dataView, offset) { + return dataView.getUint32(offset, false); + } + function getFixed(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; + } + function getTag(dataView, offset) { + var tag = ""; + for (var i2 = offset; i2 < offset + 4; i2 += 1) { + tag += String.fromCharCode(dataView.getInt8(i2)); + } + return tag; + } + function getOffset(dataView, offset, offSize) { + var v2 = 0; + for (var i2 = 0; i2 < offSize; i2 += 1) { + v2 <<= 8; + v2 += dataView.getUint8(offset + i2); + } + return v2; + } + function getBytes(dataView, startOffset, endOffset) { + var bytes = []; + for (var i2 = startOffset; i2 < endOffset; i2 += 1) { + bytes.push(dataView.getUint8(i2)); + } + return bytes; + } + function bytesToString(bytes) { + var s = ""; + for (var i2 = 0; i2 < bytes.length; i2 += 1) { + s += String.fromCharCode(bytes[i2]); + } + return s; + } + var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 + }; + function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; + } + Parser.prototype.parseByte = function() { + var v2 = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v2; + }; + Parser.prototype.parseChar = function() { + var v2 = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v2; + }; + Parser.prototype.parseCard8 = Parser.prototype.parseByte; + Parser.prototype.parseUShort = function() { + var v2 = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v2; + }; + Parser.prototype.parseCard16 = Parser.prototype.parseUShort; + Parser.prototype.parseSID = Parser.prototype.parseUShort; + Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + Parser.prototype.parseShort = function() { + var v2 = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v2; + }; + Parser.prototype.parseF2Dot14 = function() { + var v2 = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v2; + }; + Parser.prototype.parseULong = function() { + var v2 = getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v2; + }; + Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + Parser.prototype.parseFixed = function() { + var v2 = getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v2; + }; + Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ""; + this.relativeOffset += length; + for (var i2 = 0; i2 < length; i2++) { + string += String.fromCharCode(dataView.getUint8(offset + i2)); + } + return string; + }; + Parser.prototype.parseTag = function() { + return this.parseString(4); + }; + Parser.prototype.parseLongDateTime = function() { + var v2 = getULong(this.data, this.offset + this.relativeOffset + 4); + v2 -= 2082844800; + this.relativeOffset += 8; + return v2; + }; + Parser.prototype.parseVersion = function(minorBase) { + var major = getUShort(this.data, this.offset + this.relativeOffset); + var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + if (minorBase === void 0) { + minorBase = 4096; + } + return major + minor / minorBase / 10; + }; + Parser.prototype.skip = function(type, amount) { + if (amount === void 0) { + amount = 1; + } + this.relativeOffset += typeOffsets[type] * amount; + }; + Parser.prototype.parseULongList = function(count) { + if (count === void 0) { + count = this.parseULong(); + } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i2 = 0; i2 < count; i2++) { + offsets[i2] = dataView.getUint32(offset); + offset += 4; + } + this.relativeOffset += count * 4; + return offsets; + }; + Parser.prototype.parseOffset16List = Parser.prototype.parseUShortList = function(count) { + if (count === void 0) { + count = this.parseUShort(); + } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i2 = 0; i2 < count; i2++) { + offsets[i2] = dataView.getUint16(offset); + offset += 2; + } + this.relativeOffset += count * 2; + return offsets; + }; + Parser.prototype.parseShortList = function(count) { + var list2 = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i2 = 0; i2 < count; i2++) { + list2[i2] = dataView.getInt16(offset); + offset += 2; + } + this.relativeOffset += count * 2; + return list2; + }; + Parser.prototype.parseByteList = function(count) { + var list2 = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i2 = 0; i2 < count; i2++) { + list2[i2] = dataView.getUint8(offset++); + } + this.relativeOffset += count; + return list2; + }; + Parser.prototype.parseList = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseUShort(); + } + var list2 = new Array(count); + for (var i2 = 0; i2 < count; i2++) { + list2[i2] = itemCallback.call(this); + } + return list2; + }; + Parser.prototype.parseList32 = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseULong(); + } + var list2 = new Array(count); + for (var i2 = 0; i2 < count; i2++) { + list2[i2] = itemCallback.call(this); + } + return list2; + }; + Parser.prototype.parseRecordList = function(count, recordDescription) { + if (!recordDescription) { + recordDescription = count; + count = this.parseUShort(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i2 = 0; i2 < count; i2++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i2] = rec; + } + return records; + }; + Parser.prototype.parseRecordList32 = function(count, recordDescription) { + if (!recordDescription) { + recordDescription = count; + count = this.parseULong(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i2 = 0; i2 < count; i2++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i2] = rec; + } + return records; + }; + Parser.prototype.parseStruct = function(description) { + if (typeof description === "function") { + return description.call(this); + } else { + var fields = Object.keys(description); + var struct = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = description[fieldName]; + struct[fieldName] = fieldType.call(this); + } + return struct; + } + }; + Parser.prototype.parseValueRecord = function(valueFormat) { + if (valueFormat === void 0) { + valueFormat = this.parseUShort(); + } + if (valueFormat === 0) { + return; + } + var valueRecord = {}; + if (valueFormat & 1) { + valueRecord.xPlacement = this.parseShort(); + } + if (valueFormat & 2) { + valueRecord.yPlacement = this.parseShort(); + } + if (valueFormat & 4) { + valueRecord.xAdvance = this.parseShort(); + } + if (valueFormat & 8) { + valueRecord.yAdvance = this.parseShort(); + } + if (valueFormat & 16) { + valueRecord.xPlaDevice = void 0; + this.parseShort(); + } + if (valueFormat & 32) { + valueRecord.yPlaDevice = void 0; + this.parseShort(); + } + if (valueFormat & 64) { + valueRecord.xAdvDevice = void 0; + this.parseShort(); + } + if (valueFormat & 128) { + valueRecord.yAdvDevice = void 0; + this.parseShort(); + } + return valueRecord; + }; + Parser.prototype.parseValueRecordList = function() { + var valueFormat = this.parseUShort(); + var valueCount = this.parseUShort(); + var values = new Array(valueCount); + for (var i2 = 0; i2 < valueCount; i2++) { + values[i2] = this.parseValueRecord(valueFormat); + } + return values; + }; + Parser.prototype.parsePointer = function(description) { + var structOffset = this.parseOffset16(); + if (structOffset > 0) { + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return void 0; + }; + Parser.prototype.parsePointer32 = function(description) { + var structOffset = this.parseOffset32(); + if (structOffset > 0) { + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return void 0; + }; + Parser.prototype.parseListOfLists = function(itemCallback) { + var offsets = this.parseOffset16List(); + var count = offsets.length; + var relativeOffset = this.relativeOffset; + var list2 = new Array(count); + for (var i2 = 0; i2 < count; i2++) { + var start = offsets[i2]; + if (start === 0) { + list2[i2] = void 0; + continue; + } + this.relativeOffset = start; + if (itemCallback) { + var subOffsets = this.parseOffset16List(); + var subList = new Array(subOffsets.length); + for (var j = 0; j < subOffsets.length; j++) { + this.relativeOffset = start + subOffsets[j]; + subList[j] = itemCallback.call(this); + } + list2[i2] = subList; + } else { + list2[i2] = this.parseUShortList(); + } + } + this.relativeOffset = relativeOffset; + return list2; + }; + Parser.prototype.parseCoverage = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + var count = this.parseUShort(); + if (format === 1) { + return { + format: 1, + glyphs: this.parseUShortList(count) + }; + } else if (format === 2) { + var ranges = new Array(count); + for (var i2 = 0; i2 < count; i2++) { + ranges[i2] = { + start: this.parseUShort(), + end: this.parseUShort(), + index: this.parseUShort() + }; + } + return { + format: 2, + ranges + }; + } + throw new Error("0x" + startOffset.toString(16) + ": Coverage format must be 1 or 2."); + }; + Parser.prototype.parseClassDef = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + if (format === 1) { + return { + format: 1, + startGlyph: this.parseUShort(), + classes: this.parseUShortList() + }; + } else if (format === 2) { + return { + format: 2, + ranges: this.parseRecordList({ + start: Parser.uShort, + end: Parser.uShort, + classId: Parser.uShort + }) + }; + } + throw new Error("0x" + startOffset.toString(16) + ": ClassDef format must be 1 or 2."); + }; + Parser.list = function(count, itemCallback) { + return function() { + return this.parseList(count, itemCallback); + }; + }; + Parser.list32 = function(count, itemCallback) { + return function() { + return this.parseList32(count, itemCallback); + }; + }; + Parser.recordList = function(count, recordDescription) { + return function() { + return this.parseRecordList(count, recordDescription); + }; + }; + Parser.recordList32 = function(count, recordDescription) { + return function() { + return this.parseRecordList32(count, recordDescription); + }; + }; + Parser.pointer = function(description) { + return function() { + return this.parsePointer(description); + }; + }; + Parser.pointer32 = function(description) { + return function() { + return this.parsePointer32(description); + }; + }; + Parser.tag = Parser.prototype.parseTag; + Parser.byte = Parser.prototype.parseByte; + Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; + Parser.uShortList = Parser.prototype.parseUShortList; + Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; + Parser.uLongList = Parser.prototype.parseULongList; + Parser.struct = Parser.prototype.parseStruct; + Parser.coverage = Parser.prototype.parseCoverage; + Parser.classDef = Parser.prototype.parseClassDef; + var langSysTable = { + reserved: Parser.uShort, + reqFeatureIndex: Parser.uShort, + featureIndexes: Parser.uShortList + }; + Parser.prototype.parseScriptList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + script: Parser.pointer({ + defaultLangSys: Parser.pointer(langSysTable), + langSysRecords: Parser.recordList({ + tag: Parser.tag, + langSys: Parser.pointer(langSysTable) + }) + }) + })) || []; + }; + Parser.prototype.parseFeatureList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + feature: Parser.pointer({ + featureParams: Parser.offset16, + lookupListIndexes: Parser.uShortList + }) + })) || []; + }; + Parser.prototype.parseLookupList = function(lookupTableParsers) { + return this.parsePointer(Parser.list(Parser.pointer(function() { + var lookupType = this.parseUShort(); + check.argument(1 <= lookupType && lookupType <= 9, "GPOS/GSUB lookup type " + lookupType + " unknown."); + var lookupFlag = this.parseUShort(); + var useMarkFilteringSet = lookupFlag & 16; + return { + lookupType, + lookupFlag, + subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), + markFilteringSet: useMarkFilteringSet ? this.parseUShort() : void 0 + }; + }))) || []; + }; + Parser.prototype.parseFeatureVariationsList = function() { + return this.parsePointer32(function() { + var majorVersion = this.parseUShort(); + var minorVersion = this.parseUShort(); + check.argument(majorVersion === 1 && minorVersion < 1, "GPOS/GSUB feature variations table unknown."); + var featureVariations = this.parseRecordList32({ + conditionSetOffset: Parser.offset32, + featureTableSubstitutionOffset: Parser.offset32 + }); + return featureVariations; + }) || []; + }; + var parse = { + getByte, + getCard8: getByte, + getUShort, + getCard16: getUShort, + getShort, + getULong, + getFixed, + getTag, + getOffset, + getBytes, + bytesToString, + Parser + }; + function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v2; + if ((flag & shortVectorBitMask) > 0) { + v2 = p.parseByte(); + if ((flag & sameBitMask) === 0) { + v2 = -v2; + } + v2 = previousValue + v2; + } else { + if ((flag & sameBitMask) > 0) { + v2 = previousValue; + } else { + v2 = previousValue + p.parseShort(); + } + } + return v2; + } + function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph._xMin = p.parseShort(); + glyph._yMin = p.parseShort(); + glyph._xMax = p.parseShort(); + glyph._yMax = p.parseShort(); + var flags; + var flag; + if (glyph.numberOfContours > 0) { + var endPointIndices = glyph.endPointIndices = []; + for (var i2 = 0; i2 < glyph.numberOfContours; i2 += 1) { + endPointIndices.push(p.parseUShort()); + } + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { + glyph.instructions.push(p.parseByte()); + } + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { + flag = p.parseByte(); + flags.push(flag); + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i$2 += 1; + } + } + } + check.argument(flags.length === numberOfCoordinates, "Bad flags."); + if (endPointIndices.length > 0) { + var points = []; + var point; + if (numberOfCoordinates > 0) { + for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { + flag = flags[i$3]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; + points.push(point); + } + var px = 0; + for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { + flag = flags[i$4]; + point = points[i$4]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + var py = 0; + for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { + flag = flags[i$5]; + point = points[i$5]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + if ((flags & 2) > 0) { + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + component.matchedPoints = [p.parseUShort(), p.parseUShort()]; + } + } else { + if ((flags & 2) > 0) { + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } else { + component.matchedPoints = [p.parseByte(), p.parseByte()]; + } + } + if ((flags & 8) > 0) { + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + if (flags & 256) { + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { + glyph.instructions.push(p.parseByte()); + } + } + } + } + function transformPoints(points, transform) { + var newPoints = []; + for (var i2 = 0; i2 < points.length; i2 += 1) { + var pt = points[i2]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } + return newPoints; + } + function getContours(points) { + var contours = []; + var currentContour = []; + for (var i2 = 0; i2 < points.length; i2 += 1) { + var pt = points[i2]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + check.argument(currentContour.length === 0, "There are still points left in the current contour."); + return contours; + } + function getPath(points) { + var p = new Path(); + if (!points) { + return p; + } + var contours = getContours(points); + for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { + var contour = contours[contourIndex]; + var prev = null; + var curr = contour[contour.length - 1]; + var next = contour[0]; + if (curr.onCurve) { + p.moveTo(curr.x, curr.y); + } else { + if (next.onCurve) { + p.moveTo(next.x, next.y); + } else { + var start = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + p.moveTo(start.x, start.y); + } + } + for (var i2 = 0; i2 < contour.length; ++i2) { + prev = curr; + curr = next; + next = contour[(i2 + 1) % contour.length]; + if (curr.onCurve) { + p.lineTo(curr.x, curr.y); + } else { + var prev2 = prev; + var next2 = next; + if (!prev.onCurve) { + prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + } + if (!next.onCurve) { + next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + } + p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + } + } + p.closePath(); + } + return p; + } + function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + componentGlyph.getPath(); + if (componentGlyph.points) { + var transformedPoints = void 0; + if (component.matchedPoints === void 0) { + transformedPoints = transformPoints(componentGlyph.points, component); + } else { + if (component.matchedPoints[0] > glyph.points.length - 1 || component.matchedPoints[1] > componentGlyph.points.length - 1) { + throw Error("Matched points out of range in " + glyph.name); + } + var firstPt = glyph.points[component.matchedPoints[0]]; + var secondPt = componentGlyph.points[component.matchedPoints[1]]; + var transform = { + xScale: component.xScale, + scale01: component.scale01, + scale10: component.scale10, + yScale: component.yScale, + dx: 0, + dy: 0 + }; + secondPt = transformPoints([secondPt], transform)[0]; + transform.dx = firstPt.x - secondPt.x; + transform.dy = firstPt.y - secondPt.y; + transformedPoints = transformPoints(componentGlyph.points, transform); + } + glyph.points = glyph.points.concat(transformedPoints); + } + } + } + return getPath(glyph.points); + } + function parseGlyfTableAll(data, start, loca2, font) { + var glyphs = new glyphset.GlyphSet(font); + for (var i2 = 0; i2 < loca2.length - 1; i2 += 1) { + var offset = loca2[i2]; + var nextOffset = loca2[i2 + 1]; + if (offset !== nextOffset) { + glyphs.push(i2, glyphset.ttfGlyphLoader(font, i2, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i2, glyphset.glyphLoader(font, i2)); + } + } + return glyphs; + } + function parseGlyfTableOnLowMemory(data, start, loca2, font) { + var glyphs = new glyphset.GlyphSet(font); + font._push = function(i2) { + var offset = loca2[i2]; + var nextOffset = loca2[i2 + 1]; + if (offset !== nextOffset) { + glyphs.push(i2, glyphset.ttfGlyphLoader(font, i2, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i2, glyphset.glyphLoader(font, i2)); + } + }; + return glyphs; + } + function parseGlyfTable(data, start, loca2, font, opt) { + if (opt.lowMemory) { + return parseGlyfTableOnLowMemory(data, start, loca2, font); + } else { + return parseGlyfTableAll(data, start, loca2, font); + } + } + var glyf = { getPath, parse: parseGlyfTable }; + var instructionTable; + var exec; + var execGlyph; + var execComponent; + function Hinting(font) { + this.font = font; + this.getCommands = function(hPoints) { + return glyf.getPath(hPoints).commands; + }; + this._fpgmState = this._prepState = void 0; + this._errorState = 0; + } + function roundOff(v2) { + return v2; + } + function roundToGrid(v2) { + return Math.sign(v2) * Math.round(Math.abs(v2)); + } + function roundToDoubleGrid(v2) { + return Math.sign(v2) * Math.round(Math.abs(v2 * 2)) / 2; + } + function roundToHalfGrid(v2) { + return Math.sign(v2) * (Math.round(Math.abs(v2) + 0.5) - 0.5); + } + function roundUpToGrid(v2) { + return Math.sign(v2) * Math.ceil(Math.abs(v2)); + } + function roundDownToGrid(v2) { + return Math.sign(v2) * Math.floor(Math.abs(v2)); + } + var roundSuper = function(v2) { + var period = this.srPeriod; + var phase = this.srPhase; + var threshold = this.srThreshold; + var sign = 1; + if (v2 < 0) { + v2 = -v2; + sign = -1; + } + v2 += threshold - phase; + v2 = Math.trunc(v2 / period) * period; + v2 += phase; + if (v2 < 0) { + return phase * sign; + } + return v2 * sign; + }; + var xUnitVector = { + x: 1, + y: 0, + axis: "x", + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function(p1, p2, o1, o2) { + return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); + }, + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function(p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + if (!pv || pv === this) { + do1 = p.xo - rp1.xo; + do2 = p.xo - rp2.xo; + dm1 = rp1.x - rp1.xo; + dm2 = rp2.x - rp2.xo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + if (dt === 0) { + p.x = p.xo + (dm1 + dm2) / 2; + return; + } + p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + if (dt === 0) { + xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + // Slope of line normal to this + normalSlope: Number.NEGATIVE_INFINITY, + // Sets the point 'p' relative to point 'rp' + // by the distance 'd'. + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function(p, rp, d2, pv, org) { + if (!pv || pv === this) { + p.x = (org ? rp.xo : rp.x) + d2; + return; + } + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d2 * pv.x; + var rpdy = rpy + d2 * pv.y; + p.x = rpdx + (p.y - rpdy) / pv.normalSlope; + }, + // Slope of vector line. + slope: 0, + // Touches the point p. + touch: function(p) { + p.xTouched = true; + }, + // Tests if a point p is touched. + touched: function(p) { + return p.xTouched; + }, + // Untouches the point p. + untouch: function(p) { + p.xTouched = false; + } + }; + var yUnitVector = { + x: 0, + y: 1, + axis: "y", + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function(p1, p2, o1, o2) { + return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); + }, + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function(p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + if (!pv || pv === this) { + do1 = p.yo - rp1.yo; + do2 = p.yo - rp2.yo; + dm1 = rp1.y - rp1.yo; + dm2 = rp2.y - rp2.yo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + if (dt === 0) { + p.y = p.yo + (dm1 + dm2) / 2; + return; + } + p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + if (dt === 0) { + yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + // Slope of line normal to this. + normalSlope: 0, + // Sets the point 'p' relative to point 'rp' + // by the distance 'd' + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function(p, rp, d2, pv, org) { + if (!pv || pv === this) { + p.y = (org ? rp.yo : rp.y) + d2; + return; + } + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d2 * pv.x; + var rpdy = rpy + d2 * pv.y; + p.y = rpdy + pv.normalSlope * (p.x - rpdx); + }, + // Slope of vector line. + slope: Number.POSITIVE_INFINITY, + // Touches the point p. + touch: function(p) { + p.yTouched = true; + }, + // Tests if a point p is touched. + touched: function(p) { + return p.yTouched; + }, + // Untouches the point p. + untouch: function(p) { + p.yTouched = false; + } + }; + Object.freeze(xUnitVector); + Object.freeze(yUnitVector); + function UnitVector(x3, y) { + this.x = x3; + this.y = y; + this.axis = void 0; + this.slope = y / x3; + this.normalSlope = -x3 / y; + Object.freeze(this); + } + UnitVector.prototype.distance = function(p1, p2, o1, o2) { + return this.x * xUnitVector.distance(p1, p2, o1, o2) + this.y * yUnitVector.distance(p1, p2, o1, o2); + }; + UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { + var dm1; + var dm2; + var do1; + var do2; + var doa1; + var doa2; + var dt; + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + if (dt === 0) { + this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }; + UnitVector.prototype.setRelative = function(p, rp, d2, pv, org) { + pv = pv || this; + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d2 * pv.x; + var rpdy = rpy + d2 * pv.y; + var pvns = pv.normalSlope; + var fvs = this.slope; + var px = p.x; + var py = p.y; + p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); + p.y = fvs * (p.x - px) + py; + }; + UnitVector.prototype.touch = function(p) { + p.xTouched = true; + p.yTouched = true; + }; + function getUnitVector(x3, y) { + var d2 = Math.sqrt(x3 * x3 + y * y); + x3 /= d2; + y /= d2; + if (x3 === 1 && y === 0) { + return xUnitVector; + } else if (x3 === 0 && y === 1) { + return yUnitVector; + } else { + return new UnitVector(x3, y); + } + } + function HPoint(x3, y, lastPointOfContour, onCurve) { + this.x = this.xo = Math.round(x3 * 64) / 64; + this.y = this.yo = Math.round(y * 64) / 64; + this.lastPointOfContour = lastPointOfContour; + this.onCurve = onCurve; + this.prevPointOnContour = void 0; + this.nextPointOnContour = void 0; + this.xTouched = false; + this.yTouched = false; + Object.preventExtensions(this); + } + HPoint.prototype.nextTouched = function(v2) { + var p = this.nextPointOnContour; + while (!v2.touched(p) && p !== this) { + p = p.nextPointOnContour; + } + return p; + }; + HPoint.prototype.prevTouched = function(v2) { + var p = this.prevPointOnContour; + while (!v2.touched(p) && p !== this) { + p = p.prevPointOnContour; + } + return p; + }; + var HPZero = Object.freeze(new HPoint(0, 0)); + var defaultState = { + cvCutIn: 17 / 16, + // control value cut in + deltaBase: 9, + deltaShift: 0.125, + loop: 1, + // loops some instructions + minDis: 1, + // minimum distance + autoFlip: true + }; + function State(env, prog) { + this.env = env; + this.stack = []; + this.prog = prog; + switch (env) { + case "glyf": + this.zp0 = this.zp1 = this.zp2 = 1; + this.rp0 = this.rp1 = this.rp2 = 0; + /* fall through */ + case "prep": + this.fv = this.pv = this.dpv = xUnitVector; + this.round = roundToGrid; + } + } + Hinting.prototype.exec = function(glyph, ppem) { + if (typeof ppem !== "number") { + throw new Error("Point size is not a number!"); + } + if (this._errorState > 2) { + return; + } + var font = this.font; + var prepState = this._prepState; + if (!prepState || prepState.ppem !== ppem) { + var fpgmState = this._fpgmState; + if (!fpgmState) { + State.prototype = defaultState; + fpgmState = this._fpgmState = new State("fpgm", font.tables.fpgm); + fpgmState.funcs = []; + fpgmState.font = font; + if (exports2.DEBUG) { + console.log("---EXEC FPGM---"); + fpgmState.step = -1; + } + try { + exec(fpgmState); + } catch (e) { + console.log("Hinting error in FPGM:" + e); + this._errorState = 3; + return; + } + } + State.prototype = fpgmState; + prepState = this._prepState = new State("prep", font.tables.prep); + prepState.ppem = ppem; + var oCvt = font.tables.cvt; + if (oCvt) { + var cvt = prepState.cvt = new Array(oCvt.length); + var scale = ppem / font.unitsPerEm; + for (var c2 = 0; c2 < oCvt.length; c2++) { + cvt[c2] = oCvt[c2] * scale; + } + } else { + prepState.cvt = []; + } + if (exports2.DEBUG) { + console.log("---EXEC PREP---"); + prepState.step = -1; + } + try { + exec(prepState); + } catch (e) { + if (this._errorState < 2) { + console.log("Hinting error in PREP:" + e); + } + this._errorState = 2; + } + } + if (this._errorState > 1) { + return; + } + try { + return execGlyph(glyph, prepState); + } catch (e) { + if (this._errorState < 1) { + console.log("Hinting error:" + e); + console.log("Note: further hinting errors are silenced"); + } + this._errorState = 1; + return void 0; + } + }; + execGlyph = function(glyph, prepState) { + var xScale = prepState.ppem / prepState.font.unitsPerEm; + var yScale = xScale; + var components = glyph.components; + var contours; + var gZone; + var state; + State.prototype = prepState; + if (!components) { + state = new State("glyf", glyph.instructions); + if (exports2.DEBUG) { + console.log("---EXEC GLYPH---"); + state.step = -1; + } + execComponent(glyph, state, xScale, yScale); + gZone = state.gZone; + } else { + var font = prepState.font; + gZone = []; + contours = []; + for (var i2 = 0; i2 < components.length; i2++) { + var c2 = components[i2]; + var cg = font.glyphs.get(c2.glyphIndex); + state = new State("glyf", cg.instructions); + if (exports2.DEBUG) { + console.log("---EXEC COMP " + i2 + "---"); + state.step = -1; + } + execComponent(cg, state, xScale, yScale); + var dx = Math.round(c2.dx * xScale); + var dy = Math.round(c2.dy * yScale); + var gz = state.gZone; + var cc = state.contours; + for (var pi2 = 0; pi2 < gz.length; pi2++) { + var p = gz[pi2]; + p.xTouched = p.yTouched = false; + p.xo = p.x = p.x + dx; + p.yo = p.y = p.y + dy; + } + var gLen = gZone.length; + gZone.push.apply(gZone, gz); + for (var j = 0; j < cc.length; j++) { + contours.push(cc[j] + gLen); + } + } + if (glyph.instructions && !state.inhibitGridFit) { + state = new State("glyf", glyph.instructions); + state.gZone = state.z0 = state.z1 = state.z2 = gZone; + state.contours = contours; + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + if (exports2.DEBUG) { + console.log("---EXEC COMPOSITE---"); + state.step = -1; + } + exec(state); + gZone.length -= 2; + } + } + return gZone; + }; + execComponent = function(glyph, state, xScale, yScale) { + var points = glyph.points || []; + var pLen = points.length; + var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; + var contours = state.contours = []; + var cp; + for (var i2 = 0; i2 < pLen; i2++) { + cp = points[i2]; + gZone[i2] = new HPoint( + cp.x * xScale, + cp.y * yScale, + cp.lastPointOfContour, + cp.onCurve + ); + } + var sp; + var np; + for (var i$1 = 0; i$1 < pLen; i$1++) { + cp = gZone[i$1]; + if (!sp) { + sp = cp; + contours.push(i$1); + } + if (cp.lastPointOfContour) { + cp.nextPointOnContour = sp; + sp.prevPointOnContour = cp; + sp = void 0; + } else { + np = gZone[i$1 + 1]; + cp.nextPointOnContour = np; + np.prevPointOnContour = cp; + } + } + if (state.inhibitGridFit) { + return; + } + if (exports2.DEBUG) { + console.log("PROCESSING GLYPH", state.stack); + for (var i$2 = 0; i$2 < pLen; i$2++) { + console.log(i$2, gZone[i$2].x, gZone[i$2].y); + } + } + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + exec(state); + gZone.length -= 2; + if (exports2.DEBUG) { + console.log("FINISHED GLYPH", state.stack); + for (var i$3 = 0; i$3 < pLen; i$3++) { + console.log(i$3, gZone[i$3].x, gZone[i$3].y); + } + } + }; + exec = function(state) { + var prog = state.prog; + if (!prog) { + return; + } + var pLen = prog.length; + var ins; + for (state.ip = 0; state.ip < pLen; state.ip++) { + if (exports2.DEBUG) { + state.step++; + } + ins = instructionTable[prog[state.ip]]; + if (!ins) { + throw new Error( + "unknown instruction: 0x" + Number(prog[state.ip]).toString(16) + ); + } + ins(state); + } + }; + function initTZone(state) { + var tZone = state.tZone = new Array(state.gZone.length); + for (var i2 = 0; i2 < tZone.length; i2++) { + tZone[i2] = new HPoint(0, 0); + } + } + function skip(state, handleElse) { + var prog = state.prog; + var ip = state.ip; + var nesting = 1; + var ins; + do { + ins = prog[++ip]; + if (ins === 88) { + nesting++; + } else if (ins === 89) { + nesting--; + } else if (ins === 64) { + ip += prog[ip + 1] + 1; + } else if (ins === 65) { + ip += 2 * prog[ip + 1] + 1; + } else if (ins >= 176 && ins <= 183) { + ip += ins - 176 + 1; + } else if (ins >= 184 && ins <= 191) { + ip += (ins - 184 + 1) * 2; + } else if (handleElse && nesting === 1 && ins === 27) { + break; + } + } while (nesting > 0); + state.ip = ip; + } + function SVTCA(v2, state) { + if (exports2.DEBUG) { + console.log(state.step, "SVTCA[" + v2.axis + "]"); + } + state.fv = state.pv = state.dpv = v2; + } + function SPVTCA(v2, state) { + if (exports2.DEBUG) { + console.log(state.step, "SPVTCA[" + v2.axis + "]"); + } + state.pv = state.dpv = v2; + } + function SFVTCA(v2, state) { + if (exports2.DEBUG) { + console.log(state.step, "SFVTCA[" + v2.axis + "]"); + } + state.fv = v2; + } + function SPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + if (exports2.DEBUG) { + console.log("SPVTL[" + a + "]", p2i, p1i); + } + var dx; + var dy; + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + state.pv = state.dpv = getUnitVector(dx, dy); + } + function SFVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + if (exports2.DEBUG) { + console.log("SFVTL[" + a + "]", p2i, p1i); + } + var dx; + var dy; + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + state.fv = getUnitVector(dx, dy); + } + function SPVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x3 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SPVFS[]", y, x3); + } + state.pv = state.dpv = getUnitVector(x3, y); + } + function SFVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x3 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SPVFS[]", y, x3); + } + state.fv = getUnitVector(x3, y); + } + function GPV(state) { + var stack = state.stack; + var pv = state.pv; + if (exports2.DEBUG) { + console.log(state.step, "GPV[]"); + } + stack.push(pv.x * 16384); + stack.push(pv.y * 16384); + } + function GFV(state) { + var stack = state.stack; + var fv = state.fv; + if (exports2.DEBUG) { + console.log(state.step, "GFV[]"); + } + stack.push(fv.x * 16384); + stack.push(fv.y * 16384); + } + function SFVTPV(state) { + state.fv = state.pv; + if (exports2.DEBUG) { + console.log(state.step, "SFVTPV[]"); + } + } + function ISECT(state) { + var stack = state.stack; + var pa0i = stack.pop(); + var pa1i = stack.pop(); + var pb0i = stack.pop(); + var pb1i = stack.pop(); + var pi2 = stack.pop(); + var z0 = state.z0; + var z1 = state.z1; + var pa0 = z0[pa0i]; + var pa1 = z0[pa1i]; + var pb0 = z1[pb0i]; + var pb1 = z1[pb1i]; + var p = state.z2[pi2]; + if (exports2.DEBUG) { + console.log("ISECT[], ", pa0i, pa1i, pb0i, pb1i, pi2); + } + var x1 = pa0.x; + var y1 = pa0.y; + var x22 = pa1.x; + var y2 = pa1.y; + var x3 = pb0.x; + var y3 = pb0.y; + var x4 = pb1.x; + var y4 = pb1.y; + var div = (x1 - x22) * (y3 - y4) - (y1 - y2) * (x3 - x4); + var f1 = x1 * y2 - y1 * x22; + var f2 = x3 * y4 - y3 * x4; + p.x = (f1 * (x3 - x4) - f2 * (x1 - x22)) / div; + p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; + } + function SRP0(state) { + state.rp0 = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SRP0[]", state.rp0); + } + } + function SRP1(state) { + state.rp1 = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SRP1[]", state.rp1); + } + } + function SRP2(state) { + state.rp2 = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SRP2[]", state.rp2); + } + } + function SZP0(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SZP0[]", n); + } + state.zp0 = n; + switch (n) { + case 0: + if (!state.tZone) { + initTZone(state); + } + state.z0 = state.tZone; + break; + case 1: + state.z0 = state.gZone; + break; + default: + throw new Error("Invalid zone pointer"); + } + } + function SZP1(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SZP1[]", n); + } + state.zp1 = n; + switch (n) { + case 0: + if (!state.tZone) { + initTZone(state); + } + state.z1 = state.tZone; + break; + case 1: + state.z1 = state.gZone; + break; + default: + throw new Error("Invalid zone pointer"); + } + } + function SZP2(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SZP2[]", n); + } + state.zp2 = n; + switch (n) { + case 0: + if (!state.tZone) { + initTZone(state); + } + state.z2 = state.tZone; + break; + case 1: + state.z2 = state.gZone; + break; + default: + throw new Error("Invalid zone pointer"); + } + } + function SZPS(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SZPS[]", n); + } + state.zp0 = state.zp1 = state.zp2 = n; + switch (n) { + case 0: + if (!state.tZone) { + initTZone(state); + } + state.z0 = state.z1 = state.z2 = state.tZone; + break; + case 1: + state.z0 = state.z1 = state.z2 = state.gZone; + break; + default: + throw new Error("Invalid zone pointer"); + } + } + function SLOOP(state) { + state.loop = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SLOOP[]", state.loop); + } + } + function RTG(state) { + if (exports2.DEBUG) { + console.log(state.step, "RTG[]"); + } + state.round = roundToGrid; + } + function RTHG(state) { + if (exports2.DEBUG) { + console.log(state.step, "RTHG[]"); + } + state.round = roundToHalfGrid; + } + function SMD(state) { + var d2 = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SMD[]", d2); + } + state.minDis = d2 / 64; + } + function ELSE(state) { + if (exports2.DEBUG) { + console.log(state.step, "ELSE[]"); + } + skip(state, false); + } + function JMPR(state) { + var o = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "JMPR[]", o); + } + state.ip += o - 1; + } + function SCVTCI(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SCVTCI[]", n); + } + state.cvCutIn = n / 64; + } + function DUP(state) { + var stack = state.stack; + if (exports2.DEBUG) { + console.log(state.step, "DUP[]"); + } + stack.push(stack[stack.length - 1]); + } + function POP(state) { + if (exports2.DEBUG) { + console.log(state.step, "POP[]"); + } + state.stack.pop(); + } + function CLEAR(state) { + if (exports2.DEBUG) { + console.log(state.step, "CLEAR[]"); + } + state.stack.length = 0; + } + function SWAP(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SWAP[]"); + } + stack.push(a); + stack.push(b); + } + function DEPTH(state) { + var stack = state.stack; + if (exports2.DEBUG) { + console.log(state.step, "DEPTH[]"); + } + stack.push(stack.length); + } + function LOOPCALL(state) { + var stack = state.stack; + var fn2 = stack.pop(); + var c2 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "LOOPCALL[]", fn2, c2); + } + var cip = state.ip; + var cprog = state.prog; + state.prog = state.funcs[fn2]; + for (var i2 = 0; i2 < c2; i2++) { + exec(state); + if (exports2.DEBUG) { + console.log( + ++state.step, + i2 + 1 < c2 ? "next loopcall" : "done loopcall", + i2 + ); + } + } + state.ip = cip; + state.prog = cprog; + } + function CALL(state) { + var fn2 = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "CALL[]", fn2); + } + var cip = state.ip; + var cprog = state.prog; + state.prog = state.funcs[fn2]; + exec(state); + state.ip = cip; + state.prog = cprog; + if (exports2.DEBUG) { + console.log(++state.step, "returning from", fn2); + } + } + function CINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "CINDEX[]", k); + } + stack.push(stack[stack.length - k]); + } + function MINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "MINDEX[]", k); + } + stack.push(stack.splice(stack.length - k, 1)[0]); + } + function FDEF(state) { + if (state.env !== "fpgm") { + throw new Error("FDEF not allowed here"); + } + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + var fn2 = stack.pop(); + var ipBegin = ip; + if (exports2.DEBUG) { + console.log(state.step, "FDEF[]", fn2); + } + while (prog[++ip] !== 45) { + } + state.ip = ip; + state.funcs[fn2] = prog.slice(ipBegin + 1, ip); + } + function MDAP(round, state) { + var pi2 = state.stack.pop(); + var p = state.z0[pi2]; + var fv = state.fv; + var pv = state.pv; + if (exports2.DEBUG) { + console.log(state.step, "MDAP[" + round + "]", pi2); + } + var d2 = pv.distance(p, HPZero); + if (round) { + d2 = state.round(d2); + } + fv.setRelative(p, HPZero, d2, pv); + fv.touch(p); + state.rp0 = state.rp1 = pi2; + } + function IUP(v2, state) { + var z2 = state.z2; + var pLen = z2.length - 2; + var cp; + var pp; + var np; + if (exports2.DEBUG) { + console.log(state.step, "IUP[" + v2.axis + "]"); + } + for (var i2 = 0; i2 < pLen; i2++) { + cp = z2[i2]; + if (v2.touched(cp)) { + continue; + } + pp = cp.prevTouched(v2); + if (pp === cp) { + continue; + } + np = cp.nextTouched(v2); + if (pp === np) { + v2.setRelative(cp, cp, v2.distance(pp, pp, false, true), v2, true); + } + v2.interpolate(cp, pp, np, v2); + } + } + function SHP(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var loop = state.loop; + var z2 = state.z2; + while (loop--) { + var pi2 = stack.pop(); + var p = z2[pi2]; + var d2 = pv.distance(rp, rp, false, true); + fv.setRelative(p, p, d2, pv); + fv.touch(p); + if (exports2.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? "loop " + (state.loop - loop) + ": " : "") + "SHP[" + (a ? "rp1" : "rp2") + "]", + pi2 + ); + } + } + state.loop = 1; + } + function SHC(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var ci2 = stack.pop(); + var sp = state.z2[state.contours[ci2]]; + var p = sp; + if (exports2.DEBUG) { + console.log(state.step, "SHC[" + a + "]", ci2); + } + var d2 = pv.distance(rp, rp, false, true); + do { + if (p !== rp) { + fv.setRelative(p, p, d2, pv); + } + p = p.nextPointOnContour; + } while (p !== sp); + } + function SHZ(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var e = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SHZ[" + a + "]", e); + } + var z; + switch (e) { + case 0: + z = state.tZone; + break; + case 1: + z = state.gZone; + break; + default: + throw new Error("Invalid zone"); + } + var p; + var d2 = pv.distance(rp, rp, false, true); + var pLen = z.length - 2; + for (var i2 = 0; i2 < pLen; i2++) { + p = z[i2]; + fv.setRelative(p, p, d2, pv); + } + } + function SHPIX(state) { + var stack = state.stack; + var loop = state.loop; + var fv = state.fv; + var d2 = stack.pop() / 64; + var z2 = state.z2; + while (loop--) { + var pi2 = stack.pop(); + var p = z2[pi2]; + if (exports2.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? "loop " + (state.loop - loop) + ": " : "") + "SHPIX[]", + pi2, + d2 + ); + } + fv.setRelative(p, p, d2); + fv.touch(p); + } + state.loop = 1; + } + function IP(state) { + var stack = state.stack; + var rp1i = state.rp1; + var rp2i = state.rp2; + var loop = state.loop; + var rp1 = state.z0[rp1i]; + var rp2 = state.z1[rp2i]; + var fv = state.fv; + var pv = state.dpv; + var z2 = state.z2; + while (loop--) { + var pi2 = stack.pop(); + var p = z2[pi2]; + if (exports2.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? "loop " + (state.loop - loop) + ": " : "") + "IP[]", + pi2, + rp1i, + "<->", + rp2i + ); + } + fv.interpolate(p, rp1, rp2, pv); + fv.touch(p); + } + state.loop = 1; + } + function MSIRP(a, state) { + var stack = state.stack; + var d2 = stack.pop() / 64; + var pi2 = stack.pop(); + var p = state.z1[pi2]; + var rp0 = state.z0[state.rp0]; + var fv = state.fv; + var pv = state.pv; + fv.setRelative(p, rp0, d2, pv); + fv.touch(p); + if (exports2.DEBUG) { + console.log(state.step, "MSIRP[" + a + "]", d2, pi2); + } + state.rp1 = state.rp0; + state.rp2 = pi2; + if (a) { + state.rp0 = pi2; + } + } + function ALIGNRP(state) { + var stack = state.stack; + var rp0i = state.rp0; + var rp0 = state.z0[rp0i]; + var loop = state.loop; + var fv = state.fv; + var pv = state.pv; + var z1 = state.z1; + while (loop--) { + var pi2 = stack.pop(); + var p = z1[pi2]; + if (exports2.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? "loop " + (state.loop - loop) + ": " : "") + "ALIGNRP[]", + pi2 + ); + } + fv.setRelative(p, rp0, 0, pv); + fv.touch(p); + } + state.loop = 1; + } + function RTDG(state) { + if (exports2.DEBUG) { + console.log(state.step, "RTDG[]"); + } + state.round = roundToDoubleGrid; + } + function MIAP(round, state) { + var stack = state.stack; + var n = stack.pop(); + var pi2 = stack.pop(); + var p = state.z0[pi2]; + var fv = state.fv; + var pv = state.pv; + var cv = state.cvt[n]; + if (exports2.DEBUG) { + console.log( + state.step, + "MIAP[" + round + "]", + n, + "(", + cv, + ")", + pi2 + ); + } + var d2 = pv.distance(p, HPZero); + if (round) { + if (Math.abs(d2 - cv) < state.cvCutIn) { + d2 = cv; + } + d2 = state.round(d2); + } + fv.setRelative(p, HPZero, d2, pv); + if (state.zp0 === 0) { + p.xo = p.x; + p.yo = p.y; + } + fv.touch(p); + state.rp0 = state.rp1 = pi2; + } + function NPUSHB(state) { + var prog = state.prog; + var ip = state.ip; + var stack = state.stack; + var n = prog[++ip]; + if (exports2.DEBUG) { + console.log(state.step, "NPUSHB[]", n); + } + for (var i2 = 0; i2 < n; i2++) { + stack.push(prog[++ip]); + } + state.ip = ip; + } + function NPUSHW(state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + var n = prog[++ip]; + if (exports2.DEBUG) { + console.log(state.step, "NPUSHW[]", n); + } + for (var i2 = 0; i2 < n; i2++) { + var w2 = prog[++ip] << 8 | prog[++ip]; + if (w2 & 32768) { + w2 = -((w2 ^ 65535) + 1); + } + stack.push(w2); + } + state.ip = ip; + } + function WS(state) { + var stack = state.stack; + var store = state.store; + if (!store) { + store = state.store = []; + } + var v2 = stack.pop(); + var l2 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "WS", v2, l2); + } + store[l2] = v2; + } + function RS(state) { + var stack = state.stack; + var store = state.store; + var l2 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "RS", l2); + } + var v2 = store && store[l2] || 0; + stack.push(v2); + } + function WCVTP(state) { + var stack = state.stack; + var v2 = stack.pop(); + var l2 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "WCVTP", v2, l2); + } + state.cvt[l2] = v2 / 64; + } + function RCVT(state) { + var stack = state.stack; + var cvte = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "RCVT", cvte); + } + stack.push(state.cvt[cvte] * 64); + } + function GC(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var p = state.z2[pi2]; + if (exports2.DEBUG) { + console.log(state.step, "GC[" + a + "]", pi2); + } + stack.push(state.dpv.distance(p, HPZero, a, false) * 64); + } + function MD(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var pi1 = stack.pop(); + var p2 = state.z1[pi2]; + var p1 = state.z0[pi1]; + var d2 = state.dpv.distance(p1, p2, a, a); + if (exports2.DEBUG) { + console.log(state.step, "MD[" + a + "]", pi2, pi1, "->", d2); + } + state.stack.push(Math.round(d2 * 64)); + } + function MPPEM(state) { + if (exports2.DEBUG) { + console.log(state.step, "MPPEM[]"); + } + state.stack.push(state.ppem); + } + function FLIPON(state) { + if (exports2.DEBUG) { + console.log(state.step, "FLIPON[]"); + } + state.autoFlip = true; + } + function LT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "LT[]", e2, e1); + } + stack.push(e1 < e2 ? 1 : 0); + } + function LTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "LTEQ[]", e2, e1); + } + stack.push(e1 <= e2 ? 1 : 0); + } + function GT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "GT[]", e2, e1); + } + stack.push(e1 > e2 ? 1 : 0); + } + function GTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "GTEQ[]", e2, e1); + } + stack.push(e1 >= e2 ? 1 : 0); + } + function EQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "EQ[]", e2, e1); + } + stack.push(e2 === e1 ? 1 : 0); + } + function NEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "NEQ[]", e2, e1); + } + stack.push(e2 !== e1 ? 1 : 0); + } + function ODD(state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "ODD[]", n); + } + stack.push(Math.trunc(n) % 2 ? 1 : 0); + } + function EVEN(state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "EVEN[]", n); + } + stack.push(Math.trunc(n) % 2 ? 0 : 1); + } + function IF(state) { + var test = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "IF[]", test); + } + if (!test) { + skip(state, true); + if (exports2.DEBUG) { + console.log(state.step, "EIF[]"); + } + } + } + function EIF(state) { + if (exports2.DEBUG) { + console.log(state.step, "EIF[]"); + } + } + function AND(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "AND[]", e2, e1); + } + stack.push(e2 && e1 ? 1 : 0); + } + function OR(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "OR[]", e2, e1); + } + stack.push(e2 || e1 ? 1 : 0); + } + function NOT(state) { + var stack = state.stack; + var e = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "NOT[]", e); + } + stack.push(e ? 0 : 1); + } + function DELTAP123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var fv = state.fv; + var pv = state.pv; + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds2 = state.deltaShift; + var z0 = state.z0; + if (exports2.DEBUG) { + console.log(state.step, "DELTAP[" + b + "]", n, stack); + } + for (var i2 = 0; i2 < n; i2++) { + var pi2 = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 240) >> 4); + if (appem !== ppem) { + continue; + } + var mag = (arg & 15) - 8; + if (mag >= 0) { + mag++; + } + if (exports2.DEBUG) { + console.log(state.step, "DELTAPFIX", pi2, "by", mag * ds2); + } + var p = z0[pi2]; + fv.setRelative(p, p, mag * ds2, pv); + } + } + function SDB(state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SDB[]", n); + } + state.deltaBase = n; + } + function SDS(state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SDS[]", n); + } + state.deltaShift = Math.pow(0.5, n); + } + function ADD(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "ADD[]", n2, n1); + } + stack.push(n1 + n2); + } + function SUB(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SUB[]", n2, n1); + } + stack.push(n1 - n2); + } + function DIV(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "DIV[]", n2, n1); + } + stack.push(n1 * 64 / n2); + } + function MUL(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "MUL[]", n2, n1); + } + stack.push(n1 * n2 / 64); + } + function ABS(state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "ABS[]", n); + } + stack.push(Math.abs(n)); + } + function NEG(state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "NEG[]", n); + } + stack.push(-n); + } + function FLOOR(state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "FLOOR[]", n); + } + stack.push(Math.floor(n / 64) * 64); + } + function CEILING(state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "CEILING[]", n); + } + stack.push(Math.ceil(n / 64) * 64); + } + function ROUND(dt, state) { + var stack = state.stack; + var n = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "ROUND[]"); + } + stack.push(state.round(n / 64) * 64); + } + function WCVTF(state) { + var stack = state.stack; + var v2 = stack.pop(); + var l2 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "WCVTF[]", v2, l2); + } + state.cvt[l2] = v2 * state.ppem / state.font.unitsPerEm; + } + function DELTAC123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds2 = state.deltaShift; + if (exports2.DEBUG) { + console.log(state.step, "DELTAC[" + b + "]", n, stack); + } + for (var i2 = 0; i2 < n; i2++) { + var c2 = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 240) >> 4); + if (appem !== ppem) { + continue; + } + var mag = (arg & 15) - 8; + if (mag >= 0) { + mag++; + } + var delta = mag * ds2; + if (exports2.DEBUG) { + console.log(state.step, "DELTACFIX", c2, "by", delta); + } + state.cvt[c2] += delta; + } + } + function SROUND(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SROUND[]", n); + } + state.round = roundSuper; + var period; + switch (n & 192) { + case 0: + period = 0.5; + break; + case 64: + period = 1; + break; + case 128: + period = 2; + break; + default: + throw new Error("invalid SROUND value"); + } + state.srPeriod = period; + switch (n & 48) { + case 0: + state.srPhase = 0; + break; + case 16: + state.srPhase = 0.25 * period; + break; + case 32: + state.srPhase = 0.5 * period; + break; + case 48: + state.srPhase = 0.75 * period; + break; + default: + throw new Error("invalid SROUND value"); + } + n &= 15; + if (n === 0) { + state.srThreshold = 0; + } else { + state.srThreshold = (n / 8 - 0.5) * period; + } + } + function S45ROUND(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "S45ROUND[]", n); + } + state.round = roundSuper; + var period; + switch (n & 192) { + case 0: + period = Math.sqrt(2) / 2; + break; + case 64: + period = Math.sqrt(2); + break; + case 128: + period = 2 * Math.sqrt(2); + break; + default: + throw new Error("invalid S45ROUND value"); + } + state.srPeriod = period; + switch (n & 48) { + case 0: + state.srPhase = 0; + break; + case 16: + state.srPhase = 0.25 * period; + break; + case 32: + state.srPhase = 0.5 * period; + break; + case 48: + state.srPhase = 0.75 * period; + break; + default: + throw new Error("invalid S45ROUND value"); + } + n &= 15; + if (n === 0) { + state.srThreshold = 0; + } else { + state.srThreshold = (n / 8 - 0.5) * period; + } + } + function ROFF(state) { + if (exports2.DEBUG) { + console.log(state.step, "ROFF[]"); + } + state.round = roundOff; + } + function RUTG(state) { + if (exports2.DEBUG) { + console.log(state.step, "RUTG[]"); + } + state.round = roundUpToGrid; + } + function RDTG(state) { + if (exports2.DEBUG) { + console.log(state.step, "RDTG[]"); + } + state.round = roundDownToGrid; + } + function SCANCTRL(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SCANCTRL[]", n); + } + } + function SDPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + if (exports2.DEBUG) { + console.log(state.step, "SDPVTL[" + a + "]", p2i, p1i); + } + var dx; + var dy; + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + state.dpv = getUnitVector(dx, dy); + } + function GETINFO(state) { + var stack = state.stack; + var sel = stack.pop(); + var r = 0; + if (exports2.DEBUG) { + console.log(state.step, "GETINFO[]", sel); + } + if (sel & 1) { + r = 35; + } + if (sel & 32) { + r |= 4096; + } + stack.push(r); + } + function ROLL(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + var c2 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "ROLL[]"); + } + stack.push(b); + stack.push(a); + stack.push(c2); + } + function MAX(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "MAX[]", e2, e1); + } + stack.push(Math.max(e1, e2)); + } + function MIN(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "MIN[]", e2, e1); + } + stack.push(Math.min(e1, e2)); + } + function SCANTYPE(state) { + var n = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "SCANTYPE[]", n); + } + } + function INSTCTRL(state) { + var s = state.stack.pop(); + var v2 = state.stack.pop(); + if (exports2.DEBUG) { + console.log(state.step, "INSTCTRL[]", s, v2); + } + switch (s) { + case 1: + state.inhibitGridFit = !!v2; + return; + case 2: + state.ignoreCvt = !!v2; + return; + default: + throw new Error("invalid INSTCTRL[] selector"); + } + } + function PUSHB(n, state) { + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + if (exports2.DEBUG) { + console.log(state.step, "PUSHB[" + n + "]"); + } + for (var i2 = 0; i2 < n; i2++) { + stack.push(prog[++ip]); + } + state.ip = ip; + } + function PUSHW(n, state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + if (exports2.DEBUG) { + console.log(state.ip, "PUSHW[" + n + "]"); + } + for (var i2 = 0; i2 < n; i2++) { + var w2 = prog[++ip] << 8 | prog[++ip]; + if (w2 & 32768) { + w2 = -((w2 ^ 65535) + 1); + } + stack.push(w2); + } + state.ip = ip; + } + function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { + var stack = state.stack; + var cvte = indirect && stack.pop(); + var pi2 = stack.pop(); + var rp0i = state.rp0; + var rp = state.z0[rp0i]; + var p = state.z1[pi2]; + var md = state.minDis; + var fv = state.fv; + var pv = state.dpv; + var od; + var d2; + var sign; + var cv; + d2 = od = pv.distance(p, rp, true, true); + sign = d2 >= 0 ? 1 : -1; + d2 = Math.abs(d2); + if (indirect) { + cv = state.cvt[cvte]; + if (ro && Math.abs(d2 - cv) < state.cvCutIn) { + d2 = cv; + } + } + if (keepD && d2 < md) { + d2 = md; + } + if (ro) { + d2 = state.round(d2); + } + fv.setRelative(p, rp, sign * d2, pv); + fv.touch(p); + if (exports2.DEBUG) { + console.log( + state.step, + (indirect ? "MIRP[" : "MDRP[") + (setRp0 ? "M" : "m") + (keepD ? ">" : "_") + (ro ? "R" : "_") + (dt === 0 ? "Gr" : dt === 1 ? "Bl" : dt === 2 ? "Wh" : "") + "]", + indirect ? cvte + "(" + state.cvt[cvte] + "," + cv + ")" : "", + pi2, + "(d =", + od, + "->", + sign * d2, + ")" + ); + } + state.rp1 = state.rp0; + state.rp2 = pi2; + if (setRp0) { + state.rp0 = pi2; + } + } + instructionTable = [ + /* 0x00 */ + SVTCA.bind(void 0, yUnitVector), + /* 0x01 */ + SVTCA.bind(void 0, xUnitVector), + /* 0x02 */ + SPVTCA.bind(void 0, yUnitVector), + /* 0x03 */ + SPVTCA.bind(void 0, xUnitVector), + /* 0x04 */ + SFVTCA.bind(void 0, yUnitVector), + /* 0x05 */ + SFVTCA.bind(void 0, xUnitVector), + /* 0x06 */ + SPVTL.bind(void 0, 0), + /* 0x07 */ + SPVTL.bind(void 0, 1), + /* 0x08 */ + SFVTL.bind(void 0, 0), + /* 0x09 */ + SFVTL.bind(void 0, 1), + /* 0x0A */ + SPVFS, + /* 0x0B */ + SFVFS, + /* 0x0C */ + GPV, + /* 0x0D */ + GFV, + /* 0x0E */ + SFVTPV, + /* 0x0F */ + ISECT, + /* 0x10 */ + SRP0, + /* 0x11 */ + SRP1, + /* 0x12 */ + SRP2, + /* 0x13 */ + SZP0, + /* 0x14 */ + SZP1, + /* 0x15 */ + SZP2, + /* 0x16 */ + SZPS, + /* 0x17 */ + SLOOP, + /* 0x18 */ + RTG, + /* 0x19 */ + RTHG, + /* 0x1A */ + SMD, + /* 0x1B */ + ELSE, + /* 0x1C */ + JMPR, + /* 0x1D */ + SCVTCI, + /* 0x1E */ + void 0, + // TODO SSWCI + /* 0x1F */ + void 0, + // TODO SSW + /* 0x20 */ + DUP, + /* 0x21 */ + POP, + /* 0x22 */ + CLEAR, + /* 0x23 */ + SWAP, + /* 0x24 */ + DEPTH, + /* 0x25 */ + CINDEX, + /* 0x26 */ + MINDEX, + /* 0x27 */ + void 0, + // TODO ALIGNPTS + /* 0x28 */ + void 0, + /* 0x29 */ + void 0, + // TODO UTP + /* 0x2A */ + LOOPCALL, + /* 0x2B */ + CALL, + /* 0x2C */ + FDEF, + /* 0x2D */ + void 0, + // ENDF (eaten by FDEF) + /* 0x2E */ + MDAP.bind(void 0, 0), + /* 0x2F */ + MDAP.bind(void 0, 1), + /* 0x30 */ + IUP.bind(void 0, yUnitVector), + /* 0x31 */ + IUP.bind(void 0, xUnitVector), + /* 0x32 */ + SHP.bind(void 0, 0), + /* 0x33 */ + SHP.bind(void 0, 1), + /* 0x34 */ + SHC.bind(void 0, 0), + /* 0x35 */ + SHC.bind(void 0, 1), + /* 0x36 */ + SHZ.bind(void 0, 0), + /* 0x37 */ + SHZ.bind(void 0, 1), + /* 0x38 */ + SHPIX, + /* 0x39 */ + IP, + /* 0x3A */ + MSIRP.bind(void 0, 0), + /* 0x3B */ + MSIRP.bind(void 0, 1), + /* 0x3C */ + ALIGNRP, + /* 0x3D */ + RTDG, + /* 0x3E */ + MIAP.bind(void 0, 0), + /* 0x3F */ + MIAP.bind(void 0, 1), + /* 0x40 */ + NPUSHB, + /* 0x41 */ + NPUSHW, + /* 0x42 */ + WS, + /* 0x43 */ + RS, + /* 0x44 */ + WCVTP, + /* 0x45 */ + RCVT, + /* 0x46 */ + GC.bind(void 0, 0), + /* 0x47 */ + GC.bind(void 0, 1), + /* 0x48 */ + void 0, + // TODO SCFS + /* 0x49 */ + MD.bind(void 0, 0), + /* 0x4A */ + MD.bind(void 0, 1), + /* 0x4B */ + MPPEM, + /* 0x4C */ + void 0, + // TODO MPS + /* 0x4D */ + FLIPON, + /* 0x4E */ + void 0, + // TODO FLIPOFF + /* 0x4F */ + void 0, + // TODO DEBUG + /* 0x50 */ + LT, + /* 0x51 */ + LTEQ, + /* 0x52 */ + GT, + /* 0x53 */ + GTEQ, + /* 0x54 */ + EQ, + /* 0x55 */ + NEQ, + /* 0x56 */ + ODD, + /* 0x57 */ + EVEN, + /* 0x58 */ + IF, + /* 0x59 */ + EIF, + /* 0x5A */ + AND, + /* 0x5B */ + OR, + /* 0x5C */ + NOT, + /* 0x5D */ + DELTAP123.bind(void 0, 1), + /* 0x5E */ + SDB, + /* 0x5F */ + SDS, + /* 0x60 */ + ADD, + /* 0x61 */ + SUB, + /* 0x62 */ + DIV, + /* 0x63 */ + MUL, + /* 0x64 */ + ABS, + /* 0x65 */ + NEG, + /* 0x66 */ + FLOOR, + /* 0x67 */ + CEILING, + /* 0x68 */ + ROUND.bind(void 0, 0), + /* 0x69 */ + ROUND.bind(void 0, 1), + /* 0x6A */ + ROUND.bind(void 0, 2), + /* 0x6B */ + ROUND.bind(void 0, 3), + /* 0x6C */ + void 0, + // TODO NROUND[ab] + /* 0x6D */ + void 0, + // TODO NROUND[ab] + /* 0x6E */ + void 0, + // TODO NROUND[ab] + /* 0x6F */ + void 0, + // TODO NROUND[ab] + /* 0x70 */ + WCVTF, + /* 0x71 */ + DELTAP123.bind(void 0, 2), + /* 0x72 */ + DELTAP123.bind(void 0, 3), + /* 0x73 */ + DELTAC123.bind(void 0, 1), + /* 0x74 */ + DELTAC123.bind(void 0, 2), + /* 0x75 */ + DELTAC123.bind(void 0, 3), + /* 0x76 */ + SROUND, + /* 0x77 */ + S45ROUND, + /* 0x78 */ + void 0, + // TODO JROT[] + /* 0x79 */ + void 0, + // TODO JROF[] + /* 0x7A */ + ROFF, + /* 0x7B */ + void 0, + /* 0x7C */ + RUTG, + /* 0x7D */ + RDTG, + /* 0x7E */ + POP, + // actually SANGW, supposed to do only a pop though + /* 0x7F */ + POP, + // actually AA, supposed to do only a pop though + /* 0x80 */ + void 0, + // TODO FLIPPT + /* 0x81 */ + void 0, + // TODO FLIPRGON + /* 0x82 */ + void 0, + // TODO FLIPRGOFF + /* 0x83 */ + void 0, + /* 0x84 */ + void 0, + /* 0x85 */ + SCANCTRL, + /* 0x86 */ + SDPVTL.bind(void 0, 0), + /* 0x87 */ + SDPVTL.bind(void 0, 1), + /* 0x88 */ + GETINFO, + /* 0x89 */ + void 0, + // TODO IDEF + /* 0x8A */ + ROLL, + /* 0x8B */ + MAX, + /* 0x8C */ + MIN, + /* 0x8D */ + SCANTYPE, + /* 0x8E */ + INSTCTRL, + /* 0x8F */ + void 0, + /* 0x90 */ + void 0, + /* 0x91 */ + void 0, + /* 0x92 */ + void 0, + /* 0x93 */ + void 0, + /* 0x94 */ + void 0, + /* 0x95 */ + void 0, + /* 0x96 */ + void 0, + /* 0x97 */ + void 0, + /* 0x98 */ + void 0, + /* 0x99 */ + void 0, + /* 0x9A */ + void 0, + /* 0x9B */ + void 0, + /* 0x9C */ + void 0, + /* 0x9D */ + void 0, + /* 0x9E */ + void 0, + /* 0x9F */ + void 0, + /* 0xA0 */ + void 0, + /* 0xA1 */ + void 0, + /* 0xA2 */ + void 0, + /* 0xA3 */ + void 0, + /* 0xA4 */ + void 0, + /* 0xA5 */ + void 0, + /* 0xA6 */ + void 0, + /* 0xA7 */ + void 0, + /* 0xA8 */ + void 0, + /* 0xA9 */ + void 0, + /* 0xAA */ + void 0, + /* 0xAB */ + void 0, + /* 0xAC */ + void 0, + /* 0xAD */ + void 0, + /* 0xAE */ + void 0, + /* 0xAF */ + void 0, + /* 0xB0 */ + PUSHB.bind(void 0, 1), + /* 0xB1 */ + PUSHB.bind(void 0, 2), + /* 0xB2 */ + PUSHB.bind(void 0, 3), + /* 0xB3 */ + PUSHB.bind(void 0, 4), + /* 0xB4 */ + PUSHB.bind(void 0, 5), + /* 0xB5 */ + PUSHB.bind(void 0, 6), + /* 0xB6 */ + PUSHB.bind(void 0, 7), + /* 0xB7 */ + PUSHB.bind(void 0, 8), + /* 0xB8 */ + PUSHW.bind(void 0, 1), + /* 0xB9 */ + PUSHW.bind(void 0, 2), + /* 0xBA */ + PUSHW.bind(void 0, 3), + /* 0xBB */ + PUSHW.bind(void 0, 4), + /* 0xBC */ + PUSHW.bind(void 0, 5), + /* 0xBD */ + PUSHW.bind(void 0, 6), + /* 0xBE */ + PUSHW.bind(void 0, 7), + /* 0xBF */ + PUSHW.bind(void 0, 8), + /* 0xC0 */ + MDRP_MIRP.bind(void 0, 0, 0, 0, 0, 0), + /* 0xC1 */ + MDRP_MIRP.bind(void 0, 0, 0, 0, 0, 1), + /* 0xC2 */ + MDRP_MIRP.bind(void 0, 0, 0, 0, 0, 2), + /* 0xC3 */ + MDRP_MIRP.bind(void 0, 0, 0, 0, 0, 3), + /* 0xC4 */ + MDRP_MIRP.bind(void 0, 0, 0, 0, 1, 0), + /* 0xC5 */ + MDRP_MIRP.bind(void 0, 0, 0, 0, 1, 1), + /* 0xC6 */ + MDRP_MIRP.bind(void 0, 0, 0, 0, 1, 2), + /* 0xC7 */ + MDRP_MIRP.bind(void 0, 0, 0, 0, 1, 3), + /* 0xC8 */ + MDRP_MIRP.bind(void 0, 0, 0, 1, 0, 0), + /* 0xC9 */ + MDRP_MIRP.bind(void 0, 0, 0, 1, 0, 1), + /* 0xCA */ + MDRP_MIRP.bind(void 0, 0, 0, 1, 0, 2), + /* 0xCB */ + MDRP_MIRP.bind(void 0, 0, 0, 1, 0, 3), + /* 0xCC */ + MDRP_MIRP.bind(void 0, 0, 0, 1, 1, 0), + /* 0xCD */ + MDRP_MIRP.bind(void 0, 0, 0, 1, 1, 1), + /* 0xCE */ + MDRP_MIRP.bind(void 0, 0, 0, 1, 1, 2), + /* 0xCF */ + MDRP_MIRP.bind(void 0, 0, 0, 1, 1, 3), + /* 0xD0 */ + MDRP_MIRP.bind(void 0, 0, 1, 0, 0, 0), + /* 0xD1 */ + MDRP_MIRP.bind(void 0, 0, 1, 0, 0, 1), + /* 0xD2 */ + MDRP_MIRP.bind(void 0, 0, 1, 0, 0, 2), + /* 0xD3 */ + MDRP_MIRP.bind(void 0, 0, 1, 0, 0, 3), + /* 0xD4 */ + MDRP_MIRP.bind(void 0, 0, 1, 0, 1, 0), + /* 0xD5 */ + MDRP_MIRP.bind(void 0, 0, 1, 0, 1, 1), + /* 0xD6 */ + MDRP_MIRP.bind(void 0, 0, 1, 0, 1, 2), + /* 0xD7 */ + MDRP_MIRP.bind(void 0, 0, 1, 0, 1, 3), + /* 0xD8 */ + MDRP_MIRP.bind(void 0, 0, 1, 1, 0, 0), + /* 0xD9 */ + MDRP_MIRP.bind(void 0, 0, 1, 1, 0, 1), + /* 0xDA */ + MDRP_MIRP.bind(void 0, 0, 1, 1, 0, 2), + /* 0xDB */ + MDRP_MIRP.bind(void 0, 0, 1, 1, 0, 3), + /* 0xDC */ + MDRP_MIRP.bind(void 0, 0, 1, 1, 1, 0), + /* 0xDD */ + MDRP_MIRP.bind(void 0, 0, 1, 1, 1, 1), + /* 0xDE */ + MDRP_MIRP.bind(void 0, 0, 1, 1, 1, 2), + /* 0xDF */ + MDRP_MIRP.bind(void 0, 0, 1, 1, 1, 3), + /* 0xE0 */ + MDRP_MIRP.bind(void 0, 1, 0, 0, 0, 0), + /* 0xE1 */ + MDRP_MIRP.bind(void 0, 1, 0, 0, 0, 1), + /* 0xE2 */ + MDRP_MIRP.bind(void 0, 1, 0, 0, 0, 2), + /* 0xE3 */ + MDRP_MIRP.bind(void 0, 1, 0, 0, 0, 3), + /* 0xE4 */ + MDRP_MIRP.bind(void 0, 1, 0, 0, 1, 0), + /* 0xE5 */ + MDRP_MIRP.bind(void 0, 1, 0, 0, 1, 1), + /* 0xE6 */ + MDRP_MIRP.bind(void 0, 1, 0, 0, 1, 2), + /* 0xE7 */ + MDRP_MIRP.bind(void 0, 1, 0, 0, 1, 3), + /* 0xE8 */ + MDRP_MIRP.bind(void 0, 1, 0, 1, 0, 0), + /* 0xE9 */ + MDRP_MIRP.bind(void 0, 1, 0, 1, 0, 1), + /* 0xEA */ + MDRP_MIRP.bind(void 0, 1, 0, 1, 0, 2), + /* 0xEB */ + MDRP_MIRP.bind(void 0, 1, 0, 1, 0, 3), + /* 0xEC */ + MDRP_MIRP.bind(void 0, 1, 0, 1, 1, 0), + /* 0xED */ + MDRP_MIRP.bind(void 0, 1, 0, 1, 1, 1), + /* 0xEE */ + MDRP_MIRP.bind(void 0, 1, 0, 1, 1, 2), + /* 0xEF */ + MDRP_MIRP.bind(void 0, 1, 0, 1, 1, 3), + /* 0xF0 */ + MDRP_MIRP.bind(void 0, 1, 1, 0, 0, 0), + /* 0xF1 */ + MDRP_MIRP.bind(void 0, 1, 1, 0, 0, 1), + /* 0xF2 */ + MDRP_MIRP.bind(void 0, 1, 1, 0, 0, 2), + /* 0xF3 */ + MDRP_MIRP.bind(void 0, 1, 1, 0, 0, 3), + /* 0xF4 */ + MDRP_MIRP.bind(void 0, 1, 1, 0, 1, 0), + /* 0xF5 */ + MDRP_MIRP.bind(void 0, 1, 1, 0, 1, 1), + /* 0xF6 */ + MDRP_MIRP.bind(void 0, 1, 1, 0, 1, 2), + /* 0xF7 */ + MDRP_MIRP.bind(void 0, 1, 1, 0, 1, 3), + /* 0xF8 */ + MDRP_MIRP.bind(void 0, 1, 1, 1, 0, 0), + /* 0xF9 */ + MDRP_MIRP.bind(void 0, 1, 1, 1, 0, 1), + /* 0xFA */ + MDRP_MIRP.bind(void 0, 1, 1, 1, 0, 2), + /* 0xFB */ + MDRP_MIRP.bind(void 0, 1, 1, 1, 0, 3), + /* 0xFC */ + MDRP_MIRP.bind(void 0, 1, 1, 1, 1, 0), + /* 0xFD */ + MDRP_MIRP.bind(void 0, 1, 1, 1, 1, 1), + /* 0xFE */ + MDRP_MIRP.bind(void 0, 1, 1, 1, 1, 2), + /* 0xFF */ + MDRP_MIRP.bind(void 0, 1, 1, 1, 1, 3) + ]; + function Token(char) { + this.char = char; + this.state = {}; + this.activeState = null; + } + function ContextRange(startIndex, endOffset, contextName) { + this.contextName = contextName; + this.startIndex = startIndex; + this.endOffset = endOffset; + } + function ContextChecker(contextName, checkStart, checkEnd) { + this.contextName = contextName; + this.openRange = null; + this.ranges = []; + this.checkStart = checkStart; + this.checkEnd = checkEnd; + } + function ContextParams(context, currentIndex) { + this.context = context; + this.index = currentIndex; + this.length = context.length; + this.current = context[currentIndex]; + this.backtrack = context.slice(0, currentIndex); + this.lookahead = context.slice(currentIndex + 1); + } + function Event(eventId) { + this.eventId = eventId; + this.subscribers = []; + } + function initializeCoreEvents(events) { + var this$1 = this; + var coreEvents = [ + "start", + "end", + "next", + "newToken", + "contextStart", + "contextEnd", + "insertToken", + "removeToken", + "removeRange", + "replaceToken", + "replaceRange", + "composeRUD", + "updateContextsRanges" + ]; + coreEvents.forEach(function(eventId) { + Object.defineProperty(this$1.events, eventId, { + value: new Event(eventId) + }); + }); + if (!!events) { + coreEvents.forEach(function(eventId) { + var event = events[eventId]; + if (typeof event === "function") { + this$1.events[eventId].subscribe(event); + } + }); + } + var requiresContextUpdate = [ + "insertToken", + "removeToken", + "removeRange", + "replaceToken", + "replaceRange", + "composeRUD" + ]; + requiresContextUpdate.forEach(function(eventId) { + this$1.events[eventId].subscribe( + this$1.updateContextsRanges + ); + }); + } + function Tokenizer(events) { + this.tokens = []; + this.registeredContexts = {}; + this.contextCheckers = []; + this.events = {}; + this.registeredModifiers = []; + initializeCoreEvents.call(this, events); + } + Token.prototype.setState = function(key, value) { + this.state[key] = value; + this.activeState = { key, value: this.state[key] }; + return this.activeState; + }; + Token.prototype.getState = function(stateId) { + return this.state[stateId] || null; + }; + Tokenizer.prototype.inboundIndex = function(index) { + return index >= 0 && index < this.tokens.length; + }; + Tokenizer.prototype.composeRUD = function(RUDs) { + var this$1 = this; + var silent = true; + var state = RUDs.map(function(RUD) { + return this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)); + }); + var hasFAILObject = function(obj) { + return typeof obj === "object" && obj.hasOwnProperty("FAIL"); + }; + if (state.every(hasFAILObject)) { + return { + FAIL: "composeRUD: one or more operations hasn't completed successfully", + report: state.filter(hasFAILObject) + }; + } + this.dispatch("composeRUD", [state.filter(function(op) { + return !hasFAILObject(op); + })]); + }; + Tokenizer.prototype.replaceRange = function(startIndex, offset, tokens, silent) { + offset = offset !== null ? offset : this.tokens.length; + var isTokenType = tokens.every(function(token) { + return token instanceof Token; + }); + if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { + var replaced = this.tokens.splice.apply( + this.tokens, + [startIndex, offset].concat(tokens) + ); + if (!silent) { + this.dispatch("replaceToken", [startIndex, offset, tokens]); + } + return [replaced, tokens]; + } else { + return { FAIL: "replaceRange: invalid tokens or startIndex." }; + } + }; + Tokenizer.prototype.replaceToken = function(index, token, silent) { + if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { + var replaced = this.tokens.splice(index, 1, token); + if (!silent) { + this.dispatch("replaceToken", [index, token]); + } + return [replaced[0], token]; + } else { + return { FAIL: "replaceToken: invalid token or index." }; + } + }; + Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { + offset = !isNaN(offset) ? offset : this.tokens.length; + var tokens = this.tokens.splice(startIndex, offset); + if (!silent) { + this.dispatch("removeRange", [tokens, startIndex, offset]); + } + return tokens; + }; + Tokenizer.prototype.removeToken = function(index, silent) { + if (!isNaN(index) && this.inboundIndex(index)) { + var token = this.tokens.splice(index, 1); + if (!silent) { + this.dispatch("removeToken", [token, index]); + } + return token; + } else { + return { FAIL: "removeToken: invalid token index." }; + } + }; + Tokenizer.prototype.insertToken = function(tokens, index, silent) { + var tokenType = tokens.every( + function(token) { + return token instanceof Token; + } + ); + if (tokenType) { + this.tokens.splice.apply( + this.tokens, + [index, 0].concat(tokens) + ); + if (!silent) { + this.dispatch("insertToken", [tokens, index]); + } + return tokens; + } else { + return { FAIL: "insertToken: invalid token(s)." }; + } + }; + Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { + this.events.newToken.subscribe(function(token, contextParams) { + var conditionParams = [token, contextParams]; + var canApplyModifier = condition === null || condition.apply(this, conditionParams) === true; + var modifierParams = [token, contextParams]; + if (canApplyModifier) { + var newStateValue = modifier.apply(this, modifierParams); + token.setState(modifierId, newStateValue); + } + }); + this.registeredModifiers.push(modifierId); + }; + Event.prototype.subscribe = function(eventHandler) { + if (typeof eventHandler === "function") { + return this.subscribers.push(eventHandler) - 1; + } else { + return { FAIL: "invalid '" + this.eventId + "' event handler" }; + } + }; + Event.prototype.unsubscribe = function(subsId) { + this.subscribers.splice(subsId, 1); + }; + ContextParams.prototype.setCurrentIndex = function(index) { + this.index = index; + this.current = this.context[index]; + this.backtrack = this.context.slice(0, index); + this.lookahead = this.context.slice(index + 1); + }; + ContextParams.prototype.get = function(offset) { + switch (true) { + case offset === 0: + return this.current; + case (offset < 0 && Math.abs(offset) <= this.backtrack.length): + return this.backtrack.slice(offset)[0]; + case (offset > 0 && offset <= this.lookahead.length): + return this.lookahead[offset - 1]; + default: + return null; + } + }; + Tokenizer.prototype.rangeToText = function(range) { + if (range instanceof ContextRange) { + return this.getRangeTokens(range).map(function(token) { + return token.char; + }).join(""); + } + }; + Tokenizer.prototype.getText = function() { + return this.tokens.map(function(token) { + return token.char; + }).join(""); + }; + Tokenizer.prototype.getContext = function(contextName) { + var context = this.registeredContexts[contextName]; + return !!context ? context : null; + }; + Tokenizer.prototype.on = function(eventName, eventHandler) { + var event = this.events[eventName]; + if (!!event) { + return event.subscribe(eventHandler); + } else { + return null; + } + }; + Tokenizer.prototype.dispatch = function(eventName, args) { + var this$1 = this; + var event = this.events[eventName]; + if (event instanceof Event) { + event.subscribers.forEach(function(subscriber) { + subscriber.apply(this$1, args || []); + }); + } + }; + Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { + if (!!this.getContext(contextName)) { + return { + FAIL: "context name '" + contextName + "' is already registered." + }; + } + if (typeof contextStartCheck !== "function") { + return { + FAIL: "missing context start check." + }; + } + if (typeof contextEndCheck !== "function") { + return { + FAIL: "missing context end check." + }; + } + var contextCheckers = new ContextChecker( + contextName, + contextStartCheck, + contextEndCheck + ); + this.registeredContexts[contextName] = contextCheckers; + this.contextCheckers.push(contextCheckers); + return contextCheckers; + }; + Tokenizer.prototype.getRangeTokens = function(range) { + var endIndex = range.startIndex + range.endOffset; + return [].concat( + this.tokens.slice(range.startIndex, endIndex) + ); + }; + Tokenizer.prototype.getContextRanges = function(contextName) { + var context = this.getContext(contextName); + if (!!context) { + return context.ranges; + } else { + return { FAIL: "context checker '" + contextName + "' is not registered." }; + } + }; + Tokenizer.prototype.resetContextsRanges = function() { + var registeredContexts = this.registeredContexts; + for (var contextName in registeredContexts) { + if (registeredContexts.hasOwnProperty(contextName)) { + var context = registeredContexts[contextName]; + context.ranges = []; + } + } + }; + Tokenizer.prototype.updateContextsRanges = function() { + this.resetContextsRanges(); + var chars = this.tokens.map(function(token) { + return token.char; + }); + for (var i2 = 0; i2 < chars.length; i2++) { + var contextParams = new ContextParams(chars, i2); + this.runContextCheck(contextParams); + } + this.dispatch("updateContextsRanges", [this.registeredContexts]); + }; + Tokenizer.prototype.setEndOffset = function(offset, contextName) { + var startIndex = this.getContext(contextName).openRange.startIndex; + var range = new ContextRange(startIndex, offset, contextName); + var ranges = this.getContext(contextName).ranges; + range.rangeId = contextName + "." + ranges.length; + ranges.push(range); + this.getContext(contextName).openRange = null; + return range; + }; + Tokenizer.prototype.runContextCheck = function(contextParams) { + var this$1 = this; + var index = contextParams.index; + this.contextCheckers.forEach(function(contextChecker) { + var contextName = contextChecker.contextName; + var openRange = this$1.getContext(contextName).openRange; + if (!openRange && contextChecker.checkStart(contextParams)) { + openRange = new ContextRange(index, null, contextName); + this$1.getContext(contextName).openRange = openRange; + this$1.dispatch("contextStart", [contextName, index]); + } + if (!!openRange && contextChecker.checkEnd(contextParams)) { + var offset = index - openRange.startIndex + 1; + var range = this$1.setEndOffset(offset, contextName); + this$1.dispatch("contextEnd", [contextName, range]); + } + }); + }; + Tokenizer.prototype.tokenize = function(text2) { + this.tokens = []; + this.resetContextsRanges(); + var chars = Array.from(text2); + this.dispatch("start"); + for (var i2 = 0; i2 < chars.length; i2++) { + var char = chars[i2]; + var contextParams = new ContextParams(chars, i2); + this.dispatch("next", [contextParams]); + this.runContextCheck(contextParams); + var token = new Token(char); + this.tokens.push(token); + this.dispatch("newToken", [token, contextParams]); + } + this.dispatch("end", [this.tokens]); + return this.tokens; + }; + function isArabicChar(c2) { + return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c2); + } + function isIsolatedArabicChar(char) { + return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); + } + function isTashkeelArabicChar(char) { + return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); + } + function isLatinChar(c2) { + return /[A-z]/.test(c2); + } + function isWhiteSpace(c2) { + return /\s/.test(c2); + } + function FeatureQuery(font) { + this.font = font; + this.features = {}; + } + function SubstitutionAction(action) { + this.id = action.id; + this.tag = action.tag; + this.substitution = action.substitution; + } + function lookupCoverage(glyphIndex, coverage) { + if (!glyphIndex) { + return -1; + } + switch (coverage.format) { + case 1: + return coverage.glyphs.indexOf(glyphIndex); + case 2: + var ranges = coverage.ranges; + for (var i2 = 0; i2 < ranges.length; i2++) { + var range = ranges[i2]; + if (glyphIndex >= range.start && glyphIndex <= range.end) { + var offset = glyphIndex - range.start; + return range.index + offset; + } + } + break; + default: + return -1; + } + return -1; + } + function singleSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { + return null; + } + return glyphIndex + subtable.deltaGlyphId; + } + function singleSubstitutionFormat2(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { + return null; + } + return subtable.substitute[substituteIndex]; + } + function lookupCoverageList(coverageList, contextParams) { + var lookupList = []; + for (var i2 = 0; i2 < coverageList.length; i2++) { + var coverage = coverageList[i2]; + var glyphIndex = contextParams.current; + glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; + var lookupIndex = lookupCoverage(glyphIndex, coverage); + if (lookupIndex !== -1) { + lookupList.push(lookupIndex); + } + } + if (lookupList.length !== coverageList.length) { + return -1; + } + return lookupList; + } + function chainingSubstitutionFormat3(contextParams, subtable) { + var lookupsCount = subtable.inputCoverage.length + subtable.lookaheadCoverage.length + subtable.backtrackCoverage.length; + if (contextParams.context.length < lookupsCount) { + return []; + } + var inputLookups = lookupCoverageList( + subtable.inputCoverage, + contextParams + ); + if (inputLookups === -1) { + return []; + } + var lookaheadOffset = subtable.inputCoverage.length - 1; + if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { + return []; + } + var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); + while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { + lookaheadContext.shift(); + } + var lookaheadParams = new ContextParams(lookaheadContext, 0); + var lookaheadLookups = lookupCoverageList( + subtable.lookaheadCoverage, + lookaheadParams + ); + var backtrackContext = [].concat(contextParams.backtrack); + backtrackContext.reverse(); + while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { + backtrackContext.shift(); + } + if (backtrackContext.length < subtable.backtrackCoverage.length) { + return []; + } + var backtrackParams = new ContextParams(backtrackContext, 0); + var backtrackLookups = lookupCoverageList( + subtable.backtrackCoverage, + backtrackParams + ); + var contextRulesMatch = inputLookups.length === subtable.inputCoverage.length && lookaheadLookups.length === subtable.lookaheadCoverage.length && backtrackLookups.length === subtable.backtrackCoverage.length; + var substitutions = []; + if (contextRulesMatch) { + for (var i2 = 0; i2 < subtable.lookupRecords.length; i2++) { + var lookupRecord = subtable.lookupRecords[i2]; + var lookupListIndex = lookupRecord.lookupListIndex; + var lookupTable = this.getLookupByIndex(lookupListIndex); + for (var s = 0; s < lookupTable.subtables.length; s++) { + var subtable$1 = lookupTable.subtables[s]; + var lookup = this.getLookupMethod(lookupTable, subtable$1); + var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); + if (substitutionType === "12") { + for (var n = 0; n < inputLookups.length; n++) { + var glyphIndex = contextParams.get(n); + var substitution = lookup(glyphIndex); + if (substitution) { + substitutions.push(substitution); + } + } + } + } + } + } + return substitutions; + } + function ligatureSubstitutionFormat1(contextParams, subtable) { + var glyphIndex = contextParams.current; + var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (ligSetIndex === -1) { + return null; + } + var ligature; + var ligatureSet = subtable.ligatureSets[ligSetIndex]; + for (var s = 0; s < ligatureSet.length; s++) { + ligature = ligatureSet[s]; + for (var l2 = 0; l2 < ligature.components.length; l2++) { + var lookaheadItem = contextParams.lookahead[l2]; + var component = ligature.components[l2]; + if (lookaheadItem !== component) { + break; + } + if (l2 === ligature.components.length - 1) { + return ligature; + } + } + } + return null; + } + function decompositionSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { + return null; + } + return subtable.sequences[substituteIndex]; + } + FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function() { + var scripts = this.font.tables.gsub.scripts; + for (var s = 0; s < scripts.length; s++) { + var script = scripts[s]; + if (script.tag === "DFLT") { + return script.script.defaultLangSys.featureIndexes; + } + } + return []; + }; + FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { + var tables = this.font.tables; + if (!tables.gsub) { + return []; + } + if (!scriptTag) { + return this.getDefaultScriptFeaturesIndexes(); + } + var scripts = this.font.tables.gsub.scripts; + for (var i2 = 0; i2 < scripts.length; i2++) { + var script = scripts[i2]; + if (script.tag === scriptTag && script.script.defaultLangSys) { + return script.script.defaultLangSys.featureIndexes; + } else { + var langSysRecords = script.langSysRecords; + if (!!langSysRecords) { + for (var j = 0; j < langSysRecords.length; j++) { + var langSysRecord = langSysRecords[j]; + if (langSysRecord.tag === scriptTag) { + var langSys = langSysRecord.langSys; + return langSys.featureIndexes; + } + } + } + } + } + return this.getDefaultScriptFeaturesIndexes(); + }; + FeatureQuery.prototype.mapTagsToFeatures = function(features, scriptTag) { + var tags = {}; + for (var i2 = 0; i2 < features.length; i2++) { + var tag = features[i2].tag; + var feature = features[i2].feature; + tags[tag] = feature; + } + this.features[scriptTag].tags = tags; + }; + FeatureQuery.prototype.getScriptFeatures = function(scriptTag) { + var features = this.features[scriptTag]; + if (this.features.hasOwnProperty(scriptTag)) { + return features; + } + var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); + if (!featuresIndexes) { + return null; + } + var gsub2 = this.font.tables.gsub; + features = featuresIndexes.map(function(index) { + return gsub2.features[index]; + }); + this.features[scriptTag] = features; + this.mapTagsToFeatures(features, scriptTag); + return features; + }; + FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { + var lookupType = lookupTable.lookupType.toString(); + var substFormat = subtable.substFormat.toString(); + return lookupType + substFormat; + }; + FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { + var this$1 = this; + var substitutionType = this.getSubstitutionType(lookupTable, subtable); + switch (substitutionType) { + case "11": + return function(glyphIndex) { + return singleSubstitutionFormat1.apply( + this$1, + [glyphIndex, subtable] + ); + }; + case "12": + return function(glyphIndex) { + return singleSubstitutionFormat2.apply( + this$1, + [glyphIndex, subtable] + ); + }; + case "63": + return function(contextParams) { + return chainingSubstitutionFormat3.apply( + this$1, + [contextParams, subtable] + ); + }; + case "41": + return function(contextParams) { + return ligatureSubstitutionFormat1.apply( + this$1, + [contextParams, subtable] + ); + }; + case "21": + return function(glyphIndex) { + return decompositionSubstitutionFormat1.apply( + this$1, + [glyphIndex, subtable] + ); + }; + default: + throw new Error( + "lookupType: " + lookupTable.lookupType + " - substFormat: " + subtable.substFormat + " is not yet supported" + ); + } + }; + FeatureQuery.prototype.lookupFeature = function(query) { + var contextParams = query.contextParams; + var currentIndex = contextParams.index; + var feature = this.getFeature({ + tag: query.tag, + script: query.script + }); + if (!feature) { + return new Error( + "font '" + this.font.names.fullName.en + "' doesn't support feature '" + query.tag + "' for script '" + query.script + "'." + ); + } + var lookups = this.getFeatureLookups(feature); + var substitutions = [].concat(contextParams.context); + for (var l2 = 0; l2 < lookups.length; l2++) { + var lookupTable = lookups[l2]; + var subtables = this.getLookupSubtables(lookupTable); + for (var s = 0; s < subtables.length; s++) { + var subtable = subtables[s]; + var substType = this.getSubstitutionType(lookupTable, subtable); + var lookup = this.getLookupMethod(lookupTable, subtable); + var substitution = void 0; + switch (substType) { + case "11": + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 11, + tag: query.tag, + substitution + })); + } + break; + case "12": + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 12, + tag: query.tag, + substitution + })); + } + break; + case "63": + substitution = lookup(contextParams); + if (Array.isArray(substitution) && substitution.length) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 63, + tag: query.tag, + substitution + })); + } + break; + case "41": + substitution = lookup(contextParams); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 41, + tag: query.tag, + substitution + })); + } + break; + case "21": + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 21, + tag: query.tag, + substitution + })); + } + break; + } + contextParams = new ContextParams(substitutions, currentIndex); + if (Array.isArray(substitution) && !substitution.length) { + continue; + } + substitution = null; + } + } + return substitutions.length ? substitutions : null; + }; + FeatureQuery.prototype.supports = function(query) { + if (!query.script) { + return false; + } + this.getScriptFeatures(query.script); + var supportedScript = this.features.hasOwnProperty(query.script); + if (!query.tag) { + return supportedScript; + } + var supportedFeature = this.features[query.script].some(function(feature) { + return feature.tag === query.tag; + }); + return supportedScript && supportedFeature; + }; + FeatureQuery.prototype.getLookupSubtables = function(lookupTable) { + return lookupTable.subtables || null; + }; + FeatureQuery.prototype.getLookupByIndex = function(index) { + var lookups = this.font.tables.gsub.lookups; + return lookups[index] || null; + }; + FeatureQuery.prototype.getFeatureLookups = function(feature) { + return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); + }; + FeatureQuery.prototype.getFeature = function getFeature(query) { + if (!this.font) { + return { FAIL: "No font was found" }; + } + if (!this.features.hasOwnProperty(query.script)) { + this.getScriptFeatures(query.script); + } + var scriptFeatures = this.features[query.script]; + if (!scriptFeatures) { + return { FAIL: "No feature for script " + query.script }; + } + if (!scriptFeatures.tags[query.tag]) { + return null; + } + return this.features[query.script].tags[query.tag]; + }; + function arabicWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? arabic first char + prevChar === null && isArabicChar(char) || // ? arabic char preceded with a non arabic char + !isArabicChar(prevChar) && isArabicChar(char) + ); + } + function arabicWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last arabic char + nextChar === null || // ? next char is not arabic + !isArabicChar(nextChar) + ); + } + var arabicWordCheck = { + startCheck: arabicWordStartCheck, + endCheck: arabicWordEndCheck + }; + function arabicSentenceStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? an arabic char preceded with a non arabic char + (isArabicChar(char) || isTashkeelArabicChar(char)) && !isArabicChar(prevChar) + ); + } + function arabicSentenceEndCheck(contextParams) { + var nextChar = contextParams.get(1); + switch (true) { + case nextChar === null: + return true; + case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): + var nextIsWhitespace = isWhiteSpace(nextChar); + if (!nextIsWhitespace) { + return true; + } + if (nextIsWhitespace) { + var arabicCharAhead = false; + arabicCharAhead = contextParams.lookahead.some( + function(c2) { + return isArabicChar(c2) || isTashkeelArabicChar(c2); + } + ); + if (!arabicCharAhead) { + return true; + } + } + break; + default: + return false; + } + } + var arabicSentenceCheck = { + startCheck: arabicSentenceStartCheck, + endCheck: arabicSentenceEndCheck + }; + function singleSubstitutionFormat1$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); + } + function singleSubstitutionFormat2$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); + } + function chainingSubstitutionFormat3$1(action, tokens, index) { + action.substitution.forEach(function(subst, offset) { + var token = tokens[index + offset]; + token.setState(action.tag, subst); + }); + } + function ligatureSubstitutionFormat1$1(action, tokens, index) { + var token = tokens[index]; + token.setState(action.tag, action.substitution.ligGlyph); + var compsCount = action.substitution.components.length; + for (var i2 = 0; i2 < compsCount; i2++) { + token = tokens[index + i2 + 1]; + token.setState("deleted", true); + } + } + var SUBSTITUTIONS = { + 11: singleSubstitutionFormat1$1, + 12: singleSubstitutionFormat2$1, + 63: chainingSubstitutionFormat3$1, + 41: ligatureSubstitutionFormat1$1 + }; + function applySubstitution(action, tokens, index) { + if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { + SUBSTITUTIONS[action.id](action, tokens, index); + } + } + function willConnectPrev(charContextParams) { + var backtrack = [].concat(charContextParams.backtrack); + for (var i2 = backtrack.length - 1; i2 >= 0; i2--) { + var prevChar = backtrack[i2]; + var isolated = isIsolatedArabicChar(prevChar); + var tashkeel = isTashkeelArabicChar(prevChar); + if (!isolated && !tashkeel) { + return true; + } + if (isolated) { + return false; + } + } + return false; + } + function willConnectNext(charContextParams) { + if (isIsolatedArabicChar(charContextParams.current)) { + return false; + } + for (var i2 = 0; i2 < charContextParams.lookahead.length; i2++) { + var nextChar = charContextParams.lookahead[i2]; + var tashkeel = isTashkeelArabicChar(nextChar); + if (!tashkeel) { + return true; + } + } + return false; + } + function arabicPresentationForms(range) { + var this$1 = this; + var script = "arab"; + var tags = this.featuresTags[script]; + var tokens = this.tokenizer.getRangeTokens(range); + if (tokens.length === 1) { + return; + } + var contextParams = new ContextParams( + tokens.map( + function(token) { + return token.getState("glyphIndex"); + } + ), + 0 + ); + var charContextParams = new ContextParams( + tokens.map( + function(token) { + return token.char; + } + ), + 0 + ); + tokens.forEach(function(token, index) { + if (isTashkeelArabicChar(token.char)) { + return; + } + contextParams.setCurrentIndex(index); + charContextParams.setCurrentIndex(index); + var CONNECT = 0; + if (willConnectPrev(charContextParams)) { + CONNECT |= 1; + } + if (willConnectNext(charContextParams)) { + CONNECT |= 2; + } + var tag; + switch (CONNECT) { + case 1: + tag = "fina"; + break; + case 2: + tag = "init"; + break; + case 3: + tag = "medi"; + break; + } + if (tags.indexOf(tag) === -1) { + return; + } + var substitutions = this$1.query.lookupFeature({ + tag, + script, + contextParams + }); + if (substitutions instanceof Error) { + return console.info(substitutions.message); + } + substitutions.forEach(function(action, index2) { + if (action instanceof SubstitutionAction) { + applySubstitution(action, tokens, index2); + contextParams.context[index2] = action.substitution; + } + }); + }); + } + function getContextParams(tokens, index) { + var context = tokens.map(function(token) { + return token.activeState.value; + }); + return new ContextParams(context, index || 0); + } + function arabicRequiredLigatures(range) { + var this$1 = this; + var script = "arab"; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams(tokens); + contextParams.context.forEach(function(glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: "rlig", + script, + contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function(action) { + return applySubstitution(action, tokens, index); + } + ); + contextParams = getContextParams(tokens); + } + }); + } + function latinWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? latin first char + prevChar === null && isLatinChar(char) || // ? latin char preceded with a non latin char + !isLatinChar(prevChar) && isLatinChar(char) + ); + } + function latinWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last latin char + nextChar === null || // ? next char is not latin + !isLatinChar(nextChar) + ); + } + var latinWordCheck = { + startCheck: latinWordStartCheck, + endCheck: latinWordEndCheck + }; + function getContextParams$1(tokens, index) { + var context = tokens.map(function(token) { + return token.activeState.value; + }); + return new ContextParams(context, index || 0); + } + function latinLigature(range) { + var this$1 = this; + var script = "latn"; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams$1(tokens); + contextParams.context.forEach(function(glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: "liga", + script, + contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function(action) { + return applySubstitution(action, tokens, index); + } + ); + contextParams = getContextParams$1(tokens); + } + }); + } + function Bidi(baseDir) { + this.baseDir = baseDir || "ltr"; + this.tokenizer = new Tokenizer(); + this.featuresTags = {}; + } + Bidi.prototype.setText = function(text2) { + this.text = text2; + }; + Bidi.prototype.contextChecks = { + latinWordCheck, + arabicWordCheck, + arabicSentenceCheck + }; + function registerContextChecker(checkId) { + var check2 = this.contextChecks[checkId + "Check"]; + return this.tokenizer.registerContextChecker( + checkId, + check2.startCheck, + check2.endCheck + ); + } + function tokenizeText() { + registerContextChecker.call(this, "latinWord"); + registerContextChecker.call(this, "arabicWord"); + registerContextChecker.call(this, "arabicSentence"); + return this.tokenizer.tokenize(this.text); + } + function reverseArabicSentences() { + var this$1 = this; + var ranges = this.tokenizer.getContextRanges("arabicSentence"); + ranges.forEach(function(range) { + var rangeTokens = this$1.tokenizer.getRangeTokens(range); + this$1.tokenizer.replaceRange( + range.startIndex, + range.endOffset, + rangeTokens.reverse() + ); + }); + } + Bidi.prototype.registerFeatures = function(script, tags) { + var this$1 = this; + var supportedTags = tags.filter( + function(tag) { + return this$1.query.supports({ script, tag }); + } + ); + if (!this.featuresTags.hasOwnProperty(script)) { + this.featuresTags[script] = supportedTags; + } else { + this.featuresTags[script] = this.featuresTags[script].concat(supportedTags); + } + }; + Bidi.prototype.applyFeatures = function(font, features) { + if (!font) { + throw new Error( + "No valid font was provided to apply features" + ); + } + if (!this.query) { + this.query = new FeatureQuery(font); + } + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + if (!this.query.supports({ script: feature.script })) { + continue; + } + this.registerFeatures(feature.script, feature.tags); + } + }; + Bidi.prototype.registerModifier = function(modifierId, condition, modifier) { + this.tokenizer.registerModifier(modifierId, condition, modifier); + }; + function checkGlyphIndexStatus() { + if (this.tokenizer.registeredModifiers.indexOf("glyphIndex") === -1) { + throw new Error( + "glyphIndex modifier is required to apply arabic presentation features." + ); + } + } + function applyArabicPresentationForms() { + var this$1 = this; + var script = "arab"; + if (!this.featuresTags.hasOwnProperty(script)) { + return; + } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges("arabicWord"); + ranges.forEach(function(range) { + arabicPresentationForms.call(this$1, range); + }); + } + function applyArabicRequireLigatures() { + var this$1 = this; + var script = "arab"; + if (!this.featuresTags.hasOwnProperty(script)) { + return; + } + var tags = this.featuresTags[script]; + if (tags.indexOf("rlig") === -1) { + return; + } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges("arabicWord"); + ranges.forEach(function(range) { + arabicRequiredLigatures.call(this$1, range); + }); + } + function applyLatinLigatures() { + var this$1 = this; + var script = "latn"; + if (!this.featuresTags.hasOwnProperty(script)) { + return; + } + var tags = this.featuresTags[script]; + if (tags.indexOf("liga") === -1) { + return; + } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges("latinWord"); + ranges.forEach(function(range) { + latinLigature.call(this$1, range); + }); + } + Bidi.prototype.checkContextReady = function(contextId) { + return !!this.tokenizer.getContext(contextId); + }; + Bidi.prototype.applyFeaturesToContexts = function() { + if (this.checkContextReady("arabicWord")) { + applyArabicPresentationForms.call(this); + applyArabicRequireLigatures.call(this); + } + if (this.checkContextReady("latinWord")) { + applyLatinLigatures.call(this); + } + if (this.checkContextReady("arabicSentence")) { + reverseArabicSentences.call(this); + } + }; + Bidi.prototype.processText = function(text2) { + if (!this.text || this.text !== text2) { + this.setText(text2); + tokenizeText.call(this); + this.applyFeaturesToContexts(); + } + }; + Bidi.prototype.getBidiText = function(text2) { + this.processText(text2); + return this.tokenizer.getText(); + }; + Bidi.prototype.getTextGlyphs = function(text2) { + this.processText(text2); + var indexes = []; + for (var i2 = 0; i2 < this.tokenizer.tokens.length; i2++) { + var token = this.tokenizer.tokens[i2]; + if (token.state.deleted) { + continue; + } + var index = token.activeState.value; + indexes.push(Array.isArray(index) ? index[0] : index); + } + return indexes; + }; + function Font(options) { + options = options || {}; + options.tables = options.tables || {}; + if (!options.empty) { + checkArgument( + options.familyName, + "When creating a new Font object, familyName is required." + ); + checkArgument( + options.styleName, + "When creating a new Font object, styleName is required." + ); + checkArgument( + options.unitsPerEm, + "When creating a new Font object, unitsPerEm is required." + ); + checkArgument( + options.ascender, + "When creating a new Font object, ascender is required." + ); + checkArgument( + options.descender <= 0, + "When creating a new Font object, negative descender value is required." + ); + this.unitsPerEm = options.unitsPerEm || 1e3; + this.ascender = options.ascender; + this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; + this.tables = Object.assign(options.tables, { + os2: Object.assign( + { + usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, + usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, + fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR + }, + options.tables.os2 + ) + }); + } + this.supported = true; + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new DefaultEncoding(this); + this.position = new Position(this); + this.substitution = new Substitution(this); + this.tables = this.tables || {}; + this._push = null; + this._hmtxTableData = {}; + Object.defineProperty(this, "hinting", { + get: function() { + if (this._hinting) { + return this._hinting; + } + if (this.outlinesFormat === "truetype") { + return this._hinting = new Hinting(this); + } + } + }); + } + Font.prototype.hasChar = function(c2) { + return this.encoding.charToGlyphIndex(c2) !== null; + }; + Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); + }; + Font.prototype.charToGlyph = function(c2) { + var glyphIndex = this.charToGlyphIndex(c2); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + glyph = this.glyphs.get(0); + } + return glyph; + }; + Font.prototype.updateFeatures = function(options) { + return this.defaultRenderOptions.features.map(function(feature) { + if (feature.script === "latn") { + return { + script: "latn", + tags: feature.tags.filter(function(tag) { + return options[tag]; + }) + }; + } else { + return feature; + } + }); + }; + Font.prototype.stringToGlyphs = function(s, options) { + var this$1 = this; + var bidi = new Bidi(); + var charToGlyphIndexMod = function(token) { + return this$1.charToGlyphIndex(token.char); + }; + bidi.registerModifier("glyphIndex", null, charToGlyphIndexMod); + var features = options ? this.updateFeatures(options.features) : this.defaultRenderOptions.features; + bidi.applyFeatures(this, features); + var indexes = bidi.getTextGlyphs(s); + var length = indexes.length; + var glyphs = new Array(length); + var notdef = this.glyphs.get(0); + for (var i2 = 0; i2 < length; i2 += 1) { + glyphs[i2] = this.glyphs.get(indexes[i2]) || notdef; + } + return glyphs; + }; + Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.position.defaultKerningTables; + if (gposKerning) { + return this.position.getKerningValue( + gposKerning, + leftGlyph, + rightGlyph + ); + } + return this.kerningPairs[leftGlyph + "," + rightGlyph] || 0; + }; + Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: "arab", tags: ["init", "medi", "fina", "rlig"] }, + { script: "latn", tags: ["liga", "rlig"] } + ] + }; + Font.prototype.forEachGlyph = function(text2, x3, y, fontSize, options, callback) { + x3 = x3 !== void 0 ? x3 : 0; + y = y !== void 0 ? y : 0; + fontSize = fontSize !== void 0 ? fontSize : 72; + options = Object.assign({}, this.defaultRenderOptions, options); + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text2, options); + var kerningLookups; + if (options.kerning) { + var script = options.script || this.position.getDefaultScriptName(); + kerningLookups = this.position.getKerningTables( + script, + options.language + ); + } + for (var i2 = 0; i2 < glyphs.length; i2 += 1) { + var glyph = glyphs[i2]; + callback.call(this, glyph, x3, y, fontSize, options); + if (glyph.advanceWidth) { + x3 += glyph.advanceWidth * fontScale; + } + if (options.kerning && i2 < glyphs.length - 1) { + var kerningValue = kerningLookups ? this.position.getKerningValue( + kerningLookups, + glyph.index, + glyphs[i2 + 1].index + ) : this.getKerningValue(glyph, glyphs[i2 + 1]); + x3 += kerningValue * fontScale; + } + if (options.letterSpacing) { + x3 += options.letterSpacing * fontSize; + } else if (options.tracking) { + x3 += options.tracking / 1e3 * fontSize; + } + } + return x3; + }; + Font.prototype.getPath = function(text2, x3, y, fontSize, options) { + var fullPath = new Path(); + this.forEachGlyph( + text2, + x3, + y, + fontSize, + options, + function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + fullPath.extend(glyphPath); + } + ); + return fullPath; + }; + Font.prototype.getPaths = function(text2, x3, y, fontSize, options) { + var glyphPaths = []; + this.forEachGlyph( + text2, + x3, + y, + fontSize, + options, + function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + glyphPaths.push(glyphPath); + } + ); + return glyphPaths; + }; + Font.prototype.getAdvanceWidth = function(text2, fontSize, options) { + return this.forEachGlyph(text2, 0, 0, fontSize, options, function() { + }); + }; + Font.prototype.fsSelectionValues = { + ITALIC: 1, + //1 + UNDERSCORE: 2, + //2 + NEGATIVE: 4, + //4 + OUTLINED: 8, + //8 + STRIKEOUT: 16, + //16 + BOLD: 32, + //32 + REGULAR: 64, + //64 + USER_TYPO_METRICS: 128, + //128 + WWS: 256, + //256 + OBLIQUE: 512 + //512 + }; + Font.prototype.usWidthClasses = { + ULTRA_CONDENSED: 1, + EXTRA_CONDENSED: 2, + CONDENSED: 3, + SEMI_CONDENSED: 4, + MEDIUM: 5, + SEMI_EXPANDED: 6, + EXPANDED: 7, + EXTRA_EXPANDED: 8, + ULTRA_EXPANDED: 9 + }; + Font.prototype.usWeightClasses = { + THIN: 100, + EXTRA_LIGHT: 200, + LIGHT: 300, + NORMAL: 400, + MEDIUM: 500, + SEMI_BOLD: 600, + BOLD: 700, + EXTRA_BOLD: 800, + BLACK: 900 + }; + function parseCmapTableFormat12(cmap2, p) { + p.parseUShort(); + cmap2.length = p.parseULong(); + cmap2.language = p.parseULong(); + var groupCount; + cmap2.groupCount = groupCount = p.parseULong(); + cmap2.glyphIndexMap = {}; + for (var i2 = 0; i2 < groupCount; i2 += 1) { + var startCharCode = p.parseULong(); + var endCharCode = p.parseULong(); + var startGlyphId = p.parseULong(); + for (var c2 = startCharCode; c2 <= endCharCode; c2 += 1) { + cmap2.glyphIndexMap[c2] = startGlyphId; + startGlyphId++; + } + } + } + function parseCmapTableFormat4(cmap2, p, data, start, offset) { + cmap2.length = p.parseUShort(); + cmap2.language = p.parseUShort(); + var segCount; + cmap2.segCount = segCount = p.parseUShort() >> 1; + p.skip("uShort", 3); + cmap2.glyphIndexMap = {}; + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser( + data, + start + offset + 16 + segCount * 2 + ); + var idDeltaParser = new parse.Parser( + data, + start + offset + 16 + segCount * 4 + ); + var idRangeOffsetParser = new parse.Parser( + data, + start + offset + 16 + segCount * 6 + ); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (var i2 = 0; i2 < segCount - 1; i2 += 1) { + var glyphIndex = void 0; + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c2 = startCount; c2 <= endCount; c2 += 1) { + if (idRangeOffset !== 0) { + glyphIndexOffset = idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2; + glyphIndexOffset += idRangeOffset; + glyphIndexOffset += (c2 - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = glyphIndex + idDelta & 65535; + } + } else { + glyphIndex = c2 + idDelta & 65535; + } + cmap2.glyphIndexMap[c2] = glyphIndex; + } + } + } + function parseCmapTable(data, start) { + var cmap2 = {}; + cmap2.version = parse.getUShort(data, start); + check.argument(cmap2.version === 0, "cmap table version should be 0."); + cmap2.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (var i2 = cmap2.numTables - 1; i2 >= 0; i2 -= 1) { + var platformId = parse.getUShort(data, start + 4 + i2 * 8); + var encodingId = parse.getUShort(data, start + 4 + i2 * 8 + 2); + if (platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10) || platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4)) { + offset = parse.getULong(data, start + 4 + i2 * 8 + 4); + break; + } + } + if (offset === -1) { + throw new Error("No valid cmap sub-tables found."); + } + var p = new parse.Parser(data, start + offset); + cmap2.format = p.parseUShort(); + if (cmap2.format === 12) { + parseCmapTableFormat12(cmap2, p); + } else if (cmap2.format === 4) { + parseCmapTableFormat4(cmap2, p, data, start, offset); + } else { + throw new Error( + "Only format 4 and 12 cmap tables are supported (found format " + cmap2.format + ")." + ); + } + return cmap2; + } + var cmap = { parse: parseCmapTable }; + function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + return bias; + } + function parseCFFIndex(data, start, conversionFn) { + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + (count + 1) * offsetSize + 2; + var pos = start + 3; + for (var i2 = 0; i2 < count + 1; i2 += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { + var value = parse.getBytes( + data, + objectOffset + offsets[i$1], + objectOffset + offsets[i$1 + 1] + ); + if (conversionFn) { + value = conversionFn(value); + } + objects.push(value); + } + return { objects, startOffset: start, endOffset }; + } + function parseCFFIndexLowMemory(data, start) { + var offsets = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + (count + 1) * offsetSize + 2; + var pos = start + 3; + for (var i2 = 0; i2 < count + 1; i2 += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + return { offsets, startOffset: start, endOffset }; + } + function getCffIndexObject(i2, offsets, data, start, conversionFn) { + var count = parse.getCard16(data, start); + var objectOffset = 0; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + (count + 1) * offsetSize + 2; + } + var value = parse.getBytes( + data, + objectOffset + offsets[i2], + objectOffset + offsets[i2 + 1] + ); + if (conversionFn) { + value = conversionFn(value); + } + return value; + } + function parseFloatOperand(parser) { + var s = ""; + var eof = 15; + var lookup = [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ".", + "E", + "E-", + null, + "-" + ]; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + if (n1 === eof) { + break; + } + s += lookup[n1]; + if (n2 === eof) { + break; + } + s += lookup[n2]; + } + return parseFloat(s); + } + function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + if (b0 === 30) { + return parseFloatOperand(parser); + } + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + throw new Error("Invalid b0 " + b0); + } + function entriesToObject(entries) { + var o = {}; + for (var i2 = 0; i2 < entries.length; i2 += 1) { + var key = entries[i2][0]; + var values = entries[i2][1]; + var value = void 0; + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } + if (o.hasOwnProperty(key) && !isNaN(o[key])) { + throw new Error("Object " + o + " already has key " + key); + } + o[key] = value; + } + return o; + } + function parseCFFDict(data, start, size) { + start = start !== void 0 ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== void 0 ? size : data.length; + while (parser.relativeOffset < size) { + var op = parser.parseByte(); + if (op <= 21) { + if (op === 12) { + op = 1200 + parser.parseByte(); + } + entries.push([op, operands]); + operands = []; + } else { + operands.push(parseOperand(parser, op)); + } + } + return entriesToObject(entries); + } + function getCFFString(strings, index) { + if (index <= 390) { + index = cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } + return index; + } + function interpretDict(dict, meta2, strings) { + var newDict = {}; + var value; + for (var i2 = 0; i2 < meta2.length; i2 += 1) { + var m2 = meta2[i2]; + if (Array.isArray(m2.type)) { + var values = []; + values.length = m2.type.length; + for (var j = 0; j < m2.type.length; j++) { + value = dict[m2.op] !== void 0 ? dict[m2.op][j] : void 0; + if (value === void 0) { + value = m2.value !== void 0 && m2.value[j] !== void 0 ? m2.value[j] : null; + } + if (m2.type[j] === "SID") { + value = getCFFString(strings, value); + } + values[j] = value; + } + newDict[m2.name] = values; + } else { + value = dict[m2.op]; + if (value === void 0) { + value = m2.value !== void 0 ? m2.value : null; + } + if (m2.type === "SID") { + value = getCFFString(strings, value); + } + newDict[m2.name] = value; + } + } + return newDict; + } + function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; + } + var TOP_DICT_META = [ + { name: "version", op: 0, type: "SID" }, + { name: "notice", op: 1, type: "SID" }, + { name: "copyright", op: 1200, type: "SID" }, + { name: "fullName", op: 2, type: "SID" }, + { name: "familyName", op: 3, type: "SID" }, + { name: "weight", op: 4, type: "SID" }, + { name: "isFixedPitch", op: 1201, type: "number", value: 0 }, + { name: "italicAngle", op: 1202, type: "number", value: 0 }, + { name: "underlinePosition", op: 1203, type: "number", value: -100 }, + { name: "underlineThickness", op: 1204, type: "number", value: 50 }, + { name: "paintType", op: 1205, type: "number", value: 0 }, + { name: "charstringType", op: 1206, type: "number", value: 2 }, + { + name: "fontMatrix", + op: 1207, + type: ["real", "real", "real", "real", "real", "real"], + value: [1e-3, 0, 0, 1e-3, 0, 0] + }, + { name: "uniqueId", op: 13, type: "number" }, + { + name: "fontBBox", + op: 5, + type: ["number", "number", "number", "number"], + value: [0, 0, 0, 0] + }, + { name: "strokeWidth", op: 1208, type: "number", value: 0 }, + { name: "xuid", op: 14, type: [], value: null }, + { name: "charset", op: 15, type: "offset", value: 0 }, + { name: "encoding", op: 16, type: "offset", value: 0 }, + { name: "charStrings", op: 17, type: "offset", value: 0 }, + { name: "private", op: 18, type: ["number", "offset"], value: [0, 0] }, + { name: "ros", op: 1230, type: ["SID", "SID", "number"] }, + { name: "cidFontVersion", op: 1231, type: "number", value: 0 }, + { name: "cidFontRevision", op: 1232, type: "number", value: 0 }, + { name: "cidFontType", op: 1233, type: "number", value: 0 }, + { name: "cidCount", op: 1234, type: "number", value: 8720 }, + { name: "uidBase", op: 1235, type: "number" }, + { name: "fdArray", op: 1236, type: "offset" }, + { name: "fdSelect", op: 1237, type: "offset" }, + { name: "fontName", op: 1238, type: "SID" } + ]; + var PRIVATE_DICT_META = [ + { name: "subrs", op: 19, type: "offset", value: 0 }, + { name: "defaultWidthX", op: 20, type: "number", value: 0 }, + { name: "nominalWidthX", op: 21, type: "number", value: 0 } + ]; + function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); + } + function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); + } + function gatherCFFTopDicts(data, start, cffIndex, strings) { + var topDictArray = []; + for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { + var topDictData = new DataView( + new Uint8Array(cffIndex[iTopDict]).buffer + ); + var topDict = parseCFFTopDict(topDictData, strings); + topDict._subrs = []; + topDict._subrsBias = 0; + topDict._defaultWidthX = 0; + topDict._nominalWidthX = 0; + var privateSize = topDict.private[0]; + var privateOffset = topDict.private[1]; + if (privateSize !== 0 && privateOffset !== 0) { + var privateDict = parseCFFPrivateDict( + data, + privateOffset + start, + privateSize, + strings + ); + topDict._defaultWidthX = privateDict.defaultWidthX; + topDict._nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset + start); + topDict._subrs = subrIndex.objects; + topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); + } + topDict._privateDict = privateDict; + } + topDictArray.push(topDict); + } + return topDictArray; + } + function parseCFFCharset(data, start, nGlyphs, strings) { + var sid; + var count; + var parser = new parse.Parser(data, start); + nGlyphs -= 1; + var charset = [".notdef"]; + var format = parser.parseCard8(); + if (format === 0) { + for (var i2 = 0; i2 < nGlyphs; i2 += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (var i$1 = 0; i$1 <= count; i$1 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (var i$2 = 0; i$2 <= count; i$2 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error("Unknown charset format " + format); + } + return charset; + } + function parseCFFEncoding(data, start, charset) { + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (var i2 = 0; i2 < nCodes; i2 += 1) { + code = parser.parseCard8(); + enc[code] = i2; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error("Unknown encoding format " + format); + } + return new CffEncoding(enc, charset); + } + function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var open = false; + var x3 = 0; + var y = 0; + var subrs; + var subrsBias; + var defaultWidthX; + var nominalWidthX; + if (font.isCIDFont) { + var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; + var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; + subrs = fdDict._subrs; + subrsBias = fdDict._subrsBias; + defaultWidthX = fdDict._defaultWidthX; + nominalWidthX = fdDict._nominalWidthX; + } else { + subrs = font.tables.cff.topDict._subrs; + subrsBias = font.tables.cff.topDict._subrsBias; + defaultWidthX = font.tables.cff.topDict._defaultWidthX; + nominalWidthX = font.tables.cff.topDict._nominalWidthX; + } + var width = defaultWidthX; + function newContour(x4, y2) { + if (open) { + p.closePath(); + } + p.moveTo(x4, y2); + open = true; + } + function parseStems() { + var hasWidthArg; + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + nominalWidthX; + } + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + function parse2(code2) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + var i2 = 0; + while (i2 < code2.length) { + var v2 = code2[i2]; + i2 += 1; + switch (v2) { + case 1: + parseStems(); + break; + case 3: + parseStems(); + break; + case 4: + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + y += stack.pop(); + newContour(x3, y); + break; + case 5: + while (stack.length > 0) { + x3 += stack.shift(); + y += stack.shift(); + p.lineTo(x3, y); + } + break; + case 6: + while (stack.length > 0) { + x3 += stack.shift(); + p.lineTo(x3, y); + if (stack.length === 0) { + break; + } + y += stack.shift(); + p.lineTo(x3, y); + } + break; + case 7: + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x3, y); + if (stack.length === 0) { + break; + } + x3 += stack.shift(); + p.lineTo(x3, y); + } + break; + case 8: + while (stack.length > 0) { + c1x = x3 + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x3 = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + } + break; + case 10: + codeIndex = stack.pop() + subrsBias; + subrCode = subrs[codeIndex]; + if (subrCode) { + parse2(subrCode); + } + break; + case 11: + return; + case 12: + v2 = code2[i2]; + i2 += 1; + switch (v2) { + case 35: + c1x = x3 + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + jpx = c2x + stack.shift(); + jpy = c2y + stack.shift(); + c3x = jpx + stack.shift(); + c3y = jpy + stack.shift(); + c4x = c3x + stack.shift(); + c4y = c3y + stack.shift(); + x3 = c4x + stack.shift(); + y = c4y + stack.shift(); + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x3, y); + break; + case 34: + c1x = x3 + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + jpx = c2x + stack.shift(); + jpy = c2y; + c3x = jpx + stack.shift(); + c3y = c2y; + c4x = c3x + stack.shift(); + c4y = y; + x3 = c4x + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x3, y); + break; + case 36: + c1x = x3 + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + jpx = c2x + stack.shift(); + jpy = c2y; + c3x = jpx + stack.shift(); + c3y = c2y; + c4x = c3x + stack.shift(); + c4y = c3y + stack.shift(); + x3 = c4x + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x3, y); + break; + case 37: + c1x = x3 + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + jpx = c2x + stack.shift(); + jpy = c2y + stack.shift(); + c3x = jpx + stack.shift(); + c3y = jpy + stack.shift(); + c4x = c3x + stack.shift(); + c4y = c3y + stack.shift(); + if (Math.abs(c4x - x3) > Math.abs(c4y - y)) { + x3 = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x3, y); + break; + default: + console.log( + "Glyph " + glyph.index + ": unknown operator 1200" + v2 + ); + stack.length = 0; + } + break; + case 14: + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + if (open) { + p.closePath(); + open = false; + } + break; + case 18: + parseStems(); + break; + case 19: + // hintmask + case 20: + parseStems(); + i2 += nStems + 7 >> 3; + break; + case 21: + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + y += stack.pop(); + x3 += stack.pop(); + newContour(x3, y); + break; + case 22: + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + x3 += stack.pop(); + newContour(x3, y); + break; + case 23: + parseStems(); + break; + case 24: + while (stack.length > 2) { + c1x = x3 + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x3 = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + } + x3 += stack.shift(); + y += stack.shift(); + p.lineTo(x3, y); + break; + case 25: + while (stack.length > 6) { + x3 += stack.shift(); + y += stack.shift(); + p.lineTo(x3, y); + } + c1x = x3 + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x3 = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + break; + case 26: + if (stack.length % 2) { + x3 += stack.shift(); + } + while (stack.length > 0) { + c1x = x3; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x3 = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + } + break; + case 27: + if (stack.length % 2) { + y += stack.shift(); + } + while (stack.length > 0) { + c1x = x3 + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x3 = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + } + break; + case 28: + b1 = code2[i2]; + b2 = code2[i2 + 1]; + stack.push((b1 << 24 | b2 << 16) >> 16); + i2 += 2; + break; + case 29: + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse2(subrCode); + } + break; + case 30: + while (stack.length > 0) { + c1x = x3; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x3 = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + if (stack.length === 0) { + break; + } + c1x = x3 + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x3 = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + } + break; + case 31: + while (stack.length > 0) { + c1x = x3 + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x3 = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + if (stack.length === 0) { + break; + } + c1x = x3; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x3 = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x3, y); + } + break; + default: + if (v2 < 32) { + console.log( + "Glyph " + glyph.index + ": unknown operator " + v2 + ); + } else if (v2 < 247) { + stack.push(v2 - 139); + } else if (v2 < 251) { + b1 = code2[i2]; + i2 += 1; + stack.push((v2 - 247) * 256 + b1 + 108); + } else if (v2 < 255) { + b1 = code2[i2]; + i2 += 1; + stack.push(-(v2 - 251) * 256 - b1 - 108); + } else { + b1 = code2[i2]; + b2 = code2[i2 + 1]; + b3 = code2[i2 + 2]; + b4 = code2[i2 + 3]; + i2 += 4; + stack.push( + (b1 << 24 | b2 << 16 | b3 << 8 | b4) / 65536 + ); + } + } + } + } + parse2(code); + glyph.advanceWidth = width; + return p; + } + function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { + var fdSelect = []; + var fdIndex; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + for (var iGid = 0; iGid < nGlyphs; iGid++) { + fdIndex = parser.parseCard8(); + if (fdIndex >= fdArrayCount) { + throw new Error( + "CFF table CID Font FDSelect has bad FD index value " + fdIndex + " (FD count " + fdArrayCount + ")" + ); + } + fdSelect.push(fdIndex); + } + } else if (format === 3) { + var nRanges = parser.parseCard16(); + var first = parser.parseCard16(); + if (first !== 0) { + throw new Error( + "CFF Table CID Font FDSelect format 3 range has bad initial GID " + first + ); + } + var next; + for (var iRange = 0; iRange < nRanges; iRange++) { + fdIndex = parser.parseCard8(); + next = parser.parseCard16(); + if (fdIndex >= fdArrayCount) { + throw new Error( + "CFF table CID Font FDSelect has bad FD index value " + fdIndex + " (FD count " + fdArrayCount + ")" + ); + } + if (next > nGlyphs) { + throw new Error( + "CFF Table CID Font FDSelect format 3 range has bad GID " + next + ); + } + for (; first < next; first++) { + fdSelect.push(fdIndex); + } + first = next; + } + if (next !== nGlyphs) { + throw new Error( + "CFF Table CID Font FDSelect format 3 range has bad final GID " + next + ); + } + } else { + throw new Error( + "CFF Table CID Font FDSelect table has unsupported format " + format + ); + } + return fdSelect; + } + function parseCFFTable(data, start, font, opt) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex( + data, + header.endOffset, + parse.bytesToString + ); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex( + data, + topDictIndex.endOffset, + parse.bytesToString + ); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + var topDictArray = gatherCFFTopDicts( + data, + start, + topDictIndex.objects, + stringIndex.objects + ); + if (topDictArray.length !== 1) { + throw new Error( + "CFF table has too many fonts in 'FontSet' - count of fonts NameIndex.length = " + topDictArray.length + ); + } + var topDict = topDictArray[0]; + font.tables.cff.topDict = topDict; + if (topDict._privateDict) { + font.defaultWidthX = topDict._privateDict.defaultWidthX; + font.nominalWidthX = topDict._privateDict.nominalWidthX; + } + if (topDict.ros[0] !== void 0 && topDict.ros[1] !== void 0) { + font.isCIDFont = true; + } + if (font.isCIDFont) { + var fdArrayOffset = topDict.fdArray; + var fdSelectOffset = topDict.fdSelect; + if (fdArrayOffset === 0 || fdSelectOffset === 0) { + throw new Error( + "Font is marked as a CID font, but FDArray and/or FDSelect information is missing" + ); + } + fdArrayOffset += start; + var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); + var fdArray = gatherCFFTopDicts( + data, + start, + fdArrayIndex.objects, + stringIndex.objects + ); + topDict._fdArray = fdArray; + fdSelectOffset += start; + topDict._fdSelect = parseCFFFDSelect( + data, + fdSelectOffset, + font.numGlyphs, + fdArray.length + ); + } + var privateDictOffset = start + topDict.private[1]; + var privateDict = parseCFFPrivateDict( + data, + privateDictOffset, + topDict.private[0], + stringIndex.objects + ); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } + var charStringsIndex; + if (opt.lowMemory) { + charStringsIndex = parseCFFIndexLowMemory( + data, + start + topDict.charStrings + ); + font.nGlyphs = charStringsIndex.offsets.length; + } else { + charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + } + var charset = parseCFFCharset( + data, + start + topDict.charset, + font.nGlyphs, + stringIndex.objects + ); + if (topDict.encoding === 0) { + font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { + font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding( + data, + start + topDict.encoding, + charset + ); + } + font.encoding = font.encoding || font.cffEncoding; + font.glyphs = new glyphset.GlyphSet(font); + if (opt.lowMemory) { + font._push = function(i3) { + var charString2 = getCffIndexObject( + i3, + charStringsIndex.offsets, + data, + start + topDict.charStrings + ); + font.glyphs.push( + i3, + glyphset.cffGlyphLoader(font, i3, parseCFFCharstring, charString2) + ); + }; + } else { + for (var i2 = 0; i2 < font.nGlyphs; i2 += 1) { + var charString = charStringsIndex.objects[i2]; + font.glyphs.push( + i2, + glyphset.cffGlyphLoader(font, i2, parseCFFCharstring, charString) + ); + } + } + } + var cff = { parse: parseCFFTable }; + function parseFvarAxis(data, start, names) { + var axis = {}; + var p = new parse.Parser(data, start); + axis.tag = p.parseTag(); + axis.minValue = p.parseFixed(); + axis.defaultValue = p.parseFixed(); + axis.maxValue = p.parseFixed(); + p.skip("uShort", 1); + axis.name = names[p.parseUShort()] || {}; + return axis; + } + function parseFvarInstance(data, start, axes, names) { + var inst = {}; + var p = new parse.Parser(data, start); + inst.name = names[p.parseUShort()] || {}; + p.skip("uShort", 1); + inst.coordinates = {}; + for (var i2 = 0; i2 < axes.length; ++i2) { + inst.coordinates[axes[i2].tag] = p.parseFixed(); + } + return inst; + } + function parseFvarTable(data, start, names) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument( + tableVersion === 65536, + "Unsupported fvar table version." + ); + var offsetToData = p.parseOffset16(); + p.skip("uShort", 1); + var axisCount = p.parseUShort(); + var axisSize = p.parseUShort(); + var instanceCount = p.parseUShort(); + var instanceSize = p.parseUShort(); + var axes = []; + for (var i2 = 0; i2 < axisCount; i2++) { + axes.push( + parseFvarAxis(data, start + offsetToData + i2 * axisSize, names) + ); + } + var instances = []; + var instanceStart = start + offsetToData + axisCount * axisSize; + for (var j = 0; j < instanceCount; j++) { + instances.push( + parseFvarInstance( + data, + instanceStart + j * instanceSize, + axes, + names + ) + ); + } + return { axes, instances }; + } + var fvar = { parse: parseFvarTable }; + var attachList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + attachPoints: this.parseList(Parser.pointer(Parser.uShortList)) + }; + }; + var caretValue = function() { + var format = this.parseUShort(); + check.argument( + format === 1 || format === 2 || format === 3, + "Unsupported CaretValue table version." + ); + if (format === 1) { + return { coordinate: this.parseShort() }; + } else if (format === 2) { + return { pointindex: this.parseShort() }; + } else if (format === 3) { + return { coordinate: this.parseShort() }; + } + }; + var ligGlyph = function() { + return this.parseList(Parser.pointer(caretValue)); + }; + var ligCaretList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + ligGlyphs: this.parseList(Parser.pointer(ligGlyph)) + }; + }; + var markGlyphSets = function() { + this.parseUShort(); + return this.parseList(Parser.pointer(Parser.coverage)); + }; + function parseGDEFTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument( + tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3, + "Unsupported GDEF table version." + ); + var gdef2 = { + version: tableVersion, + classDef: p.parsePointer(Parser.classDef), + attachList: p.parsePointer(attachList), + ligCaretList: p.parsePointer(ligCaretList), + markAttachClassDef: p.parsePointer(Parser.classDef) + }; + if (tableVersion >= 1.2) { + gdef2.markGlyphSets = p.parsePointer(markGlyphSets); + } + return gdef2; + } + var gdef = { parse: parseGDEFTable }; + var subtableParsers = new Array(10); + subtableParsers[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var posformat = this.parseUShort(); + if (posformat === 1) { + return { + posFormat: 1, + coverage: this.parsePointer(Parser.coverage), + value: this.parseValueRecord() + }; + } else if (posformat === 2) { + return { + posFormat: 2, + coverage: this.parsePointer(Parser.coverage), + values: this.parseValueRecordList() + }; + } + check.assert( + false, + "0x" + start.toString(16) + ": GPOS lookup type 1 format must be 1 or 2." + ); + }; + subtableParsers[2] = function parseLookup2() { + var start = this.offset + this.relativeOffset; + var posFormat = this.parseUShort(); + check.assert( + posFormat === 1 || posFormat === 2, + "0x" + start.toString(16) + ": GPOS lookup type 2 format must be 1 or 2." + ); + var coverage = this.parsePointer(Parser.coverage); + var valueFormat1 = this.parseUShort(); + var valueFormat2 = this.parseUShort(); + if (posFormat === 1) { + return { + posFormat, + coverage, + valueFormat1, + valueFormat2, + pairSets: this.parseList( + Parser.pointer( + Parser.list(function() { + return { + // pairValueRecord + secondGlyph: this.parseUShort(), + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }) + ) + ) + }; + } else if (posFormat === 2) { + var classDef1 = this.parsePointer(Parser.classDef); + var classDef2 = this.parsePointer(Parser.classDef); + var class1Count = this.parseUShort(); + var class2Count = this.parseUShort(); + return { + // Class Pair Adjustment + posFormat, + coverage, + valueFormat1, + valueFormat2, + classDef1, + classDef2, + class1Count, + class2Count, + classRecords: this.parseList( + class1Count, + Parser.list(class2Count, function() { + return { + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }) + ) + }; + } + }; + subtableParsers[3] = function parseLookup3() { + return { error: "GPOS Lookup 3 not supported" }; + }; + subtableParsers[4] = function parseLookup4() { + return { error: "GPOS Lookup 4 not supported" }; + }; + subtableParsers[5] = function parseLookup5() { + return { error: "GPOS Lookup 5 not supported" }; + }; + subtableParsers[6] = function parseLookup6() { + return { error: "GPOS Lookup 6 not supported" }; + }; + subtableParsers[7] = function parseLookup7() { + return { error: "GPOS Lookup 7 not supported" }; + }; + subtableParsers[8] = function parseLookup8() { + return { error: "GPOS Lookup 8 not supported" }; + }; + subtableParsers[9] = function parseLookup9() { + return { error: "GPOS Lookup 9 not supported" }; + }; + function parseGposTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument( + tableVersion === 1 || tableVersion === 1.1, + "Unsupported GPOS table version " + tableVersion + ); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers), + variations: p.parseFeatureVariationsList() + }; + } + } + var gpos = { parse: parseGposTable }; + var subtableParsers$1 = new Array(9); + subtableParsers$1[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + deltaGlyphId: this.parseUShort() + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + substitute: this.parseOffset16List() + }; + } + check.assert( + false, + "0x" + start.toString(16) + ": lookup type 1 format must be 1 or 2." + ); + }; + subtableParsers$1[2] = function parseLookup2() { + var substFormat = this.parseUShort(); + check.argument( + substFormat === 1, + "GSUB Multiple Substitution Subtable identifier-format must be 1" + ); + return { + substFormat, + coverage: this.parsePointer(Parser.coverage), + sequences: this.parseListOfLists() + }; + }; + subtableParsers$1[3] = function parseLookup3() { + var substFormat = this.parseUShort(); + check.argument( + substFormat === 1, + "GSUB Alternate Substitution Subtable identifier-format must be 1" + ); + return { + substFormat, + coverage: this.parsePointer(Parser.coverage), + alternateSets: this.parseListOfLists() + }; + }; + subtableParsers$1[4] = function parseLookup4() { + var substFormat = this.parseUShort(); + check.argument( + substFormat === 1, + "GSUB ligature table identifier-format must be 1" + ); + return { + substFormat, + coverage: this.parsePointer(Parser.coverage), + ligatureSets: this.parseListOfLists(function() { + return { + ligGlyph: this.parseUShort(), + components: this.parseUShortList(this.parseUShort() - 1) + }; + }) + }; + }; + var lookupRecordDesc = { + sequenceIndex: Parser.uShort, + lookupListIndex: Parser.uShort + }; + subtableParsers$1[5] = function parseLookup5() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat, + coverage: this.parsePointer(Parser.coverage), + ruleSets: this.parseListOfLists(function() { + var glyphCount2 = this.parseUShort(); + var substCount2 = this.parseUShort(); + return { + input: this.parseUShortList(glyphCount2 - 1), + lookupRecords: this.parseRecordList( + substCount2, + lookupRecordDesc + ) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat, + coverage: this.parsePointer(Parser.coverage), + classDef: this.parsePointer(Parser.classDef), + classSets: this.parseListOfLists(function() { + var glyphCount2 = this.parseUShort(); + var substCount2 = this.parseUShort(); + return { + classes: this.parseUShortList(glyphCount2 - 1), + lookupRecords: this.parseRecordList( + substCount2, + lookupRecordDesc + ) + }; + }) + }; + } else if (substFormat === 3) { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + substFormat, + coverages: this.parseList( + glyphCount, + Parser.pointer(Parser.coverage) + ), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + } + check.assert( + false, + "0x" + start.toString(16) + ": lookup type 5 format must be 1, 2 or 3." + ); + }; + subtableParsers$1[6] = function parseLookup6() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + chainRuleSets: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + backtrackClassDef: this.parsePointer(Parser.classDef), + inputClassDef: this.parsePointer(Parser.classDef), + lookaheadClassDef: this.parsePointer(Parser.classDef), + chainClassSet: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + return { + substFormat: 3, + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + } + check.assert( + false, + "0x" + start.toString(16) + ": lookup type 6 format must be 1, 2 or 3." + ); + }; + subtableParsers$1[7] = function parseLookup7() { + var substFormat = this.parseUShort(); + check.argument( + substFormat === 1, + "GSUB Extension Substitution subtable identifier-format must be 1" + ); + var extensionLookupType = this.parseUShort(); + var extensionParser = new Parser( + this.data, + this.offset + this.parseULong() + ); + return { + substFormat: 1, + lookupType: extensionLookupType, + extension: subtableParsers$1[extensionLookupType].call(extensionParser) + }; + }; + subtableParsers$1[8] = function parseLookup8() { + var substFormat = this.parseUShort(); + check.argument( + substFormat === 1, + "GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1" + ); + return { + substFormat, + coverage: this.parsePointer(Parser.coverage), + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + substitutes: this.parseUShortList() + }; + }; + function parseGsubTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument( + tableVersion === 1 || tableVersion === 1.1, + "Unsupported GSUB table version." + ); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1), + variations: p.parseFeatureVariationsList() + }; + } + } + var gsub = { parse: parseGsubTable }; + function parseHeadTable(data, start) { + var head2 = {}; + var p = new parse.Parser(data, start); + head2.version = p.parseVersion(); + head2.fontRevision = Math.round(p.parseFixed() * 1e3) / 1e3; + head2.checkSumAdjustment = p.parseULong(); + head2.magicNumber = p.parseULong(); + check.argument( + head2.magicNumber === 1594834165, + "Font header has wrong magic number." + ); + head2.flags = p.parseUShort(); + head2.unitsPerEm = p.parseUShort(); + head2.created = p.parseLongDateTime(); + head2.modified = p.parseLongDateTime(); + head2.xMin = p.parseShort(); + head2.yMin = p.parseShort(); + head2.xMax = p.parseShort(); + head2.yMax = p.parseShort(); + head2.macStyle = p.parseUShort(); + head2.lowestRecPPEM = p.parseUShort(); + head2.fontDirectionHint = p.parseShort(); + head2.indexToLocFormat = p.parseShort(); + head2.glyphDataFormat = p.parseShort(); + return head2; + } + var head = { parse: parseHeadTable }; + function parseHheaTable(data, start) { + var hhea2 = {}; + var p = new parse.Parser(data, start); + hhea2.version = p.parseVersion(); + hhea2.ascender = p.parseShort(); + hhea2.descender = p.parseShort(); + hhea2.lineGap = p.parseShort(); + hhea2.advanceWidthMax = p.parseUShort(); + hhea2.minLeftSideBearing = p.parseShort(); + hhea2.minRightSideBearing = p.parseShort(); + hhea2.xMaxExtent = p.parseShort(); + hhea2.caretSlopeRise = p.parseShort(); + hhea2.caretSlopeRun = p.parseShort(); + hhea2.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea2.metricDataFormat = p.parseShort(); + hhea2.numberOfHMetrics = p.parseUShort(); + return hhea2; + } + var hhea = { parse: parseHheaTable }; + function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i2 = 0; i2 < numGlyphs; i2 += 1) { + if (i2 < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + var glyph = glyphs.get(i2); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } + } + function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { + font._hmtxTableData = {}; + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i2 = 0; i2 < numGlyphs; i2 += 1) { + if (i2 < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + font._hmtxTableData[i2] = { + advanceWidth, + leftSideBearing + }; + } + } + function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { + if (opt.lowMemory) { + parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); + } else { + parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); + } + } + var hmtx = { parse: parseHmtxTable }; + function parseWindowsKernTable(p) { + var pairs = {}; + p.skip("uShort"); + var subtableVersion = p.parseUShort(); + check.argument(subtableVersion === 0, "Unsupported kern sub-table version."); + p.skip("uShort", 2); + var nPairs = p.parseUShort(); + p.skip("uShort", 3); + for (var i2 = 0; i2 < nPairs; i2 += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + "," + rightIndex] = value; + } + return pairs; + } + function parseMacKernTable(p) { + var pairs = {}; + p.skip("uShort"); + var nTables = p.parseULong(); + if (nTables > 1) { + console.warn("Only the first kern subtable is supported."); + } + p.skip("uLong"); + var coverage = p.parseUShort(); + var subtableVersion = coverage & 255; + p.skip("uShort"); + if (subtableVersion === 0) { + var nPairs = p.parseUShort(); + p.skip("uShort", 3); + for (var i2 = 0; i2 < nPairs; i2 += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + "," + rightIndex] = value; + } + } + return pairs; + } + function parseKernTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + if (tableVersion === 0) { + return parseWindowsKernTable(p); + } else if (tableVersion === 1) { + return parseMacKernTable(p); + } else { + throw new Error("Unsupported kern table version (" + tableVersion + ")."); + } + } + var kern = { parse: parseKernTable }; + function parseLtagTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, "Unsupported ltag table version."); + p.skip("uLong", 1); + var numTags = p.parseULong(); + var tags = []; + for (var i2 = 0; i2 < numTags; i2++) { + var tag = ""; + var offset = start + p.parseUShort(); + var length = p.parseUShort(); + for (var j = offset; j < offset + length; ++j) { + tag += String.fromCharCode(data.getInt8(j)); + } + tags.push(tag); + } + return tags; + } + var ltag = { parse: parseLtagTable }; + function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + var glyphOffsets = []; + for (var i2 = 0; i2 < numGlyphs + 1; i2 += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + glyphOffset *= 2; + } + glyphOffsets.push(glyphOffset); + } + return glyphOffsets; + } + var loca = { parse: parseLocaTable }; + function parseMaxpTable(data, start) { + var maxp2 = {}; + var p = new parse.Parser(data, start); + maxp2.version = p.parseVersion(); + maxp2.numGlyphs = p.parseUShort(); + if (maxp2.version === 1) { + maxp2.maxPoints = p.parseUShort(); + maxp2.maxContours = p.parseUShort(); + maxp2.maxCompositePoints = p.parseUShort(); + maxp2.maxCompositeContours = p.parseUShort(); + maxp2.maxZones = p.parseUShort(); + maxp2.maxTwilightPoints = p.parseUShort(); + maxp2.maxStorage = p.parseUShort(); + maxp2.maxFunctionDefs = p.parseUShort(); + maxp2.maxInstructionDefs = p.parseUShort(); + maxp2.maxStackElements = p.parseUShort(); + maxp2.maxSizeOfInstructions = p.parseUShort(); + maxp2.maxComponentElements = p.parseUShort(); + maxp2.maxComponentDepth = p.parseUShort(); + } + return maxp2; + } + var maxp = { parse: parseMaxpTable }; + function parseOS2Table(data, start) { + var os22 = {}; + var p = new parse.Parser(data, start); + os22.version = p.parseUShort(); + os22.xAvgCharWidth = p.parseShort(); + os22.usWeightClass = p.parseUShort(); + os22.usWidthClass = p.parseUShort(); + os22.fsType = p.parseUShort(); + os22.ySubscriptXSize = p.parseShort(); + os22.ySubscriptYSize = p.parseShort(); + os22.ySubscriptXOffset = p.parseShort(); + os22.ySubscriptYOffset = p.parseShort(); + os22.ySuperscriptXSize = p.parseShort(); + os22.ySuperscriptYSize = p.parseShort(); + os22.ySuperscriptXOffset = p.parseShort(); + os22.ySuperscriptYOffset = p.parseShort(); + os22.yStrikeoutSize = p.parseShort(); + os22.yStrikeoutPosition = p.parseShort(); + os22.sFamilyClass = p.parseShort(); + os22.panose = []; + for (var i2 = 0; i2 < 10; i2++) { + os22.panose[i2] = p.parseByte(); + } + os22.ulUnicodeRange1 = p.parseULong(); + os22.ulUnicodeRange2 = p.parseULong(); + os22.ulUnicodeRange3 = p.parseULong(); + os22.ulUnicodeRange4 = p.parseULong(); + os22.achVendID = String.fromCharCode( + p.parseByte(), + p.parseByte(), + p.parseByte(), + p.parseByte() + ); + os22.fsSelection = p.parseUShort(); + os22.usFirstCharIndex = p.parseUShort(); + os22.usLastCharIndex = p.parseUShort(); + os22.sTypoAscender = p.parseShort(); + os22.sTypoDescender = p.parseShort(); + os22.sTypoLineGap = p.parseShort(); + os22.usWinAscent = p.parseUShort(); + os22.usWinDescent = p.parseUShort(); + if (os22.version >= 1) { + os22.ulCodePageRange1 = p.parseULong(); + os22.ulCodePageRange2 = p.parseULong(); + } + if (os22.version >= 2) { + os22.sxHeight = p.parseShort(); + os22.sCapHeight = p.parseShort(); + os22.usDefaultChar = p.parseUShort(); + os22.usBreakChar = p.parseUShort(); + os22.usMaxContent = p.parseUShort(); + } + return os22; + } + var os2 = { parse: parseOS2Table }; + function parsePostTable(data, start) { + var post2 = {}; + var p = new parse.Parser(data, start); + post2.version = p.parseVersion(); + post2.italicAngle = p.parseFixed(); + post2.underlinePosition = p.parseShort(); + post2.underlineThickness = p.parseShort(); + post2.isFixedPitch = p.parseULong(); + post2.minMemType42 = p.parseULong(); + post2.maxMemType42 = p.parseULong(); + post2.minMemType1 = p.parseULong(); + post2.maxMemType1 = p.parseULong(); + post2.names = []; + switch (post2.version) { + case 1: + break; + case 2: + post2.numberOfGlyphs = p.parseUShort(); + post2.glyphNameIndex = new Array(post2.numberOfGlyphs); + for (var i2 = 0; i2 < post2.numberOfGlyphs; i2++) { + post2.glyphNameIndex[i2] = p.parseUShort(); + } + break; + case 2.5: + post2.numberOfGlyphs = p.parseUShort(); + post2.offset = new Array(post2.numberOfGlyphs); + for (var i$1 = 0; i$1 < post2.numberOfGlyphs; i$1++) { + post2.offset[i$1] = p.parseChar(); + } + break; + } + return post2; + } + var post = { parse: parsePostTable }; + var decode = {}; + decode.UTF8 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes; + for (var j = 0; j < numChars; j++, offset += 1) { + codePoints[j] = data.getUint8(offset); + } + return String.fromCharCode.apply(null, codePoints); + }; + decode.UTF16 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes / 2; + for (var j = 0; j < numChars; j++, offset += 2) { + codePoints[j] = data.getUint16(offset); + } + return String.fromCharCode.apply(null, codePoints); + }; + var eightBitMacEncodings = { + "x-mac-croatian": ( + // Python: 'mac_croatian' + "\xC4\xC5\xC7\xC9\xD1\xD6\xDC\xE1\xE0\xE2\xE4\xE3\xE5\xE7\xE9\xE8\xEA\xEB\xED\xEC\xEE\xEF\xF1\xF3\xF2\xF4\xF6\xF5\xFA\xF9\xFB\xFC\u2020\xB0\xA2\xA3\xA7\u2022\xB6\xDF\xAE\u0160\u2122\xB4\xA8\u2260\u017D\xD8\u221E\xB1\u2264\u2265\u2206\xB5\u2202\u2211\u220F\u0161\u222B\xAA\xBA\u03A9\u017E\xF8\xBF\xA1\xAC\u221A\u0192\u2248\u0106\xAB\u010C\u2026\xA0\xC0\xC3\xD5\u0152\u0153\u0110\u2014\u201C\u201D\u2018\u2019\xF7\u25CA\uF8FF\xA9\u2044\u20AC\u2039\u203A\xC6\xBB\u2013\xB7\u201A\u201E\u2030\xC2\u0107\xC1\u010D\xC8\xCD\xCE\xCF\xCC\xD3\xD4\u0111\xD2\xDA\xDB\xD9\u0131\u02C6\u02DC\xAF\u03C0\xCB\u02DA\xB8\xCA\xE6\u02C7" + ), + "x-mac-cyrillic": ( + // Python: 'mac_cyrillic' + "\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041A\u041B\u041C\u041D\u041E\u041F\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042A\u042B\u042C\u042D\u042E\u042F\u2020\xB0\u0490\xA3\xA7\u2022\xB6\u0406\xAE\xA9\u2122\u0402\u0452\u2260\u0403\u0453\u221E\xB1\u2264\u2265\u0456\xB5\u0491\u0408\u0404\u0454\u0407\u0457\u0409\u0459\u040A\u045A\u0458\u0405\xAC\u221A\u0192\u2248\u2206\xAB\xBB\u2026\xA0\u040B\u045B\u040C\u045C\u0455\u2013\u2014\u201C\u201D\u2018\u2019\xF7\u201E\u040E\u045E\u040F\u045F\u2116\u0401\u0451\u044F\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043A\u043B\u043C\u043D\u043E\u043F\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044A\u044B\u044C\u044D\u044E" + ), + "x-mac-gaelic": ( + // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT + "\xC4\xC5\xC7\xC9\xD1\xD6\xDC\xE1\xE0\xE2\xE4\xE3\xE5\xE7\xE9\xE8\xEA\xEB\xED\xEC\xEE\xEF\xF1\xF3\xF2\xF4\xF6\xF5\xFA\xF9\xFB\xFC\u2020\xB0\xA2\xA3\xA7\u2022\xB6\xDF\xAE\xA9\u2122\xB4\xA8\u2260\xC6\xD8\u1E02\xB1\u2264\u2265\u1E03\u010A\u010B\u1E0A\u1E0B\u1E1E\u1E1F\u0120\u0121\u1E40\xE6\xF8\u1E41\u1E56\u1E57\u027C\u0192\u017F\u1E60\xAB\xBB\u2026\xA0\xC0\xC3\xD5\u0152\u0153\u2013\u2014\u201C\u201D\u2018\u2019\u1E61\u1E9B\xFF\u0178\u1E6A\u20AC\u2039\u203A\u0176\u0177\u1E6B\xB7\u1EF2\u1EF3\u204A\xC2\xCA\xC1\xCB\xC8\xCD\xCE\xCF\xCC\xD3\xD4\u2663\xD2\xDA\xDB\xD9\u0131\xDD\xFD\u0174\u0175\u1E84\u1E85\u1E80\u1E81\u1E82\u1E83" + ), + "x-mac-greek": ( + // Python: 'mac_greek' + "\xC4\xB9\xB2\xC9\xB3\xD6\xDC\u0385\xE0\xE2\xE4\u0384\xA8\xE7\xE9\xE8\xEA\xEB\xA3\u2122\xEE\xEF\u2022\xBD\u2030\xF4\xF6\xA6\u20AC\xF9\xFB\xFC\u2020\u0393\u0394\u0398\u039B\u039E\u03A0\xDF\xAE\xA9\u03A3\u03AA\xA7\u2260\xB0\xB7\u0391\xB1\u2264\u2265\xA5\u0392\u0395\u0396\u0397\u0399\u039A\u039C\u03A6\u03AB\u03A8\u03A9\u03AC\u039D\xAC\u039F\u03A1\u2248\u03A4\xAB\xBB\u2026\xA0\u03A5\u03A7\u0386\u0388\u0153\u2013\u2015\u201C\u201D\u2018\u2019\xF7\u0389\u038A\u038C\u038E\u03AD\u03AE\u03AF\u03CC\u038F\u03CD\u03B1\u03B2\u03C8\u03B4\u03B5\u03C6\u03B3\u03B7\u03B9\u03BE\u03BA\u03BB\u03BC\u03BD\u03BF\u03C0\u03CE\u03C1\u03C3\u03C4\u03B8\u03C9\u03C2\u03C7\u03C5\u03B6\u03CA\u03CB\u0390\u03B0\xAD" + ), + "x-mac-icelandic": ( + // Python: 'mac_iceland' + "\xC4\xC5\xC7\xC9\xD1\xD6\xDC\xE1\xE0\xE2\xE4\xE3\xE5\xE7\xE9\xE8\xEA\xEB\xED\xEC\xEE\xEF\xF1\xF3\xF2\xF4\xF6\xF5\xFA\xF9\xFB\xFC\xDD\xB0\xA2\xA3\xA7\u2022\xB6\xDF\xAE\xA9\u2122\xB4\xA8\u2260\xC6\xD8\u221E\xB1\u2264\u2265\xA5\xB5\u2202\u2211\u220F\u03C0\u222B\xAA\xBA\u03A9\xE6\xF8\xBF\xA1\xAC\u221A\u0192\u2248\u2206\xAB\xBB\u2026\xA0\xC0\xC3\xD5\u0152\u0153\u2013\u2014\u201C\u201D\u2018\u2019\xF7\u25CA\xFF\u0178\u2044\u20AC\xD0\xF0\xDE\xFE\xFD\xB7\u201A\u201E\u2030\xC2\xCA\xC1\xCB\xC8\xCD\xCE\xCF\xCC\xD3\xD4\uF8FF\xD2\xDA\xDB\xD9\u0131\u02C6\u02DC\xAF\u02D8\u02D9\u02DA\xB8\u02DD\u02DB\u02C7" + ), + "x-mac-inuit": ( + // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT + "\u1403\u1404\u1405\u1406\u140A\u140B\u1431\u1432\u1433\u1434\u1438\u1439\u1449\u144E\u144F\u1450\u1451\u1455\u1456\u1466\u146D\u146E\u146F\u1470\u1472\u1473\u1483\u148B\u148C\u148D\u148E\u1490\u1491\xB0\u14A1\u14A5\u14A6\u2022\xB6\u14A7\xAE\xA9\u2122\u14A8\u14AA\u14AB\u14BB\u14C2\u14C3\u14C4\u14C5\u14C7\u14C8\u14D0\u14EF\u14F0\u14F1\u14F2\u14F4\u14F5\u1505\u14D5\u14D6\u14D7\u14D8\u14DA\u14DB\u14EA\u1528\u1529\u152A\u152B\u152D\u2026\xA0\u152E\u153E\u1555\u1556\u1557\u2013\u2014\u201C\u201D\u2018\u2019\u1558\u1559\u155A\u155D\u1546\u1547\u1548\u1549\u154B\u154C\u1550\u157F\u1580\u1581\u1582\u1583\u1584\u1585\u158F\u1590\u1591\u1592\u1593\u1594\u1595\u1671\u1672\u1673\u1674\u1675\u1676\u1596\u15A0\u15A1\u15A2\u15A3\u15A4\u15A5\u15A6\u157C\u0141\u0142" + ), + "x-mac-ce": ( + // Python: 'mac_latin2' + "\xC4\u0100\u0101\xC9\u0104\xD6\xDC\xE1\u0105\u010C\xE4\u010D\u0106\u0107\xE9\u0179\u017A\u010E\xED\u010F\u0112\u0113\u0116\xF3\u0117\xF4\xF6\xF5\xFA\u011A\u011B\xFC\u2020\xB0\u0118\xA3\xA7\u2022\xB6\xDF\xAE\xA9\u2122\u0119\xA8\u2260\u0123\u012E\u012F\u012A\u2264\u2265\u012B\u0136\u2202\u2211\u0142\u013B\u013C\u013D\u013E\u0139\u013A\u0145\u0146\u0143\xAC\u221A\u0144\u0147\u2206\xAB\xBB\u2026\xA0\u0148\u0150\xD5\u0151\u014C\u2013\u2014\u201C\u201D\u2018\u2019\xF7\u25CA\u014D\u0154\u0155\u0158\u2039\u203A\u0159\u0156\u0157\u0160\u201A\u201E\u0161\u015A\u015B\xC1\u0164\u0165\xCD\u017D\u017E\u016A\xD3\xD4\u016B\u016E\xDA\u016F\u0170\u0171\u0172\u0173\xDD\xFD\u0137\u017B\u0141\u017C\u0122\u02C7" + ), + macintosh: ( + // Python: 'mac_roman' + "\xC4\xC5\xC7\xC9\xD1\xD6\xDC\xE1\xE0\xE2\xE4\xE3\xE5\xE7\xE9\xE8\xEA\xEB\xED\xEC\xEE\xEF\xF1\xF3\xF2\xF4\xF6\xF5\xFA\xF9\xFB\xFC\u2020\xB0\xA2\xA3\xA7\u2022\xB6\xDF\xAE\xA9\u2122\xB4\xA8\u2260\xC6\xD8\u221E\xB1\u2264\u2265\xA5\xB5\u2202\u2211\u220F\u03C0\u222B\xAA\xBA\u03A9\xE6\xF8\xBF\xA1\xAC\u221A\u0192\u2248\u2206\xAB\xBB\u2026\xA0\xC0\xC3\xD5\u0152\u0153\u2013\u2014\u201C\u201D\u2018\u2019\xF7\u25CA\xFF\u0178\u2044\u20AC\u2039\u203A\uFB01\uFB02\u2021\xB7\u201A\u201E\u2030\xC2\xCA\xC1\xCB\xC8\xCD\xCE\xCF\xCC\xD3\xD4\uF8FF\xD2\xDA\xDB\xD9\u0131\u02C6\u02DC\xAF\u02D8\u02D9\u02DA\xB8\u02DD\u02DB\u02C7" + ), + "x-mac-romanian": ( + // Python: 'mac_romanian' + "\xC4\xC5\xC7\xC9\xD1\xD6\xDC\xE1\xE0\xE2\xE4\xE3\xE5\xE7\xE9\xE8\xEA\xEB\xED\xEC\xEE\xEF\xF1\xF3\xF2\xF4\xF6\xF5\xFA\xF9\xFB\xFC\u2020\xB0\xA2\xA3\xA7\u2022\xB6\xDF\xAE\xA9\u2122\xB4\xA8\u2260\u0102\u0218\u221E\xB1\u2264\u2265\xA5\xB5\u2202\u2211\u220F\u03C0\u222B\xAA\xBA\u03A9\u0103\u0219\xBF\xA1\xAC\u221A\u0192\u2248\u2206\xAB\xBB\u2026\xA0\xC0\xC3\xD5\u0152\u0153\u2013\u2014\u201C\u201D\u2018\u2019\xF7\u25CA\xFF\u0178\u2044\u20AC\u2039\u203A\u021A\u021B\u2021\xB7\u201A\u201E\u2030\xC2\xCA\xC1\xCB\xC8\xCD\xCE\xCF\xCC\xD3\xD4\uF8FF\xD2\xDA\xDB\xD9\u0131\u02C6\u02DC\xAF\u02D8\u02D9\u02DA\xB8\u02DD\u02DB\u02C7" + ), + "x-mac-turkish": ( + // Python: 'mac_turkish' + "\xC4\xC5\xC7\xC9\xD1\xD6\xDC\xE1\xE0\xE2\xE4\xE3\xE5\xE7\xE9\xE8\xEA\xEB\xED\xEC\xEE\xEF\xF1\xF3\xF2\xF4\xF6\xF5\xFA\xF9\xFB\xFC\u2020\xB0\xA2\xA3\xA7\u2022\xB6\xDF\xAE\xA9\u2122\xB4\xA8\u2260\xC6\xD8\u221E\xB1\u2264\u2265\xA5\xB5\u2202\u2211\u220F\u03C0\u222B\xAA\xBA\u03A9\xE6\xF8\xBF\xA1\xAC\u221A\u0192\u2248\u2206\xAB\xBB\u2026\xA0\xC0\xC3\xD5\u0152\u0153\u2013\u2014\u201C\u201D\u2018\u2019\xF7\u25CA\xFF\u0178\u011E\u011F\u0130\u0131\u015E\u015F\u2021\xB7\u201A\u201E\u2030\xC2\xCA\xC1\xCB\xC8\xCD\xCE\xCF\xCC\xD3\xD4\uF8FF\xD2\xDA\xDB\xD9\uF8A0\u02C6\u02DC\xAF\u02D8\u02D9\u02DA\xB8\u02DD\u02DB\u02C7" + ) + }; + decode.MACSTRING = function(dataView, offset, dataLength, encoding) { + var table = eightBitMacEncodings[encoding]; + if (table === void 0) { + return void 0; + } + var result = ""; + for (var i2 = 0; i2 < dataLength; i2++) { + var c2 = dataView.getUint8(offset + i2); + if (c2 <= 127) { + result += String.fromCharCode(c2); + } else { + result += table[c2 & 127]; + } + } + return result; + }; + function parseMetaTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, "Unsupported META table version."); + p.parseULong(); + p.parseULong(); + var numDataMaps = p.parseULong(); + var tags = {}; + for (var i2 = 0; i2 < numDataMaps; i2++) { + var tag = p.parseTag(); + var dataOffset = p.parseULong(); + var dataLength = p.parseULong(); + var text2 = decode.UTF8(data, start + dataOffset, dataLength); + tags[tag] = text2; + } + return tags; + } + var meta = { parse: parseMetaTable }; + function parseOpenTypeTableEntries(data, numTables) { + var tableEntries = []; + var p = 12; + for (var i2 = 0; i2 < numTables; i2 += 1) { + var tag = parse.getTag(data, p); + var checksum = parse.getULong(data, p + 4); + var offset = parse.getULong(data, p + 8); + var length = parse.getULong(data, p + 12); + tableEntries.push({ + tag, + checksum, + offset, + length, + compression: false + }); + p += 16; + } + return tableEntries; + } + function parseWOFFTableEntries(data, numTables) { + var tableEntries = []; + var p = 44; + for (var i2 = 0; i2 < numTables; i2 += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 4); + var compLength = parse.getULong(data, p + 8); + var origLength = parse.getULong(data, p + 12); + var compression = void 0; + if (compLength < origLength) { + compression = "WOFF"; + } else { + compression = false; + } + tableEntries.push({ + tag, + offset, + compression, + compressedLength: compLength, + length: origLength + }); + p += 20; + } + return tableEntries; + } + function uncompressTable(data, tableEntry) { + if (tableEntry.compression === "WOFF") { + var inBuffer = new Uint8Array( + data.buffer, + tableEntry.offset + 2, + tableEntry.compressedLength - 2 + ); + var outBuffer = new Uint8Array(tableEntry.length); + inflateSync(inBuffer, outBuffer); + if (outBuffer.byteLength !== tableEntry.length) { + throw new Error( + "Decompression error: " + tableEntry.tag + " decompressed length doesn't match recorded length" + ); + } + var view = new DataView(outBuffer.buffer, 0); + return { data: view, offset: 0 }; + } else { + return { data, offset: tableEntry.offset }; + } + } + function parseBuffer(buffer, opt) { + opt = opt === void 0 || opt === null ? {} : opt; + var indexToLocFormat; + var font = new Font({ empty: true }); + var data = new DataView(buffer, 0); + var numTables; + var tableEntries = []; + var signature = parse.getTag(data, 0); + if (signature === String.fromCharCode(0, 1, 0, 0) || signature === "true" || signature === "typ1") { + font.outlinesFormat = "truetype"; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === "OTTO") { + font.outlinesFormat = "cff"; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === "wOFF") { + var flavor = parse.getTag(data, 4); + if (flavor === String.fromCharCode(0, 1, 0, 0)) { + font.outlinesFormat = "truetype"; + } else if (flavor === "OTTO") { + font.outlinesFormat = "cff"; + } else { + throw new Error("Unsupported OpenType flavor " + signature); + } + numTables = parse.getUShort(data, 12); + tableEntries = parseWOFFTableEntries(data, numTables); + } else { + throw new Error("Unsupported OpenType signature " + signature); + } + var cffTableEntry; + var fvarTableEntry; + var glyfTableEntry; + var gdefTableEntry; + var gposTableEntry; + var gsubTableEntry; + var hmtxTableEntry; + var kernTableEntry; + var locaTableEntry; + var metaTableEntry; + var p; + for (var i2 = 0; i2 < numTables; i2 += 1) { + var tableEntry = tableEntries[i2]; + var table = void 0; + switch (tableEntry.tag) { + case "cmap": + table = uncompressTable(data, tableEntry); + font.tables.cmap = cmap.parse(table.data, table.offset); + font.encoding = new CmapEncoding(font.tables.cmap); + break; + case "cvt ": + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.cvt = p.parseShortList(tableEntry.length / 2); + break; + case "fvar": + fvarTableEntry = tableEntry; + break; + case "fpgm": + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.fpgm = p.parseByteList(tableEntry.length); + break; + case "head": + table = uncompressTable(data, tableEntry); + font.tables.head = head.parse(table.data, table.offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case "hhea": + table = uncompressTable(data, tableEntry); + font.tables.hhea = hhea.parse(table.data, table.offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case "hmtx": + hmtxTableEntry = tableEntry; + break; + case "ltag": + table = uncompressTable(data, tableEntry); + ltagTable = ltag.parse(table.data, table.offset); + break; + case "maxp": + table = uncompressTable(data, tableEntry); + font.tables.maxp = maxp.parse(table.data, table.offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case "OS/2": + table = uncompressTable(data, tableEntry); + font.tables.os2 = os2.parse(table.data, table.offset); + break; + case "post": + table = uncompressTable(data, tableEntry); + font.tables.post = post.parse(table.data, table.offset); + break; + case "prep": + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.prep = p.parseByteList(tableEntry.length); + break; + case "glyf": + glyfTableEntry = tableEntry; + break; + case "loca": + locaTableEntry = tableEntry; + break; + case "CFF ": + cffTableEntry = tableEntry; + break; + case "kern": + kernTableEntry = tableEntry; + break; + case "GDEF": + gdefTableEntry = tableEntry; + break; + case "GPOS": + gposTableEntry = tableEntry; + break; + case "GSUB": + gsubTableEntry = tableEntry; + break; + case "meta": + metaTableEntry = tableEntry; + break; + } + } + if (glyfTableEntry && locaTableEntry) { + var shortVersion = indexToLocFormat === 0; + var locaTable = uncompressTable(data, locaTableEntry); + var locaOffsets = loca.parse( + locaTable.data, + locaTable.offset, + font.numGlyphs, + shortVersion + ); + var glyfTable = uncompressTable(data, glyfTableEntry); + font.glyphs = glyf.parse( + glyfTable.data, + glyfTable.offset, + locaOffsets, + font, + opt + ); + } else if (cffTableEntry) { + var cffTable = uncompressTable(data, cffTableEntry); + cff.parse(cffTable.data, cffTable.offset, font, opt); + } else { + throw new Error("Font doesn't contain TrueType or CFF outlines."); + } + var hmtxTable = uncompressTable(data, hmtxTableEntry); + hmtx.parse( + font, + hmtxTable.data, + hmtxTable.offset, + font.numberOfHMetrics, + font.numGlyphs, + font.glyphs, + opt + ); + addGlyphNames(font, opt); + if (kernTableEntry) { + var kernTable = uncompressTable(data, kernTableEntry); + font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); + } else { + font.kerningPairs = {}; + } + if (gdefTableEntry) { + var gdefTable = uncompressTable(data, gdefTableEntry); + font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset); + } + if (gposTableEntry) { + var gposTable = uncompressTable(data, gposTableEntry); + font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); + font.position.init(); + } + if (gsubTableEntry) { + var gsubTable = uncompressTable(data, gsubTableEntry); + font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); + } + if (fvarTableEntry) { + var fvarTable = uncompressTable(data, fvarTableEntry); + font.tables.fvar = fvar.parse( + fvarTable.data, + fvarTable.offset, + font.names + ); + } + if (metaTableEntry) { + var metaTable = uncompressTable(data, metaTableEntry); + font.tables.meta = meta.parse(metaTable.data, metaTable.offset); + font.metas = font.tables.meta; + } + return font; + } + function load() { + } + function loadSync() { + } + var opentype = /* @__PURE__ */ Object.freeze({ + __proto__: null, + Font, + Glyph, + Path, + _parse: parse, + parse: parseBuffer, + load, + loadSync + }); + exports2.Font = Font; + exports2.Glyph = Glyph; + exports2.Path = Path; + exports2._parse = parse; + exports2.default = opentype; + exports2.load = load; + exports2.loadSync = loadSync; + exports2.parse = parseBuffer; + Object.defineProperty(exports2, "__esModule", { value: true }); + })); + } +}); + +// node_modules/.pnpm/satori@0.26.0/node_modules/satori/dist/index.js +var dist_exports = {}; +__export(dist_exports, { + default: () => lI, + init: () => WI +}); +function Xn(A) { + function e(n, i, o) { + let a = n[i]; + n[i] = function() { + for (var u2 = arguments.length, l2 = new Array(u2), I = 0; I < u2; I++) l2[I] = arguments[I]; + return o.call(this, a, ...l2); + }; + } + for (let n of ["setPosition", "setMargin", "setFlexBasis", "setWidth", "setHeight", "setMinWidth", "setMinHeight", "setMaxWidth", "setMaxHeight", "setPadding", "setGap"]) { + let i = { [Oe.Point]: A.Node.prototype[n], [Oe.Percent]: A.Node.prototype[`${n}Percent`], [Oe.Auto]: A.Node.prototype[`${n}Auto`] }; + e(A.Node.prototype, n, function(o) { + for (var a = arguments.length, u2 = new Array(a > 1 ? a - 1 : 0), l2 = 1; l2 < a; l2++) u2[l2 - 1] = arguments[l2]; + let I = u2.pop(), E, C; + if (I === "auto") E = Oe.Auto, C = void 0; + else if (typeof I == "object") E = I.unit, C = I.valueOf(); + else if (E = typeof I == "string" && I.endsWith("%") ? Oe.Percent : Oe.Point, C = parseFloat(I), I !== void 0 && !Number.isNaN(I) && Number.isNaN(C)) throw new Error(`Invalid value ${I} for ${n}`); + if (!i[E]) throw new Error(`Failed to execute "${n}": Unsupported unit '${I}'`); + return C !== void 0 ? i[E].call(this, ...u2, C) : i[E].call(this, ...u2); + }); + } + function t(n) { + return A.MeasureCallback.implement({ measure: function() { + let { width: i, height: o } = n(...arguments); + return { width: i ?? NaN, height: o ?? NaN }; + } }); + } + e(A.Node.prototype, "setMeasureFunc", function(n, i) { + return i ? n.call(this, t(i)) : this.unsetMeasureFunc(); + }); + function r(n) { + return A.DirtiedCallback.implement({ dirtied: n }); + } + return e(A.Node.prototype, "setDirtiedFunc", function(n, i) { + n.call(this, r(i)); + }), e(A.Config.prototype, "free", function() { + A.Config.destroy(this); + }), e(A.Node, "create", (n, i) => i ? A.Node.createWithConfig(i) : A.Node.createDefault()), e(A.Node.prototype, "free", function() { + A.Node.destroy(this); + }), e(A.Node.prototype, "freeRecursive", function() { + for (let n = 0, i = this.getChildCount(); n < i; ++n) this.getChild(0).freeRecursive(); + this.free(); + }), e(A.Node.prototype, "calculateLayout", function(n) { + let i = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : NaN, o = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : NaN, a = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : qt.LTR; + return n.call(this, i, o, a); + }), { Config: A.Config, Node: A.Node, ...Rs }; +} +function FI(A) { + A = A || {}; + var e; + e || (e = typeof A < "u" ? A : {}); + var t, r; + e.ready = new Promise(function(f, s) { + t = f, r = s; + }); + var n = Object.assign({}, e), i = ""; + typeof document < "u" && document.currentScript && (i = document.currentScript.src), Ms && (i = Ms), i.indexOf("blob:") !== 0 ? i = i.substr(0, i.replace(/[?#].*/, "").lastIndexOf("/") + 1) : i = ""; + var o = e.print || console.log.bind(console), a = e.printErr || console.warn.bind(console); + Object.assign(e, n), n = null; + var u2; + e.wasmBinary && (u2 = e.wasmBinary); + var l2 = e.noExitRuntime || true; + typeof WebAssembly != "object" && rA("no native wasm support detected"); + var I, E = false; + function C(f, s, g2) { + g2 = s + g2; + for (var c2 = ""; !(s >= g2); ) { + var B = f[s++]; + if (!B) break; + if (B & 128) { + var Q = f[s++] & 63; + if ((B & 224) == 192) c2 += String.fromCharCode((B & 31) << 6 | Q); + else { + var h2 = f[s++] & 63; + B = (B & 240) == 224 ? (B & 15) << 12 | Q << 6 | h2 : (B & 7) << 18 | Q << 12 | h2 << 6 | f[s++] & 63, 65536 > B ? c2 += String.fromCharCode(B) : (B -= 65536, c2 += String.fromCharCode(55296 | B >> 10, 56320 | B & 1023)); + } + } else c2 += String.fromCharCode(B); + } + return c2; + } + var d2, p, y, k, x2, F, b, v2, M; + function L() { + var f = I.buffer; + d2 = f, e.HEAP8 = p = new Int8Array(f), e.HEAP16 = k = new Int16Array(f), e.HEAP32 = F = new Int32Array(f), e.HEAPU8 = y = new Uint8Array(f), e.HEAPU16 = x2 = new Uint16Array(f), e.HEAPU32 = b = new Uint32Array(f), e.HEAPF32 = v2 = new Float32Array(f), e.HEAPF64 = M = new Float64Array(f); + } + var O, J = [], j = [], CA = []; + function MA() { + var f = e.preRun.shift(); + J.unshift(f); + } + var dA = 0, sA = null, vA = null; + function rA(f) { + throw e.onAbort && e.onAbort(f), f = "Aborted(" + f + ")", a(f), E = true, f = new WebAssembly.RuntimeError(f + ". Build with -sASSERTIONS for more info."), r(f), f; + } + function iA(f) { + return f.startsWith("data:application/octet-stream;base64,"); + } + var wA = ""; + if (!iA(wA)) { + var aA = wA; + wA = e.locateFile ? e.locateFile(aA, i) : i + aA; + } + function FA() { + var f = wA; + try { + if (f == wA && u2) return new Uint8Array(u2); + if (iA(f)) try { + var s = bt(f.slice(37)), g2 = new Uint8Array(s.length); + for (f = 0; f < s.length; ++f) g2[f] = s.charCodeAt(f); + var c2 = g2; + } catch { + throw Error("Converting base64 string to bytes failed."); + } + else c2 = void 0; + var B = c2; + if (B) return B; + throw "both async and sync fetching of the wasm failed"; + } catch (Q) { + rA(Q); + } + } + function kA() { + return u2 || typeof fetch != "function" ? Promise.resolve().then(function() { + return FA(); + }) : fetch(wA, { credentials: "same-origin" }).then(function(f) { + if (!f.ok) throw "failed to load wasm binary file at '" + wA + "'"; + return f.arrayBuffer(); + }).catch(function() { + return FA(); + }); + } + function te(f) { + for (; 0 < f.length; ) f.shift()(e); + } + function TA(f) { + if (f === void 0) return "_unknown"; + f = f.replace(/[^a-zA-Z0-9_]/g, "$"); + var s = f.charCodeAt(0); + return 48 <= s && 57 >= s ? "_" + f : f; + } + function xA(f, s) { + return f = TA(f), function() { + return s.apply(this, arguments); + }; + } + var fA = [{}, { value: void 0 }, { value: null }, { value: true }, { value: false }], q = []; + function oA(f) { + var s = Error, g2 = xA(f, function(c2) { + this.name = f, this.message = c2, c2 = Error(c2).stack, c2 !== void 0 && (this.stack = this.toString() + ` +` + c2.replace(/^Error(:[^\n]*)?\n/, "")); + }); + return g2.prototype = Object.create(s.prototype), g2.prototype.constructor = g2, g2.prototype.toString = function() { + return this.message === void 0 ? this.name : this.name + ": " + this.message; + }, g2; + } + var BA = void 0; + function W(f) { + throw new BA(f); + } + var Y = (f) => (f || W("Cannot use deleted val. handle = " + f), fA[f].value), OA = (f) => { + switch (f) { + case void 0: + return 1; + case null: + return 2; + case true: + return 3; + case false: + return 4; + default: + var s = q.length ? q.pop() : fA.length; + return fA[s] = { ga: 1, value: f }, s; + } + }, PA = void 0, WA = void 0; + function uA(f) { + for (var s = ""; y[f]; ) s += WA[y[f++]]; + return s; + } + var hA = []; + function Ie() { + for (; hA.length; ) { + var f = hA.pop(); + f.M.$ = false, f.delete(); + } + } + var le = void 0, LA = {}; + function re(f, s) { + for (s === void 0 && W("ptr should not be undefined"); f.R; ) s = f.ba(s), f = f.R; + return s; + } + var ce = {}; + function ye(f) { + f = Wr(f); + var s = uA(f); + return ve(f), s; + } + function Pe(f, s) { + var g2 = ce[f]; + return g2 === void 0 && W(s + " has unknown type " + ye(f)), g2; + } + function Ye() { + } + var Ge = false; + function Ue(f) { + --f.count.value, f.count.value === 0 && (f.T ? f.U.W(f.T) : f.P.N.W(f.O)); + } + function we(f, s, g2) { + return s === g2 ? f : g2.R === void 0 ? null : (f = we(f, s, g2.R), f === null ? null : g2.na(f)); + } + var fe = {}; + function se(f, s) { + return s = re(f, s), LA[s]; + } + var qe = void 0; + function zA(f) { + throw new qe(f); + } + function ne(f, s) { + return s.P && s.O || zA("makeClassHandle requires ptr and ptrType"), !!s.U != !!s.T && zA("Both smartPtrType and smartPtr must be specified"), s.count = { value: 1 }, ie(Object.create(f, { M: { value: s } })); + } + function ie(f) { + return typeof FinalizationRegistry > "u" ? (ie = (s) => s, f) : (Ge = new FinalizationRegistry((s) => { + Ue(s.M); + }), ie = (s) => { + var g2 = s.M; + return g2.T && Ge.register(s, { M: g2 }, s), s; + }, Ye = (s) => { + Ge.unregister(s); + }, ie(f)); + } + var Be = {}; + function De(f) { + for (; f.length; ) { + var s = f.pop(); + f.pop()(s); + } + } + function KA(f) { + return this.fromWireType(F[f >> 2]); + } + var XA = {}, _A = {}; + function oe(f, s, g2) { + function c2(m2) { + m2 = g2(m2), m2.length !== f.length && zA("Mismatched type converter count"); + for (var w2 = 0; w2 < f.length; ++w2) Z(f[w2], m2[w2]); + } + f.forEach(function(m2) { + _A[m2] = s; + }); + var B = Array(s.length), Q = [], h2 = 0; + s.forEach((m2, w2) => { + ce.hasOwnProperty(m2) ? B[w2] = ce[m2] : (Q.push(m2), XA.hasOwnProperty(m2) || (XA[m2] = []), XA[m2].push(() => { + B[w2] = ce[m2], ++h2, h2 === Q.length && c2(B); + })); + }), Q.length === 0 && c2(B); + } + function V(f) { + switch (f) { + case 1: + return 0; + case 2: + return 1; + case 4: + return 2; + case 8: + return 3; + default: + throw new TypeError("Unknown type size: " + f); + } + } + function Z(f, s, g2 = {}) { + if (!("argPackAdvance" in s)) throw new TypeError("registerType registeredInstance requires argPackAdvance"); + var c2 = s.name; + if (f || W('type "' + c2 + '" must have a positive integer typeid pointer'), ce.hasOwnProperty(f)) { + if (g2.ua) return; + W("Cannot register type '" + c2 + "' twice"); + } + ce[f] = s, delete _A[f], XA.hasOwnProperty(f) && (s = XA[f], delete XA[f], s.forEach((B) => B())); + } + function nA(f) { + W(f.M.P.N.name + " instance already deleted"); + } + function $() { + } + function IA(f, s, g2) { + if (f[s].S === void 0) { + var c2 = f[s]; + f[s] = function() { + return f[s].S.hasOwnProperty(arguments.length) || W("Function '" + g2 + "' called with an invalid number of arguments (" + arguments.length + ") - expects one of (" + f[s].S + ")!"), f[s].S[arguments.length].apply(this, arguments); + }, f[s].S = [], f[s].S[c2.Z] = c2; + } + } + function lA(f, s) { + e.hasOwnProperty(f) ? (W("Cannot register public name '" + f + "' twice"), IA(e, f, f), e.hasOwnProperty(void 0) && W("Cannot register multiple overloads of a function with the same number of arguments (undefined)!"), e[f].S[void 0] = s) : e[f] = s; + } + function DA(f, s, g2, c2, B, Q, h2, m2) { + this.name = f, this.constructor = s, this.X = g2, this.W = c2, this.R = B, this.pa = Q, this.ba = h2, this.na = m2, this.ja = []; + } + function cA(f, s, g2) { + for (; s !== g2; ) s.ba || W("Expected null or instance of " + g2.name + ", got an instance of " + s.name), f = s.ba(f), s = s.R; + return f; + } + function gA(f, s) { + return s === null ? (this.ea && W("null is not a valid " + this.name), 0) : (s.M || W('Cannot pass "' + ZA(s) + '" as a ' + this.name), s.M.O || W("Cannot pass deleted object as a pointer of type " + this.name), cA(s.M.O, s.M.P.N, this.N)); + } + function Ee(f, s) { + if (s === null) { + if (this.ea && W("null is not a valid " + this.name), this.da) { + var g2 = this.fa(); + return f !== null && f.push(this.W, g2), g2; + } + return 0; + } + if (s.M || W('Cannot pass "' + ZA(s) + '" as a ' + this.name), s.M.O || W("Cannot pass deleted object as a pointer of type " + this.name), !this.ca && s.M.P.ca && W("Cannot convert argument of type " + (s.M.U ? s.M.U.name : s.M.P.name) + " to parameter type " + this.name), g2 = cA(s.M.O, s.M.P.N, this.N), this.da) switch (s.M.T === void 0 && W("Passing raw pointer to smart pointer is illegal"), this.Ba) { + case 0: + s.M.U === this ? g2 = s.M.T : W("Cannot convert argument of type " + (s.M.U ? s.M.U.name : s.M.P.name) + " to parameter type " + this.name); + break; + case 1: + g2 = s.M.T; + break; + case 2: + if (s.M.U === this) g2 = s.M.T; + else { + var c2 = s.clone(); + g2 = this.xa(g2, OA(function() { + c2.delete(); + })), f !== null && f.push(this.W, g2); + } + break; + default: + W("Unsupporting sharing policy"); + } + return g2; + } + function eA(f, s) { + return s === null ? (this.ea && W("null is not a valid " + this.name), 0) : (s.M || W('Cannot pass "' + ZA(s) + '" as a ' + this.name), s.M.O || W("Cannot pass deleted object as a pointer of type " + this.name), s.M.P.ca && W("Cannot convert argument of type " + s.M.P.name + " to parameter type " + this.name), cA(s.M.O, s.M.P.N, this.N)); + } + function JA(f, s, g2, c2) { + this.name = f, this.N = s, this.ea = g2, this.ca = c2, this.da = false, this.W = this.xa = this.fa = this.ka = this.Ba = this.wa = void 0, s.R !== void 0 ? this.toWireType = Ee : (this.toWireType = c2 ? gA : eA, this.V = null); + } + function RA(f, s) { + e.hasOwnProperty(f) || zA("Replacing nonexistant public symbol"), e[f] = s, e[f].Z = void 0; + } + function ut(f, s) { + var g2 = []; + return function() { + if (g2.length = 0, Object.assign(g2, arguments), f.includes("j")) { + var c2 = e["dynCall_" + f]; + c2 = g2 && g2.length ? c2.apply(null, [s].concat(g2)) : c2.call(null, s); + } else c2 = O.get(s).apply(null, g2); + return c2; + }; + } + function GA(f, s) { + f = uA(f); + var g2 = f.includes("j") ? ut(f, s) : O.get(s); + return typeof g2 != "function" && W("unknown function pointer with signature " + f + ": " + s), g2; + } + var YA = void 0; + function qA(f, s) { + function g2(Q) { + B[Q] || ce[Q] || (_A[Q] ? _A[Q].forEach(g2) : (c2.push(Q), B[Q] = true)); + } + var c2 = [], B = {}; + throw s.forEach(g2), new YA(f + ": " + c2.map(ye).join([", "])); + } + function Qe(f, s, g2, c2, B) { + var Q = s.length; + 2 > Q && W("argTypes array size mismatch! Must at least get return value and 'this' types!"); + var h2 = s[1] !== null && g2 !== null, m2 = false; + for (g2 = 1; g2 < s.length; ++g2) if (s[g2] !== null && s[g2].V === void 0) { + m2 = true; + break; + } + var w2 = s[0].name !== "void", D = Q - 2, S2 = Array(D), N = [], U = []; + return function() { + if (arguments.length !== D && W("function " + f + " called with " + arguments.length + " arguments, expected " + D + " args!"), U.length = 0, N.length = h2 ? 2 : 1, N[0] = B, h2) { + var X = s[1].toWireType(U, this); + N[1] = X; + } + for (var z = 0; z < D; ++z) S2[z] = s[z + 2].toWireType(U, arguments[z]), N.push(S2[z]); + if (z = c2.apply(null, N), m2) De(U); + else for (var AA = h2 ? 1 : 2; AA < s.length; AA++) { + var NA = AA === 1 ? X : S2[AA - 2]; + s[AA].V !== null && s[AA].V(NA); + } + return X = w2 ? s[0].fromWireType(z) : void 0, X; + }; + } + function pA(f, s) { + for (var g2 = [], c2 = 0; c2 < f; c2++) g2.push(b[s + 4 * c2 >> 2]); + return g2; + } + function mA(f) { + 4 < f && --fA[f].ga === 0 && (fA[f] = void 0, q.push(f)); + } + function ZA(f) { + if (f === null) return "null"; + var s = typeof f; + return s === "object" || s === "array" || s === "function" ? f.toString() : "" + f; + } + function jA(f, s) { + switch (s) { + case 2: + return function(g2) { + return this.fromWireType(v2[g2 >> 2]); + }; + case 3: + return function(g2) { + return this.fromWireType(M[g2 >> 3]); + }; + default: + throw new TypeError("Unknown float type: " + f); + } + } + function Se(f, s, g2) { + switch (s) { + case 0: + return g2 ? function(c2) { + return p[c2]; + } : function(c2) { + return y[c2]; + }; + case 1: + return g2 ? function(c2) { + return k[c2 >> 1]; + } : function(c2) { + return x2[c2 >> 1]; + }; + case 2: + return g2 ? function(c2) { + return F[c2 >> 2]; + } : function(c2) { + return b[c2 >> 2]; + }; + default: + throw new TypeError("Unknown integer type: " + f); + } + } + function be(f, s) { + for (var g2 = "", c2 = 0; !(c2 >= s / 2); ++c2) { + var B = k[f + 2 * c2 >> 1]; + if (B == 0) break; + g2 += String.fromCharCode(B); + } + return g2; + } + function $A(f, s, g2) { + if (g2 === void 0 && (g2 = 2147483647), 2 > g2) return 0; + g2 -= 2; + var c2 = s; + g2 = g2 < 2 * f.length ? g2 / 2 : f.length; + for (var B = 0; B < g2; ++B) k[s >> 1] = f.charCodeAt(B), s += 2; + return k[s >> 1] = 0, s - c2; + } + function Ce(f) { + return 2 * f.length; + } + function It(f, s) { + for (var g2 = 0, c2 = ""; !(g2 >= s / 4); ) { + var B = F[f + 4 * g2 >> 2]; + if (B == 0) break; + ++g2, 65536 <= B ? (B -= 65536, c2 += String.fromCharCode(55296 | B >> 10, 56320 | B & 1023)) : c2 += String.fromCharCode(B); + } + return c2; + } + function et(f, s, g2) { + if (g2 === void 0 && (g2 = 2147483647), 4 > g2) return 0; + var c2 = s; + g2 = c2 + g2 - 4; + for (var B = 0; B < f.length; ++B) { + var Q = f.charCodeAt(B); + if (55296 <= Q && 57343 >= Q) { + var h2 = f.charCodeAt(++B); + Q = 65536 + ((Q & 1023) << 10) | h2 & 1023; + } + if (F[s >> 2] = Q, s += 4, s + 4 > g2) break; + } + return F[s >> 2] = 0, s - c2; + } + function wt(f) { + for (var s = 0, g2 = 0; g2 < f.length; ++g2) { + var c2 = f.charCodeAt(g2); + 55296 <= c2 && 57343 >= c2 && ++g2, s += 4; + } + return s; + } + var Dt = {}; + function lt(f) { + var s = Dt[f]; + return s === void 0 ? uA(f) : s; + } + var ct = []; + function St(f) { + var s = ct.length; + return ct.push(f), s; + } + function Fn(f, s) { + for (var g2 = Array(f), c2 = 0; c2 < f; ++c2) g2[c2] = Pe(b[s + 4 * c2 >> 2], "parameter " + c2); + return g2; + } + var Pr = [], _r = [null, [], []]; + BA = e.BindingError = oA("BindingError"), e.count_emval_handles = function() { + for (var f = 0, s = 5; s < fA.length; ++s) fA[s] !== void 0 && ++f; + return f; + }, e.get_first_emval = function() { + for (var f = 5; f < fA.length; ++f) if (fA[f] !== void 0) return fA[f]; + return null; + }, PA = e.PureVirtualError = oA("PureVirtualError"); + for (var Jr = Array(256), ft = 0; 256 > ft; ++ft) Jr[ft] = String.fromCharCode(ft); + WA = Jr, e.getInheritedInstanceCount = function() { + return Object.keys(LA).length; + }, e.getLiveInheritedInstances = function() { + var f = [], s; + for (s in LA) LA.hasOwnProperty(s) && f.push(LA[s]); + return f; + }, e.flushPendingDeletes = Ie, e.setDelayFunction = function(f) { + le = f, hA.length && le && le(Ie); + }, qe = e.InternalError = oA("InternalError"), $.prototype.isAliasOf = function(f) { + if (!(this instanceof $ && f instanceof $)) return false; + var s = this.M.P.N, g2 = this.M.O, c2 = f.M.P.N; + for (f = f.M.O; s.R; ) g2 = s.ba(g2), s = s.R; + for (; c2.R; ) f = c2.ba(f), c2 = c2.R; + return s === c2 && g2 === f; + }, $.prototype.clone = function() { + if (this.M.O || nA(this), this.M.aa) return this.M.count.value += 1, this; + var f = ie, s = Object, g2 = s.create, c2 = Object.getPrototypeOf(this), B = this.M; + return f = f(g2.call(s, c2, { M: { value: { count: B.count, $: B.$, aa: B.aa, O: B.O, P: B.P, T: B.T, U: B.U } } })), f.M.count.value += 1, f.M.$ = false, f; + }, $.prototype.delete = function() { + this.M.O || nA(this), this.M.$ && !this.M.aa && W("Object already scheduled for deletion"), Ye(this), Ue(this.M), this.M.aa || (this.M.T = void 0, this.M.O = void 0); + }, $.prototype.isDeleted = function() { + return !this.M.O; + }, $.prototype.deleteLater = function() { + return this.M.O || nA(this), this.M.$ && !this.M.aa && W("Object already scheduled for deletion"), hA.push(this), hA.length === 1 && le && le(Ie), this.M.$ = true, this; + }, JA.prototype.qa = function(f) { + return this.ka && (f = this.ka(f)), f; + }, JA.prototype.ha = function(f) { + this.W && this.W(f); + }, JA.prototype.argPackAdvance = 8, JA.prototype.readValueFromPointer = KA, JA.prototype.deleteObject = function(f) { + f !== null && f.delete(); + }, JA.prototype.fromWireType = function(f) { + function s() { + return this.da ? ne(this.N.X, { P: this.wa, O: g2, U: this, T: f }) : ne(this.N.X, { P: this, O: f }); + } + var g2 = this.qa(f); + if (!g2) return this.ha(f), null; + var c2 = se(this.N, g2); + if (c2 !== void 0) return c2.M.count.value === 0 ? (c2.M.O = g2, c2.M.T = f, c2.clone()) : (c2 = c2.clone(), this.ha(f), c2); + if (c2 = this.N.pa(g2), c2 = fe[c2], !c2) return s.call(this); + c2 = this.ca ? c2.la : c2.pointerType; + var B = we(g2, this.N, c2.N); + return B === null ? s.call(this) : this.da ? ne(c2.N.X, { P: c2, O: B, U: this, T: f }) : ne(c2.N.X, { P: c2, O: B }); + }, YA = e.UnboundTypeError = oA("UnboundTypeError"); + var bt = typeof atob == "function" ? atob : function(f) { + var s = "", g2 = 0; + f = f.replace(/[^A-Za-z0-9\+\/=]/g, ""); + do { + var c2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(f.charAt(g2++)), B = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(f.charAt(g2++)), Q = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(f.charAt(g2++)), h2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(f.charAt(g2++)); + c2 = c2 << 2 | B >> 4, B = (B & 15) << 4 | Q >> 2; + var m2 = (Q & 3) << 6 | h2; + s += String.fromCharCode(c2), Q !== 64 && (s += String.fromCharCode(B)), h2 !== 64 && (s += String.fromCharCode(m2)); + } while (g2 < f.length); + return s; + }, Ln = { l: function(f, s, g2, c2) { + rA("Assertion failed: " + (f ? C(y, f) : "") + ", at: " + [s ? s ? C(y, s) : "" : "unknown filename", g2, c2 ? c2 ? C(y, c2) : "" : "unknown function"]); + }, q: function(f, s, g2) { + f = uA(f), s = Pe(s, "wrapper"), g2 = Y(g2); + var c2 = [].slice, B = s.N, Q = B.X, h2 = B.R.X, m2 = B.R.constructor; + f = xA(f, function() { + B.R.ja.forEach(function(D) { + if (this[D] === h2[D]) throw new PA("Pure virtual function " + D + " must be implemented in JavaScript"); + }.bind(this)), Object.defineProperty(this, "__parent", { value: Q }), this.__construct.apply(this, c2.call(arguments)); + }), Q.__construct = function() { + this === Q && W("Pass correct 'this' to __construct"); + var D = m2.implement.apply(void 0, [this].concat(c2.call(arguments))); + Ye(D); + var S2 = D.M; + D.notifyOnDestruction(), S2.aa = true, Object.defineProperties(this, { M: { value: S2 } }), ie(this), D = S2.O, D = re(B, D), LA.hasOwnProperty(D) ? W("Tried to register registered instance: " + D) : LA[D] = this; + }, Q.__destruct = function() { + this === Q && W("Pass correct 'this' to __destruct"), Ye(this); + var D = this.M.O; + D = re(B, D), LA.hasOwnProperty(D) ? delete LA[D] : W("Tried to unregister unregistered instance: " + D); + }, f.prototype = Object.create(Q); + for (var w2 in g2) f.prototype[w2] = g2[w2]; + return OA(f); + }, j: function(f) { + var s = Be[f]; + delete Be[f]; + var g2 = s.fa, c2 = s.W, B = s.ia, Q = B.map((h2) => h2.ta).concat(B.map((h2) => h2.za)); + oe([f], Q, (h2) => { + var m2 = {}; + return B.forEach((w2, D) => { + var S2 = h2[D], N = w2.ra, U = w2.sa, X = h2[D + B.length], z = w2.ya, AA = w2.Aa; + m2[w2.oa] = { read: (NA) => S2.fromWireType(N(U, NA)), write: (NA, Ae) => { + var ae = []; + z(AA, NA, X.toWireType(ae, Ae)), De(ae); + } }; + }), [{ name: s.name, fromWireType: function(w2) { + var D = {}, S2; + for (S2 in m2) D[S2] = m2[S2].read(w2); + return c2(w2), D; + }, toWireType: function(w2, D) { + for (var S2 in m2) if (!(S2 in D)) throw new TypeError('Missing field: "' + S2 + '"'); + var N = g2(); + for (S2 in m2) m2[S2].write(N, D[S2]); + return w2 !== null && w2.push(c2, N), N; + }, argPackAdvance: 8, readValueFromPointer: KA, V: c2 }]; + }); + }, v: function() { + }, B: function(f, s, g2, c2, B) { + var Q = V(g2); + s = uA(s), Z(f, { name: s, fromWireType: function(h2) { + return !!h2; + }, toWireType: function(h2, m2) { + return m2 ? c2 : B; + }, argPackAdvance: 8, readValueFromPointer: function(h2) { + if (g2 === 1) var m2 = p; + else if (g2 === 2) m2 = k; + else if (g2 === 4) m2 = F; + else throw new TypeError("Unknown boolean type size: " + s); + return this.fromWireType(m2[h2 >> Q]); + }, V: null }); + }, f: function(f, s, g2, c2, B, Q, h2, m2, w2, D, S2, N, U) { + S2 = uA(S2), Q = GA(B, Q), m2 && (m2 = GA(h2, m2)), D && (D = GA(w2, D)), U = GA(N, U); + var X = TA(S2); + lA(X, function() { + qA("Cannot construct " + S2 + " due to unbound types", [c2]); + }), oe([f, s, g2], c2 ? [c2] : [], function(z) { + if (z = z[0], c2) var AA = z.N, NA = AA.X; + else NA = $.prototype; + z = xA(X, function() { + if (Object.getPrototypeOf(this) !== Ae) throw new BA("Use 'new' to construct " + S2); + if (ae.Y === void 0) throw new BA(S2 + " has no accessible constructor"); + var Wt = ae.Y[arguments.length]; + if (Wt === void 0) throw new BA("Tried to invoke ctor of " + S2 + " with invalid number of parameters (" + arguments.length + ") - expected (" + Object.keys(ae.Y).toString() + ") parameters instead!"); + return Wt.apply(this, arguments); + }); + var Ae = Object.create(NA, { constructor: { value: z } }); + z.prototype = Ae; + var ae = new DA(S2, z, Ae, U, AA, Q, m2, D); + AA = new JA(S2, ae, true, false), NA = new JA(S2 + "*", ae, false, false); + var ke = new JA(S2 + " const*", ae, false, true); + return fe[f] = { pointerType: NA, la: ke }, RA(X, z), [AA, NA, ke]; + }); + }, d: function(f, s, g2, c2, B, Q, h2) { + var m2 = pA(g2, c2); + s = uA(s), Q = GA(B, Q), oe([], [f], function(w2) { + function D() { + qA("Cannot call " + S2 + " due to unbound types", m2); + } + w2 = w2[0]; + var S2 = w2.name + "." + s; + s.startsWith("@@") && (s = Symbol[s.substring(2)]); + var N = w2.N.constructor; + return N[s] === void 0 ? (D.Z = g2 - 1, N[s] = D) : (IA(N, s, S2), N[s].S[g2 - 1] = D), oe([], m2, function(U) { + return U = Qe(S2, [U[0], null].concat(U.slice(1)), null, Q, h2), N[s].S === void 0 ? (U.Z = g2 - 1, N[s] = U) : N[s].S[g2 - 1] = U, []; + }), []; + }); + }, p: function(f, s, g2, c2, B, Q) { + 0 < s || rA(); + var h2 = pA(s, g2); + B = GA(c2, B), oe([], [f], function(m2) { + m2 = m2[0]; + var w2 = "constructor " + m2.name; + if (m2.N.Y === void 0 && (m2.N.Y = []), m2.N.Y[s - 1] !== void 0) throw new BA("Cannot register multiple constructors with identical number of parameters (" + (s - 1) + ") for class '" + m2.name + "'! Overload resolution is currently only performed using the parameter count, not actual type info!"); + return m2.N.Y[s - 1] = () => { + qA("Cannot construct " + m2.name + " due to unbound types", h2); + }, oe([], h2, function(D) { + return D.splice(1, 0, null), m2.N.Y[s - 1] = Qe(w2, D, null, B, Q), []; + }), []; + }); + }, a: function(f, s, g2, c2, B, Q, h2, m2) { + var w2 = pA(g2, c2); + s = uA(s), Q = GA(B, Q), oe([], [f], function(D) { + function S2() { + qA("Cannot call " + N + " due to unbound types", w2); + } + D = D[0]; + var N = D.name + "." + s; + s.startsWith("@@") && (s = Symbol[s.substring(2)]), m2 && D.N.ja.push(s); + var U = D.N.X, X = U[s]; + return X === void 0 || X.S === void 0 && X.className !== D.name && X.Z === g2 - 2 ? (S2.Z = g2 - 2, S2.className = D.name, U[s] = S2) : (IA(U, s, N), U[s].S[g2 - 2] = S2), oe([], w2, function(z) { + return z = Qe(N, z, D, Q, h2), U[s].S === void 0 ? (z.Z = g2 - 2, U[s] = z) : U[s].S[g2 - 2] = z, []; + }), []; + }); + }, A: function(f, s) { + s = uA(s), Z(f, { name: s, fromWireType: function(g2) { + var c2 = Y(g2); + return mA(g2), c2; + }, toWireType: function(g2, c2) { + return OA(c2); + }, argPackAdvance: 8, readValueFromPointer: KA, V: null }); + }, n: function(f, s, g2) { + g2 = V(g2), s = uA(s), Z(f, { name: s, fromWireType: function(c2) { + return c2; + }, toWireType: function(c2, B) { + return B; + }, argPackAdvance: 8, readValueFromPointer: jA(s, g2), V: null }); + }, e: function(f, s, g2, c2, B) { + s = uA(s), B === -1 && (B = 4294967295), B = V(g2); + var Q = (m2) => m2; + if (c2 === 0) { + var h2 = 32 - 8 * g2; + Q = (m2) => m2 << h2 >>> h2; + } + g2 = s.includes("unsigned") ? function(m2, w2) { + return w2 >>> 0; + } : function(m2, w2) { + return w2; + }, Z(f, { name: s, fromWireType: Q, toWireType: g2, argPackAdvance: 8, readValueFromPointer: Se(s, B, c2 !== 0), V: null }); + }, b: function(f, s, g2) { + function c2(Q) { + Q >>= 2; + var h2 = b; + return new B(d2, h2[Q + 1], h2[Q]); + } + var B = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array][s]; + g2 = uA(g2), Z(f, { name: g2, fromWireType: c2, argPackAdvance: 8, readValueFromPointer: c2 }, { ua: true }); + }, o: function(f, s) { + s = uA(s); + var g2 = s === "std::string"; + Z(f, { name: s, fromWireType: function(c2) { + var B = b[c2 >> 2], Q = c2 + 4; + if (g2) for (var h2 = Q, m2 = 0; m2 <= B; ++m2) { + var w2 = Q + m2; + if (m2 == B || y[w2] == 0) { + if (h2 = h2 ? C(y, h2, w2 - h2) : "", D === void 0) var D = h2; + else D += String.fromCharCode(0), D += h2; + h2 = w2 + 1; + } + } + else { + for (D = Array(B), m2 = 0; m2 < B; ++m2) D[m2] = String.fromCharCode(y[Q + m2]); + D = D.join(""); + } + return ve(c2), D; + }, toWireType: function(c2, B) { + B instanceof ArrayBuffer && (B = new Uint8Array(B)); + var Q, h2 = typeof B == "string"; + if (h2 || B instanceof Uint8Array || B instanceof Uint8ClampedArray || B instanceof Int8Array || W("Cannot pass non-string to std::string"), g2 && h2) { + var m2 = 0; + for (Q = 0; Q < B.length; ++Q) { + var w2 = B.charCodeAt(Q); + 127 >= w2 ? m2++ : 2047 >= w2 ? m2 += 2 : 55296 <= w2 && 57343 >= w2 ? (m2 += 4, ++Q) : m2 += 3; + } + Q = m2; + } else Q = B.length; + if (m2 = vt(4 + Q + 1), w2 = m2 + 4, b[m2 >> 2] = Q, g2 && h2) { + if (h2 = w2, w2 = Q + 1, Q = y, 0 < w2) { + w2 = h2 + w2 - 1; + for (var D = 0; D < B.length; ++D) { + var S2 = B.charCodeAt(D); + if (55296 <= S2 && 57343 >= S2) { + var N = B.charCodeAt(++D); + S2 = 65536 + ((S2 & 1023) << 10) | N & 1023; + } + if (127 >= S2) { + if (h2 >= w2) break; + Q[h2++] = S2; + } else { + if (2047 >= S2) { + if (h2 + 1 >= w2) break; + Q[h2++] = 192 | S2 >> 6; + } else { + if (65535 >= S2) { + if (h2 + 2 >= w2) break; + Q[h2++] = 224 | S2 >> 12; + } else { + if (h2 + 3 >= w2) break; + Q[h2++] = 240 | S2 >> 18, Q[h2++] = 128 | S2 >> 12 & 63; + } + Q[h2++] = 128 | S2 >> 6 & 63; + } + Q[h2++] = 128 | S2 & 63; + } + } + Q[h2] = 0; + } + } else if (h2) for (h2 = 0; h2 < Q; ++h2) D = B.charCodeAt(h2), 255 < D && (ve(w2), W("String has UTF-16 code units that do not fit in 8 bits")), y[w2 + h2] = D; + else for (h2 = 0; h2 < Q; ++h2) y[w2 + h2] = B[h2]; + return c2 !== null && c2.push(ve, m2), m2; + }, argPackAdvance: 8, readValueFromPointer: KA, V: function(c2) { + ve(c2); + } }); + }, i: function(f, s, g2) { + if (g2 = uA(g2), s === 2) var c2 = be, B = $A, Q = Ce, h2 = () => x2, m2 = 1; + else s === 4 && (c2 = It, B = et, Q = wt, h2 = () => b, m2 = 2); + Z(f, { name: g2, fromWireType: function(w2) { + for (var D = b[w2 >> 2], S2 = h2(), N, U = w2 + 4, X = 0; X <= D; ++X) { + var z = w2 + 4 + X * s; + (X == D || S2[z >> m2] == 0) && (U = c2(U, z - U), N === void 0 ? N = U : (N += String.fromCharCode(0), N += U), U = z + s); + } + return ve(w2), N; + }, toWireType: function(w2, D) { + typeof D != "string" && W("Cannot pass non-string to C++ string type " + g2); + var S2 = Q(D), N = vt(4 + S2 + s); + return b[N >> 2] = S2 >> m2, B(D, N + 4, S2 + s), w2 !== null && w2.push(ve, N), N; + }, argPackAdvance: 8, readValueFromPointer: KA, V: function(w2) { + ve(w2); + } }); + }, k: function(f, s, g2, c2, B, Q) { + Be[f] = { name: uA(s), fa: GA(g2, c2), W: GA(B, Q), ia: [] }; + }, h: function(f, s, g2, c2, B, Q, h2, m2, w2, D) { + Be[f].ia.push({ oa: uA(s), ta: g2, ra: GA(c2, B), sa: Q, za: h2, ya: GA(m2, w2), Aa: D }); + }, C: function(f, s) { + s = uA(s), Z(f, { va: true, name: s, argPackAdvance: 0, fromWireType: function() { + }, toWireType: function() { + } }); + }, s: function(f, s, g2, c2, B) { + f = ct[f], s = Y(s), g2 = lt(g2); + var Q = []; + return b[c2 >> 2] = OA(Q), f(s, g2, Q, B); + }, t: function(f, s, g2, c2) { + f = ct[f], s = Y(s), g2 = lt(g2), f(s, g2, null, c2); + }, g: mA, m: function(f, s) { + var g2 = Fn(f, s), c2 = g2[0]; + s = c2.name + "_$" + g2.slice(1).map(function(h2) { + return h2.name; + }).join("_") + "$"; + var B = Pr[s]; + if (B !== void 0) return B; + var Q = Array(f - 1); + return B = St((h2, m2, w2, D) => { + for (var S2 = 0, N = 0; N < f - 1; ++N) Q[N] = g2[N + 1].readValueFromPointer(D + S2), S2 += g2[N + 1].argPackAdvance; + for (h2 = h2[m2].apply(h2, Q), N = 0; N < f - 1; ++N) g2[N + 1].ma && g2[N + 1].ma(Q[N]); + if (!c2.va) return c2.toWireType(w2, h2); + }), Pr[s] = B; + }, D: function(f) { + 4 < f && (fA[f].ga += 1); + }, r: function(f) { + var s = Y(f); + De(s), mA(f); + }, c: function() { + rA(""); + }, x: function(f, s, g2) { + y.copyWithin(f, s, s + g2); + }, w: function(f) { + var s = y.length; + if (f >>>= 0, 2147483648 < f) return false; + for (var g2 = 1; 4 >= g2; g2 *= 2) { + var c2 = s * (1 + 0.2 / g2); + c2 = Math.min(c2, f + 100663296); + var B = Math; + c2 = Math.max(f, c2), B = B.min.call(B, 2147483648, c2 + (65536 - c2 % 65536) % 65536); + A: { + try { + I.grow(B - d2.byteLength + 65535 >>> 16), L(); + var Q = 1; + break A; + } catch { + } + Q = void 0; + } + if (Q) return true; + } + return false; + }, z: function() { + return 52; + }, u: function() { + return 70; + }, y: function(f, s, g2, c2) { + for (var B = 0, Q = 0; Q < g2; Q++) { + var h2 = b[s >> 2], m2 = b[s + 4 >> 2]; + s += 8; + for (var w2 = 0; w2 < m2; w2++) { + var D = y[h2 + w2], S2 = _r[f]; + D === 0 || D === 10 ? ((f === 1 ? o : a)(C(S2, 0)), S2.length = 0) : S2.push(D); + } + B += m2; + } + return b[c2 >> 2] = B, 0; + } }; + (function() { + function f(B) { + e.asm = B.exports, I = e.asm.E, L(), O = e.asm.J, j.unshift(e.asm.F), dA--, e.monitorRunDependencies && e.monitorRunDependencies(dA), dA == 0 && (sA !== null && (clearInterval(sA), sA = null), vA && (B = vA, vA = null, B())); + } + function s(B) { + f(B.instance); + } + function g2(B) { + return kA().then(function(Q) { + return Q instanceof WebAssembly.Instance ? Q : WebAssembly.instantiate(Q, c2); + }).then(function(Q) { + return Q; + }).then(B, function(Q) { + a("failed to asynchronously prepare wasm: " + Q), rA(Q); + }); + } + var c2 = { a: Ln }; + if (dA++, e.monitorRunDependencies && e.monitorRunDependencies(dA), e.instantiateWasm) try { + return e.instantiateWasm(c2, f); + } catch (B) { + a("Module.instantiateWasm callback failed with error: " + B), r(B); + } + return (function() { + return u2 || typeof WebAssembly.instantiateStreaming != "function" || iA(wA) || typeof fetch != "function" ? g2(s) : fetch(wA, { credentials: "same-origin" }).then(function(B) { + return WebAssembly.instantiateStreaming(B, c2).then(s, function(Q) { + return a("wasm streaming compile failed: " + Q), a("falling back to ArrayBuffer instantiation"), g2(s); + }); + }); + })().catch(r), {}; + })(), e.___wasm_call_ctors = function() { + return (e.___wasm_call_ctors = e.asm.F).apply(null, arguments); + }; + var Wr = e.___getTypeName = function() { + return (Wr = e.___getTypeName = e.asm.G).apply(null, arguments); + }; + e.__embind_initialize_bindings = function() { + return (e.__embind_initialize_bindings = e.asm.H).apply(null, arguments); + }; + var vt = e._malloc = function() { + return (vt = e._malloc = e.asm.I).apply(null, arguments); + }, ve = e._free = function() { + return (ve = e._free = e.asm.K).apply(null, arguments); + }; + e.dynCall_jiji = function() { + return (e.dynCall_jiji = e.asm.L).apply(null, arguments); + }; + var de; + vA = function f() { + de || Bt(), de || (vA = f); + }; + function Bt() { + function f() { + if (!de && (de = true, e.calledRun = true, !E)) { + if (te(j), t(e), e.onRuntimeInitialized && e.onRuntimeInitialized(), e.postRun) for (typeof e.postRun == "function" && (e.postRun = [e.postRun]); e.postRun.length; ) { + var s = e.postRun.shift(); + CA.unshift(s); + } + te(CA); + } + } + if (!(0 < dA)) { + if (e.preRun) for (typeof e.preRun == "function" && (e.preRun = [e.preRun]); e.preRun.length; ) MA(); + te(J), 0 < dA || (e.setStatus ? (e.setStatus("Running..."), setTimeout(function() { + setTimeout(function() { + e.setStatus(""); + }, 1), f(); + }, 1)) : f()); + } + } + if (e.preInit) for (typeof e.preInit == "function" && (e.preInit = [e.preInit]); 0 < e.preInit.length; ) e.preInit.pop()(); + return Bt(), A.ready; +} +async function jr(A) { + let { default: e } = process.env.SATORI_STANDALONE === "1" ? await Promise.resolve().then(() => (Ls(), Fs)) : await Promise.resolve().then(() => (Us(), Gs)); + return Xn(await e(A)); +} +async function OI(A, e) { + let t; + if (typeof A == "string" || typeof Request == "function" && A instanceof Request || typeof URL == "function" && A instanceof URL ? t = await fetch(A) : t = await A, typeof Response == "function" && t instanceof Response) { + if (typeof WebAssembly.instantiateStreaming == "function") try { + return await WebAssembly.instantiateStreaming(t, e); + } catch (i) { + t.headers.get("Content-Type") !== "application/wasm" && console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", i); + } + let n = await t.arrayBuffer(); + return await WebAssembly.instantiate(n, e); + } + let r = await WebAssembly.instantiate("buffer" in t ? t.buffer.slice(t.byteOffset, t.byteOffset + t.byteLength) : t, e); + return r instanceof WebAssembly.Instance ? { instance: r, module: t } : r; +} +function TI(A) { + UI({ instantiateWasm(e, t) { + return OI(A, e).then(({ instance: r }) => { + t(r); + }).catch(zn), {}; + } }).then(Hs).catch(zn); +} +function PI() { + return HI; +} +function JI() { + return _I; +} +function Un(A) { + if (/\.\D?$/.test(A)) throw new Error("The dot should be followed by a number"); + if (/^[+-]{2}/.test(A)) throw new Error("Only one leading +/- is allowed"); + if (DI(A) > 1) throw new Error("Only one dot is allowed"); + if (/%$/.test(A)) { + this.type = "percentage", this.value = Gn(A), this.unit = "%"; + return; + } + var e = bI(A); + if (!e) { + this.type = "number", this.value = Gn(A); + return; + } + this.type = kI(e), this.value = Gn(A.substr(0, A.length - e.length)), this.unit = e; +} +function He(A) { + return new Un(A); +} +function DI(A) { + var e = A.match(/\./g); + return e ? e.length : 0; +} +function Gn(A) { + var e = parseFloat(A); + if (isNaN(e)) throw new Error("Invalid number: " + A); + return e; +} +function bI(A) { + var e = A.match(/\D+$/), t = e && e[0]; + if (t && SI.indexOf(t) === -1) throw new Error("Invalid unit: " + t); + return t; +} +function qr(A, e) { + return Object.fromEntries(A.map((t) => [t, e])); +} +function kI(A) { + return vI[A] || "length"; +} +function Rt(A) { + let e = typeof A; + return !(e === "number" || e === "bigint" || e === "string" || e === "boolean"); +} +function Cs(A) { + return /^class\s/.test(A.toString()); +} +function Tn(A) { + return A && A.$$typeof === /* @__PURE__ */ Symbol.for("react.forward_ref"); +} +function ds(A) { + return typeof A == "function" || Tn(A); +} +function hs(A) { + return "dangerouslySetInnerHTML" in A; +} +function ps(A) { + let e = typeof A > "u" ? [] : [].concat(A).flat(1 / 0), t = []; + for (let r = 0; r < e.length; r++) { + let n = e[r]; + typeof n > "u" || typeof n == "boolean" || n === null || (typeof n == "number" && (n = String(n)), typeof n == "string" && t.length && typeof t[t.length - 1] == "string" ? t[t.length - 1] += n : t.push(n)); + } + return t; +} +function tA(A, e, t, r, n = false) { + if (typeof A == "number") return A; + try { + if (A = A.trim(), /[ /\(,]/.test(A)) return; + if (A === String(+A)) return +A; + let i = new He(A); + if (i.type === "length") switch (i.unit) { + case "em": + return i.value * e; + case "rem": + return i.value * 16; + case "vw": + return ~~(i.value * r._viewportWidth / 100); + case "vh": + return ~~(i.value * r._viewportHeight / 100); + default: + return i.value; + } + else { + if (i.type === "angle") return Pn(A); + if (i.type === "percentage" && n) return i.value / 100 * t; + } + } catch { + } +} +function Pn(A) { + let e = new He(A); + switch (e.unit) { + case "deg": + return e.value; + case "rad": + return e.value * 180 / Math.PI; + case "turn": + return e.value * 360; + case "grad": + return 0.9 * e.value; + } +} +function Yt(A, e) { + return [A[0] * e[0] + A[2] * e[1], A[1] * e[0] + A[3] * e[1], A[0] * e[2] + A[2] * e[3], A[1] * e[2] + A[3] * e[3], A[0] * e[4] + A[2] * e[5] + A[4], A[1] * e[4] + A[3] * e[5] + A[5]]; +} +function ue(A, e, t, r) { + let n = e[A]; + if (typeof n > "u") { + if (r && typeof A < "u") throw new Error(`Invalid value for CSS property "${r}". Allowed values: ${Object.keys(e).map((i) => `"${i}"`).join(" | ")}. Received: "${A}".`); + n = t; + } + return n; +} +function ge(A, e, t) { + let r = `${e}:${t || ""}:${A}`; + if (xt.has(r)) return xt.get(r); + if (!Hn || !On) { + if (!(typeof Intl < "u" && "Segmenter" in Intl)) throw new Error("Intl.Segmenter does not exist, please use import a polyfill."); + Hn = new Intl.Segmenter(t, { granularity: "word" }), On = new Intl.Segmenter(t, { granularity: "grapheme" }); + } + let n; + if (e === "grapheme") n = [...On.segment(A)].map((i) => i.segment); + else { + let i = [...Hn.segment(A)].map((u2) => u2.segment), o = [], a = 0; + for (; a < i.length; ) { + let u2 = i[a]; + if (u2 == "\xA0") { + let l2 = a === 0 ? "" : o.pop(), I = a === i.length - 1 ? "" : i[a + 1]; + o.push(l2 + "\xA0" + I), a += 2; + } else o.push(u2), a++; + } + n = o; + } + if (xt.size >= RI) { + let i = xt.keys().next().value; + xt.delete(i); + } + return xt.set(r, n), n; +} +function H(A, e, t) { + let r = ""; + for (let [n, i] of Object.entries(e)) typeof i < "u" && (r += ` ${n}="${i}"`); + return t ? `<${A}${r}>${t}` : `<${A}${r}/>`; +} +function ys(A = 20) { + let e = /* @__PURE__ */ new Map(); + function t(i) { + let o = e.get(i); + if (o !== void 0) return e.delete(i), e.set(i, o), o; + } + function r(i, o) { + if (e.has(i)) e.delete(i); + else if (e.size >= A) { + let a = e.keys().next().value; + e.delete(a); + } + e.set(i, o); + } + function n() { + e.clear(); + } + return { set: r, get: t, clear: n }; +} +function Nt(A) { + return A ? A.split(/[, ]/).filter(Boolean).map(Number) : null; +} +function Xr(A) { + return typeof A == "string"; +} +function ws(A) { + return typeof A == "number"; +} +function Ds(A) { + return typeof A > "u"; +} +function Ve(A, e) { + if (typeof A == "number") return A; + if (A.endsWith("%")) { + let t = parseFloat(A.slice(0, -1)); + if (isNaN(t)) { + console.warn(`Invalid value "${A}"${typeof e == "string" ? ` for "${e}"` : ""}. Expected a percentage value (e.g., "50%").`); + return; + } + return `${t}%`; + } + console.warn(`Invalid value "${A}"${typeof e == "string" ? ` for "${e}"` : ""}. Expected a number or a percentage value (e.g., "50%").`); +} +function tt(A, e) { + if (typeof A == "number") return A; + if (A === "auto") return "auto"; + if (A.endsWith("%")) { + let t = parseFloat(A.slice(0, -1)); + if (isNaN(t)) { + console.warn(`Invalid value "${A}"${typeof e == "string" ? ` for "${e}"` : ""}. Expected a percentage value (e.g., "50%").`); + return; + } + return `${t}%`; + } + console.warn(`Invalid value "${A}"${typeof e == "string" ? ` for "${e}"` : ""}. Expected a number, "auto", or a percentage value (e.g., "50%").`); +} +function Ss(A, e) { + if (e === "break-all") return { words: ge(A, "grapheme"), requiredBreaks: [] }; + if (e === "keep-all") return { words: ge(A, "word"), requiredBreaks: [] }; + let t = new $557adaaeb0c7885f$exports(A), r = 0, n = t.nextBreak(), i = [], o = [false]; + for (; n; ) { + let a = A.slice(r, n.position); + i.push(a), n.required ? o.push(true) : o.push(false), r = n.position, n = t.nextBreak(); + } + return { words: i, requiredBreaks: o }; +} +function Vr(A, e = ",") { + let t = [], r = 0, n = 0; + e = new RegExp(e); + for (let i = 0; i < A.length; i++) A[i] === "(" ? n++ : A[i] === ")" && n--, n === 0 && e.test(A[i]) && (t.push(A.slice(r, i).trim()), r = i + 1); + return t.push(A.slice(r).trim()), t; +} +function WI(A) { + if (process.env.SATORI_STANDALONE === "1") return Promise.resolve().then(() => (jn(), Zn)).then((e) => e.init(A)); +} +function rt() { + return process.env.SATORI_STANDALONE === "1" ? Promise.resolve().then(() => (jn(), Zn)).then((A) => A.getYoga()) : Promise.resolve().then(() => (Ai(), $n)).then((A) => A.getYoga()); +} +function Ps(A) { + let e = new DataView(A), t = 4, r = e.byteLength; + for (; t < r; ) { + let n = e.getUint16(t, false); + if (n > r) throw new TypeError("Invalid JPEG"); + let i = e.getUint8(n + 1 + t); + if (i === 192 || i === 193 || i === 194) return [e.getUint16(n + 7 + t, false), e.getUint16(n + 5 + t, false)]; + t += n + 2; + } + throw new TypeError("Invalid JPEG"); +} +function _s(A) { + let e = new Uint8Array(A.slice(6, 10)); + return [e[0] | e[1] << 8, e[2] | e[3] << 8]; +} +function Js(A) { + let e = new DataView(A); + return [e.getUint16(18, false), e.getUint16(22, false)]; +} +function jI(A) { + let e = new Uint8Array(A), t = 32768, r = ""; + for (let n = 0; n < e.length; n += t) { + let i = e.subarray(n, Math.min(n + t, e.length)); + r += String.fromCharCode(...i); + } + return btoa(r); +} +function $I(A) { + let e = atob(A), t = e.length, r = new Uint8Array(t); + for (let n = 0; n < t; n++) r[n] = e.charCodeAt(n); + return r.buffer; +} +function Os(A, e) { + let t = e.match(XI); + if (!t) throw new Error(`Failed to parse SVG from ${A}`); + let r = t[0], n = VI.exec(r), i = zI.exec(r), o = ZI.exec(r), a = n ? Nt(n[1]) : null; + if (!a && (!i || !o)) throw new Error(`Failed to parse SVG from ${A}: missing "viewBox"`); + let u2 = a ? [a[2], a[3]] : [+i[1], +o[1]], l2 = u2[0] / u2[1]; + return i && o ? [+i[1], +o[1]] : i ? [+i[1], +i[1] / l2] : o ? [+o[1] * l2, +o[1]] : [u2[0], u2[1]]; +} +function Ts(A) { + let e, t = Al(new Uint8Array(A)); + switch (t) { + case An: + case $r: + e = Js(A); + break; + case tn: + e = _s(A); + break; + case en: + e = Ps(A); + break; + } + if (!qI.includes(t)) throw new Error(`Unsupported image type: ${t || "unknown"}`); + return [`data:${t};base64,${jI(A)}`, e]; +} +async function Gt(A) { + if (!A) throw new Error("Image source is not provided."); + if (typeof A == "object") { + let [n, i] = Ts(A); + return [n, ...i]; + } + if ((A.startsWith('"') && A.endsWith('"') || A.startsWith("'") && A.endsWith("'")) && (A = A.slice(1, -1)), typeof window > "u" && !A.startsWith("http") && !A.startsWith("data:")) throw new Error(`Image source must be an absolute URL: ${A}`); + if (A.startsWith("data:")) { + let n; + try { + n = /data:(?[a-z/+]+)(;[^;=]+=[^;=]+)*?(;(?[^;,]+))?,(?.*)/g.exec(A).groups; + } catch { + return console.warn("Image data URI resolved without size:" + A), [A]; + } + let { imageType: i, encodingType: o, dataString: a } = n; + if (i === ei) { + let u2 = o === "base64" ? atob(a) : decodeURIComponent(a.replace(/ /g, "%20")), l2 = o === "base64" ? A : `data:image/svg+xml;base64,${btoa(u2)}`, I = Os(A, u2); + return _e.set(A, [l2, ...I]), [l2, ...I]; + } else if (o === "base64") { + let u2, l2 = $I(a); + switch (i) { + case An: + case $r: + u2 = Js(l2); + break; + case tn: + u2 = _s(l2); + break; + case en: + u2 = Ps(l2); + break; + } + return _e.set(A, [A, ...u2]), [A, ...u2]; + } else return console.warn("Image data URI resolved without size:" + A), _e.set(A, [A]), [A]; + } + if (!globalThis.fetch) throw new Error("`fetch` is required to be polyfilled to load images."); + if (Xt.has(A)) return Xt.get(A); + let e = _e.get(A); + if (e) return e; + let t = A, r = fetch(t).then((n) => { + let i = n.headers.get("content-type"); + return i === "image/svg+xml" || i === "application/svg+xml" ? n.text() : n.arrayBuffer(); + }).then((n) => { + if (typeof n == "string") try { + let a = `data:image/svg+xml;base64,${btoa(n)}`, u2 = Os(t, n); + return [a, ...u2]; + } catch (a) { + throw new Error(`Failed to parse SVG image: ${a.message}`); + } + let [i, o] = Ts(n); + return [i, ...o]; + }).then((n) => (_e.set(t, n), n)).catch((n) => (console.error(`Can't load image ${t}: ` + n.message), _e.set(t, []), [])); + return Xt.set(t, r), r; +} +function Al(A) { + return [255, 216, 255].every((e, t) => A[t] === e) ? en : [137, 80, 78, 71, 13, 10, 26, 10].every((e, t) => A[t] === e) ? el(A) ? $r : An : [71, 73, 70, 56].every((e, t) => A[t] === e) ? tn : [82, 73, 70, 70, 0, 0, 0, 0, 87, 69, 66, 80].every((e, t) => !e || A[t] === e) ? YI : [60, 63, 120, 109, 108].every((e, t) => A[t] === e) ? ei : [0, 0, 0, 0, 102, 116, 121, 112, 97, 118, 105, 102].every((e, t) => !e || A[t] === e) ? KI : null; +} +function el(A) { + let e = new DataView(A.buffer), t, r, n = 8, i = false; + for (; !i && t !== "IEND" && n < A.length; ) { + r = e.getUint32(n); + let o = A.subarray(n + 4, n + 8); + t = String.fromCharCode(...o), i = t === "acTL", n += 12 + r; + } + return i; +} +function ri(A, e) { + if (!A) return ""; + if (Array.isArray(A)) return A.map((l2) => ri(l2, e)).join(""); + if (typeof A != "object") return String(A); + let t = A.type; + if (t === "text") throw new Error(" nodes are not currently supported, please convert them to "); + let { children: r, style: n, ...i } = A.props || {}, o = (n == null ? void 0 : n.color) || e, a = `${Object.entries(i).map(([l2, I]) => (typeof I == "string" && I.toLowerCase() === "currentcolor" && (I = o), (l2 === "href" || l2 === "xlinkHref") && t === "image" ? ` ${ti[l2] || l2}="${_e.get(I)[0]}"` : ` ${ti[l2] || l2}="${I}"`)).join("")}`, u2 = n ? ` style="${Object.entries(n).map(([l2, I]) => `${bs(l2)}:${I}`).join(";")}"` : ""; + return `<${t}${a}${u2}>${ri(r, o)}`; +} +async function Ws(A) { + let e = /* @__PURE__ */ new Set(), t = (r) => { + if (r && Rt(r)) { + if (Array.isArray(r)) { + r.forEach((n) => t(n)); + return; + } else if (typeof r == "object") if (r.type === "image") { + let n = r.props.href || r.props.xlinkHref; + n && (e.has(n) || e.add(n)); + } else r.type === "img" && (e.has(r.props.src) || e.add(r.props.src)); + Array.isArray(r.props.children) ? r.props.children.map((n) => t(n)) : t(r.props.children); + } + }; + return t(A), Promise.all(Array.from(e).map((r) => Gt(r))); +} +async function Ks(A, e) { + let { viewBox: t, viewbox: r, width: n, height: i, className: o, style: a, children: u2, ...l2 } = A.props || {}; + t ||= r, l2.xmlns = "http://www.w3.org/2000/svg"; + let I = (a == null ? void 0 : a.color) || e, E = Nt(t), C = E ? E[3] / E[2] : null; + return n = n || C && i ? i / C : null, i = i || C && n ? n * C : null, l2.width = n, l2.height = i, t && (l2.viewBox = t), `data:image/svg+xml;utf8,${` (typeof p == "string" && p.toLowerCase() === "currentcolor" && (p = I), ` ${ti[d2] || d2}="${p}"`)).join("")}>${ri(u2, I)}`.replace(tl, encodeURIComponent)}`; +} +function ni(A) { + let e = {}; + for (let t in A) (rl.has(t) || t.startsWith("--")) && (e[t] = A[t]); + return e; +} +function il(A, e) { + try { + let t = new He(A); + switch (t.unit) { + case "px": + return { absolute: t.value }; + case "em": + return { absolute: t.value * e }; + case "rem": + return { absolute: t.value * 16 }; + case "%": + return { relative: t.value }; + default: + return {}; + } + } catch { + return {}; + } +} +function ii(A, e, t) { + switch (A) { + case "top": + return { yRelative: 0 }; + case "left": + return { xRelative: 0 }; + case "right": + return { xRelative: 100 }; + case "bottom": + return { yRelative: 100 }; + case "center": + return {}; + default: { + let r = il(A, e); + return r.absolute ? { [t ? "xAbsolute" : "yAbsolute"]: r.absolute } : r.relative ? { [t ? "xRelative" : "yRelative"]: r.relative } : {}; + } + } +} +function oi(A, e) { + if (typeof A == "number") return { xAbsolute: A }; + let t; + try { + t = (0, import_postcss_value_parser.default)(A).nodes.filter((r) => r.type === "word").map((r) => r.value); + } catch { + return {}; + } + return t.length === 1 ? ii(t[0], e, true) : t.length === 2 ? ((t[0] === "top" || t[0] === "bottom" || t[1] === "left" || t[1] === "right") && t.reverse(), { ...ii(t[0], e, true), ...ii(t[1], e, false) }) : {}; +} +function Vt(A, e) { + let t = (0, import_css_to_react_native2.getPropertyName)(`mask-${e}`); + return A[t] || A[`WebkitM${t.substring(1)}`]; +} +function qs(A) { + let e = A.maskImage || A.WebkitMaskImage, t = { position: Vt(A, "position") || "0% 0%", size: Vt(A, "size") || "100% 100%", repeat: Vt(A, "repeat") || "repeat", origin: Vt(A, "origin") || "border-box", clip: Vt(A, "origin") || "border-box" }; + return Vr(e).filter((n) => n && n !== "none").reverse().map((n) => ({ image: n, ...t })); +} +function Vs(A) { + let e = {}, t = {}; + for (let r in A) r.startsWith("--") ? e[r] = String(A[r]) : t[r] = A[r]; + return { variables: e, remainingStyle: t }; +} +function zs(A, e) { + return { ...A, ...e }; +} +function Zt(A, e, t = /* @__PURE__ */ new Set()) { + if (typeof A != "string" || !A.includes("var(")) return A; + try { + let r = (0, import_postcss_value_parser2.default)(A), n = false; + if (r.walk((i) => { + if (i.type === "function" && i.value === "var") { + n = true; + let o = sl(i); + if (!o) return; + let { varName: a, fallback: u2 } = o; + if (t.has(a)) { + console.warn(`Circular reference detected for CSS variable: ${a}`), u2 !== void 0 ? zt(i, u2) : zt(i, "initial"); + return; + } + let l2 = e[a]; + if (l2 !== void 0) { + let I = new Set(t); + I.add(a); + let E = Zt(l2, e, I); + zt(i, String(E)); + } else if (u2 !== void 0) { + let I = Zt(u2, e, t); + zt(i, String(I)); + } else zt(i, "initial"); + } + }), n) return r.toString(); + } catch { + console.warn(`Failed to parse CSS value for variable resolution: ${A}`); + } + return A; +} +function sl(A) { + if (!A.nodes || A.nodes.length === 0) return null; + let e, t = -1; + for (let n = 0; n < A.nodes.length; n++) { + let i = A.nodes[n]; + if (i.type === "word" && !e) e = i; + else if (i.type === "div" && i.value === ",") { + t = n; + break; + } + } + if (!e || e.type !== "word") return null; + let r = e.value.trim(); + if (t !== -1 && t < A.nodes.length - 1) { + let n = A.nodes.slice(t + 1), i = import_postcss_value_parser2.default.stringify(n).trim(); + return { varName: r, fallback: i }; + } + return { varName: r }; +} +function zt(A, e) { + A.type = "word", A.value = e, delete A.nodes; +} +function fl(A, e, t, r) { + return A === "textDecoration" && !t.includes(e.textDecorationColor) && (e.textDecorationColor = r), e; +} +function Et(A, e) { + let t = Number(e); + return isNaN(t) ? e : ll.has(A) ? cl.has(A) ? t : String(e) : t + "px"; +} +function Bl(A, e, t) { + if (A === "zIndex") return console.warn("`z-index` is currently not supported."), { [A]: e }; + if (A === "lineHeight") return { lineHeight: Et(A, e) }; + if (A === "fontFamily") return { fontFamily: e.split(",").map((r) => r.trim().replace(/(^['"])|(['"]$)/g, "").toLocaleLowerCase()) }; + if (A === "borderRadius") { + if (typeof e != "string" || !e.includes("/")) return; + let [r, n] = e.split("/"), i = (0, import_css_to_react_native.getStylesForProperty)(A, r, true), o = (0, import_css_to_react_native.getStylesForProperty)(A, n, true); + for (let a in i) o[a] = Et(A, i[a]) + " " + Et(A, o[a]); + return o; + } + if (/^border(Top|Right|Bottom|Left)?$/.test(A)) { + let r = (0, import_css_to_react_native.getStylesForProperty)("border", e, true); + r.borderWidth === 1 && !String(e).includes("1px") && (r.borderWidth = 3), r.borderColor === "black" && !String(e).includes("black") && (r.borderColor = t); + let n = { Width: Et(A + "Width", r.borderWidth), Style: ue(r.borderStyle, { solid: "solid", dashed: "dashed" }, "solid", A + "Style"), Color: r.borderColor }, i = {}; + for (let o of A === "border" ? ["Top", "Right", "Bottom", "Left"] : [A.slice(6)]) for (let a in n) i["border" + o + a] = n[a]; + return i; + } + if (A === "boxShadow") { + if (!e) throw new Error('Invalid `boxShadow` value: "' + e + '".'); + return { [A]: typeof e == "string" ? (0, import_css_box_shadow.parse)(e) : e }; + } + if (A === "transform") { + if (typeof e != "string") throw new Error("Invalid `transform` value."); + let r = {}, n = e.replace(/(-?[\d.]+%)/g, (o, a) => { + let u2 = ~~(Math.random() * 1e9); + return r[u2] = a, u2 + "px"; + }), i = (0, import_css_to_react_native.getStylesForProperty)("transform", n, true); + for (let o of i.transform) for (let a in o) r[o[a]] && (o[a] = r[o[a]]); + return i; + } + if (A === "background") return e = e.toString().trim(), /^(linear-gradient|radial-gradient|url|repeating-linear-gradient|repeating-radial-gradient)\(/.test(e) ? (0, import_css_to_react_native.getStylesForProperty)("backgroundImage", e, true) : (0, import_css_to_react_native.getStylesForProperty)("background", e, true); + if (A === "textShadow") { + e = e.toString().trim(); + let r = {}, n = Vr(e); + for (let i of n) { + let o = (0, import_css_to_react_native.getStylesForProperty)("textShadow", i, true); + for (let a in o) r[a] ? r[a].push(o[a]) : r[a] = [o[a]]; + } + return r; + } + if (A === "WebkitTextStroke") { + e = e.toString().trim(); + let r = e.split(" "); + if (r.length !== 2) throw new Error("Invalid `WebkitTextStroke` value."); + return { WebkitTextStrokeWidth: Et(A, r[0]), WebkitTextStrokeColor: Et(A, r[1]) }; + } + if (A === "textDecorationSkipInk") { + let r = e.toString().trim().toLowerCase(); + if (!["auto", "none", "all"].includes(r)) throw new Error("Invalid `textDecorationSkipInk` value."); + return { textDecorationSkipInk: r }; + } +} +function Zs(A) { + return A === "transform" ? " Only absolute lengths such as `10px` are supported." : ""; +} +function Aa(A) { + if (typeof A == "string" && js.test(A.trim())) return A.trim().replace(js, (e, t, r, n, i) => `rgba(${t}, ${r}, ${n}, ${i})`); + if (typeof A == "object" && A !== null) { + for (let e in A) A[e] = Aa(A[e]); + return A; + } + return A; +} +function rn(A, e) { + let t = {}, r = {}; + for (let u2 in e) u2.startsWith("--") && (r[u2] = String(e[u2])); + let n = {}, i = A; + if (A) { + let { variables: u2, remainingStyle: l2 } = Vs(A); + n = u2, i = l2; + } + let o = zs(r, n); + for (let u2 in o) t[u2] = o[u2]; + if (i) { + let u2 = i.color ? Zt(i.color, o) : void 0, l2 = Ql(u2, e.color); + t.color = l2; + for (let I in i) { + if (I.startsWith("_")) { + t[I] = i[I]; + continue; + } + if (I === "color") continue; + let E = (0, import_css_to_react_native.getPropertyName)(I), C = Zt(i[I], o), d2 = dl(C, l2); + try { + let p = Bl(E, d2, l2) || fl(E, (0, import_css_to_react_native.getStylesForProperty)(E, Et(E, d2), true), d2, l2); + Object.assign(t, p); + } catch (p) { + throw new Error(p.message + (p.message.includes(d2) ? ` + ` + Zs(E) : ` + in CSS rule \`${E}: ${d2}\`.${Zs(E)}`)); + } + } + } + if (t.backgroundImage) { + let { backgrounds: u2 } = (0, import_css_background_parser.parseElementStyle)(t); + t.backgroundImage = u2; + } + (t.maskImage || t.WebkitMaskImage) && (t.maskImage = qs(t)); + let a = El(t.fontSize, e.fontSize); + typeof t.fontSize < "u" && (t.fontSize = a), t.transformOrigin && (t.transformOrigin = oi(t.transformOrigin, a)); + for (let u2 in t) { + let l2 = t[u2]; + if (u2 === "lineHeight") typeof l2 == "string" && l2 !== "normal" && (l2 = t[u2] = tA(l2, a, a, e, true) / a); + else { + if (typeof l2 == "string") { + let I = tA(l2, a, a, e); + typeof I < "u" && (t[u2] = I), l2 = t[u2]; + } + if (typeof l2 == "string" || typeof l2 == "object") { + let I = Aa(l2); + I && (t[u2] = I), l2 = t[u2]; + } + } + if (u2 === "opacity" && typeof l2 == "number" && (t.opacity = l2 * e.opacity), u2 === "transform") { + let I = l2; + for (let E of I) { + let C = Object.keys(E)[0], d2 = E[C], p = typeof d2 == "string" ? tA(d2, a, a, e) ?? d2 : d2; + E[C] = p; + } + } + if (u2 === "textShadowRadius") { + let I = l2; + t.textShadowRadius = I.map((E) => tA(E, a, 0, e, false)); + } + if (u2 === "textShadowOffset") { + let I = l2; + t.textShadowOffset = I.map(({ height: E, width: C }) => ({ height: tA(E, a, 0, e, false), width: tA(C, a, 0, e, false) })); + } + } + return t; +} +function El(A, e) { + if (typeof A == "number") return A; + try { + let t = new He(A); + switch (t.unit) { + case "em": + return t.value * e; + case "rem": + return t.value * 16; + } + } catch { + return e; + } +} +function $s(A) { + if (A.startsWith("hsl")) { + let e = (0, import_parse_css_color.default)(A), [t, r, n] = e.values; + return `hsl(${[t, `${r}%`, `${n}%`].concat(e.alpha === 1 ? [] : [e.alpha]).join(",")})`; + } + return A; +} +function Ql(A, e) { + return A && A.toLowerCase() !== "currentcolor" ? $s(A) : $s(e); +} +function Cl(A, e) { + return A.replace(/currentcolor/gi, e); +} +function dl(A, e) { + return Xr(A) && (A = Cl(A, e)), A; +} +async function si(A, e, t, r, n) { + let i = await rt(), o = Object.assign({}, t, rn(Ys[e], t), rn(r, t)); + if (e === "img") { + let [a, u2, l2] = await Gt(n.src); + if (u2 === void 0 && l2 === void 0) { + if (n.width === void 0 || n.height === void 0) throw new Error("Image size cannot be determined. Please provide the width and height of the image."); + u2 = parseInt(n.width), l2 = parseInt(n.height); + } + let I = l2 / u2, E = (o.borderLeftWidth || 0) + (o.borderRightWidth || 0) + (o.paddingLeft || 0) + (o.paddingRight || 0), C = (o.borderTopWidth || 0) + (o.borderBottomWidth || 0) + (o.paddingTop || 0) + (o.paddingBottom || 0), d2 = o.width || n.width, p = o.height || n.height, y = typeof d2 == "number" && typeof p == "number"; + y && (d2 -= E, p -= C), d2 === void 0 && p === void 0 ? (d2 = "100%", A.setAspectRatio(1 / I)) : d2 === void 0 ? typeof p == "number" ? d2 = p / I : A.setAspectRatio(1 / I) : p === void 0 && (typeof d2 == "number" ? p = d2 * I : A.setAspectRatio(1 / I)), o.width = y ? d2 + E : d2, o.height = y ? p + C : p, o.__src = a, o.__naturalWidth = u2, o.__naturalHeight = l2; + } + if (e === "svg") { + let a = n.viewBox || n.viewbox, u2 = Nt(a), l2 = u2 ? u2[3] / u2[2] : null, { width: I, height: E } = n; + typeof I > "u" && E ? l2 == null ? I = 0 : typeof E == "string" && E.endsWith("%") ? I = parseInt(E) / l2 + "%" : (E = tA(E, t.fontSize, 1, t), I = E / l2) : typeof E > "u" && I ? l2 == null ? I = 0 : typeof I == "string" && I.endsWith("%") ? E = parseInt(I) * l2 + "%" : (I = tA(I, t.fontSize, 1, t), E = I * l2) : (typeof I < "u" && (I = tA(I, t.fontSize, 1, t) || I), typeof E < "u" && (E = tA(E, t.fontSize, 1, t) || E), I ||= u2 == null ? void 0 : u2[2], E ||= u2 == null ? void 0 : u2[3]), !o.width && I && (o.width = I), !o.height && E && (o.height = E); + } + return A.setDisplay(ue(o.display, { flex: i.DISPLAY_FLEX, block: i.DISPLAY_FLEX, contents: i.DISPLAY_CONTENTS, none: i.DISPLAY_NONE, "-webkit-box": i.DISPLAY_FLEX }, i.DISPLAY_FLEX, "display")), A.setAlignContent(ue(o.alignContent, { stretch: i.ALIGN_STRETCH, center: i.ALIGN_CENTER, "flex-start": i.ALIGN_FLEX_START, "flex-end": i.ALIGN_FLEX_END, "space-between": i.ALIGN_SPACE_BETWEEN, "space-around": i.ALIGN_SPACE_AROUND, baseline: i.ALIGN_BASELINE, normal: i.ALIGN_AUTO }, i.ALIGN_AUTO, "alignContent")), A.setAlignItems(ue(o.alignItems, { stretch: i.ALIGN_STRETCH, center: i.ALIGN_CENTER, "flex-start": i.ALIGN_FLEX_START, "flex-end": i.ALIGN_FLEX_END, baseline: i.ALIGN_BASELINE, normal: i.ALIGN_AUTO }, i.ALIGN_STRETCH, "alignItems")), A.setAlignSelf(ue(o.alignSelf, { stretch: i.ALIGN_STRETCH, center: i.ALIGN_CENTER, "flex-start": i.ALIGN_FLEX_START, "flex-end": i.ALIGN_FLEX_END, baseline: i.ALIGN_BASELINE, normal: i.ALIGN_AUTO }, i.ALIGN_AUTO, "alignSelf")), A.setJustifyContent(ue(o.justifyContent, { center: i.JUSTIFY_CENTER, "flex-start": i.JUSTIFY_FLEX_START, "flex-end": i.JUSTIFY_FLEX_END, "space-between": i.JUSTIFY_SPACE_BETWEEN, "space-around": i.JUSTIFY_SPACE_AROUND }, i.JUSTIFY_FLEX_START, "justifyContent")), A.setFlexDirection(ue(o.flexDirection, { row: i.FLEX_DIRECTION_ROW, column: i.FLEX_DIRECTION_COLUMN, "row-reverse": i.FLEX_DIRECTION_ROW_REVERSE, "column-reverse": i.FLEX_DIRECTION_COLUMN_REVERSE }, i.FLEX_DIRECTION_ROW, "flexDirection")), A.setFlexWrap(ue(o.flexWrap, { wrap: i.WRAP_WRAP, nowrap: i.WRAP_NO_WRAP, "wrap-reverse": i.WRAP_WRAP_REVERSE }, i.WRAP_NO_WRAP, "flexWrap")), typeof o.gap < "u" && A.setGap(i.GUTTER_ALL, o.gap), typeof o.rowGap < "u" && A.setGap(i.GUTTER_ROW, o.rowGap), typeof o.columnGap < "u" && A.setGap(i.GUTTER_COLUMN, o.columnGap), typeof o.flexBasis < "u" && A.setFlexBasis(tt(o.flexBasis, "flexBasis")), A.setFlexGrow(typeof o.flexGrow > "u" ? 0 : o.flexGrow), A.setFlexShrink(typeof o.flexShrink > "u" ? 0 : o.flexShrink), typeof o.maxHeight < "u" && A.setMaxHeight(Ve(o.maxHeight, "maxHeight")), typeof o.maxWidth < "u" && A.setMaxWidth(Ve(o.maxWidth, "maxWidth")), typeof o.minHeight < "u" && A.setMinHeight(Ve(o.minHeight, "minHeight")), typeof o.minWidth < "u" && A.setMinWidth(Ve(o.minWidth, "minWidth")), A.setOverflow(ue(o.overflow, { visible: i.OVERFLOW_VISIBLE, hidden: i.OVERFLOW_HIDDEN }, i.OVERFLOW_VISIBLE, "overflow")), A.setMargin(i.EDGE_TOP, tt(o.marginTop || 0)), A.setMargin(i.EDGE_BOTTOM, tt(o.marginBottom || 0)), A.setMargin(i.EDGE_LEFT, tt(o.marginLeft || 0)), A.setMargin(i.EDGE_RIGHT, tt(o.marginRight || 0)), A.setBorder(i.EDGE_TOP, o.borderTopWidth || 0), A.setBorder(i.EDGE_BOTTOM, o.borderBottomWidth || 0), A.setBorder(i.EDGE_LEFT, o.borderLeftWidth || 0), A.setBorder(i.EDGE_RIGHT, o.borderRightWidth || 0), A.setPadding(i.EDGE_TOP, o.paddingTop || 0), A.setPadding(i.EDGE_BOTTOM, o.paddingBottom || 0), A.setPadding(i.EDGE_LEFT, o.paddingLeft || 0), A.setPadding(i.EDGE_RIGHT, o.paddingRight || 0), A.setBoxSizing(ue(o.boxSizing, { "border-box": i.BOX_SIZING_BORDER_BOX, "content-box": i.BOX_SIZING_CONTENT_BOX }, i.BOX_SIZING_BORDER_BOX, "boxSizing")), A.setPositionType(ue(o.position, { absolute: i.POSITION_TYPE_ABSOLUTE, relative: i.POSITION_TYPE_RELATIVE, static: i.POSITION_TYPE_STATIC }, i.POSITION_TYPE_RELATIVE, "position")), typeof o.top < "u" && A.setPosition(i.EDGE_TOP, Ve(o.top, "top")), typeof o.bottom < "u" && A.setPosition(i.EDGE_BOTTOM, Ve(o.bottom, "bottom")), typeof o.left < "u" && A.setPosition(i.EDGE_LEFT, Ve(o.left, "left")), typeof o.right < "u" && A.setPosition(i.EDGE_RIGHT, Ve(o.right, "right")), typeof o.height < "u" ? A.setHeight(tt(o.height, "height")) : A.setHeightAuto(), typeof o.width < "u" ? A.setWidth(tt(o.width, "width")) : A.setWidthAuto(), [o, ni(o)]; +} +function hl(A, e, t) { + let r = [...ea]; + for (let n of A) { + let i = Object.keys(n)[0], o = n[i]; + if (typeof o == "string") if (i === "translateX") o = parseFloat(o) / 100 * e, n[i] = o; + else if (i === "translateY") o = parseFloat(o) / 100 * t, n[i] = o; + else throw new Error(`Invalid transform: "${i}: ${o}".`); + let a = o, u2 = [...ea]; + switch (i) { + case "translateX": + u2[4] = a; + break; + case "translateY": + u2[5] = a; + break; + case "scale": + u2[0] = a, u2[3] = a; + break; + case "scaleX": + u2[0] = a; + break; + case "scaleY": + u2[3] = a; + break; + case "rotate": { + let l2 = a * Math.PI / 180, I = Math.cos(l2), E = Math.sin(l2); + u2[0] = I, u2[1] = E, u2[2] = -E, u2[3] = I; + break; + } + case "skewX": + u2[2] = Math.tan(a * Math.PI / 180); + break; + case "skewY": + u2[1] = Math.tan(a * Math.PI / 180); + break; + } + r = Yt(u2, r); + } + A.splice(0, A.length), A.push(...r), A.__resolved = true; +} +function jt({ left: A, top: e, width: t, height: r }, n, i, o) { + let a; + n.__resolved || hl(n, t, r); + let u2 = n; + if (i) a = u2; + else { + let l2 = (o == null ? void 0 : o.xAbsolute) ?? ((o == null ? void 0 : o.xRelative) ?? 50) * t / 100, I = (o == null ? void 0 : o.yAbsolute) ?? ((o == null ? void 0 : o.yRelative) ?? 50) * r / 100, E = A + l2, C = e + I; + a = Yt([1, 0, 0, 1, E, C], Yt(u2, [1, 0, 0, 1, -E, -C])), u2.__parent && (a = Yt(u2.__parent, a)), u2.splice(0, 6, ...a); + } + return `matrix(${a.map((l2) => l2.toFixed(2)).join(",")})`; +} +function ra({ left: A, top: e, width: t, height: r, isInheritingTransform: n }, i) { + let o = "", a = 1; + return i.transform && (o = jt({ left: A, top: e, width: t, height: r }, i.transform, n, i.transformOrigin)), i.opacity !== void 0 && (a = +i.opacity), { matrix: o, opacity: a }; +} +function ai({ id: A, content: e, filter: t, left: r, top: n, width: i, height: o, matrix: a, opacity: u2, image: l2, clipPathId: I, debug: E, shape: C, decorationShape: d2 }, p) { + let y = ""; + if (E && (y = H("rect", { x: r, y: n - o, width: i, height: o, fill: "transparent", stroke: "#575eff", "stroke-width": 1, transform: a || void 0, "clip-path": I ? `url(#${I})` : void 0 })), l2) { + let x2 = { href: l2, x: r, y: n, width: i, height: o, transform: a || void 0, "clip-path": I ? `url(#${I})` : void 0, style: p.filter ? `filter:${p.filter}` : void 0 }; + return [(t ? `${t}` : "") + H("image", { ...x2, opacity: u2 !== 1 ? u2 : void 0 }) + (d2 || "") + (t ? "" : "") + y, ""]; + } + let k = { x: r, y: n, width: i, height: o, "font-weight": p.fontWeight, "font-style": p.fontStyle, "font-size": p.fontSize, "font-family": p.fontFamily, "letter-spacing": p.letterSpacing || void 0, transform: a || void 0, "clip-path": I ? `url(#${I})` : void 0, style: p.filter ? `filter:${p.filter}` : void 0, "stroke-width": p.WebkitTextStrokeWidth ? `${p.WebkitTextStrokeWidth}px` : void 0, stroke: p.WebkitTextStrokeWidth ? p.WebkitTextStrokeColor : void 0, "stroke-linejoin": p.WebkitTextStrokeWidth ? "round" : void 0, "paint-order": p.WebkitTextStrokeWidth ? "stroke" : void 0 }; + return [(t ? `${t}` : "") + H("text", { ...k, fill: p.color, opacity: u2 !== 1 ? u2 : void 0 }, (0, import_escape_html.default)(e)) + (d2 || "") + (t ? "" : "") + y, C ? H("text", k, (0, import_escape_html.default)(e)) : ""]; +} +function pl(A, e, t) { + return A.replace(/([MA])([0-9.-]+),([0-9.-]+)/g, function(r, n, i, o) { + return n + (parseFloat(i) + e) + "," + (parseFloat(o) + t); + }); +} +function na({ id: A, width: e, height: t }, r, n = false) { + if (!r.shadowColor || !r.shadowOffset || typeof r.shadowRadius > "u") return ""; + let i = r.shadowColor.length, o = "", a = "", u2 = 0, l2 = e, I = 0, E = t; + for (let C = 0; C < i; C++) { + let d2 = r.shadowRadius[C] * r.shadowRadius[C] / 4; + if (u2 = Math.min(r.shadowOffset[C].width - d2, u2), l2 = Math.max(r.shadowOffset[C].width + d2 + e, l2), I = Math.min(r.shadowOffset[C].height - d2, I), E = Math.max(r.shadowOffset[C].height + d2 + t, E), n) { + let p = `satori_s-${A}-result-${C}`; + o += H("feGaussianBlur", { in: "SourceAlpha", stdDeviation: r.shadowRadius[C] / 2, result: `${p}-blur` }) + H("feOffset", { in: `${p}-blur`, dx: r.shadowOffset[C].width, dy: r.shadowOffset[C].height, result: `${p}-offset` }) + H("feFlood", { "flood-color": r.shadowColor[C], "flood-opacity": 1, result: `${p}-color` }) + H("feComposite", { in: `${p}-color`, in2: `${p}-offset`, operator: "in", result: i > 1 ? p : void 0 }); + } else o += H("feDropShadow", { dx: r.shadowOffset[C].width, dy: r.shadowOffset[C].height, stdDeviation: r.shadowRadius[C] / 2, "flood-color": r.shadowColor[C], "flood-opacity": 1, ...i > 1 ? { in: "SourceGraphic", result: `satori_s-${A}-result-${C}` } : {} }); + i > 1 && (a = H("feMergeNode", { in: `satori_s-${A}-result-${C}` }) + a); + } + return H("filter", { id: `satori_s-${A}`, x: (u2 / e * 100 * nn).toFixed(2) + "%", y: (I / t * 100 * nn).toFixed(2) + "%", width: ((l2 - u2) / e * 100 * nn).toFixed(2) + "%", height: ((E - I) / t * 100 * nn).toFixed(2) + "%" }, o + (a ? H("feMerge", {}, a) : "")); +} +function ia({ width: A, height: e, shape: t, opacity: r, id: n }, i) { + if (!i.boxShadow) return null; + let o = "", a = ""; + for (let u2 = i.boxShadow.length - 1; u2 >= 0; u2--) { + let l2 = "", I = i.boxShadow[u2]; + I.spreadRadius && I.inset && (I.spreadRadius = -I.spreadRadius); + let E = I.blurRadius * I.blurRadius / 4 + (I.spreadRadius || 0), C = Math.min(-E - (I.inset ? I.offsetX : 0), 0), d2 = Math.max(E + A - (I.inset ? I.offsetX : 0), A), p = Math.min(-E - (I.inset ? I.offsetY : 0), 0), y = Math.max(E + e - (I.inset ? I.offsetY : 0), e), k = `satori_s-${n}-${u2}`, x2 = `satori_ms-${n}-${u2}`, F = I.spreadRadius ? t.replace('stroke-width="0"', `stroke-width="${I.spreadRadius * 2}"`) : t; + l2 += H("mask", { id: x2, maskUnits: "userSpaceOnUse" }, H("rect", { x: 0, y: 0, width: i._viewportWidth || "100%", height: i._viewportHeight || "100%", fill: I.inset ? "#000" : "#fff" }) + F.replace('fill="#fff"', I.inset ? 'fill="#fff"' : 'fill="#000"').replace('stroke="#fff"', "")); + let b = F.replace(/d="([^"]+)"/, (v2, M) => 'd="' + pl(M, I.offsetX, I.offsetY) + '"').replace(/x="([^"]+)"/, (v2, M) => 'x="' + (parseFloat(M) + I.offsetX) + '"').replace(/y="([^"]+)"/, (v2, M) => 'y="' + (parseFloat(M) + I.offsetY) + '"'); + I.spreadRadius && I.spreadRadius < 0 && (l2 += H("mask", { id: x2 + "-neg", maskUnits: "userSpaceOnUse" }, b.replace('stroke="#fff"', 'stroke="#000"').replace(/stroke-width="[^"]+"/, `stroke-width="${-I.spreadRadius * 2}"`))), I.spreadRadius && I.spreadRadius < 0 && (b = H("g", { mask: `url(#${x2}-neg)` }, b)), l2 += H("defs", {}, H("filter", { id: k, x: `${C / A * 100}%`, y: `${p / e * 100}%`, width: `${(d2 - C) / A * 100}%`, height: `${(y - p) / e * 100}%` }, H("feGaussianBlur", { stdDeviation: I.blurRadius / 2, result: "b" }) + H("feFlood", { "flood-color": I.color, in: "SourceGraphic", result: "f" }) + H("feComposite", { in: "f", in2: "b", operator: I.inset ? "out" : "in" }))) + H("g", { mask: `url(#${x2})`, filter: `url(#${k})`, opacity: r }, b), I.inset ? a += l2 : o += l2; + } + return [o, a]; +} +function ml(A, e, t, r, n, i) { + let o = n / 2, a = Math.max(o, n * 1.25), u2 = []; + for (let E of t) { + if (E.y2 < i + o || E.y1 > r + o) continue; + let C = Math.max(A, E.x1 - a), d2 = Math.min(e, E.x2 + a); + if (C >= d2) continue; + if (u2.length === 0) { + u2.push([C, d2]); + continue; + } + let p = u2[u2.length - 1]; + C <= p[1] ? p[1] = Math.max(p[1], d2) : u2.push([C, d2]); + } + if (!u2.length) return [[A, e]]; + let l2 = [], I = A; + for (let [E, C] of u2) if (E > I && l2.push([I, E]), I = Math.max(I, C), I >= e) break; + return I < e && l2.push([I, e]), l2; +} +function gi({ width: A, left: e, top: t, ascender: r, clipPathId: n, matrix: i, glyphBoxes: o }, a) { + let { textDecorationColor: u2, textDecorationStyle: l2, textDecorationLine: I, textDecorationSkipInk: E, fontSize: C, color: d2 } = a; + if (!I || I === "none") return ""; + let p = Math.max(1, C * 0.1), y = I === "line-through" ? t + r * 0.7 : I === "underline" ? t + r * 1.1 : t, k = l2 === "dashed" ? `${p * 1.2} ${p * 2}` : l2 === "dotted" ? `0 ${p * 2}` : void 0, x2 = I === "underline" && (E || "auto") !== "none" && (o == null ? void 0 : o.length), F = t + r, b = x2 ? ml(e, e + A, o, y, p, F) : [[e, e + A]], v2 = l2 === "double" ? b.map(([M, L]) => H("line", { x1: M, y1: y + p + 1, x2: L, y2: y + p + 1, stroke: u2 || d2, "stroke-width": p, "stroke-dasharray": k, "stroke-linecap": l2 === "dotted" ? "round" : "square", transform: i })).join("") : ""; + return (n ? `` : "") + b.map(([M, L]) => H("line", { x1: M, y1: y, x2: L, y2: y, stroke: u2 || d2, "stroke-width": p, "stroke-dasharray": k, "stroke-linecap": l2 === "dotted" ? "round" : "square", transform: i })).join("") + v2 + (n ? "" : ""); +} +function ui(A) { + return A = A.replace("U+", "0x"), String.fromCodePoint(Number(A)); +} +function oa(A, e, t) { + let { fontSize: r, letterSpacing: n } = t, i = /* @__PURE__ */ new Map(); + function o(l2) { + let I = i.get(l2); + return I === void 0 && (I = A.measure(l2, { fontSize: r, letterSpacing: n }), i.set(l2, I)), I; + } + function a(l2) { + let I = 0; + for (let E of l2) e(E) ? I += r : I += o(E); + return I; + } + function u2(l2) { + return a(ge(l2, "grapheme")); + } + return { measureGrapheme: o, measureGraphemeArray: a, measureText: u2 }; +} +function sa(A, e, t) { + let { textTransform: r, whiteSpace: n, wordBreak: i } = e; + A = yl(A, r, t); + let { content: o, shouldCollapseTabsAndSpaces: a, allowSoftWrap: u2 } = Sl(A, n), { words: l2, requiredBreaks: I, allowBreakWord: E } = Dl(o, i), [C, d2] = wl(e, u2); + return { words: l2, requiredBreaks: I, allowSoftWrap: u2, allowBreakWord: E, processedContent: o, shouldCollapseTabsAndSpaces: a, lineLimit: C, blockEllipsis: d2 }; +} +function yl(A, e, t) { + return e === "uppercase" ? A = A.toLocaleUpperCase(t) : e === "lowercase" ? A = A.toLocaleLowerCase(t) : e === "capitalize" && (A = ge(A, "word", t).map((r) => ge(r, "grapheme", t).map((n, i) => i === 0 ? n.toLocaleUpperCase(t) : n).join("")).join("")), A; +} +function wl(A, e) { + let { textOverflow: t, lineClamp: r, WebkitLineClamp: n, WebkitBoxOrient: i, overflow: o, display: a } = A; + if (a === "block" && r) { + let [u2, l2 = Ut] = bl(r); + if (u2) return [u2, l2]; + } + return t === "ellipsis" && a === "-webkit-box" && i === "vertical" && ws(n) && n > 0 ? [n, Ut] : t === "ellipsis" && o === "hidden" && !e ? [1, Ut] : [1 / 0]; +} +function Dl(A, e) { + let t = ["break-all", "break-word"].includes(e), { words: r, requiredBreaks: n } = Ss(A, e); + return { words: r, requiredBreaks: n, allowBreakWord: t }; +} +function Sl(A, e) { + let t = ["pre", "pre-wrap", "pre-line"].includes(e), r = ["normal", "nowrap", "pre-line"].includes(e), n = !["pre", "nowrap"].includes(e); + return t || (A = A.replace(/\n/g, Qt)), r && (A = A.replace(/([ ]|\t)+/g, Qt).replace(/^[ ]|[ ]$/g, "")), { content: A, shouldCollapseTabsAndSpaces: r, allowSoftWrap: n }; +} +function bl(A) { + if (typeof A == "number") return [A]; + let e = /^(\d+)\s*"(.*)"$/, t = /^(\d+)\s*'(.*)'$/, r = e.exec(A), n = t.exec(A); + if (r) { + let i = +r[1], o = r[2]; + return [i, o]; + } else if (n) { + let i = +n[1], o = n[2]; + return [i, o]; + } + return []; +} +function kl(A) { + return vl.has(A); +} +function li(A) { + if (A === "transparent") return true; + let e = (0, import_parse_css_color2.default)(A); + return e ? e.alpha === 0 : false; +} +function aa(A) { + if (!A) return false; + let e = (0, import_parse_css_color2.default)(A); + if (!e) return false; + let [t, r, n, i] = e.values; + return t === 255 && r === 255 && n === 255 && (i === void 0 || i === 1); +} +async function* ci(A, e) { + let t = await rt(), { parentStyle: r, inheritedStyle: n, parent: i, font: o, id: a, isInheritingTransform: u2, debug: l2, embedFont: I, graphemeImages: E, locale: C, canLoadAdditionalAssets: d2 } = e, { textAlign: p, textIndent: y = 0, lineHeight: k, textWrap: x2, fontSize: F, filter: b, tabSize: v2 = 8, letterSpacing: M, _inheritedBackgroundClipTextPath: L, _inheritedBackgroundClipTextHasBackground: O, flexShrink: J } = r, { words: j, requiredBreaks: CA, allowSoftWrap: MA, allowBreakWord: dA, processedContent: sA, shouldCollapseTabsAndSpaces: vA, lineLimit: rA, blockEllipsis: iA } = sa(A, r, C), wA = xl(t, p); + i.insertChild(wA, i.getChildCount()), Ds(J) && i.setFlexShrink(1); + let aA = o.getEngine(F, k, r, C), FA = d2 ? ge(sA, "grapheme").filter((V) => !kl(V) && !aA.has(V)) : []; + yield FA.map((V) => ({ word: V, locale: C })), FA.length && (aA = o.getEngine(F, k, r, C)); + function kA(V) { + return !!(E && E[V]); + } + let { measureGrapheme: te, measureGraphemeArray: TA, measureText: xA } = oa(aA, kA, { fontSize: F, letterSpacing: M }), fA = Xr(v2) ? tA(v2, F, 1, r) : te(Qt) * v2, q = (V, Z) => { + if (V.length === 0) return { originWidth: 0, endingSpacesWidth: 0, text: V }; + let { index: nA, tabCount: $ } = Rl(V), IA = 0; + if ($ > 0) { + let DA = V.slice(0, nA), cA = V.slice(nA + $), gA = xA(DA), Ee = gA + Z; + IA = (fA === 0 ? gA : (Math.floor(Ee / fA) + $) * fA) + xA(cA); + } else IA = xA(V); + let lA = V.trimEnd() === V ? IA : xA(V.trimEnd()); + return { originWidth: IA, endingSpacesWidth: IA - lA, text: V }; + }, oA = [], BA = [], W = [], Y = [], OA = []; + function PA(V) { + let Z = 0, nA = 0, $ = -1, IA = 0, lA = 0, DA = 0, cA = 0; + oA = [], W = [0], Y = [], OA = []; + let gA = 0, Ee = 0; + for (; gA < j.length && Z < rA; ) { + let eA = j[gA], JA = CA[gA], RA = 0, { originWidth: ut, endingSpacesWidth: GA, text: YA } = q(eA, lA); + eA = YA, RA = ut; + let qA = GA; + JA && DA === 0 && (DA = aA.height(eA)); + let Qe = p === "justify", pA = gA && lA + RA > V + qA && MA; + if (dA && RA > V && (!lA || pA || JA)) { + let jA = ge(eA, "grapheme"); + j.splice(gA, 1, ...jA), lA > 0 && (oA.push(lA - Ee), BA.push(cA), Z++, IA += DA, lA = 0, DA = 0, cA = 0, W.push(1), $ = -1), Ee = qA; + continue; + } + if (JA || pA) vA && eA === Qt && (RA = 0), oA.push(lA - Ee), BA.push(cA), Z++, IA += DA, lA = RA, DA = RA ? Math.round(aA.height(eA)) : 0, cA = RA ? Math.round(aA.baseline(eA)) : 0, W.push(1), $ = -1, JA || (nA = Math.max(nA, V)); + else { + lA += RA; + let jA = Math.round(aA.height(eA)); + jA > DA && (DA = jA, cA = Math.round(aA.baseline(eA))), Qe && W[W.length - 1]++; + } + Qe && $++, nA = Math.max(nA, lA); + let ZA = lA - RA; + if (RA === 0) OA.push({ y: IA, x: ZA, width: 0, line: Z, lineIndex: $, isImage: false }); + else { + let jA = ge(eA, "word"); + for (let Se = 0; Se < jA.length; Se++) { + let be = jA[Se], $A = 0, Ce = false; + kA(be) ? ($A = F, Ce = true) : !I && be.length > 1 ? $A = xA(be) : $A = te(be), Y.push(be), OA.push({ y: IA, x: ZA, width: $A, line: Z, lineIndex: $, isImage: Ce }), ZA += $A; + } + } + gA++, Ee = qA; + } + return lA && (Z < rA && (IA += DA), Z++, oA.push(lA), BA.push(cA)), { width: nA, height: IA }; + } + let WA = { width: 0, height: 0 }; + wA.setMeasureFunc((V) => { + let { width: Z, height: nA } = PA(V); + if (x2 === "balance") { + let IA = Z / 2, lA = Z, DA = Z; + for (; IA + 1 < lA; ) { + DA = (IA + lA) / 2; + let { height: gA } = PA(DA); + gA > nA ? IA = DA : lA = DA; + } + PA(lA); + let cA = Math.ceil(lA); + return WA = { width: cA, height: nA }, { width: cA, height: nA }; + } + if (x2 === "pretty" && oA[oA.length - 1] < Z / 3) { + let DA = Z * 0.9, cA = PA(DA); + if (cA.height <= nA * 1.3) return WA = { width: Z, height: cA.height }, { width: Z, height: cA.height }; + } + let $ = Math.ceil(Z); + return WA = { width: $, height: nA }, { width: $, height: nA }; + }); + let [uA, hA] = yield, Ie = "", le = "", LA = n._inheritedClipPathId, re = n._inheritedMaskId, { left: ce, top: ye, width: Pe, height: Ye } = wA.getComputedLayout(), Ge = typeof y == "string" ? tA(y, F, Pe, r, true) || 0 : y, Ue = i.getComputedWidth() - i.getComputedPadding(t.EDGE_LEFT) - i.getComputedPadding(t.EDGE_RIGHT) - i.getComputedBorder(t.EDGE_LEFT) - i.getComputedBorder(t.EDGE_RIGHT), we = uA + ce, fe = hA + ye, { matrix: se, opacity: qe } = ra({ left: ce, top: ye, width: Pe, height: Ye, isInheritingTransform: u2 }, r), zA = ""; + if (r.textShadowOffset) { + let { textShadowColor: V, textShadowOffset: Z, textShadowRadius: nA } = r; + zA = na({ width: WA.width, height: WA.height, id: a }, { shadowColor: V, shadowOffset: Z, shadowRadius: nA }, li(r.color) || O && aa(r.color)), zA = H("defs", {}, zA); + } + let ne = "", ie = "", Be = "", De = -1, KA = {}, XA = {}, _A = null, oe = 0; + for (let V = 0; V < Y.length; V++) { + let Z = OA[V], nA = OA[V + 1]; + if (!Z) continue; + let $ = Y[V], IA = null, lA = false, DA = E ? E[$] : null, cA = Z.y, gA = Z.x, Ee = Z.width, eA = Z.line, JA = r.textDecorationLine === "underline" && (r.textDecorationSkipInk || "auto") !== "none"; + if (eA === De) continue; + let RA = false; + if (eA === 0 && Ge !== 0 && (gA += Ge), oA.length > 1) { + let pA = Pe - oA[eA]; + if (p === "right" || p === "end") gA += pA; + else if (p === "center") gA += pA / 2; + else if (p === "justify" && eA < oA.length - 1) { + let mA = W[eA], ZA = mA > 1 ? pA / (mA - 1) : 0; + gA += ZA * Z.lineIndex, RA = true; + } + I && (gA = Math.round(gA)); + } + let ut = BA[eA], GA = aA.baseline($), YA = aA.height($), qA = ut - GA, Qe = (pA) => !JA || r.textDecorationLine !== "underline" ? void 0 : { underlineY: fe + pA + qA + GA + GA * 0.1, strokeWidth: Math.max(1, F * 0.1) }; + if (KA[eA] || (KA[eA] = { left: gA, top: fe + cA + qA, ascender: GA, width: RA ? Pe : oA[eA] }), rA !== 1 / 0) { + let be = function($A, Ce) { + let It = ge(Ce, "grapheme", C), et = "", wt = 0; + for (let Dt of It) { + let lt = $A + TA([et + Dt]); + if (et && lt + mA > Ue) break; + et += Dt, wt = lt; + } + return { subset: et, resolvedWidth: wt }; + }, pA = iA, mA = te(iA); + mA > Ue && (pA = Ut, mA = te(pA)); + let ZA = te(Qt), jA = eA < oA.length - 1; + if (eA + 1 === rA && (jA || oA[eA] > Ue)) { + if (gA + Ee + mA + ZA > Ue) { + let { subset: $A, resolvedWidth: Ce } = be(gA, $); + $ = $A + pA, De = eA, KA[eA].width = Math.max(0, Ce - KA[eA].left), lA = true; + } else if (nA && nA.line !== eA) if (p === "center") { + let { subset: $A, resolvedWidth: Ce } = be(gA, $); + $ = $A + pA, De = eA, KA[eA].width = Math.max(0, Ce - KA[eA].left), lA = true; + } else { + let $A = Y[V + 1], { subset: Ce, resolvedWidth: It } = be(Ee + gA, $A); + $ = $ + Ce + pA, De = eA, KA[eA].width = Math.max(0, It - KA[eA].left), lA = true; + } + } + } + if (DA) cA += 0; + else if (I) { + if (!$.includes(Ii) && !ms.includes($) && Y[V + 1] && nA && !nA.isImage && cA === nA.y && !lA) { + _A === null && (oe = gA), _A = _A === null ? $ : _A + $; + continue; + } + let pA = _A === null ? $ : _A + $, mA = _A === null ? gA : oe, ZA = Z.width + gA - mA, jA = Qe(cA), Se = aA.getSVG(pA.replace(/(\t)+/g, ""), { fontSize: F, left: we + mA, top: fe + cA + GA + qA, letterSpacing: M }, jA); + IA = Se.path, JA && Se.boxes && Se.boxes.length && (XA[eA] || (XA[eA] = [])).push(...Se.boxes), _A = null, l2 && (Be += H("rect", { x: we + mA, y: fe + cA + qA, width: ZA, height: YA, fill: "transparent", stroke: "#575eff", "stroke-width": 1, transform: se || void 0, "clip-path": LA ? `url(#${LA})` : void 0 }) + H("line", { x1: we + gA, x2: we + gA + Z.width, y1: fe + cA + qA + GA, y2: fe + cA + qA + GA, stroke: "#14c000", "stroke-width": 1, transform: se || void 0, "clip-path": LA ? `url(#${LA})` : void 0 })); + } else if (cA += GA + qA, JA && !DA) { + let pA = Qe(cA), mA = aA.getSVG($.replace(/(\t)+/g, ""), { fontSize: F, left: we + gA, top: fe + cA, letterSpacing: M }, pA); + mA.boxes && mA.boxes.length && (XA[eA] || (XA[eA] = [])).push(...mA.boxes); + } + if (IA !== null) ie += IA + " "; + else { + let [pA, mA] = ai({ content: $, filter: zA, id: a, left: we + gA, top: fe + cA, width: Ee, height: YA, matrix: se, opacity: qe, image: DA, clipPathId: LA, debug: l2, shape: !!L }, r); + Ie += pA, le += mA; + } + if (lA) break; + } + if (r.textDecorationLine && (ne = Object.entries(KA).map(([V, Z]) => { + if (!Z) return ""; + let nA = XA[V] || []; + return gi({ left: we + Z.left, top: Z.top, width: Z.width, ascender: Z.ascender, clipPathId: LA, matrix: se, glyphBoxes: nA }, r); + }).join("")), ie) { + let V = (!li(r.color) || zA) && qe !== 0 ? `` + H("path", { fill: zA && (li(r.color) || O && aa(r.color)) ? "black" : r.color, d: ie, transform: se || void 0, opacity: qe !== 1 ? qe : void 0, style: b ? `filter:${b}` : void 0, "stroke-width": n.WebkitTextStrokeWidth ? `${n.WebkitTextStrokeWidth}px` : void 0, stroke: n.WebkitTextStrokeWidth ? n.WebkitTextStrokeColor : void 0, "stroke-linejoin": n.WebkitTextStrokeWidth ? "round" : void 0, "paint-order": n.WebkitTextStrokeWidth ? "stroke" : void 0 }) + "" : ""; + L && (le = H("path", { d: ie, transform: se || void 0 })), Ie += (zA ? zA + H("g", { filter: `url(#satori_s-${a})` }, V + ne) : V + ne) + Be; + } else ne && (Ie += zA ? H("g", { filter: `url(#satori_s-${a})` }, ne) : ne); + return le && (r._inheritedBackgroundClipTextPath.value += le), Ie; +} +function xl(A, e) { + let t = A.Node.create(); + return t.setAlignItems(A.ALIGN_BASELINE), t.setJustifyContent(ue(e, { left: A.JUSTIFY_FLEX_START, right: A.JUSTIFY_FLEX_END, center: A.JUSTIFY_CENTER, justify: A.JUSTIFY_SPACE_BETWEEN, start: A.JUSTIFY_FLEX_START, end: A.JUSTIFY_FLEX_END }, A.JUSTIFY_FLEX_START, "textAlign")), t; +} +function Rl(A) { + let e = /(\t)+/.exec(A); + return e ? { index: e.index, tabCount: e[0].length } : { index: null, tabCount: 0 }; +} +function on(A, e, t, r, n) { + let i = [], o = e.at(-1), a = o && o.offset && o.offset.unit === "%" && r ? +o.offset.value : 100; + for (let E of e) { + let { color: C } = E; + if (!i.length && (i.push({ offset: 0, color: C }), !E.offset || E.offset.value === "0")) continue; + let d2 = typeof E.offset > "u" ? void 0 : E.offset.unit === "%" ? +E.offset.value / a : Number(tA(`${E.offset.value}${E.offset.unit}`, t.fontSize, A, t, true)) / A; + i.push({ offset: d2, color: C }); + } + i.length || i.push({ offset: 0, color: "transparent" }); + let u2 = i[i.length - 1]; + u2.offset !== 1 && (typeof u2.offset > "u" ? u2.offset = 1 : r ? i[i.length - 1] = { offset: 1, color: u2.color } : i.push({ offset: 1, color: u2.color })); + let l2 = 0, I = 1; + for (let E = 0; E < i.length; E++) if (typeof i[E].offset > "u") { + for (I < E && (I = E); typeof i[I].offset > "u"; ) I++; + i[E].offset = (i[I].offset - i[l2].offset) / (I - l2) * (E - l2) + i[l2].offset; + } else l2 = E; + return n === "mask" ? i.map((E) => { + let C = (0, import_parse_css_color3.default)(E.color); + return C ? C.alpha === 0 ? { ...E, color: "rgba(0, 0, 0, 1)" } : { ...E, color: `rgba(255, 255, 255, ${C.alpha})` } : E; + }) : i; +} +function ua({ id: A, width: e, height: t, repeatX: r, repeatY: n }, i, o, a, u2, l2) { + let I = P(i), [E, C] = o, d2 = i.startsWith("repeating"), p, y, k; + if (I.orientation.type === "directional") p = Ll(I.orientation.value), y = Math.sqrt(Math.pow((p.x2 - p.x1) * E, 2) + Math.pow((p.y2 - p.y1) * C, 2)); + else if (I.orientation.type === "angular") { + let { length: M, ...L } = Gl(Pn(`${I.orientation.value.value}${I.orientation.value.unit}`) / 180 * Math.PI, E, C); + y = M, p = L; + } + k = d2 ? Ul(I.stops, y, p, u2) : p; + let x2 = on(d2 ? Fl(I.stops, y) : y, I.stops, u2, d2, l2), F = `satori_bi${A}`, b = `satori_pattern_${A}`, v2 = H("pattern", { id: b, x: a[0] / e, y: a[1] / t, width: r ? E / e : "1", height: n ? C / t : "1", patternUnits: "objectBoundingBox" }, H("linearGradient", { id: F, ...k, spreadMethod: d2 ? "repeat" : "pad" }, x2.map((M) => H("stop", { offset: (M.offset ?? 0) * 100 + "%", "stop-color": M.color })).join("")) + H("rect", { x: 0, y: 0, width: E, height: C, fill: `url(#${F})` })); + return [b, v2]; +} +function Fl(A, e) { + let t = A[A.length - 1], { offset: r } = t; + return r ? r.unit === "%" ? Number(r.value) / 100 * e : Number(r.value) : e; +} +function Ll(A) { + let e = 0, t = 0, r = 0, n = 0; + return A.includes("top") ? t = 1 : A.includes("bottom") && (n = 1), A.includes("left") ? e = 1 : A.includes("right") && (r = 1), !e && !r && !t && !n && (t = 1), { x1: e, y1: t, x2: r, y2: n }; +} +function Gl(A, e, t) { + let r = Math.pow(t / e, 2); + A = (A % (Math.PI * 2) + Math.PI * 2) % (Math.PI * 2); + let n, i, o, a, u2, l2, I, E, C = (d2) => { + if (d2 === 0) { + n = 0, i = t, o = 0, a = 0, u2 = t; + return; + } else if (d2 === Math.PI / 2) { + n = 0, i = 0, o = e, a = 0, u2 = e; + return; + } + if (d2 > 0 && d2 < Math.PI / 2) { + n = (r * e / 2 / Math.tan(d2) - t / 2) / (Math.tan(d2) + r / Math.tan(d2)), i = Math.tan(d2) * n + t, o = Math.abs(e / 2 - n) + e / 2, a = t / 2 - Math.abs(i - t / 2), u2 = Math.sqrt(Math.pow(o - n, 2) + Math.pow(a - i, 2)), I = (e / 2 / Math.tan(d2) - t / 2) / (Math.tan(d2) + 1 / Math.tan(d2)), E = Math.tan(d2) * I + t, u2 = 2 * Math.sqrt(Math.pow(e / 2 - I, 2) + Math.pow(t / 2 - E, 2)); + return; + } else if (d2 > Math.PI / 2 && d2 < Math.PI) { + n = (t / 2 + r * e / 2 / Math.tan(d2)) / (Math.tan(d2) + r / Math.tan(d2)), i = Math.tan(d2) * n, o = Math.abs(e / 2 - n) + e / 2, a = t / 2 + Math.abs(i - t / 2), I = (e / 2 / Math.tan(d2) + t / 2) / (Math.tan(d2) + 1 / Math.tan(d2)), E = Math.tan(d2) * I, u2 = 2 * Math.sqrt(Math.pow(e / 2 - I, 2) + Math.pow(t / 2 - E, 2)); + return; + } else d2 >= Math.PI && (C(d2 - Math.PI), l2 = n, n = o, o = l2, l2 = i, i = a, a = l2); + }; + return C(A), { x1: n / e, y1: i / t, x2: o / e, y2: a / t, length: u2 }; +} +function Ul(A, e, t, r) { + let { x1: n, x2: i, y1: o, y2: a } = t, u2 = A[0].offset ? A[0].offset.unit === "%" ? Number(A[0].offset.value) / 100 : tA(`${A[0].offset.value}${A[0].offset.unit}`, r.fontSize, e, r, true) / e : 0, l2 = A.at(-1).offset ? A.at(-1).offset.unit === "%" ? Number(A.at(-1).offset.value) / 100 : tA(`${A.at(-1).offset.value}${A.at(-1).offset.unit}`, r.fontSize, e, r, true) / e : 1, I = (i - n) * u2 + n, E = (a - o) * u2 + o, C = (i - n) * l2 + n, d2 = (a - o) * l2 + o; + return { x1: I, y1: E, x2: C, y2: d2 }; +} +function la({ id: A, width: e, height: t, repeatX: r, repeatY: n }, i, o, a, u2, l2) { + var sA; + let { shape: I, stops: E, position: C, size: d2, repeating: p } = K(i), [y, k] = o, x2 = y / 2, F = k / 2, b = Tl(C.x, C.y, y, k, u2.fontSize, u2); + x2 = b.x, F = b.y; + let v2 = Ol(e, E, p, u2), M = on(v2, E, u2, p, l2), L = `satori_radial_${A}`, O = `satori_pattern_${A}`, J = `satori_mask_${A}`, j = _l(I, d2, u2.fontSize, { x: x2, y: F }, [y, k], u2, p), CA = Pl(I, u2.fontSize, E, [y, k], u2, p, j), MA = H("pattern", { id: O, x: a[0] / e, y: a[1] / t, width: r ? y / e : "1", height: n ? k / t : "1", patternUnits: "objectBoundingBox" }, H("radialGradient", { id: L, ...CA }, M.map((vA) => H("stop", { offset: vA.offset || 0, "stop-color": vA.color })).join("")) + H("mask", { id: J }, H("rect", { x: 0, y: 0, width: y, height: k, fill: "#fff" })) + H("rect", { x: 0, y: 0, width: y, height: k, fill: ((sA = M.at(-1)) == null ? void 0 : sA.color) || "transparent" }) + H(I, { cx: x2, cy: F, width: y, height: k, ...j, fill: `url(#${L})`, mask: `url(#${J})` })); + return [O, MA]; +} +function Ol(A, e, t, r) { + if (!t) return A; + let n = e.at(-1); + return !n || !n.offset || n.offset.unit === "%" ? A : tA(`${n.offset.value}${n.offset.unit}`, +r.fontSize, A, r, true); +} +function Tl(A, e, t, r, n, i) { + let o = { x: t / 2, y: r / 2 }; + return A.type === "keyword" ? Object.assign(o, Ia(A.value, t, r, "x")) : o.x = tA(`${A.value.value}${A.value.unit}`, n, t, i, true) ?? t / 2, e.type === "keyword" ? Object.assign(o, Ia(e.value, t, r, "y")) : o.y = tA(`${e.value.value}${e.value.unit}`, n, r, i, true) ?? r / 2, o; +} +function Ia(A, e, t, r) { + switch (A) { + case "center": + return { [r]: r === "x" ? e / 2 : t / 2 }; + case "left": + return { x: 0 }; + case "top": + return { y: 0 }; + case "right": + return { x: e }; + case "bottom": + return { y: t }; + } +} +function Pl(A, e, t, [r, n], i, o, a) { + let { r: u2, rx: l2, ratio: I = 1 } = a; + if (!o) return { spreadMethod: "pad" }; + let E = t.at(-1), C = A === "circle" ? u2 * 2 : l2 * 2; + return { spreadMethod: "repeat", cx: "50%", cy: "50%", r: E.offset.unit === "%" ? `${Number(E.offset.value) * Math.min(n / r, 1) / I}%` : Number(tA(`${E.offset.value}${E.offset.unit}`, e, r, i, true) / C) }; +} +function _l(A, e, t, r, n, i, o) { + let [a, u2] = n, { x: l2, y: I } = r, E = {}, C = 0, d2 = 0; + if (Jl(e)) { + if (e.some((p) => p.value.value.startsWith("-"))) throw new Error("disallow setting negative values to the size of the shape. Check https://w3c.github.io/csswg-drafts/css-images/#valdef-rg-size-length-0"); + return A === "circle" ? Object.assign(E, { r: Number(tA(`${e[0].value.value}${e[0].value.unit}`, t, a, i, true)) }) : Object.assign(E, { rx: Number(tA(`${e[0].value.value}${e[0].value.unit}`, t, a, i, true)), ry: Number(tA(`${e[1].value.value}${e[1].value.unit}`, t, u2, i, true)) }), sn(E, a, u2, l2, I, o, A), E; + } + switch (e[0].value) { + case "farthest-corner": + C = Math.max(Math.abs(a - l2), Math.abs(l2)), d2 = Math.max(Math.abs(u2 - I), Math.abs(I)); + break; + case "closest-corner": + C = Math.min(Math.abs(a - l2), Math.abs(l2)), d2 = Math.min(Math.abs(u2 - I), Math.abs(I)); + break; + case "farthest-side": + return A === "circle" ? E.r = Math.max(Math.abs(a - l2), Math.abs(l2), Math.abs(u2 - I), Math.abs(I)) : (E.rx = Math.max(Math.abs(a - l2), Math.abs(l2)), E.ry = Math.max(Math.abs(u2 - I), Math.abs(I))), sn(E, a, u2, l2, I, o, A), E; + case "closest-side": + return A === "circle" ? E.r = Math.min(Math.abs(a - l2), Math.abs(l2), Math.abs(u2 - I), Math.abs(I)) : (E.rx = Math.min(Math.abs(a - l2), Math.abs(l2)), E.ry = Math.min(Math.abs(u2 - I), Math.abs(I))), sn(E, a, u2, l2, I, o, A), E; + } + return A === "circle" ? E.r = Math.sqrt(C * C + d2 * d2) : Object.assign(E, ca(C, d2)), sn(E, a, u2, l2, I, o, A), E; +} +function sn(A, e, t, r, n, i, o) { + if (i) if (o === "ellipse") { + let a = Math.max(Math.abs(e - r), Math.abs(r)), u2 = Math.max(Math.abs(t - n), Math.abs(n)), { rx: l2, ry: I } = ca(a, u2); + A.ratio = Math.max(l2 / A.rx, I / A.ry), A.ratio > 1 && (A.rx *= A.ratio, A.ry *= A.ratio); + } else { + let a = Math.max(Math.abs(e - r), Math.abs(r)), u2 = Math.max(Math.abs(t - n), Math.abs(n)), l2 = Math.sqrt(a * a + u2 * u2); + A.ratio = l2 / A.r, A.ratio > 1 && (A.r = l2); + } +} +function ca(A, e) { + let t = e !== 0 ? A / e : 1; + if (A === 0) return { rx: 0, ry: 0 }; + { + let r = Math.sqrt(A * A + e * e * t * t) / t; + return { ry: r, rx: r * t }; + } +} +function Jl(A) { + return !A.some((e) => e.type === "keyword"); +} +function Bi(A, e) { + return typeof A == "string" && A.endsWith("%") ? e * parseFloat(A) / 100 : +A; +} +function Wl(A, e, t, r, n) { + if (!r || !n) return [e, t]; + if (A === "cover") { + let i = e / r, o = t / n, a = Math.max(i, o); + return [r * a, n * a]; + } + if (A === "contain") { + let i = e / r, o = t / n, a = Math.min(i, o); + return [r * a, n * a]; + } + if (A === "auto" || A.includes("auto")) { + let i = A.split(" "), o = i[0] || "auto", a = i[1] || i[0] || "auto", u2 = r, l2 = n; + if (o === "auto" && a !== "auto") { + let I = Bi(a, t); + l2 = I, u2 = r / n * I; + } else if (a === "auto" && o !== "auto") { + let I = Bi(o, e); + u2 = I, l2 = n / r * I; + } + return [u2, l2]; + } + return [e, t]; +} +function fi(A, { x: e, y: t, defaultX: r, defaultY: n }) { + return (A ? A.split(" ").map((i) => { + try { + let o = new He(i); + return o.type === "length" || o.type === "number" ? o.value : o.value + o.unit; + } catch { + return null; + } + }).filter((i) => i !== null) : [r, n]).map((i, o) => Bi(i, [e, t][o])); +} +async function $t({ id: A, width: e, height: t, left: r, top: n }, { image: i, size: o, position: a, repeat: u2 }, l2, I) { + u2 = u2 || "repeat", I = I || "background"; + let E = u2 === "repeat-x" || u2 === "repeat", C = u2 === "repeat-y" || u2 === "repeat", d2 = o && (o === "cover" || o === "contain" || o === "auto" || o.includes("auto")), p = i.startsWith("linear-gradient(") || i.startsWith("repeating-linear-gradient(") || i.startsWith("radial-gradient(") || i.startsWith("repeating-radial-gradient("), y = d2 && p ? [e, t] : d2 ? [0, 0] : fi(o, { x: e, y: t, defaultX: e, defaultY: t }), k = fi(a, { x: e, y: t, defaultX: 0, defaultY: 0 }); + if (i.startsWith("linear-gradient(") || i.startsWith("repeating-linear-gradient(")) return ua({ id: A, width: e, height: t, repeatX: E, repeatY: C }, i, y, k, l2, I); + if (i.startsWith("radial-gradient(") || i.startsWith("repeating-radial-gradient(")) return la({ id: A, width: e, height: t, repeatX: E, repeatY: C }, i, y, k, l2, I); + if (i.startsWith("url(")) { + let [x2, F, b] = await Gt(i.slice(4, -1)), v2, M; + if (d2) { + let [L, O] = Wl(o, e, t, F, b); + v2 = L, M = O; + } else { + let L = fi(o, { x: e, y: t, defaultX: 0, defaultY: 0 }); + v2 = I === "mask" ? F || L[0] : L[0] || F, M = I === "mask" ? b || L[1] : L[1] || b; + } + return [`satori_bi${A}`, H("pattern", { id: `satori_bi${A}`, patternContentUnits: "userSpaceOnUse", patternUnits: "userSpaceOnUse", x: k[0] + r, y: k[1] + n, width: E ? v2 : "100%", height: C ? M : "100%" }, H("image", { x: 0, y: 0, width: v2, height: M, preserveAspectRatio: "none", href: x2 }))]; + } + if ((0, import_parse_css_color4.default)(i)) { + let x2 = (0, import_parse_css_color4.default)(i), [F, b, v2, M] = x2.values, O = `rgba(${F},${b},${v2},${M !== void 0 ? M : 1})`; + return [`satori_bi${A}`, H("pattern", { id: `satori_bi${A}`, patternContentUnits: "userSpaceOnUse", patternUnits: "userSpaceOnUse", x: r, y: n, width: e, height: t }, H("rect", { x: 0, y: 0, width: e, height: t, fill: O }))]; + } + throw new Error(`Invalid background image: "${i}"`); +} +function Kl([A, e]) { + return Math.round(A * 1e3) === 0 && Math.round(e * 1e3) === 0 ? 0 : Math.round(A * e / Math.sqrt(A * A + e * e) * 1e3) / 1e3; +} +function an(A, e, t) { + return t < A + e && (t / 2 < A && t / 2 < e ? A = e = t / 2 : t / 2 < A ? A = t - e : t / 2 < e && (e = t - A)), [A, e]; +} +function gn(A) { + A[0] = A[1] = Math.min(A[0], A[1]); +} +function un(A, e, t, r, n) { + if (typeof A == "string") { + let i = A.split(" ").map((a) => a.trim()), o = !i[1] && !i[0].endsWith("%"); + return i[1] = i[1] || i[0], [o, [Math.min(tA(i[0], r, e, n, true), e), Math.min(tA(i[1], r, t, n, true), t)]]; + } + return typeof A == "number" ? [true, [Math.min(A, e), Math.min(A, t)]] : [true, void 0]; +} +function Ba({ id: A, borderRadiusPath: e, borderType: t, left: r, top: n, width: i, height: o }, a) { + let u2 = `satori_brc-${A}`; + return [H("clipPath", { id: u2 }, H(t, { x: r, y: n, width: i, height: o, d: e || void 0 })), u2]; +} +function it({ left: A, top: e, width: t, height: r }, n, i) { + let { borderTopLeftRadius: o, borderTopRightRadius: a, borderBottomLeftRadius: u2, borderBottomRightRadius: l2, fontSize: I } = n, E, C, d2, p; + if ([E, o] = un(o, t, r, I, n), [C, a] = un(a, t, r, I, n), [d2, u2] = un(u2, t, r, I, n), [p, l2] = un(l2, t, r, I, n), !i && !In(o) && !In(a) && !In(u2) && !In(l2)) return ""; + o ||= [0, 0], a ||= [0, 0], u2 ||= [0, 0], l2 ||= [0, 0], [o[0], a[0]] = an(o[0], a[0], t), [u2[0], l2[0]] = an(u2[0], l2[0], t), [o[1], u2[1]] = an(o[1], u2[1], r), [a[1], l2[1]] = an(a[1], l2[1], r), E && gn(o), C && gn(a), d2 && gn(u2), p && gn(l2); + let y = []; + y[0] = [a, a], y[1] = [l2, [-l2[0], l2[1]]], y[2] = [u2, [-u2[0], -u2[1]]], y[3] = [o, [o[0], -o[1]]]; + let k = `h${t - o[0] - a[0]} a${y[0][0]} 0 0 1 ${y[0][1]}`, x2 = `v${r - a[1] - l2[1]} a${y[1][0]} 0 0 1 ${y[1][1]}`, F = `h${l2[0] + u2[0] - t} a${y[2][0]} 0 0 1 ${y[2][1]}`, b = `v${u2[1] + o[1] - r} a${y[3][0]} 0 0 1 ${y[3][1]}`; + if (i) { + let M = function(dA) { + let sA = Kl([o, a, l2, u2][dA]); + return dA === 0 ? [[A + o[0] - sA, e + o[1] - sA], [A + o[0], e]] : dA === 1 ? [[A + t - a[0] + sA, e + a[1] - sA], [A + t, e + a[1]]] : dA === 2 ? [[A + t - l2[0] + sA, e + r - l2[1] + sA], [A + t - l2[0], e + r]] : [[A + u2[0] - sA, e + r - u2[1] + sA], [A, e + r - u2[1]]]; + }, v2 = i.indexOf(false); + if (!i.includes(true)) throw new Error("Invalid `partialSides`."); + if (v2 === -1) v2 = 0; + else for (; !i[v2]; ) v2 = (v2 + 1) % 4; + let L = "", O = M(v2), J = `M${O[0]} A${y[(v2 + 3) % 4][0]} 0 0 1 ${O[1]}`, j = 0; + for (; j < 4 && i[(v2 + j) % 4]; j++) L += J + " ", J = [k, x2, F, b][(v2 + j) % 4]; + let CA = (v2 + j) % 4; + L += J.split(" ")[0]; + let MA = M(CA); + return L += ` A${y[(CA + 3) % 4][0]} 0 0 1 ${MA[0]}`, L; + } + return `M${A + o[0]},${e} ${k} ${x2} ${F} ${b}`; +} +function Ea(A, e, t) { + return t[A + "Width"] === t[e + "Width"] && t[A + "Style"] === t[e + "Style"] && t[A + "Color"] === t[e + "Color"]; +} +function Qa({ id: A, currentClipPathId: e, borderPath: t, borderType: r, left: n, top: i, width: o, height: a }, u2) { + if (!(u2.borderTopWidth || u2.borderRightWidth || u2.borderBottomWidth || u2.borderLeftWidth)) return null; + let I = `satori_bc-${A}`; + return [H("clipPath", { id: I, "clip-path": e ? `url(#${e})` : void 0 }, H(r, { x: n, y: i, width: o, height: a, d: t || void 0 })), I]; +} +function Ar({ left: A, top: e, width: t, height: r, props: n, asContentMask: i, maskBorderOnly: o }, a) { + let u2 = ["borderTop", "borderRight", "borderBottom", "borderLeft"]; + if (!i && !u2.some((d2) => a[d2 + "Width"])) return ""; + let l2 = "", I = 0; + for (; I > 0 && Ea(u2[I], u2[(I + 3) % 4], a); ) I = (I + 3) % 4; + let E = [false, false, false, false], C = []; + for (let d2 = 0; d2 < 4; d2++) { + let p = (I + d2) % 4, y = (I + d2 + 1) % 4, k = u2[p], x2 = u2[y]; + if (E[p] = true, C = [a[k + "Width"], a[k + "Style"], a[k + "Color"], k], !Ea(k, x2, a)) { + let F = (C[0] || 0) + (i && !o && a[k.replace("border", "padding")] || 0); + F && (l2 += H("path", { width: t, height: r, ...n, fill: "none", stroke: i ? "#000" : C[2], "stroke-width": F * 2, "stroke-dasharray": !i && C[1] === "dashed" ? F * 2 + " " + F : void 0, d: it({ left: A, top: e, width: t, height: r }, a, E) })), E = [false, false, false, false]; + } + } + if (E.some(Boolean)) { + let d2 = (C[0] || 0) + (i && !o && a[C[3].replace("border", "padding")] || 0); + d2 && (l2 += H("path", { width: t, height: r, ...n, fill: "none", stroke: i ? "#000" : C[2], "stroke-width": d2 * 2, "stroke-dasharray": !i && C[1] === "dashed" ? d2 * 2 + " " + d2 : void 0, d: it({ left: A, top: e, width: t, height: r }, a, E) })); + } + return l2; +} +function Ei({ id: A, left: e, top: t, width: r, height: n, matrix: i, borderOnly: o }, a) { + let u2 = (a.borderLeftWidth || 0) + (o ? 0 : a.paddingLeft || 0), l2 = (a.borderTopWidth || 0) + (o ? 0 : a.paddingTop || 0), I = (a.borderRightWidth || 0) + (o ? 0 : a.paddingRight || 0), E = (a.borderBottomWidth || 0) + (o ? 0 : a.paddingBottom || 0), C = { x: e + u2, y: t + l2, width: r - u2 - I, height: n - l2 - E }; + return H("mask", { id: A }, H("rect", { ...C, fill: "#fff", transform: a.overflow === "hidden" && a.transform && i ? i : void 0, mask: a._inheritedMaskId ? `url(#${a._inheritedMaskId})` : void 0 }) + Ar({ left: e, top: t, width: r, height: n, props: { transform: i || void 0 }, asContentMask: true, maskBorderOnly: o }, a)); +} +function pa({ width: A, height: e }, t, r) { + function n(l2) { + let I = l2.match(er.circle); + if (!I) return null; + let [, E] = I, [C, d2 = ""] = E.split("at").map((k) => k.trim()), { x: p, y } = ha(d2, A, e); + return { type: "circle", r: tA(C, r.fontSize, Math.sqrt(Math.pow(A, 2) + Math.pow(e, 2)) / Math.sqrt(2), r, true), cx: tA(p, r.fontSize, A, r, true), cy: tA(y, r.fontSize, e, r, true) }; + } + function i(l2) { + let I = l2.match(er.ellipse); + if (!I) return null; + let [, E] = I, [C, d2 = ""] = E.split("at").map((F) => F.trim()), [p, y] = C.split(" "), { x: k, y: x2 } = ha(d2, A, e); + return { type: "ellipse", rx: tA(p || "50%", r.fontSize, A, r, true), ry: tA(y || "50%", r.fontSize, e, r, true), cx: tA(k, r.fontSize, A, r, true), cy: tA(x2, r.fontSize, e, r, true) }; + } + function o(l2) { + let I = l2.match(er.path); + if (!I) return null; + let [E, C] = da(I[1]); + return { type: "path", d: C, "fill-rule": E }; + } + function a(l2) { + let I = l2.match(er.polygon); + if (!I) return null; + let [E, C] = da(I[1]); + return { type: "polygon", "fill-rule": E, points: C.split(",").map((d2) => d2.split(" ").map((p, y) => tA(p, r.fontSize, y === 0 ? A : e, r, true)).join(" ")).join(",") }; + } + function u2(l2) { + let I = l2.match(er.inset); + if (!I) return null; + let [E, C] = (I[1].includes("round") ? I[1] : `${I[1].trim()} round 0`).split("round"), d2 = (0, import_css_to_react_native3.getStylesForProperty)("borderRadius", C, true), p = Object.values(d2).map((v2) => String(v2)).map((v2, M) => tA(v2, r.fontSize, M === 0 || M === 2 ? e : A, r, true) || 0), y = Object.values((0, import_css_to_react_native3.getStylesForProperty)("margin", E, true)).map((v2) => String(v2)).map((v2, M) => tA(v2, r.fontSize, M === 0 || M === 2 ? e : A, r, true) || 0), k = y[3], x2 = y[0], F = A - (y[1] + y[3]), b = e - (y[0] + y[2]); + return p.some((v2) => v2 > 0) ? { type: "path", d: it({ left: k, top: x2, width: F, height: b }, { ...t, ...d2 }) } : { type: "rect", x: k, y: x2, width: F, height: b }; + } + return { parseCircle: n, parseEllipse: i, parsePath: o, parsePolygon: a, parseInset: u2 }; +} +function da(A) { + let [, e = "nonzero", t] = A.replace(/('|")/g, "").match(/^(nonzero|evenodd)?,?(.+)/) || []; + return [e, t]; +} +function ha(A, e, t) { + let r = A.split(" "), n = { x: r[0] || "50%", y: r[1] || "50%" }; + return r.forEach((i) => { + i === "top" ? n.y = 0 : i === "bottom" ? n.y = t : i === "left" ? n.x = 0 : i === "right" ? n.x = e : i === "center" && (n.x = e / 2, n.y = t / 2); + }), n; +} +function ln(A) { + return `satori_cp-${A}`; +} +function ma(A) { + return `url(#${ln(A)})`; +} +function ya(A, e, t) { + if (e.clipPath === "none") return ""; + let r = pa(A, e, t), n = e.clipPath, i = { type: "" }; + for (let o of Object.keys(r)) if (i = r[o](n), i) break; + if (i) { + let { type: o, ...a } = i; + return H("clipPath", { id: ln(A.id), "clip-path": A.currentClipPath, transform: `translate(${A.left}, ${A.top})` }, H(o, a)); + } + return ""; +} +function Qi({ left: A, top: e, width: t, height: r, path: n, matrix: i, id: o, currentClipPath: a, src: u2 }, l2, I) { + let E = "", C = l2.clipPath && l2.clipPath !== "none" ? ya({ left: A, top: e, width: t, height: r, path: n, id: o, matrix: i, currentClipPath: a, src: u2 }, l2, I) : ""; + if (l2.overflow !== "hidden" && !u2) E = ""; + else { + let p = C ? `satori_ocp-${o}` : ln(o); + E = H("clipPath", { id: p, "clip-path": a }, H(n ? "path" : "rect", { x: A, y: e, width: t, height: r, d: n || void 0, transform: l2.overflow === "hidden" && l2.transform && i ? i : void 0 })); + } + let d2 = Ei({ id: `satori_om-${o}`, left: A, top: e, width: t, height: r, matrix: i, borderOnly: !u2 }, l2); + return C + E + d2; +} +async function Ci(A, e, t) { + if (!e.maskImage) return ["", ""]; + let { left: r, top: n, width: i, height: o, id: a } = A, u2 = e.maskImage, l2 = u2.length; + if (!l2) return ["", ""]; + let I = Yl(a), E = ""; + for (let C = 0; C < l2; C++) { + let d2 = u2[C], [p, y] = await $t({ id: `${I}-${C}`, left: r, top: n, width: i, height: o }, d2, t, "mask"); + E += y + H("rect", { x: r, y: n, width: i, height: o, fill: `url(#${p})` }); + } + return E = H("mask", { id: I }, E), [I, E]; +} +function ql(A, e, t) { + let r = A.toLowerCase().trim().split(/\s+/), n = (u2, l2) => ({ left: "0%", center: "50%", right: "100%", top: "0%", bottom: "100%" })[u2] || u2, i, o; + if (r.length === 1) { + let u2 = r[0]; + u2 === "left" || u2 === "center" || u2 === "right" ? (i = n(u2, "x"), o = "50%") : u2 === "top" || u2 === "bottom" ? (i = "50%", o = n(u2, "y")) : (i = u2, o = "50%"); + } else { + let u2 = r[0], l2 = r[1]; + u2 === "top" || u2 === "bottom" ? (o = n(u2, "y"), l2 === "left" || l2 === "right" || l2 === "center" ? i = n(l2, "x") : (i = "50%", o = u2 === "top" || u2 === "bottom" ? n(u2, "y") : l2)) : (i = n(u2, "x"), o = n(l2, "y")); + } + let a = (u2, l2) => { + try { + if (u2.endsWith("%")) return l2 * parseFloat(u2) / 100; + let I = new He(u2); + return I.type === "length" || I.type === "number" ? I.value : 0; + } catch { + return 0; + } + }; + return [a(i, e), a(o, t)]; +} +async function tr({ id: A, left: e, top: t, width: r, height: n, isInheritingTransform: i, src: o, debug: a }, u2, l2) { + if (u2.display === "none") return ""; + let I = !!o, E = "rect", C = "", d2 = "", p = [], y = 1, k = ""; + u2.backgroundColor && p.push(u2.backgroundColor), u2.opacity !== void 0 && (y = +u2.opacity), u2.transform && (C = jt({ left: e, top: t, width: r, height: n }, u2.transform, i, u2.transformOrigin)); + let x2 = ""; + if (u2.backgroundImage) { + let rA = []; + for (let iA = 0; iA < u2.backgroundImage.length; iA++) { + let wA = u2.backgroundImage[iA], aA = await $t({ id: A + "_" + iA, width: r, height: n, left: e, top: t }, wA, l2); + aA && rA.unshift(aA); + } + for (let iA of rA) p.push(`url(#${iA[0]})`), d2 += iA[1], iA[2] && (x2 += iA[2]); + } + let [F, b] = await Ci({ id: A, left: e, top: t, width: r, height: n }, u2, l2); + d2 += b; + let v2 = F ? `url(#${F})` : u2._inheritedMaskId ? `url(#${u2._inheritedMaskId})` : void 0, M = it({ left: e, top: t, width: r, height: n }, u2); + M && (E = "path"); + let L = u2._inheritedClipPathId; + a && (k = H("rect", { x: e, y: t, width: r, height: n, fill: "transparent", stroke: "#ff5757", "stroke-width": 1, transform: C || void 0, "clip-path": L ? `url(#${L})` : void 0 })); + let { backgroundClip: O, filter: J } = u2, j = O === "text" ? `url(#satori_bct-${A})` : L ? `url(#${L})` : u2.clipPath ? ma(A) : void 0, CA = Qi({ left: e, top: t, width: r, height: n, path: M, id: A, matrix: C, currentClipPath: j, src: o }, u2, l2), MA = p.map((rA) => H(E, { x: e, y: t, width: r, height: n, fill: rA, d: M || void 0, transform: C || void 0, "clip-path": u2.transform ? void 0 : j, style: J ? `filter:${J}` : void 0, mask: u2.transform ? void 0 : v2 })).join(""), dA = Qa({ id: A, left: e, top: t, width: r, height: n, currentClipPathId: L, borderPath: M, borderType: E }, u2), sA; + if (I) { + let rA = (u2.borderLeftWidth || 0) + (u2.paddingLeft || 0), iA = (u2.borderTopWidth || 0) + (u2.paddingTop || 0), wA = (u2.borderRightWidth || 0) + (u2.paddingRight || 0), aA = (u2.borderBottomWidth || 0) + (u2.paddingBottom || 0), FA = r - rA - wA, kA = n - iA - aA, te = (u2.objectPosition || "center").toString(), [TA, xA] = ql(te, FA, kA), fA = u2.__naturalWidth || FA, q = u2.__naturalHeight || kA, oA, BA = FA, W = kA, Y = e + rA, OA = t + iA; + if (u2.objectFit === "contain") { + let PA = FA / fA, WA = kA / q, uA = Math.min(PA, WA); + BA = fA * uA, W = q * uA, Y = e + rA + TA - BA * TA / FA, OA = t + iA + xA - W * xA / kA, oA = "none"; + } else if (u2.objectFit === "cover") { + let PA = FA / fA, WA = kA / q, uA = Math.max(PA, WA); + BA = fA * uA, W = q * uA, Y = e + rA + TA - BA * TA / FA, OA = t + iA + xA - W * xA / kA, oA = "none"; + } else if (u2.objectFit === "fill") oA = "none"; + else if (u2.objectFit === "scale-down") if (fA && q) { + let PA = FA / fA, WA = kA / q, uA = Math.min(PA, WA); + if (uA >= 1) BA = fA, W = q, oA = "none", Y = e + rA + TA - BA * TA / FA, OA = t + iA + xA - W * xA / kA; + else { + let hA = uA; + BA = fA * hA, W = q * hA, Y = e + rA + TA - BA * TA / FA, OA = t + iA + xA - W * xA / kA, oA = "none"; + } + } else { + let PA = FA / fA, WA = kA / q, uA = Math.min(PA, WA); + BA = fA * uA, W = q * uA, Y = e + rA + TA - BA * TA / FA, OA = t + iA + xA - W * xA / kA, oA = "none"; + } + else oA = "none"; + u2.transform && (sA = Ba({ id: A, borderRadiusPath: M, borderType: E, left: e, top: t, width: r, height: n }, u2)), MA += H("image", { x: Y, y: OA, width: BA, height: W, href: o, preserveAspectRatio: oA, transform: C || void 0, style: J ? `filter:${J}` : void 0, "clip-path": u2.transform ? sA ? `url(#${sA[1]})` : void 0 : `url(#satori_cp-${A})`, mask: u2.transform ? void 0 : F ? `url(#${F})` : `url(#satori_om-${A})` }); + } + if (dA) { + d2 += dA[0]; + let rA = dA[1]; + MA += Ar({ left: e, top: t, width: r, height: n, props: { transform: C || void 0, "clip-path": `url(#${rA})` } }, u2); + } + let vA = ia({ width: r, height: n, id: A, opacity: y, shape: H(E, { x: e, y: t, width: r, height: n, fill: "#fff", stroke: "#fff", "stroke-width": 0, d: M || void 0, transform: C || void 0, "clip-path": j, mask: v2 }) }, u2); + return (d2 ? H("defs", {}, d2) : "") + (vA ? vA[0] : "") + (sA ? sA[0] : "") + CA + (y !== 1 ? `` : "") + (u2.transform && (j || v2) ? `` : "") + (x2 || MA) + (u2.transform && (j || v2) ? "" : "") + (y !== 1 ? "" : "") + (vA ? vA[1] : "") + k; +} +function ba(A) { + return cn.includes(A); +} +function va(A, e) { + for (let r of Object.keys(di)) if (di[r].test(A)) return [r]; + let t = Object.keys(hi).filter((r) => hi[r].test(A)); + if (t.length === 0) return ["unknown"]; + if (e) { + let r = t.findIndex((n) => n === e); + r !== -1 && (t.splice(r, 1), t.unshift(e)); + } + return t; +} +function ka(A) { + if (A) return cn.find((e) => e.toLowerCase().startsWith(A.toLowerCase())); +} +async function* rr(A, e) { + var fA; + let t = await rt(), { id: r, inheritedStyle: n, parent: i, font: o, debug: a, locale: u2, embedFont: l2 = true, graphemeImages: I, canLoadAdditionalAssets: E, getTwStyles: C } = e; + if (A === null || typeof A > "u") return yield, yield, ""; + if (!Rt(A) || ds(A.type)) { + let q; + if (!Rt(A)) q = ci(String(A), e), yield (await q.next()).value; + else { + if (Cs(A.type)) throw new Error("Class component is not supported."); + let BA; + Tn(A.type) ? BA = A.type.render : BA = A.type, q = rr(await BA(A.props), e), yield (await q.next()).value; + } + await q.next(); + let oA = yield; + return (await q.next(oA)).value; + } + let { type: d2, props: p } = A, y = d2; + if (p && hs(p)) throw new Error("dangerouslySetInnerHTML property is not supported. See documentation for more information https://github.com/vercel/satori#jsx."); + let { style: k, children: x2, tw: F, lang: b = u2 } = p || {}, v2 = ka(b); + if (F) { + let q = C(F, k); + k = Object.assign(q, k); + } + let M = t.Node.create(); + i.insertChild(M, i.getChildCount()); + let [L, O] = await si(M, y, n, k, p), J = L.transform === n.transform; + if (J || (L.transform.__parent = n.transform), (L.overflow === "hidden" || L.clipPath && L.clipPath !== "none") && (O._inheritedClipPathId = `satori_cp-${r}`, O._inheritedMaskId = `satori_om-${r}`), L.maskImage && (O._inheritedMaskId = `satori_mi-${r}`), L.backgroundClip === "text") { + let q = { value: "" }; + O._inheritedBackgroundClipTextPath = q, L._inheritedBackgroundClipTextPath = q, L.backgroundImage && (O._inheritedBackgroundClipTextHasBackground = "true", L._inheritedBackgroundClipTextHasBackground = "true"); + } + let j = ps(x2), CA = [], MA = 0, dA = []; + for (let q of j) { + let oA = rr(q, { id: r + "-" + MA++, parentStyle: L, inheritedStyle: O, isInheritingTransform: true, parent: M, font: o, embedFont: l2, debug: a, graphemeImages: I, canLoadAdditionalAssets: E, locale: v2, getTwStyles: C, onNodeDetected: e.onNodeDetected }); + E ? dA.push(...(await oA.next()).value || []) : await oA.next(), CA.push(oA); + } + yield dA; + for (let q of CA) await q.next(); + let [sA, vA] = yield, { left: rA, top: iA, width: wA, height: aA } = M.getComputedLayout(); + rA += sA, iA += vA; + let FA = "", kA = "", te = "", { children: TA, ...xA } = p; + if ((fA = e.onNodeDetected) == null || fA.call(e, { left: rA, top: iA, width: wA, height: aA, type: y, props: xA, key: A.key, textContent: Rt(TA) ? void 0 : TA }), y === "img") { + let q = L.__src; + kA = await tr({ id: r, left: rA, top: iA, width: wA, height: aA, src: q, isInheritingTransform: J, debug: a }, L, O); + } else if (y === "svg") { + let q = L.color, oA = await Ks(A, q); + kA = await tr({ id: r, left: rA, top: iA, width: wA, height: aA, src: oA, isInheritingTransform: J, debug: a }, L, O); + } else { + let q = k == null ? void 0 : k.display; + if (y === "div" && x2 && typeof x2 != "string" && q !== "flex" && q !== "none" && q !== "contents") throw new Error('Expected
to have explicit "display: flex", "display: contents", or "display: none" if it has more than one child node.'); + kA = await tr({ id: r, left: rA, top: iA, width: wA, height: aA, isInheritingTransform: J, debug: a }, L, O); + } + for (let q of CA) FA += (await q.next([rA, iA])).value; + return L._inheritedBackgroundClipTextPath && (te += H("clipPath", { id: `satori_bct-${r}`, "clip-path": L._inheritedClipPathId ? `url(#${L._inheritedClipPathId})` : void 0 }, L._inheritedBackgroundClipTextPath.value)), te + kA + FA; +} +function Vl(A) { + let e = [], t = [0, 0], r = [0, 0], n = (i, o) => { + let a = i[0]; + for (let u2 = 1; u2 <= o; u2++) { + let l2 = u2 / o, I = zl(i, l2); + e.push({ from: a, to: I }), a = I; + } + r = i[i.length - 1]; + }; + for (let i of A) { + if (i.type === "M") { + t = r = [i.x, i.y]; + continue; + } + if (i.type === "L") { + let o = [i.x, i.y]; + e.push({ from: r, to: o }), r = o; + continue; + } + if (i.type === "Q") { + n([r, [i.x1, i.y1], [i.x, i.y]], 12); + continue; + } + if (i.type === "C") { + n([r, [i.x1, i.y1], [i.x2, i.y2], [i.x, i.y]], 16); + continue; + } + i.type === "Z" && (e.push({ from: r, to: t }), r = t); + } + return e; +} +function zl(A, e) { + let t = A; + for (; t.length > 1; ) { + let r = []; + for (let n = 0; n < t.length - 1; n++) r.push([t[n][0] + (t[n + 1][0] - t[n][0]) * e, t[n][1] + (t[n + 1][1] - t[n][1]) * e]); + t = r; + } + return t[0]; +} +function Zl(A, e) { + if (!e) return []; + let t = e.strokeWidth, r = e.underlineY - t * 0.25, n = e.underlineY + t * 2.5, i = Vl(A); + if (!i.length) return []; + let o = n - r, a = Math.max(12, Math.ceil(o / 0.25)), u2 = o / a, l2 = r + u2 / 2, I = /* @__PURE__ */ new Set(); + for (let b = 0; b < a; b++) { + let v2 = l2 + u2 * b, M = []; + for (let L of i) { + let [O, J] = L.from, [j, CA] = L.to; + if (J === CA) continue; + let MA = Math.min(J, CA), dA = Math.max(J, CA); + if (v2 < MA || v2 >= dA) continue; + let sA = (v2 - J) / (CA - J), vA = O + (j - O) * sA; + M.push(vA); + } + if (M.length) { + M.sort((L, O) => L - O); + for (let L = 0; L < M.length - 1; L += 2) { + let O = Math.min(M[L], M[L + 1]), J = Math.max(M[L], M[L + 1]), j = Math.floor(O), CA = Math.ceil(J); + for (let MA = j; MA < CA; MA++) I.add(MA); + } + } + } + if (!I.size) return []; + let E = Array.from(I.values()).sort((b, v2) => b - v2), C = [], d2 = E[0], p = E[0]; + for (let b = 1; b < E.length; b++) { + let v2 = E[b]; + v2 > p + 1 && (C.push([d2, p + 1]), d2 = v2), p = v2; + } + C.push([d2, p + 1]); + let y = [], k = t * 0.6, x2 = C[0][0], F = C[C.length - 1][1]; + for (let [b, v2] of C) { + let M = Math.min(b, x2) - k, L = Math.max(v2, F) + k; + y.push({ x1: M, x2: L, y1: r, y2: n }); + } + return y; +} +function jl(A, e, [t, r], [n, i]) { + if (t !== n) return t ? !n || t === A ? -1 : n === A ? 1 : A === 400 && t === 500 || A === 500 && t === 400 ? -1 : A === 400 && n === 500 || A === 500 && n === 400 ? 1 : A < 400 ? t < A && n < A ? n - t : t < A ? -1 : n < A ? 1 : t - n : A < t && A < n ? t - n : A < t ? -1 : A < n ? 1 : n - t : 1; + if (r !== i) { + if (r === e) return -1; + if (i === e) return 1; + } + return -1; +} +function $l(A) { + let e = A.split("_"), t = e[e.length - 1]; + return t === xa ? void 0 : t; +} +function mi({ width: A, height: e, content: t }) { + return H("svg", { width: A, height: e, viewBox: `0 0 ${A} ${e}`, xmlns: "http://www.w3.org/2000/svg" }, t); +} +function _u(A) { + return TC.includes(A); +} +function Ju(A) { + return PC.includes(A); +} +function Vo(A) { + return typeof A == "string"; +} +function zo(A) { + return typeof A == "object"; +} +function R2(A) { + return { kind: "complete", style: A }; +} +function VA(A, e = {}) { + let { fractions: t } = e; + if (t && A.includes("/")) { + let [i = "", o = ""] = A.split("/", 2), a = VA(i), u2 = VA(o); + return !a || !u2 ? null : [a[0] / u2[0], u2[1]]; + } + let r = parseFloat(A); + if (Number.isNaN(r)) return null; + let n = A.match(/(([a-z]{2,}|%))$/); + if (!n) return [r, QA.none]; + switch (n == null ? void 0 : n[1]) { + case "rem": + return [r, QA.rem]; + case "px": + return [r, QA.px]; + case "em": + return [r, QA.em]; + case "%": + return [r, QA.percent]; + case "vw": + return [r, QA.vw]; + case "vh": + return [r, QA.vh]; + default: + return null; + } +} +function At(A, e, t = {}) { + let r = We(e, t); + return r === null ? null : R2({ [A]: r }); +} +function Rn(A, e, t) { + let r = We(e); + return r !== null && (t[A] = r), t; +} +function Ku(A, e) { + let t = We(e); + return t === null ? null : { [A]: t }; +} +function We(A, e = {}) { + if (A === void 0) return null; + let t = VA(String(A), e); + return t ? gt(...t, e) : null; +} +function gt(A, e, t = {}) { + let { isNegative: r, device: n } = t; + switch (e) { + case QA.rem: + return A * 16 * (r ? -1 : 1); + case QA.px: + return A * (r ? -1 : 1); + case QA.percent: + return `${r ? "-" : ""}${A}%`; + case QA.none: + return A * (r ? -1 : 1); + case QA.vw: + return n != null && n.windowDimensions ? n.windowDimensions.width * (A / 100) : (me("`vw` CSS unit requires configuration with `useDeviceContext()`"), null); + case QA.vh: + return n != null && n.windowDimensions ? n.windowDimensions.height * (A / 100) : (me("`vh` CSS unit requires configuration with `useDeviceContext()`"), null); + default: + return null; + } +} +function jo(A) { + let e = VA(A); + if (!e) return null; + let [t, r] = e; + switch (r) { + case QA.rem: + return t * 16; + case QA.px: + return t; + default: + return null; + } +} +function $o(A) { + return _C[A ?? ""] || "All"; +} +function As(A) { + let e = "All"; + return [A.replace(/^-(t|b|r|l|tr|tl|br|bl)(-|$)/, (r, n) => (e = $o(n), "")), e]; +} +function mt(A, e = {}) { + if (A.includes("/")) { + let t = Wu(A, { ...e, fractions: true }); + if (t) return t; + } + return A[0] === "[" && (A = A.slice(1, -1)), Wu(A, e); +} +function Le(A, e, t = {}) { + let r = mt(e, t); + return r === null ? null : R2({ [A]: r }); +} +function Wu(A, e = {}) { + if (A === "px") return 1; + let t = VA(A, e); + if (!t) return null; + let [r, n] = t; + return e.fractions && (n = QA.percent, r *= 100), n === QA.none && (r = r / 4, n = QA.rem), gt(r, n, e); +} +function JC(...A) { + console.warn(...A); +} +function WC(...A) { +} +function Gr(A) { + return { kind: "dependent", complete(e) { + (!e.fontVariant || !Array.isArray(e.fontVariant)) && (e.fontVariant = []), e.fontVariant.push(A); + } }; +} +function ts(A, e, t = {}) { + let r = e == null ? void 0 : e[A]; + if (!r) return Le("fontSize", A, t); + if (typeof r == "string") return At("fontSize", r); + let n = {}, [i, o] = r, a = Ku("fontSize", i); + if (a && (n = a), typeof o == "string") return R2(Rn("lineHeight", Yu(o, n), n)); + let { lineHeight: u2, letterSpacing: l2 } = o; + return u2 && Rn("lineHeight", Yu(u2, n), n), l2 && Rn("letterSpacing", l2, n), R2(n); +} +function Yu(A, e) { + let t = VA(A); + if (t) { + let [r, n] = t; + if ((n === QA.none || n === QA.em) && typeof e.fontSize == "number") return e.fontSize * r; + } + return A; +} +function rs(A, e) { + var t; + let r = (t = e == null ? void 0 : e[A]) !== null && t !== void 0 ? t : A.startsWith("[") ? A.slice(1, -1) : A, n = VA(r); + if (!n) return null; + let [i, o] = n; + if (o === QA.none) return { kind: "dependent", complete(u2) { + if (typeof u2.fontSize != "number") return "relative line-height utilities require that font-size be set"; + u2.lineHeight = u2.fontSize * i; + } }; + let a = gt(i, o); + return a !== null ? R2({ lineHeight: a }) : null; +} +function ns(A, e, t, r, n) { + let i = ""; + if (r[0] === "[") i = r.slice(1, -1); + else { + let l2 = n == null ? void 0 : n[r]; + if (l2) i = l2; + else { + let I = mt(r); + return I && typeof I == "number" ? qu(I, QA.px, e, A) : null; + } + } + if (i === "auto") return Xu(e, A, "auto"); + let o = VA(i); + if (!o) return null; + let [a, u2] = o; + return t && (a = -a), qu(a, u2, e, A); +} +function qu(A, e, t, r) { + let n = gt(A, e); + return n === null ? null : Xu(t, r, n); +} +function Xu(A, e, t) { + switch (A) { + case "All": + return { kind: "complete", style: { [`${e}Top`]: t, [`${e}Right`]: t, [`${e}Bottom`]: t, [`${e}Left`]: t } }; + case "Bottom": + case "Top": + case "Left": + case "Right": + return { kind: "complete", style: { [`${e}${A}`]: t } }; + case "Vertical": + return { kind: "complete", style: { [`${e}Top`]: t, [`${e}Bottom`]: t } }; + case "Horizontal": + return { kind: "complete", style: { [`${e}Left`]: t, [`${e}Right`]: t } }; + default: + return null; + } +} +function is(A) { + if (!A) return {}; + let e = Object.entries(A).reduce((n, [i, o]) => { + let a = [0, 1 / 0, 0], u2 = typeof o == "string" ? { min: o } : o, l2 = u2.min ? jo(u2.min) : 0; + l2 === null ? me(`invalid screen config value: ${i}->min: ${u2.min}`) : a[0] = l2; + let I = u2.max ? jo(u2.max) : 1 / 0; + return I === null ? me(`invalid screen config value: ${i}->max: ${u2.max}`) : a[1] = I, n[i] = a, n; + }, {}), t = Object.values(e); + t.sort((n, i) => { + let [o, a] = n, [u2, l2] = i; + return a === 1 / 0 || l2 === 1 / 0 ? o - u2 : a - l2; + }); + let r = 0; + return t.forEach((n) => n[2] = r++), e; +} +function os(A, e) { + let t = e == null ? void 0 : e[A]; + if (!t) return null; + if (typeof t == "string") return R2({ fontFamily: t }); + let r = t[0]; + return r ? R2({ fontFamily: r }) : null; +} +function yt(A, e, t) { + if (!t) return null; + let r; + e.includes("/") && ([e = "", r] = e.split("/", 2)); + let n = ""; + if (e.startsWith("[#") || e.startsWith("[rgb") ? n = e.slice(1, -1) : n = Zu(e, t), !n) return null; + if (r) { + let i = Number(r); + if (!Number.isNaN(i)) return n = Vu(n, i / 100), R2({ [Nn[A].color]: n }); + } + return { kind: "dependent", complete(i) { + let o = Nn[A].opacity, a = i[o]; + typeof a == "number" && (n = Vu(n, a)), i[Nn[A].color] = n; + } }; +} +function Hr(A, e) { + let t = parseInt(e, 10); + if (Number.isNaN(t)) return null; + let r = t / 100; + return { kind: "complete", style: { [Nn[A].opacity]: r } }; +} +function Vu(A, e) { + return A.startsWith("#") ? A = YC(A) : A.startsWith("rgb(") && (A = A.replace(/^rgb\(/, "rgba(").replace(/\)$/, ", 1)")), A.replace(/, ?\d*\.?(\d+)\)$/, `, ${e})`); +} +function zu(A) { + for (let e in A) e.startsWith("__opacity_") && delete A[e]; +} +function YC(A) { + let e = A; + A = A.replace(qC, (o, a, u2, l2) => a + a + u2 + u2 + l2 + l2); + let t = XC.exec(A); + if (!t) return me(`invalid config hex color value: ${e}`), "rgba(0, 0, 0, 1)"; + let r = parseInt(t[1], 16), n = parseInt(t[2], 16), i = parseInt(t[3], 16); + return `rgba(${r}, ${n}, ${i}, 1)`; +} +function Zu(A, e) { + let t = e[A]; + if (Vo(t)) return t; + if (zo(t) && Vo(t.DEFAULT)) return t.DEFAULT; + let [r = "", ...n] = A.split("-"); + for (; r !== A; ) { + let i = e[r]; + if (zo(i)) return Zu(n.join("-"), i); + if (n.length === 0) return ""; + r = `${r}-${n.shift()}`; + } + return ""; +} +function $u(A, e) { + let [t, r] = As(A); + if (t.match(/^(-?(\d)+)?$/)) return VC(t, r, e == null ? void 0 : e.borderWidth); + if (t = t.replace(/^-/, ""), ["dashed", "solid", "dotted"].includes(t)) return R2({ borderStyle: t }); + let i = "border"; + switch (r) { + case "Bottom": + i = "borderBottom"; + break; + case "Top": + i = "borderTop"; + break; + case "Left": + i = "borderLeft"; + break; + case "Right": + i = "borderRight"; + break; + } + let o = yt(i, t, e == null ? void 0 : e.borderColor); + if (o) return o; + let a = `border${r === "All" ? "" : r}Width`; + t = t.replace(/^-/, ""); + let u2 = t.slice(1, -1), l2 = Le(a, u2); + return typeof (l2 == null ? void 0 : l2.style[a]) != "number" ? null : l2; +} +function VC(A, e, t) { + if (!t) return null; + A = A.replace(/^-/, ""); + let n = t[A === "" ? "DEFAULT" : A]; + if (n === void 0) return null; + let i = `border${e === "All" ? "" : e}Width`; + return At(i, n); +} +function AI(A, e) { + if (!e) return null; + let [t, r] = As(A); + t = t.replace(/^-/, ""), t === "" && (t = "DEFAULT"); + let n = `border${r === "All" ? "" : r}Radius`, i = e[t]; + if (i) return ju(At(n, i)); + let o = Le(n, t); + return typeof (o == null ? void 0 : o.style[n]) != "number" ? null : ju(o); +} +function ju(A) { + if ((A == null ? void 0 : A.kind) !== "complete") return A; + let e = A.style.borderTopRadius; + e !== void 0 && (A.style.borderTopLeftRadius = e, A.style.borderTopRightRadius = e, delete A.style.borderTopRadius); + let t = A.style.borderBottomRadius; + t !== void 0 && (A.style.borderBottomLeftRadius = t, A.style.borderBottomRightRadius = t, delete A.style.borderBottomRadius); + let r = A.style.borderLeftRadius; + r !== void 0 && (A.style.borderBottomLeftRadius = r, A.style.borderTopLeftRadius = r, delete A.style.borderLeftRadius); + let n = A.style.borderRightRadius; + return n !== void 0 && (A.style.borderBottomRightRadius = n, A.style.borderTopRightRadius = n, delete A.style.borderRightRadius), A; +} +function _t(A, e, t, r) { + let n = null; + A === "inset" && (e = e.replace(/^(x|y)-/, (a, u2) => (n = u2 === "x" ? "x" : "y", ""))); + let i = r == null ? void 0 : r[e]; + if (i) { + let a = We(i, { isNegative: t }); + if (a !== null) return eI(A, n, a); + } + let o = mt(e, { isNegative: t }); + return o !== null ? eI(A, n, o) : null; +} +function eI(A, e, t) { + if (A !== "inset") return R2({ [A]: t }); + switch (e) { + case null: + return R2({ top: t, left: t, right: t, bottom: t }); + case "y": + return R2({ top: t, bottom: t }); + case "x": + return R2({ left: t, right: t }); + } +} +function Or(A, e, t) { + var r; + e = e.replace(/^-/, ""); + let n = e === "" ? "DEFAULT" : e, i = Number((r = t == null ? void 0 : t[n]) !== null && r !== void 0 ? r : e); + return Number.isNaN(i) ? null : R2({ [`flex${A}`]: i }); +} +function tI(A, e) { + var t, r; + if (A = (e == null ? void 0 : e[A]) || A, ["min-content", "revert", "unset"].includes(A)) return null; + if (A.match(/^\d+(\.\d+)?$/)) return R2({ flexGrow: Number(A), flexBasis: "0%" }); + let n = A.match(/^(\d+)\s+(\d+)$/); + if (n) return R2({ flexGrow: Number(n[1]), flexShrink: Number(n[2]) }); + if (n = A.match(/^(\d+)\s+([^ ]+)$/), n) { + let i = We((t = n[2]) !== null && t !== void 0 ? t : ""); + return i ? R2({ flexGrow: Number(n[1]), flexBasis: i }) : null; + } + if (n = A.match(/^(\d+)\s+(\d+)\s+(.+)$/), n) { + let i = We((r = n[3]) !== null && r !== void 0 ? r : ""); + return i ? R2({ flexGrow: Number(n[1]), flexShrink: Number(n[2]), flexBasis: i }) : null; + } + return null; +} +function ss(A, e, t = {}, r) { + let n = r == null ? void 0 : r[e]; + return n !== void 0 ? At(A, n, t) : Le(A, e, t); +} +function Tr(A, e, t = {}, r) { + let n = We(r == null ? void 0 : r[e], t); + return n ? R2({ [A]: n }) : (e === "screen" && (e = A.includes("Width") ? "100vw" : "100vh"), Le(A, e, t)); +} +function rI(A, e, t) { + let r = t == null ? void 0 : t[A]; + if (r) { + let n = VA(r, { isNegative: e }); + if (!n) return null; + let [i, o] = n; + if (o === QA.em) return zC(i); + if (o === QA.percent) return me("percentage-based letter-spacing configuration currently unsupported, switch to `em`s, or open an issue if you'd like to see support added."), null; + let a = gt(i, o, { isNegative: e }); + return a !== null ? R2({ letterSpacing: a }) : null; + } + return Le("letterSpacing", A, { isNegative: e }); +} +function zC(A) { + return { kind: "dependent", complete(e) { + let t = e.fontSize; + if (typeof t != "number" || Number.isNaN(t)) return "tracking-X relative letter spacing classes require font-size to be set"; + e.letterSpacing = Math.round((A * t + Number.EPSILON) * 100) / 100; + } }; +} +function nI(A, e) { + let t = e == null ? void 0 : e[A]; + if (t) { + let n = VA(String(t)); + if (n) return R2({ opacity: n[0] }); + } + let r = VA(A); + return r ? R2({ opacity: r[0] / 100 }) : null; +} +function iI(A) { + let e = parseInt(A, 10); + return Number.isNaN(e) ? null : { kind: "complete", style: { shadowOpacity: e / 100 } }; +} +function oI(A) { + if (A.includes("/")) { + let [t = "", r = ""] = A.split("/", 2), n = as(t), i = as(r); + return n === null || i === null ? null : { kind: "complete", style: { shadowOffset: { width: n, height: i } } }; + } + let e = as(A); + return e === null ? null : { kind: "complete", style: { shadowOffset: { width: e, height: e } } }; +} +function as(A) { + let e = mt(A); + return typeof e == "number" ? e : null; +} +function sI(A) { + let e = [], t = null; + return A.forEach((r) => { + if (typeof r == "string") e = [...e, ...gs(r)]; + else if (Array.isArray(r)) e = [...e, ...r.flatMap(gs)]; + else if (typeof r == "object" && r !== null) for (let [n, i] of Object.entries(r)) typeof i == "boolean" ? e = [...e, ...i ? gs(n) : []] : t ? t[n] = i : t = { [n]: i }; + }), [e.filter(Boolean).filter(ZC), t]; +} +function gs(A) { + return A.trim().split(/\s+/); +} +function ZC(A, e, t) { + return t.indexOf(A) === e; +} +function aI(A) { + var e; + return (e = A == null ? void 0 : A.reduce((t, r) => ({ ...t, ...jC(r.handler) }), {})) !== null && e !== void 0 ? e : {}; +} +function jC(A) { + let e = {}; + return A({ addUtilities: (t) => { + e = t; + }, ...$C }), e; +} +function Ke(A) { + throw new Error(`tailwindcss plugin function argument object prop "${A}" not implemented`); +} +function uI(A, e) { + let t = (0, gI.default)(Ad(A)), r = {}, n = aI(t.plugins), i = {}, o = Object.entries(n).map(([p, y]) => typeof y == "string" ? (i[p] = y, [p, { kind: "null" }]) : [p, R2(y)]).filter(([, p]) => p.kind !== "null"); + function a() { + return [r.windowDimensions ? `w${r.windowDimensions.width}` : false, r.windowDimensions ? `h${r.windowDimensions.height}` : false, r.fontScale ? `fs${r.fontScale}` : false, r.colorScheme === "dark" ? "dark" : false, r.pixelDensity === 2 ? "retina" : false].filter(Boolean).join("--") || "default"; + } + let u2 = a(), l2 = {}; + function I() { + let p = l2[u2]; + if (p) return p; + let y = new Ur(o); + return l2[u2] = y, y; + } + function E(...p) { + let y = I(), k = {}, x2 = [], F = [], [b, v2] = sI(p), M = b.join(" "), L = y.getStyle(M); + if (L) return { ...L, ...v2 || {} }; + for (let O of b) { + let J = y.getIr(O); + if (!J && O in i) { + let CA = E(i[O]); + y.setIr(O, R2(CA)), k = { ...k, ...CA }; + continue; + } + switch (J = new Jt(O, t, y, r, e).parse(), J.kind) { + case "complete": + k = { ...k, ...J.style }, y.setIr(O, J); + break; + case "dependent": + x2.push(J); + break; + case "ordered": + F.push(J); + break; + case "null": + y.setIr(O, J); + break; + } + } + if (F.length > 0) { + F.sort((O, J) => O.order - J.order); + for (let O of F) switch (O.styleIr.kind) { + case "complete": + k = { ...k, ...O.styleIr.style }; + break; + case "dependent": + x2.push(O.styleIr); + break; + } + } + if (x2.length > 0) { + for (let O of x2) { + let J = O.complete(k); + J && me(J); + } + zu(k); + } + return M !== "" && y.setStyle(M, k), v2 && (k = { ...k, ...v2 }), k; + } + function C(p) { + let y = E(p.split(/\s+/g).map((k) => k.replace(/^(bg|text|border)-/, "")).map((k) => `bg-${k}`).join(" ")); + return typeof y.backgroundColor == "string" ? y.backgroundColor : void 0; + } + let d2 = (p, ...y) => { + let k = ""; + return p.forEach((x2, F) => { + var b; + k += x2 + ((b = y[F]) !== null && b !== void 0 ? b : ""); + }), E(k); + }; + return d2.style = E, d2.color = C, d2.prefixMatch = (...p) => { + let y = p.sort().join(":"), k = I(), x2 = k.getPrefixMatch(y); + if (x2 !== void 0) return x2; + let v2 = new Jt(`${y}:flex`, t, k, r, e).parse().kind !== "null"; + return k.setPrefixMatch(y, v2), v2; + }, d2.setWindowDimensions = (p) => { + r.windowDimensions = p, u2 = a(); + }, d2.setFontScale = (p) => { + r.fontScale = p, u2 = a(); + }, d2.setPixelDensity = (p) => { + r.pixelDensity = p, u2 = a(); + }, d2.setColorScheme = (p) => { + r.colorScheme = p, u2 = a(); + }, d2; +} +function Ad(A) { + return { ...A, content: ["_no_warnings_please"] }; +} +function rd(A) { + return uI({ ...A, plugins: [...(A == null ? void 0 : A.plugins) ?? [], td] }, "web"); +} +function us({ width: A, height: e, config: t }) { + return Mn || (Mn = rd(t)), Mn.setWindowDimensions({ width: +A, height: +e }), Mn; +} +async function lI(A, e) { + let t = await rt(); + if (!t || !t.Node) throw new Error("Satori is not initialized: expect `yoga` to be loaded, got " + t); + e.fonts = e.fonts || []; + let r; + Is.has(e.fonts) ? r = Is.get(e.fonts) : Is.set(e.fonts, r = new ir(e.fonts)); + let n = "width" in e ? e.width : void 0, i = "height" in e ? e.height : void 0, o = nd(t, e.pointScaleFactor); + n && o.setWidth(n), i && o.setHeight(i), o.setFlexDirection(t.FLEX_DIRECTION_ROW), o.setFlexWrap(t.WRAP_WRAP), o.setAlignContent(t.ALIGN_AUTO), o.setAlignItems(t.ALIGN_FLEX_START), o.setJustifyContent(t.JUSTIFY_FLEX_START), o.setOverflow(t.OVERFLOW_HIDDEN); + let a = { ...e.graphemeImages }, u2 = /* @__PURE__ */ new Set(); + _e.clear(), Xt.clear(), await Ws(A); + let l2 = rr(A, { id: "id", parentStyle: {}, inheritedStyle: { fontSize: 16, fontWeight: "normal", fontFamily: "serif", fontStyle: "normal", lineHeight: "normal", color: "black", opacity: 1, whiteSpace: "normal", _viewportWidth: n, _viewportHeight: i }, parent: o, font: r, embedFont: e.embedFont, debug: e.debug, graphemeImages: a, canLoadAdditionalAssets: !!e.loadAdditionalAsset, onNodeDetected: e.onNodeDetected, getTwStyles: (p, y) => { + let x2 = { ...us({ width: n, height: i, config: e.tailwindConfig })([p]) }; + return typeof x2.lineHeight == "number" && (x2.lineHeight = x2.lineHeight / (+x2.fontSize || y.fontSize || 16)), x2.shadowColor && x2.boxShadow && (x2.boxShadow = x2.boxShadow.replace(/rgba?\([^)]+\)/, x2.shadowColor)), x2; + } }), I = (await l2.next()).value; + if (e.loadAdditionalAsset && I.length) { + let p = id(I), y = [], k = {}; + await Promise.all(Object.entries(p).flatMap(([x2, F]) => F.map((b) => { + let v2 = `${x2}_${b}`; + return u2.has(v2) ? null : (u2.add(v2), e.loadAdditionalAsset(x2, b).then((M) => { + typeof M == "string" ? k[b] = M : M && (Array.isArray(M) ? y.push(...M) : y.push(M)); + })); + }))), r.addFonts(y), Object.assign(a, k); + } + await l2.next(), o.calculateLayout(n, i, t.DIRECTION_LTR); + let E = (await l2.next([0, 0])).value, C = o.getComputedWidth(), d2 = o.getComputedHeight(); + return o.freeRecursive(), mi({ width: C, height: d2, content: E }); +} +function nd(A, e) { + if (e) { + let t = A.Config.create(); + return t.setPointScaleFactor(e), A.Node.createWithConfig(t); + } else return A.Node.create(); +} +function id(A) { + let e = {}, t = {}; + for (let { word: r, locale: n } of A) { + let i = va(r, n).join("|"); + t[i] = t[i] || "", t[i] += r; + } + return Object.keys(t).forEach((r) => { + e[r] = e[r] || [], r === "emoji" ? e[r].push(...II(ge(t[r], "grapheme"))) : (e[r][0] = e[r][0] || "", e[r][0] += II(ge(t[r], "grapheme", r === "unknown" ? void 0 : r)).join("")); + }), e; +} +function II(A) { + return Array.from(new Set(A)); +} +var import_css_to_react_native, import_css_background_parser, import_css_box_shadow, import_parse_css_color, import_postcss_value_parser, import_css_to_react_native2, import_postcss_value_parser2, import_escape_html, import_parse_css_color2, import_parse_css_color3, import_parse_css_color4, import_css_to_react_native3, import_opentype, cI, Kr, fI, BI, EI, QI, Xe, K2, kt, cs, CI, Yr, ze, vs, ks, qt, _n, Ze, Mt, NI, zr, Jn, Ft, Lt, Wn, xs, Kn, Yn, Oe, qn, MI, Rs, Zr, Ns, Fs, Ms, Ls, Gs, LI, GI, Us, Vn, Zn, UI, Hs, zn, HI, jn, $n, _I, Ai, wi, Ra, Na, bi, fn, rc, Bn, Fa, La, Ga, Ha, Oa, Ta, Li, Ja, Ka, qa, ar, je, ee, pn, Hi, Ti, mn, Ji, Ki, qi, yn, zi, ji, Ao, Qg, oo, ao, uo, lo, wg, co, vg, Gg, Hg, Og, Jg, Wg, qg, Xg, zg, Ro, Mo, ru, iu, su, fu, mu, yu, vu, ku, bC, xu, Fu, Gu, Hu, Tu, Kt, dI, hI, pI, mI, yI, wI, fs, Bs, Es, Qs, SI, vI, Hn, On, ms, xt, RI, bs, KI, YI, $r, An, en, tn, ei, _e, Xt, qI, XI, VI, zI, ZI, ti, tl, xe, Ys, rl, ll, cl, js, ea, nn, Qt, Ii, Ut, vl, In, er, Yl, Da, wa, Sa, Xl, di, hi, cn, xa, pi, ir, gI, TC, PC, Pu, QA, Zo, _C, me, KC, es, Ur, Nn, qC, XC, Jt, $C, td, Mn, Is; +var init_dist2 = __esm({ + "node_modules/.pnpm/satori@0.26.0/node_modules/satori/dist/index.js"() { + init_module(); + import_css_to_react_native = __toESM(require_css_to_react_native(), 1); + import_css_background_parser = __toESM(require_css_background_parser(), 1); + import_css_box_shadow = __toESM(require_css_box_shadow(), 1); + import_parse_css_color = __toESM(require_index_cjs(), 1); + import_postcss_value_parser = __toESM(require_lib(), 1); + import_css_to_react_native2 = __toESM(require_css_to_react_native(), 1); + import_postcss_value_parser2 = __toESM(require_lib(), 1); + import_escape_html = __toESM(require_escape_html(), 1); + import_parse_css_color2 = __toESM(require_index_cjs(), 1); + init_dist(); + import_parse_css_color3 = __toESM(require_index_cjs(), 1); + init_dist(); + import_parse_css_color4 = __toESM(require_index_cjs(), 1); + import_css_to_react_native3 = __toESM(require_css_to_react_native(), 1); + import_opentype = __toESM(require_opentype(), 1); + cI = Object.create; + Kr = Object.defineProperty; + fI = Object.getOwnPropertyDescriptor; + BI = Object.getOwnPropertyNames; + EI = Object.getPrototypeOf; + QI = Object.prototype.hasOwnProperty; + Xe = (A, e) => () => (A && (e = A(A = 0)), e); + K2 = (A, e) => () => (e || A((e = { exports: {} }).exports, e), e.exports); + kt = (A, e) => { + for (var t in e) Kr(A, t, { get: e[t], enumerable: true }); + }; + cs = (A, e, t, r) => { + if (e && typeof e == "object" || typeof e == "function") for (let n of BI(e)) !QI.call(A, n) && n !== t && Kr(A, n, { get: () => e[n], enumerable: !(r = fI(e, n)) || r.enumerable }); + return A; + }; + CI = (A, e, t) => (t = A != null ? cI(EI(A)) : {}, cs(e || !A || !A.__esModule ? Kr(t, "default", { value: A, enumerable: true }) : t, A)); + Yr = (A) => cs(Kr({}, "__esModule", { value: true }), A); + Zr = Xe(() => { + ze = (function(A) { + return A[A.Auto = 0] = "Auto", A[A.FlexStart = 1] = "FlexStart", A[A.Center = 2] = "Center", A[A.FlexEnd = 3] = "FlexEnd", A[A.Stretch = 4] = "Stretch", A[A.Baseline = 5] = "Baseline", A[A.SpaceBetween = 6] = "SpaceBetween", A[A.SpaceAround = 7] = "SpaceAround", A[A.SpaceEvenly = 8] = "SpaceEvenly", A; + })({}), vs = (function(A) { + return A[A.BorderBox = 0] = "BorderBox", A[A.ContentBox = 1] = "ContentBox", A; + })({}), ks = (function(A) { + return A[A.Width = 0] = "Width", A[A.Height = 1] = "Height", A; + })({}), qt = (function(A) { + return A[A.Inherit = 0] = "Inherit", A[A.LTR = 1] = "LTR", A[A.RTL = 2] = "RTL", A; + })({}), _n = (function(A) { + return A[A.Flex = 0] = "Flex", A[A.None = 1] = "None", A[A.Contents = 2] = "Contents", A; + })({}), Ze = (function(A) { + return A[A.Left = 0] = "Left", A[A.Top = 1] = "Top", A[A.Right = 2] = "Right", A[A.Bottom = 3] = "Bottom", A[A.Start = 4] = "Start", A[A.End = 5] = "End", A[A.Horizontal = 6] = "Horizontal", A[A.Vertical = 7] = "Vertical", A[A.All = 8] = "All", A; + })({}), Mt = (function(A) { + return A[A.None = 0] = "None", A[A.StretchFlexBasis = 1] = "StretchFlexBasis", A[A.AbsolutePositionWithoutInsetsExcludesPadding = 2] = "AbsolutePositionWithoutInsetsExcludesPadding", A[A.AbsolutePercentAgainstInnerSize = 4] = "AbsolutePercentAgainstInnerSize", A[A.All = 2147483647] = "All", A[A.Classic = 2147483646] = "Classic", A; + })({}), NI = (function(A) { + return A[A.WebFlexBasis = 0] = "WebFlexBasis", A; + })({}), zr = (function(A) { + return A[A.Column = 0] = "Column", A[A.ColumnReverse = 1] = "ColumnReverse", A[A.Row = 2] = "Row", A[A.RowReverse = 3] = "RowReverse", A; + })({}), Jn = (function(A) { + return A[A.Column = 0] = "Column", A[A.Row = 1] = "Row", A[A.All = 2] = "All", A; + })({}), Ft = (function(A) { + return A[A.FlexStart = 0] = "FlexStart", A[A.Center = 1] = "Center", A[A.FlexEnd = 2] = "FlexEnd", A[A.SpaceBetween = 3] = "SpaceBetween", A[A.SpaceAround = 4] = "SpaceAround", A[A.SpaceEvenly = 5] = "SpaceEvenly", A; + })({}), Lt = (function(A) { + return A[A.Error = 0] = "Error", A[A.Warn = 1] = "Warn", A[A.Info = 2] = "Info", A[A.Debug = 3] = "Debug", A[A.Verbose = 4] = "Verbose", A[A.Fatal = 5] = "Fatal", A; + })({}), Wn = (function(A) { + return A[A.Undefined = 0] = "Undefined", A[A.Exactly = 1] = "Exactly", A[A.AtMost = 2] = "AtMost", A; + })({}), xs = (function(A) { + return A[A.Default = 0] = "Default", A[A.Text = 1] = "Text", A; + })({}), Kn = (function(A) { + return A[A.Visible = 0] = "Visible", A[A.Hidden = 1] = "Hidden", A[A.Scroll = 2] = "Scroll", A; + })({}), Yn = (function(A) { + return A[A.Static = 0] = "Static", A[A.Relative = 1] = "Relative", A[A.Absolute = 2] = "Absolute", A; + })({}), Oe = (function(A) { + return A[A.Undefined = 0] = "Undefined", A[A.Point = 1] = "Point", A[A.Percent = 2] = "Percent", A[A.Auto = 3] = "Auto", A; + })({}), qn = (function(A) { + return A[A.NoWrap = 0] = "NoWrap", A[A.Wrap = 1] = "Wrap", A[A.WrapReverse = 2] = "WrapReverse", A; + })({}), MI = { ALIGN_AUTO: ze.Auto, ALIGN_FLEX_START: ze.FlexStart, ALIGN_CENTER: ze.Center, ALIGN_FLEX_END: ze.FlexEnd, ALIGN_STRETCH: ze.Stretch, ALIGN_BASELINE: ze.Baseline, ALIGN_SPACE_BETWEEN: ze.SpaceBetween, ALIGN_SPACE_AROUND: ze.SpaceAround, ALIGN_SPACE_EVENLY: ze.SpaceEvenly, BOX_SIZING_BORDER_BOX: vs.BorderBox, BOX_SIZING_CONTENT_BOX: vs.ContentBox, DIMENSION_WIDTH: ks.Width, DIMENSION_HEIGHT: ks.Height, DIRECTION_INHERIT: qt.Inherit, DIRECTION_LTR: qt.LTR, DIRECTION_RTL: qt.RTL, DISPLAY_FLEX: _n.Flex, DISPLAY_NONE: _n.None, DISPLAY_CONTENTS: _n.Contents, EDGE_LEFT: Ze.Left, EDGE_TOP: Ze.Top, EDGE_RIGHT: Ze.Right, EDGE_BOTTOM: Ze.Bottom, EDGE_START: Ze.Start, EDGE_END: Ze.End, EDGE_HORIZONTAL: Ze.Horizontal, EDGE_VERTICAL: Ze.Vertical, EDGE_ALL: Ze.All, ERRATA_NONE: Mt.None, ERRATA_STRETCH_FLEX_BASIS: Mt.StretchFlexBasis, ERRATA_ABSOLUTE_POSITION_WITHOUT_INSETS_EXCLUDES_PADDING: Mt.AbsolutePositionWithoutInsetsExcludesPadding, ERRATA_ABSOLUTE_PERCENT_AGAINST_INNER_SIZE: Mt.AbsolutePercentAgainstInnerSize, ERRATA_ALL: Mt.All, ERRATA_CLASSIC: Mt.Classic, EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS: NI.WebFlexBasis, FLEX_DIRECTION_COLUMN: zr.Column, FLEX_DIRECTION_COLUMN_REVERSE: zr.ColumnReverse, FLEX_DIRECTION_ROW: zr.Row, FLEX_DIRECTION_ROW_REVERSE: zr.RowReverse, GUTTER_COLUMN: Jn.Column, GUTTER_ROW: Jn.Row, GUTTER_ALL: Jn.All, JUSTIFY_FLEX_START: Ft.FlexStart, JUSTIFY_CENTER: Ft.Center, JUSTIFY_FLEX_END: Ft.FlexEnd, JUSTIFY_SPACE_BETWEEN: Ft.SpaceBetween, JUSTIFY_SPACE_AROUND: Ft.SpaceAround, JUSTIFY_SPACE_EVENLY: Ft.SpaceEvenly, LOG_LEVEL_ERROR: Lt.Error, LOG_LEVEL_WARN: Lt.Warn, LOG_LEVEL_INFO: Lt.Info, LOG_LEVEL_DEBUG: Lt.Debug, LOG_LEVEL_VERBOSE: Lt.Verbose, LOG_LEVEL_FATAL: Lt.Fatal, MEASURE_MODE_UNDEFINED: Wn.Undefined, MEASURE_MODE_EXACTLY: Wn.Exactly, MEASURE_MODE_AT_MOST: Wn.AtMost, NODE_TYPE_DEFAULT: xs.Default, NODE_TYPE_TEXT: xs.Text, OVERFLOW_VISIBLE: Kn.Visible, OVERFLOW_HIDDEN: Kn.Hidden, OVERFLOW_SCROLL: Kn.Scroll, POSITION_TYPE_STATIC: Yn.Static, POSITION_TYPE_RELATIVE: Yn.Relative, POSITION_TYPE_ABSOLUTE: Yn.Absolute, UNIT_UNDEFINED: Oe.Undefined, UNIT_POINT: Oe.Point, UNIT_PERCENT: Oe.Percent, UNIT_AUTO: Oe.Auto, WRAP_NO_WRAP: qn.NoWrap, WRAP_WRAP: qn.Wrap, WRAP_WRAP_REVERSE: qn.WrapReverse }, Rs = MI; + }); + Ns = Xe(() => { + Zr(); + Zr(); + }); + Fs = {}; + kt(Fs, { default: () => FI }); + Ls = Xe(() => { + Ms = ""; + }); + Gs = {}; + kt(Gs, { default: () => GI }); + Us = Xe(() => { + LI = (() => { + var A = import.meta.url; + return function(e) { + e = e || {}; + var t; + t || (t = typeof e < "u" ? e : {}); + var r, n; + t.ready = new Promise(function(s, g2) { + r = s, n = g2; + }); + var i = Object.assign({}, t), o = ""; + typeof document < "u" && document.currentScript && (o = document.currentScript.src), A && (o = A), o.indexOf("blob:") !== 0 ? o = o.substr(0, o.replace(/[?#].*/, "").lastIndexOf("/") + 1) : o = ""; + var a = t.print || console.log.bind(console), u2 = t.printErr || console.warn.bind(console); + Object.assign(t, i), i = null; + var l2; + t.wasmBinary && (l2 = t.wasmBinary); + var I = t.noExitRuntime || true; + typeof WebAssembly != "object" && iA("no native wasm support detected"); + var E, C = false; + function d2(s, g2, c2) { + c2 = g2 + c2; + for (var B = ""; !(g2 >= c2); ) { + var Q = s[g2++]; + if (!Q) break; + if (Q & 128) { + var h2 = s[g2++] & 63; + if ((Q & 224) == 192) B += String.fromCharCode((Q & 31) << 6 | h2); + else { + var m2 = s[g2++] & 63; + Q = (Q & 240) == 224 ? (Q & 15) << 12 | h2 << 6 | m2 : (Q & 7) << 18 | h2 << 12 | m2 << 6 | s[g2++] & 63, 65536 > Q ? B += String.fromCharCode(Q) : (Q -= 65536, B += String.fromCharCode(55296 | Q >> 10, 56320 | Q & 1023)); + } + } else B += String.fromCharCode(Q); + } + return B; + } + var p, y, k, x2, F, b, v2, M, L; + function O() { + var s = E.buffer; + p = s, t.HEAP8 = y = new Int8Array(s), t.HEAP16 = x2 = new Int16Array(s), t.HEAP32 = b = new Int32Array(s), t.HEAPU8 = k = new Uint8Array(s), t.HEAPU16 = F = new Uint16Array(s), t.HEAPU32 = v2 = new Uint32Array(s), t.HEAPF32 = M = new Float32Array(s), t.HEAPF64 = L = new Float64Array(s); + } + var J, j = [], CA = [], MA = []; + function dA() { + var s = t.preRun.shift(); + j.unshift(s); + } + var sA = 0, vA = null, rA = null; + function iA(s) { + throw t.onAbort && t.onAbort(s), s = "Aborted(" + s + ")", u2(s), C = true, s = new WebAssembly.RuntimeError(s + ". Build with -sASSERTIONS for more info."), n(s), s; + } + function wA(s) { + return s.startsWith("data:application/octet-stream;base64,"); + } + var aA; + if (aA = "data:application/octet-stream;base64,AGFzbQEAAAABugM3YAF/AGACf38AYAF/AX9gA39/fwBgAn98AGACf38Bf2ADf39/AX9gBH9/f30BfWADf398AGAAAGAEf39/fwBgAX8BfGACf38BfGAFf39/f38Bf2AAAX9gA39/fwF9YAZ/f31/fX8AYAV/f39/fwBgAn9/AX1gBX9/f319AX1gAX8BfWADf35/AX5gB39/f39/f38AYAZ/f39/f38AYAR/f39/AX9gBn9/f319fQF9YAR/f31/AGADf399AX1gBn98f39/fwF/YAR/fHx/AGACf30AYAh/f39/f39/fwBgDX9/f39/f39/f39/f38AYAp/f39/f39/f39/AGAFf39/f38BfGAEfHx/fwF9YA1/fX1/f399fX9/f39/AX9gB39/f319f38AYAJ+fwF/YAN/fX0BfWABfAF8YAN/fHwAYAR/f319AGAHf39/fX19fQF9YA1/fX99f31/fX19fX1/AX9gC39/f39/f399fX19AX9gCH9/f39/f319AGAEf39+fgBgB39/f39/f38Bf2ACfH8BfGAFf398fH8AYAN/f38BfGAEf39/fABgA39/fQBgBn9/fX99fwF/ArUBHgFhAWEAHwFhAWIAAwFhAWMACQFhAWQAFgFhAWUAEQFhAWYAIAFhAWcAAAFhAWgAIQFhAWkAAwFhAWoAAAFhAWsAFwFhAWwACgFhAW0ABQFhAW4AAwFhAW8AAQFhAXAAFwFhAXEABgFhAXIAAAFhAXMAIgFhAXQACgFhAXUADQFhAXYAFgFhAXcAAgFhAXgAAwFhAXkAGAFhAXoAAgFhAUEAAQFhAUIAEQFhAUMAAQFhAUQAAAOiAqACAgMSBwcACRkDAAoRBgYKEwAPDxMBBiMTCgcHGgMUASQFJRQHAwMKCgMmAQYYDxobFAAKBw8KBwMDAgkCAAAFGwACBwIHBgIDAQMIDAABKAkHBQURACkZASoAAAIrLAIALQcHBy4HLwkFCgMCMA0xAgMJAgACAQYKAQIBBQEACQIFAQEABQAODQ0GFQIBHBUGAgkCEAAAAAUyDzMMBQYINAUCAwUODg41AgMCAgIDBgICNgIBDAwMAQsLCwsLCx0CAAIAAAABABABBQICAQMCEgMMCwEBAQEBAQsLAQICAwICAgICAgIDAgIICAEICAgEBAQEBAQEBAQABAQABAQEBAAE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GiABQQA2AvADIAFCADcC6AMgAigC7AMiAyACKALoAyIERwRAIAMgBGsiBEEASA0FIAEgBBAeIgM2AuwDIAEgAzYC6AMgASADIARqNgLwAyACKALoAyIEIAIoAuwDIghHBEADQCADIAQoAgA2AgAgA0EEaiEDIARBBGoiBCAIRw0ACwsgASADNgLsAwsgASACKQL0AzcC9AMgASACKAKEBDYChAQgASACKQL8AzcC/AMgAUEANgLkAwsgBSABNgIAIAEgADYC5AMLIAZBAWohBiAFQQRqIgUgB0cNAAsLDwsQAgALUAACQAJAAkACQAJAIAIOBAQAAQIDCyAAIAEgAUEwahBDDwsgACABIAFBMGogAxBEDwsgACABIAFBMGoQQg8LECQACyAAIAEgAUEwaiADEEULcAIBfwF9IwBBEGsiBCQAIARBCGogACABQQJ0QdwlaigCACACEDZDAADAfyEFAkACQAJAIAQtAAxBAWsOAgABAgsgBCoCCCEFDAELIAQqAgggA5RDCtcjPJQhBQsgBEEQaiQAIAVDAAAAACAFIAVbGwt5AgF/AX0jAEEQayIDJAAgA0EIaiAAIAFBAnRB7CVqKAIAIAIQU0MAAMB/IQQCQAJAAkAgAy0ADEEBaw4CAAECCyADKgIIIQQMAQsgAyoCCEMAAAAAlEMK1yM8lCEECyADQRBqJAAgBEMAAAAAl0MAAAAAIAQgBFsbC1QAAkACQAJAAkACQCACDgQEAAECAwsgACABIAFBwgBqEEMPCyAAIAEgAUHCAGogAxBEDwsgACABIAFBwgBqEEIPCxAkAAsgACABIAFBwgBqIAMQRQsvACAAIAJFQQF0IgIgASADEGAgACACIAEQS5IgACACIAEgAxB/IAAgAiABEFKSkgvOAQIDfwJ9IwBBEGsiAyQAQQEhBCADQQhqIABB/ABqIgUgACABQQF0akH2AGoiAS8BABAfAkACQCADKgIIIgcgAioCACIGXARAIAcgB1sEQCACLQAEIQIMAgsgBiAGXCEECyACLQAEIQIgBEUNACADLQAMIAJB/wFxRg0BCyAFIAEgBiACEDkDQCAALQAAIgFBBHENASAAIAFBBHI6AAAgACgCECIBBEAgACABEQAACyAAQYCAgP4HNgKcASAAKALkAyIADQALCyADQRBqJAALzgECA38CfSMAQRBrIgMk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AWsOAgABAgsgBCoCCCEFDAELIAQqAgggA5RDCtcjPJQhBQsgBEEQaiQAIAVDAAAAACAFIAVbGwscACAAIAFBCCACpyACQiCIpyADpyADQiCIpxAVCwUAEFgACzkAIABFBEBBAA8LAn8gAUGAf3FBgL8DRiABQf8ATXJFBEBB/DtBGTYCAEF/DAELIAAgAToAAEEBCwvEAgACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCABQQlrDhIACgsMCgsCAwQFDAsMDAoLBwgJCyACIAIoAgAiAUEEajYCACAAIAEoAgA2AgAPCwALIAIgAigCACIBQQRqNgIAIAAgATIBADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATMBADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATAAADcDAA8LIAIgAigCACIBQQRqNgIAIAAgATEAADcDAA8LAAsgAiACKAIAQQdqQXhxIgFBCGo2AgAgACABKwMAOQMADwsgACACIAMRAQALDwsgAiACKAIAIgFBBGo2AgAgACABNAIANwMADwsgAiACKAIAIgFBBGo2AgAgACABNQIANwMADwsgAiACKAIAQQdqQXhxIgFBCGo2AgAgACABKQMANwMAC84BAgN/An0jAEEQayIDJABBASEEIANBCGogAEH8AGoiBSAAIAFBAXRqQegAaiIBLwEAEB8CQAJAIAMqAggiByACKgIAIgZcBEAgByAHWwRAIAItAAQhAgwCCyAGIAZcIQQLIAItAAQhAiAERQ0AIAMtAAwgAkH/AXFGDQELIAUgASAGIAIQOQNAIAAtAAAiAUEEcQ0BIAAgAUEEcjoAACAAKAIQIgEEQCAAIAERAAALIABBgICA/gc2ApwBIAAoAuQDIgANAAsLIANBEGokAAtdAQR/IAAoAgAhAgNAIAIsAAAiAxBXBEBBfyEEIAAgAkEBaiICNgIAIAFBzJmz5gBNBH9BfyADQTBrIgMgAUEKbCIEaiADIARB/////wdzShsFIAQLIQEMAQsLIAELrhQCEn8BfiMAQdAAayIIJAAgCCABNgJMIAhBN2ohFyAIQThqIRQCQAJAAkACQANAIAEhDSAHIA5B/////wdzSg0BIAcgDmohDgJAAkACQCANIgctAAAiCQRAA0ACQAJAIAlB/wFxIgFFBEAgByEBDAELIAFBJUcNASAH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IQEgAEIANwIEIAAgATYCACABIAA2AgQgAkEQaiQAIAALGwEBfyAABEAgACgCACIBBEAgARAjCyAAECMLC0kBAn9BBBAeIQFBIBAeIgBBADYCHCAAQoCAgICAgIDAPzcCFCAAQgA3AgwgAEEAOgAIIABBAzYCBCAAQQA2AgAgASAANgIAIAELIAAgAkEFR0EAIAIbRQRAQbgwIAMgBBBJDwsgAyAEEHALIgEBfiABIAKtIAOtQiCGhCAEIAARFQAiBUIgiKckASAFpwuoAQEFfyAAKAJUIgMoAgAhBSADKAIEIgQgACgCFCAAKAIcIgdrIgYgBCAGSRsiBgRAIAUgByAGECsaIAMgAygCACAGaiIFNgIAIAMgAygCBCAGayIENgIECyAEIAIgAiAESxsiBARAIAUgASAEECsaIAMgAygCACAEaiIFNgIAIAMgAygCBCAEazYCBAsgBUEAOgAAIAAgACgCLCIBNgIcIAAgATYCFCACCwQAQgALBABBAAuKBQIGfgJ/IAEgASgCAEEHakF4cSIBQRBqNgIAIAAhCSABKQMAIQMgASkDCCEGIwBBIGsiCCQAAkAgBkL///////////8AgyIEQoCAgICAgMCAPH0gBEKAgICAgIDA/8MAfVQEQCAGQgSGIANCPIiEIQQgA0L//////////w+DIgNCgYCAgICAgIAIWgRAIARCgYCAgICAgIDAAHwhAgwCCyAEQoCAgICAgICAQH0hAiADQoCAgICAgICACFINASACIARCAYN8IQIMAQsgA1AgBEKAgICAgIDA//8AVCAEQoCAgICAgMD//wBRG0UEQCAGQgSGIANCPIiEQv////////8Dg0KAgICAgICA/P8AhCECDAELQoCAgICAgID4/wAhAiAEQv///////7//wwBWDQBCACECIARCMIinIgBBkfcASQ0AIAMhAiAGQv///////z+DQoCAgICAgMAAhCIFIQcCQCAAQYH3AGsiAUHAAHEEQCACIAFBQGqthiEHQgAhAgwBCyABRQ0AIAcgAa0iBIYgAkHAACABa62IhCEHIAIgBIYhAgsgCCACNwMQIAggBzcDGAJAQYH4ACAAayIAQcAAcQRAIAUgAEFAaq2IIQNCACEFDAELIABFDQAgBUHAACAAa62GIAMgAK0iAoiEIQMgBSACiCEFCyAIIAM3AwAgCCAF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ASAAKAIAIQAgASACQQFxBH8gASgCACAAaigCAAUgAAsRCwALYQECfyMAQRBrIgIkACABIAAoAgQiA0EBdWohASAAKAIAIQAgAiABIANBAXEEfyABKAIAIABqKAIABSAACxEBAEEQEB4iACACKQMINwMIIAAgAikDADcDACACQRBqJAAgAAtjAQJ/IwBBEGsiAyQAIAEgACgCBCIEQQF1aiEBIAAoAgAhACADIAEgAiAEQQFxBH8gASgCACAAaigCAAUgAAsRAwBBEBAeIgAgAykDCDcDCCAAIAMpAwA3AwAgA0EQaiQAIAALNwEBfyABIAAoAgQiA0EBdWohASAAKAIAIQAgASACIANBAXEEfyABKAIAIABqKAIABSAACxEEAAs5AQF/IAEgACgCBCIEQQF1aiEBIAAoAgAhACABIAIgAyAEQQFxBH8gASgCACAAaigCAAUgAAsRCAALCQAgASAAEQIACwUAQcM7Cw8AIAEgACgCAGogAjYCAAsNACABIAAoAgBqKAIACxgBAX9BEBAeIgBCADcDCCAAQQA2AgAgAAsYAQF/QRAQHiIAQgA3AwAgAEIANwMIIAALDABBMBAeQQBBMBAqCzcBAX8gASAAKAIEIgNBAXVqIQEgACgCACEAIAEgAiADQQFxBH8gASgCACAAaigCAAUgAAsRHgALBQBBvjsLIQAgACABKAIAIAEgASwAC0EASBtBuzsgAigCABAQNgIACyoBAX9BDBAeIgFBADoABCABIAAoAgA2AgggAEEANgIAIAFB2Cc2AgAgAQsFAEG7OwsFAEG4OwshACAAIAEoAgAgASABLAALQQBIG0GkOyACKAIAEBA2AgAL2AEBBH8jAEEgayIDJAAgASgCACIEQfD///8HSQRAAkACQCAEQQtPBEAgBEEPckEBaiIFEB4hBiADIAVBgICAgHhyNgIQIAMgBjYCCCADIAQ2AgwgBCAGaiEFDAELIAMgBDoAEyADQQhqIgYgBGohBSAERQ0BCyAGIAFBBGogBBArGgsgBUEAOgAAIAMgAjYCACADQRhqIANBCGogAyAAEQMAIAMoAhgQHSADKAIYIgAQBiADKAIAEAYgAywAE0EASARAIAMoAggQIwsgA0EgaiQAIAAPCxACAAsqAQF/QQwQHiIBQQA6AAQgASAAKAIANgIIIABBADYCACABQeAmNgIA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AkEIaiAIIAYQHyACIAcgAC8AABAfQQEhAyACKgIIIgogAioCACILXAR/IAogClsNAyALIAtcBSADC0UNAiACLQAMIAItAARHDQILIABBAmohAEEBIQMgBCEGQQEhBCAGRQ0ACyABLwB6IgBBB3FFBEAgBS0AekEHcUUNAgsgCCAAECAiCiAHIAUvAHoQICILWw0BIAogClsNACALIAtcDQELIAFBFGogBUEUakHoABArGiABQfwAaiAFQfwAahCgAQNAIAEtAAAiAEEEcQ0BIAEgAEEEcjoAACABKAIQIgAEQCABIAARAAALIAFBgICA/gc2ApwBIAEoAuQDIgENAAsLIAJBEGokAAvGAwEEfyMAQaAEayICJAAgACgCBCEBIABBADYCBCABBEAgASABKAIAKAIEEQAACyAAKAIIIQEgAEEANgIIIAEEQCABIAEoAgAoAgQRAAALAkAgACgCACIAKALoAyAAKALsA0YEQCAAKALkAw0BIAAgAkEYaiAAKAL0AxBcIgEpAgA3AgAgACABKAIQNgIQIAAgASkCCDcCCCAAQRRqIAFBFGpB6AAQKxogACABKQKMATcCjAEgACABKQKEATcChAEgACABKQJ8NwJ8IAEoApQBIQQgAUEANgKUASAAKAKUASEDIAAgBDYClAEgAwRAIAMQWwsgAEGYAWogAUGYAWpB0AIQKxogACgC6AMiAwRAIAAgAzYC7AMgAxAjCyAAIAEoAugDNgLoAyAAIAEoAuwDNgLsAyAAIAEoAvADNgLwAyABQQA2AvADIAFCADcC6AMgACABKQL8AzcC/AMgACABKQL0AzcC9AMgACABKAKEBDYChAQgASgClAEhACABQQA2ApQBIAAEQCAAEFsLIAJBoARqJAAPCyACQfAcNgIQIABBBUHYJSACQRBqECwQJAALIAJB5hE2AgAgAEEFQdglIAIQLBAkAAsLAEEMEB4gABCiAQsLAEEMEB5BABCiAQsNACAAKAIALQAIQQFxCwoAIAAoAgAoAhQLGQAgAUH/AXEEQBACAAsgACgCACgCEEEBcQsYACAAKAIAIgAgAC0ACEH+AXEgAXI6AAgLJgAgASAAKAIAIgAoAhRHBEAgACABNgIUIAAgACgCDEEBajYCDAsLkgEBAn8jAEEQayICJAAgACgCACEAIAFDAAAAAGAEQCABIAAqAhhcBEAgACABOAIYIAAgACgCDEEBajYCDAsgAkEQaiQADwsgAkGIFDYCACMAQRBrIgMkACADIAI2AgwCQCAARQRAQbgwQdglIAIQSRoMAQsgAEEAQQVB2CUgAiAAKAIEEQ0AGgsgA0EQaiQAECQACz8AIAFB/wFxRQRAIAIgACgCACIAKAIQIgFBAXFHBEAgACABQX5xIAJyNgIQIAAgACgCDEEBajYCDAsPCxACAAsL4CYjAEGACAuBHk9ubHkgbGVhZiBub2RlcyB3aXRoIGN1c3RvbSBtZWFzdXJlIGZ1bmN0aW9ucyBzaG91bGQgbWFudWFsbHkgbWFyayB0aGVtc2VsdmVzIGFzIGRpcnR5AGlzRGlydHkAbWFya0RpcnR5AGRlc3Ryb3kAc2V0RGlzcGxheQBnZXREaXNwbGF5AHNldEZsZXgALSsgICAwWDB4AC0wWCswWCAwWC0weCsweCAweABzZXRGbGV4R3JvdwBnZXRGbGV4R3JvdwBzZXRPdmVyZmxvdwBnZXRPdmVyZmxvdwBoYXNOZXdMYXlvdXQAY2FsY3VsYXRlTGF5b3V0AGdldENvbXB1dGVkTGF5b3V0AHVuc2lnbmVkIHNob3J0AGdldENoaWxkQ291bnQAdW5zaWduZWQgaW50AHNldEp1c3RpZnlDb250ZW50AGdldEp1c3RpZnlDb250ZW50AGF2YWlsYWJsZUhlaWdodCBpcyBpbmRlZmluaXRlIHNvIGhlaWdodFNpemluZ01vZGUgbXVzdCBiZSBTaXppbmdNb2RlOjpNYXhDb250ZW50AGF2YWlsYWJsZVdpZHRoIGlzIGluZGVmaW5pdGUgc28gd2lkdGhTaXppbmdNb2RlIG11c3QgYmUgU2l6aW5nTW9kZTo6TWF4Q29udGVudABzZXRBbGlnbkNvbnRlbnQAZ2V0QWxpZ25Db250ZW50AGdldFBhcmVudABpbXBsZW1lbnQAc2V0TWF4SGVpZ2h0UGVyY2VudABzZXRIZWlnaHRQZXJjZW50AHNldE1pbkhlaWdodFBlcmNlbnQAc2V0RmxleEJhc2lzUGVyY2VudABzZXRHYXBQZXJjZW50AHNldFBvc2l0aW9uUGVyY2VudABzZXRNYXJnaW5QZXJjZW50AHNldE1heFdpZHRoUGVyY2VudABzZXRXaWR0aFBlcmNlbnQAc2V0TWluV2lkdGhQZXJjZW50AHNldFBhZGRpbmdQZXJjZW50AGhhbmRsZS50eXBlKCkgPT0gU3R5bGVWYWx1ZUhhbmRsZTo6VHlwZTo6UG9pbnQgfHwgaGFuZGxlLnR5cGUoKSA9PSBTdHlsZVZhbHVlSGFuZGxlOjpUeXBlOjpQZXJjZW50AGNyZWF0ZURlZmF1bHQAdW5pdAByaWdodABoZWlnaHQAc2V0TWF4SGVpZ2h0AGdldE1heEhlaWdodABzZXRIZWlnaHQAZ2V0SGVpZ2h0AHNldE1pbkhlaWdodABnZXRNaW5IZWlnaHQAZ2V0Q29tcHV0ZWRIZWlnaHQAZ2V0Q29tcHV0ZWRSaWdodABsZWZ0AGdldENvbXB1dGVkTGVmdAByZXNldABfX2Rlc3RydWN0AGZsb2F0AHVpbnQ2NF90AHVzZVdlYkRlZmF1bHRzAHNldFVzZVdlYkRlZmF1bHRzAHNldEFsaWduSXRlbXMAZ2V0QWxpZ25JdGVtcwBzZXRGbGV4QmFzaXMAZ2V0RmxleEJhc2lzAENhbm5vdCBnZXQgbGF5b3V0IHByb3BlcnRpZXMgb2YgbXVsdGktZWRnZSBzaG9ydGhhbmRzAHNldFBvaW50U2NhbGVGYWN0b3IATWVhc3VyZUNhbGxiYWNrV3JhcHBlcgBEaXJ0aWVkQ2FsbGJhY2tXcmFwcGVyAENhbm5vdCByZXNldCBhIG5vZGUgc3RpbGwgYXR0YWNoZWQgdG8gYSBvd25lcgBzZXRCb3JkZXIAZ2V0Qm9yZGVyAGdldENvbXB1dGVkQm9yZGVyAGdldE51bWJlcgBoYW5kbGUudHlwZSgpID09IFN0eWxlVmFsdWVIYW5kbGU6OlR5cGU6Ok51bWJlcgB1bnNpZ25lZCBjaGFyAHRvcABnZXRDb21wdXRlZFRvcABzZXRGbGV4V3JhcABnZXRGbGV4V3JhcABzZXRHYXAAZ2V0R2FwACVwAHNldEhlaWdodEF1dG8Ac2V0RmxleEJhc2lzQXV0bwBzZXRQb3NpdGlvbkF1dG8Ac2V0TWFyZ2luQXV0bwBzZXRXaWR0aEF1dG8AU2NhbGUgZmFjdG9yIHNob3VsZCBub3QgYmUgbGVzcyB0aGFuIHplcm8Ac2V0QXNwZWN0UmF0aW8AZ2V0QXNwZWN0UmF0aW8Ac2V0UG9zaXRpb24AZ2V0UG9zaXRpb24Abm90aWZ5T25EZXN0cnVjdGlvbgBzZXRGbGV4RGlyZWN0aW9uAGdldEZsZXhEaXJlY3Rpb24Ac2V0RGlyZWN0aW9uAGdldERpcmVjdGlvbgBzZXRNYXJnaW4AZ2V0TWFyZ2luAGdldENvbXB1dGVkTWFyZ2luAG1hcmtMYXlvdXRTZWVuAG5hbgBib3R0b20AZ2V0Q29tcHV0ZWRCb3R0b20AYm9vbABlbXNjcmlwdGVuOjp2YWwAc2V0RmxleFNocmluawBnZXRGbGV4U2hyaW5rAHNldEFsd2F5c0Zvcm1zQ29udGFpbmluZ0Jsb2NrAE1lYXN1cmVDYWxsYmFjawBEaXJ0aWVkQ2FsbGJhY2sAZ2V0TGVuZ3RoAHdpZHRoAHNldE1heFdpZHRoAGdldE1heFdpZHRoAHNldFdpZHRoAGdldFdpZHRoAHNldE1pbldpZHRoAGdldE1pbldpZHRoAGdldENvbXB1dGVkV2lkdGgAcHVzaAAvaG9tZS9ydW5uZXIvd29yay95b2dhL3lvZ2EvamF2YXNjcmlwdC8uLi95b2dhL3N0eWxlL1NtYWxsVmFsdWVCdWZmZXIuaAAvaG9tZS9ydW5uZXIvd29yay95b2dhL3lvZ2EvamF2YXNjcmlwdC8uLi95b2dhL3N0eWxlL1N0eWxlVmFsdWVQb29sLmgAdW5zaWduZWQgbG9uZwBzZXRCb3hTaXppbmcAZ2V0Qm94U2l6aW5nAHN0ZDo6d3N0cmluZwBzdGQ6OnN0cmluZwBzdGQ6OnUxNnN0cmluZwBzdGQ6OnUzMnN0cmluZwBzZXRQYWRkaW5nAGdldFBhZGRpbmcAZ2V0Q29tcHV0ZWRQYWRkaW5nAFRyaWVkIHRvIGNvbnN0cnVjdCBZR05vZGUgd2l0aCBudWxsIGNvbmZpZwBBdHRlbXB0aW5nIHRvIGNvbnN0cnVjdCBOb2RlIHdpdGggbnVsbCBjb25maWcAY3JlYXRlV2l0aENvbmZpZwBpbmYAc2V0QWxpZ25TZWxmAGdldEFsaWduU2VsZgBTaXplAHZhbHVlAFZhbHVlAGNyZWF0ZQBtZWFzdXJlAHNldFBvc2l0aW9uVHlwZQBnZXRQb3NpdGlvblR5cGUAaXNSZWZlcmVuY2VCYXNlbGluZQBzZXRJc1JlZmVyZW5jZUJhc2VsaW5lAGNvcHlTdHlsZQBkb3VibGUATm9kZQBleHRlbmQAaW5zZXJ0Q2hpbGQAZ2V0Q2hpbGQAcmVtb3ZlQ2hpbGQAdm9pZABzZXRFeHBlcmltZW50YWxGZWF0dXJlRW5hYmxlZABpc0V4cGVyaW1lbnRhbEZlYXR1cmVFbmFibGVkAGRpcnRpZWQAQ2Fubm90IHJlc2V0IGEgbm9kZSB3aGljaCBzdGlsbCBoYXMgY2hpbGRyZW4gYXR0YWNoZWQAdW5zZXRNZWFzdXJlRnVuYwB1bnNldERpcnRpZWRGdW5jAHNldEVycmF0YQBnZXRFcnJhdGEATWVhc3VyZSBmdW5jdGlvbiByZXR1cm5lZCBhbiBpbnZhbGlkIGRpbWVuc2lvbiB0byBZb2dhOiBbd2lkdGg9JWYsIGhlaWdodD0lZl0ARXhwZWN0IGN1c3RvbSBiYXNlbGluZSBmdW5jdGlvbiB0byBub3QgcmV0dXJuIE5hTgBOQU4ASU5GAGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHNob3J0PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1bnNpZ25lZCBzaG9ydD4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8aW50PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1bnNpZ25lZCBpbnQ+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PGZsb2F0PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1aW50OF90PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxpbnQ4X3Q+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHVpbnQxNl90PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxpbnQxNl90PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1aW50MzJfdD4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8aW50MzJfdD4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8Y2hhcj4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8dW5zaWduZWQgY2hhcj4Ac3RkOjpiYXNpY19zdHJpbmc8dW5zaWduZWQgY2hhcj4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8c2lnbmVk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", !wA(aA)) { + var FA = aA; + aA = t.locateFile ? t.locateFile(FA, o) : o + FA; + } + function kA() { + var s = aA; + try { + if (s == aA && l2) return new Uint8Array(l2); + if (wA(s)) try { + var g2 = Ln(s.slice(37)), c2 = new Uint8Array(g2.length); + for (s = 0; s < g2.length; ++s) c2[s] = g2.charCodeAt(s); + var B = c2; + } catch { + throw Error("Converting base64 string to bytes failed."); + } + else B = void 0; + var Q = B; + if (Q) return Q; + throw "both async and sync fetching of the wasm failed"; + } catch (h2) { + iA(h2); + } + } + function te() { + return l2 || typeof fetch != "function" ? Promise.resolve().then(function() { + return kA(); + }) : fetch(aA, { credentials: "same-origin" }).then(function(s) { + if (!s.ok) throw "failed to load wasm binary file at '" + aA + "'"; + return s.arrayBuffer(); + }).catch(function() { + return kA(); + }); + } + function TA(s) { + for (; 0 < s.length; ) s.shift()(t); + } + function xA(s) { + if (s === void 0) return "_unknown"; + s = s.replace(/[^a-zA-Z0-9_]/g, "$"); + var g2 = s.charCodeAt(0); + return 48 <= g2 && 57 >= g2 ? "_" + s : s; + } + function fA(s, g2) { + return s = xA(s), function() { + return g2.apply(this, arguments); + }; + } + var q = [{}, { value: void 0 }, { value: null }, { value: true }, { value: false }], oA = []; + function BA(s) { + var g2 = Error, c2 = fA(s, function(B) { + this.name = s, this.message = B, B = Error(B).stack, B !== void 0 && (this.stack = this.toString() + ` +` + B.replace(/^Error(:[^\n]*)?\n/, "")); + }); + return c2.prototype = Object.create(g2.prototype), c2.prototype.constructor = c2, c2.prototype.toString = function() { + return this.message === void 0 ? this.name : this.name + ": " + this.message; + }, c2; + } + var W = void 0; + function Y(s) { + throw new W(s); + } + var OA = (s) => (s || Y("Cannot use deleted val. handle = " + s), q[s].value), PA = (s) => { + switch (s) { + case void 0: + return 1; + case null: + return 2; + case true: + return 3; + case false: + return 4; + default: + var g2 = oA.length ? oA.pop() : q.length; + return q[g2] = { ga: 1, value: s }, g2; + } + }, WA = void 0, uA = void 0; + function hA(s) { + for (var g2 = ""; k[s]; ) g2 += uA[k[s++]]; + return g2; + } + var Ie = []; + function le() { + for (; Ie.length; ) { + var s = Ie.pop(); + s.M.$ = false, s.delete(); + } + } + var LA = void 0, re = {}; + function ce(s, g2) { + for (g2 === void 0 && Y("ptr should not be undefined"); s.R; ) g2 = s.ba(g2), s = s.R; + return g2; + } + var ye = {}; + function Pe(s) { + s = vt(s); + var g2 = hA(s); + return de(s), g2; + } + function Ye(s, g2) { + var c2 = ye[s]; + return c2 === void 0 && Y(g2 + " has unknown type " + Pe(s)), c2; + } + function Ge() { + } + var Ue = false; + function we(s) { + --s.count.value, s.count.value === 0 && (s.T ? s.U.W(s.T) : s.P.N.W(s.O)); + } + function fe(s, g2, c2) { + return g2 === c2 ? s : c2.R === void 0 ? null : (s = fe(s, g2, c2.R), s === null ? null : c2.na(s)); + } + var se = {}; + function qe(s, g2) { + return g2 = ce(s, g2), re[g2]; + } + var zA = void 0; + function ne(s) { + throw new zA(s); + } + function ie(s, g2) { + return g2.P && g2.O || ne("makeClassHandle requires ptr and ptrType"), !!g2.U != !!g2.T && ne("Both smartPtrType and smartPtr must be specified"), g2.count = { value: 1 }, Be(Object.create(s, { M: { value: g2 } })); + } + function Be(s) { + return typeof FinalizationRegistry > "u" ? (Be = (g2) => g2, s) : (Ue = new FinalizationRegistry((g2) => { + we(g2.M); + }), Be = (g2) => { + var c2 = g2.M; + return c2.T && Ue.register(g2, { M: c2 }, g2), g2; + }, Ge = (g2) => { + Ue.unregister(g2); + }, Be(s)); + } + var De = {}; + function KA(s) { + for (; s.length; ) { + var g2 = s.pop(); + s.pop()(g2); + } + } + function XA(s) { + return this.fromWireType(b[s >> 2]); + } + var _A = {}, oe = {}; + function V(s, g2, c2) { + function B(w2) { + w2 = c2(w2), w2.length !== s.length && ne("Mismatched type converter count"); + for (var D = 0; D < s.length; ++D) nA(s[D], w2[D]); + } + s.forEach(function(w2) { + oe[w2] = g2; + }); + var Q = Array(g2.length), h2 = [], m2 = 0; + g2.forEach((w2, D) => { + ye.hasOwnProperty(w2) ? Q[D] = ye[w2] : (h2.push(w2), _A.hasOwnProperty(w2) || (_A[w2] = []), _A[w2].push(() => { + Q[D] = ye[w2], ++m2, m2 === h2.length && B(Q); + })); + }), h2.length === 0 && B(Q); + } + function Z(s) { + switch (s) { + case 1: + return 0; + case 2: + return 1; + case 4: + return 2; + case 8: + return 3; + default: + throw new TypeError("Unknown type size: " + s); + } + } + function nA(s, g2, c2 = {}) { + if (!("argPackAdvance" in g2)) throw new TypeError("registerType registeredInstance requires argPackAdvance"); + var B = g2.name; + if (s || Y('type "' + B + '" must have a positive integer typeid pointer'), ye.hasOwnProperty(s)) { + if (c2.ua) return; + Y("Cannot register type '" + B + "' twice"); + } + ye[s] = g2, delete oe[s], _A.hasOwnProperty(s) && (g2 = _A[s], delete _A[s], g2.forEach((Q) => Q())); + } + function $(s) { + Y(s.M.P.N.name + " instance already deleted"); + } + function IA() { + } + function lA(s, g2, c2) { + if (s[g2].S === void 0) { + var B = s[g2]; + s[g2] = function() { + return s[g2].S.hasOwnProperty(arguments.length) || Y("Function '" + c2 + "' called with an invalid number of arguments (" + arguments.length + ") - expects one of (" + s[g2].S + ")!"), s[g2].S[arguments.length].apply(this, arguments); + }, s[g2].S = [], s[g2].S[B.Z] = B; + } + } + function DA(s, g2) { + t.hasOwnProperty(s) ? (Y("Cannot register public name '" + s + "' twice"), lA(t, s, s), t.hasOwnProperty(void 0) && Y("Cannot register multiple overloads of a function with the same number of arguments (undefined)!"), t[s].S[void 0] = g2) : t[s] = g2; + } + function cA(s, g2, c2, B, Q, h2, m2, w2) { + this.name = s, this.constructor = g2, this.X = c2, this.W = B, this.R = Q, this.pa = h2, this.ba = m2, this.na = w2, this.ja = []; + } + function gA(s, g2, c2) { + for (; g2 !== c2; ) g2.ba || Y("Expected null or instance of " + c2.name + ", got an instance of " + g2.name), s = g2.ba(s), g2 = g2.R; + return s; + } + function Ee(s, g2) { + return g2 === null ? (this.ea && Y("null is not a valid " + this.name), 0) : (g2.M || Y('Cannot pass "' + jA(g2) + '" as a ' + this.name), g2.M.O || Y("Cannot pass deleted object as a pointer of type " + this.name), gA(g2.M.O, g2.M.P.N, this.N)); + } + function eA(s, g2) { + if (g2 === null) { + if (this.ea && Y("null is not a valid " + this.name), this.da) { + var c2 = this.fa(); + return s !== null && s.push(this.W, c2), c2; + } + return 0; + } + if (g2.M || Y('Cannot pass "' + jA(g2) + '" as a ' + this.name), g2.M.O || Y("Cannot pass deleted object as a pointer of type " + this.name), !this.ca && g2.M.P.ca && Y("Cannot convert argument of type " + (g2.M.U ? g2.M.U.name : g2.M.P.name) + " to parameter type " + this.name), c2 = gA(g2.M.O, g2.M.P.N, this.N), this.da) switch (g2.M.T === void 0 && Y("Passing raw pointer to smart pointer is illegal"), this.Ba) { + case 0: + g2.M.U === this ? c2 = g2.M.T : Y("Cannot convert argument of type " + (g2.M.U ? g2.M.U.name : g2.M.P.name) + " to parameter type " + this.name); + break; + case 1: + c2 = g2.M.T; + break; + case 2: + if (g2.M.U === this) c2 = g2.M.T; + else { + var B = g2.clone(); + c2 = this.xa(c2, PA(function() { + B.delete(); + })), s !== null && s.push(this.W, c2); + } + break; + default: + Y("Unsupporting sharing policy"); + } + return c2; + } + function JA(s, g2) { + return g2 === null ? (this.ea && Y("null is not a valid " + this.name), 0) : (g2.M || Y('Cannot pass "' + jA(g2) + '" as a ' + this.name), g2.M.O || Y("Cannot pass deleted object as a pointer of type " + this.name), g2.M.P.ca && Y("Cannot convert argument of type " + g2.M.P.name + " to parameter type " + this.name), gA(g2.M.O, g2.M.P.N, this.N)); + } + function RA(s, g2, c2, B) { + this.name = s, this.N = g2, this.ea = c2, this.ca = B, this.da = false, this.W = this.xa = this.fa = this.ka = this.Ba = this.wa = void 0, g2.R !== void 0 ? this.toWireType = eA : (this.toWireType = B ? Ee : JA, this.V = null); + } + function ut(s, g2) { + t.hasOwnProperty(s) || ne("Replacing nonexistant public symbol"), t[s] = g2, t[s].Z = void 0; + } + function GA(s, g2) { + var c2 = []; + return function() { + if (c2.length = 0, Object.assign(c2, arguments), s.includes("j")) { + var B = t["dynCall_" + s]; + B = c2 && c2.length ? B.apply(null, [g2].concat(c2)) : B.call(null, g2); + } else B = J.get(g2).apply(null, c2); + return B; + }; + } + function YA(s, g2) { + s = hA(s); + var c2 = s.includes("j") ? GA(s, g2) : J.get(g2); + return typeof c2 != "function" && Y("unknown function pointer with signature " + s + ": " + g2), c2; + } + var qA = void 0; + function Qe(s, g2) { + function c2(h2) { + Q[h2] || ye[h2] || (oe[h2] ? oe[h2].forEach(c2) : (B.push(h2), Q[h2] = true)); + } + var B = [], Q = {}; + throw g2.forEach(c2), new qA(s + ": " + B.map(Pe).join([", "])); + } + function pA(s, g2, c2, B, Q) { + var h2 = g2.length; + 2 > h2 && Y("argTypes array size mismatch! Must at least get return value and 'this' types!"); + var m2 = g2[1] !== null && c2 !== null, w2 = false; + for (c2 = 1; c2 < g2.length; ++c2) if (g2[c2] !== null && g2[c2].V === void 0) { + w2 = true; + break; + } + var D = g2[0].name !== "void", S2 = h2 - 2, N = Array(S2), U = [], X = []; + return function() { + if (arguments.length !== S2 && Y("function " + s + " called with " + arguments.length + " arguments, expected " + S2 + " args!"), X.length = 0, U.length = m2 ? 2 : 1, U[0] = Q, m2) { + var z = g2[1].toWireType(X, this); + U[1] = z; + } + for (var AA = 0; AA < S2; ++AA) N[AA] = g2[AA + 2].toWireType(X, arguments[AA]), U.push(N[AA]); + if (AA = B.apply(null, U), w2) KA(X); + else for (var NA = m2 ? 1 : 2; NA < g2.length; NA++) { + var Ae = NA === 1 ? z : N[NA - 2]; + g2[NA].V !== null && g2[NA].V(Ae); + } + return z = D ? g2[0].fromWireType(AA) : void 0, z; + }; + } + function mA(s, g2) { + for (var c2 = [], B = 0; B < s; B++) c2.push(v2[g2 + 4 * B >> 2]); + return c2; + } + function ZA(s) { + 4 < s && --q[s].ga === 0 && (q[s] = void 0, oA.push(s)); + } + function jA(s) { + if (s === null) return "null"; + var g2 = typeof s; + return g2 === "object" || g2 === "array" || g2 === "function" ? s.toString() : "" + s; + } + function Se(s, g2) { + switch (g2) { + case 2: + return function(c2) { + return this.fromWireType(M[c2 >> 2]); + }; + case 3: + return function(c2) { + return this.fromWireType(L[c2 >> 3]); + }; + default: + throw new TypeError("Unknown float type: " + s); + } + } + function be(s, g2, c2) { + switch (g2) { + case 0: + return c2 ? function(B) { + return y[B]; + } : function(B) { + return k[B]; + }; + case 1: + return c2 ? function(B) { + return x2[B >> 1]; + } : function(B) { + return F[B >> 1]; + }; + case 2: + return c2 ? function(B) { + return b[B >> 2]; + } : function(B) { + return v2[B >> 2]; + }; + default: + throw new TypeError("Unknown integer type: " + s); + } + } + function $A(s, g2) { + for (var c2 = "", B = 0; !(B >= g2 / 2); ++B) { + var Q = x2[s + 2 * B >> 1]; + if (Q == 0) break; + c2 += String.fromCharCode(Q); + } + return c2; + } + function Ce(s, g2, c2) { + if (c2 === void 0 && (c2 = 2147483647), 2 > c2) return 0; + c2 -= 2; + var B = g2; + c2 = c2 < 2 * s.length ? c2 / 2 : s.length; + for (var Q = 0; Q < c2; ++Q) x2[g2 >> 1] = s.charCodeAt(Q), g2 += 2; + return x2[g2 >> 1] = 0, g2 - B; + } + function It(s) { + return 2 * s.length; + } + function et(s, g2) { + for (var c2 = 0, B = ""; !(c2 >= g2 / 4); ) { + var Q = b[s + 4 * c2 >> 2]; + if (Q == 0) break; + ++c2, 65536 <= Q ? (Q -= 65536, B += String.fromCharCode(55296 | Q >> 10, 56320 | Q & 1023)) : B += String.fromCharCode(Q); + } + return B; + } + function wt(s, g2, c2) { + if (c2 === void 0 && (c2 = 2147483647), 4 > c2) return 0; + var B = g2; + c2 = B + c2 - 4; + for (var Q = 0; Q < s.length; ++Q) { + var h2 = s.charCodeAt(Q); + if (55296 <= h2 && 57343 >= h2) { + var m2 = s.charCodeAt(++Q); + h2 = 65536 + ((h2 & 1023) << 10) | m2 & 1023; + } + if (b[g2 >> 2] = h2, g2 += 4, g2 + 4 > c2) break; + } + return b[g2 >> 2] = 0, g2 - B; + } + function Dt(s) { + for (var g2 = 0, c2 = 0; c2 < s.length; ++c2) { + var B = s.charCodeAt(c2); + 55296 <= B && 57343 >= B && ++c2, g2 += 4; + } + return g2; + } + var lt = {}; + function ct(s) { + var g2 = lt[s]; + return g2 === void 0 ? hA(s) : g2; + } + var St = []; + function Fn(s) { + var g2 = St.length; + return St.push(s), g2; + } + function Pr(s, g2) { + for (var c2 = Array(s), B = 0; B < s; ++B) c2[B] = Ye(v2[g2 + 4 * B >> 2], "parameter " + B); + return c2; + } + var _r = [], Jr = [null, [], []]; + W = t.BindingError = BA("BindingError"), t.count_emval_handles = function() { + for (var s = 0, g2 = 5; g2 < q.length; ++g2) q[g2] !== void 0 && ++s; + return s; + }, t.get_first_emval = function() { + for (var s = 5; s < q.length; ++s) if (q[s] !== void 0) return q[s]; + return null; + }, WA = t.PureVirtualError = BA("PureVirtualError"); + for (var ft = Array(256), bt = 0; 256 > bt; ++bt) ft[bt] = String.fromCharCode(bt); + uA = ft, t.getInheritedInstanceCount = function() { + return Object.keys(re).length; + }, t.getLiveInheritedInstances = function() { + var s = [], g2; + for (g2 in re) re.hasOwnProperty(g2) && s.push(re[g2]); + return s; + }, t.flushPendingDeletes = le, t.setDelayFunction = function(s) { + LA = s, Ie.length && LA && LA(le); + }, zA = t.InternalError = BA("InternalError"), IA.prototype.isAliasOf = function(s) { + if (!(this instanceof IA && s instanceof IA)) return false; + var g2 = this.M.P.N, c2 = this.M.O, B = s.M.P.N; + for (s = s.M.O; g2.R; ) c2 = g2.ba(c2), g2 = g2.R; + for (; B.R; ) s = B.ba(s), B = B.R; + return g2 === B && c2 === s; + }, IA.prototype.clone = function() { + if (this.M.O || $(this), this.M.aa) return this.M.count.value += 1, this; + var s = Be, g2 = Object, c2 = g2.create, B = Object.getPrototypeOf(this), Q = this.M; + return s = s(c2.call(g2, B, { M: { value: { count: Q.count, $: Q.$, aa: Q.aa, O: Q.O, P: Q.P, T: Q.T, U: Q.U } } })), s.M.count.value += 1, s.M.$ = false, s; + }, IA.prototype.delete = function() { + this.M.O || $(this), this.M.$ && !this.M.aa && Y("Object already scheduled for deletion"), Ge(this), we(this.M), this.M.aa || (this.M.T = void 0, this.M.O = void 0); + }, IA.prototype.isDeleted = function() { + return !this.M.O; + }, IA.prototype.deleteLater = function() { + return this.M.O || $(this), this.M.$ && !this.M.aa && Y("Object already scheduled for deletion"), Ie.push(this), Ie.length === 1 && LA && LA(le), this.M.$ = true, this; + }, RA.prototype.qa = function(s) { + return this.ka && (s = this.ka(s)), s; + }, RA.prototype.ha = function(s) { + this.W && this.W(s); + }, RA.prototype.argPackAdvance = 8, RA.prototype.readValueFromPointer = XA, RA.prototype.deleteObject = function(s) { + s !== null && s.delete(); + }, RA.prototype.fromWireType = function(s) { + function g2() { + return this.da ? ie(this.N.X, { P: this.wa, O: c2, U: this, T: s }) : ie(this.N.X, { P: this, O: s }); + } + var c2 = this.qa(s); + if (!c2) return this.ha(s), null; + var B = qe(this.N, c2); + if (B !== void 0) return B.M.count.value === 0 ? (B.M.O = c2, B.M.T = s, B.clone()) : (B = B.clone(), this.ha(s), B); + if (B = this.N.pa(c2), B = se[B], !B) return g2.call(this); + B = this.ca ? B.la : B.pointerType; + var Q = fe(c2, this.N, B.N); + return Q === null ? g2.call(this) : this.da ? ie(B.N.X, { P: B, O: Q, U: this, T: s }) : ie(B.N.X, { P: B, O: Q }); + }, qA = t.UnboundTypeError = BA("UnboundTypeError"); + var Ln = typeof atob == "function" ? atob : function(s) { + var g2 = "", c2 = 0; + s = s.replace(/[^A-Za-z0-9\+\/=]/g, ""); + do { + var B = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(s.charAt(c2++)), Q = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(s.charAt(c2++)), h2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(s.charAt(c2++)), m2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(s.charAt(c2++)); + B = B << 2 | Q >> 4, Q = (Q & 15) << 4 | h2 >> 2; + var w2 = (h2 & 3) << 6 | m2; + g2 += String.fromCharCode(B), h2 !== 64 && (g2 += String.fromCharCode(Q)), m2 !== 64 && (g2 += String.fromCharCode(w2)); + } while (c2 < s.length); + return g2; + }, Wr = { l: function(s, g2, c2, B) { + iA("Assertion failed: " + (s ? d2(k, s) : "") + ", at: " + [g2 ? g2 ? d2(k, g2) : "" : "unknown filename", c2, B ? B ? d2(k, B) : "" : "unknown function"]); + }, q: function(s, g2, c2) { + s = hA(s), g2 = Ye(g2, "wrapper"), c2 = OA(c2); + var B = [].slice, Q = g2.N, h2 = Q.X, m2 = Q.R.X, w2 = Q.R.constructor; + s = fA(s, function() { + Q.R.ja.forEach(function(S2) { + if (this[S2] === m2[S2]) throw new WA("Pure virtual function " + S2 + " must be implemented in JavaScript"); + }.bind(this)), Object.defineProperty(this, "__parent", { value: h2 }), this.__construct.apply(this, B.call(arguments)); + }), h2.__construct = function() { + this === h2 && Y("Pass correct 'this' to __construct"); + var S2 = w2.implement.apply(void 0, [this].concat(B.call(arguments))); + Ge(S2); + var N = S2.M; + S2.notifyOnDestruction(), N.aa = true, Object.defineProperties(this, { M: { value: N } }), Be(this), S2 = N.O, S2 = ce(Q, S2), re.hasOwnProperty(S2) ? Y("Tried to register registered instance: " + S2) : re[S2] = this; + }, h2.__destruct = function() { + this === h2 && Y("Pass correct 'this' to __destruct"), Ge(this); + var S2 = this.M.O; + S2 = ce(Q, S2), re.hasOwnProperty(S2) ? delete re[S2] : Y("Tried to unregister unregistered instance: " + S2); + }, s.prototype = Object.create(h2); + for (var D in c2) s.prototype[D] = c2[D]; + return PA(s); + }, j: function(s) { + var g2 = De[s]; + delete De[s]; + var c2 = g2.fa, B = g2.W, Q = g2.ia, h2 = Q.map((m2) => m2.ta).concat(Q.map((m2) => m2.za)); + V([s], h2, (m2) => { + var w2 = {}; + return Q.forEach((D, S2) => { + var N = m2[S2], U = D.ra, X = D.sa, z = m2[S2 + Q.length], AA = D.ya, NA = D.Aa; + w2[D.oa] = { read: (Ae) => N.fromWireType(U(X, Ae)), write: (Ae, ae) => { + var ke = []; + AA(NA, Ae, z.toWireType(ke, ae)), KA(ke); + } }; + }), [{ name: g2.name, fromWireType: function(D) { + var S2 = {}, N; + for (N in w2) S2[N] = w2[N].read(D); + return B(D), S2; + }, toWireType: function(D, S2) { + for (var N in w2) if (!(N in S2)) throw new TypeError('Missing field: "' + N + '"'); + var U = c2(); + for (N in w2) w2[N].write(U, S2[N]); + return D !== null && D.push(B, U), U; + }, argPackAdvance: 8, readValueFromPointer: XA, V: B }]; + }); + }, v: function() { + }, B: function(s, g2, c2, B, Q) { + var h2 = Z(c2); + g2 = hA(g2), nA(s, { name: g2, fromWireType: function(m2) { + return !!m2; + }, toWireType: function(m2, w2) { + return w2 ? B : Q; + }, argPackAdvance: 8, readValueFromPointer: function(m2) { + if (c2 === 1) var w2 = y; + else if (c2 === 2) w2 = x2; + else if (c2 === 4) w2 = b; + else throw new TypeError("Unknown boolean type size: " + g2); + return this.fromWireType(w2[m2 >> h2]); + }, V: null }); + }, f: function(s, g2, c2, B, Q, h2, m2, w2, D, S2, N, U, X) { + N = hA(N), h2 = YA(Q, h2), w2 && (w2 = YA(m2, w2)), S2 && (S2 = YA(D, S2)), X = YA(U, X); + var z = xA(N); + DA(z, function() { + Qe("Cannot construct " + N + " due to unbound types", [B]); + }), V([s, g2, c2], B ? [B] : [], function(AA) { + if (AA = AA[0], B) var NA = AA.N, Ae = NA.X; + else Ae = IA.prototype; + AA = fA(z, function() { + if (Object.getPrototypeOf(this) !== ae) throw new W("Use 'new' to construct " + N); + if (ke.Y === void 0) throw new W(N + " has no accessible constructor"); + var ls = ke.Y[arguments.length]; + if (ls === void 0) throw new W("Tried to invoke ctor of " + N + " with invalid number of parameters (" + arguments.length + ") - expected (" + Object.keys(ke.Y).toString() + ") parameters instead!"); + return ls.apply(this, arguments); + }); + var ae = Object.create(Ae, { constructor: { value: AA } }); + AA.prototype = ae; + var ke = new cA(N, AA, ae, X, NA, h2, w2, S2); + NA = new RA(N, ke, true, false), Ae = new RA(N + "*", ke, false, false); + var Wt = new RA(N + " const*", ke, false, true); + return se[s] = { pointerType: Ae, la: Wt }, ut(z, AA), [NA, Ae, Wt]; + }); + }, d: function(s, g2, c2, B, Q, h2, m2) { + var w2 = mA(c2, B); + g2 = hA(g2), h2 = YA(Q, h2), V([], [s], function(D) { + function S2() { + Qe("Cannot call " + N + " due to unbound types", w2); + } + D = D[0]; + var N = D.name + "." + g2; + g2.startsWith("@@") && (g2 = Symbol[g2.substring(2)]); + var U = D.N.constructor; + return U[g2] === void 0 ? (S2.Z = c2 - 1, U[g2] = S2) : (lA(U, g2, N), U[g2].S[c2 - 1] = S2), V([], w2, function(X) { + return X = pA(N, [X[0], null].concat(X.slice(1)), null, h2, m2), U[g2].S === void 0 ? (X.Z = c2 - 1, U[g2] = X) : U[g2].S[c2 - 1] = X, []; + }), []; + }); + }, p: function(s, g2, c2, B, Q, h2) { + 0 < g2 || iA(); + var m2 = mA(g2, c2); + Q = YA(B, Q), V([], [s], function(w2) { + w2 = w2[0]; + var D = "constructor " + w2.name; + if (w2.N.Y === void 0 && (w2.N.Y = []), w2.N.Y[g2 - 1] !== void 0) throw new W("Cannot register multiple constructors with identical number of parameters (" + (g2 - 1) + ") for class '" + w2.name + "'! Overload resolution is currently only performed using the parameter count, not actual type info!"); + return w2.N.Y[g2 - 1] = () => { + Qe("Cannot construct " + w2.name + " due to unbound types", m2); + }, V([], m2, function(S2) { + return S2.splice(1, 0, null), w2.N.Y[g2 - 1] = pA(D, S2, null, Q, h2), []; + }), []; + }); + }, a: function(s, g2, c2, B, Q, h2, m2, w2) { + var D = mA(c2, B); + g2 = hA(g2), h2 = YA(Q, h2), V([], [s], function(S2) { + function N() { + Qe("Cannot call " + U + " due to unbound types", D); + } + S2 = S2[0]; + var U = S2.name + "." + g2; + g2.startsWith("@@") && (g2 = Symbol[g2.substring(2)]), w2 && S2.N.ja.push(g2); + var X = S2.N.X, z = X[g2]; + return z === void 0 || z.S === void 0 && z.className !== S2.name && z.Z === c2 - 2 ? (N.Z = c2 - 2, N.className = S2.name, X[g2] = N) : (lA(X, g2, U), X[g2].S[c2 - 2] = N), V([], D, function(AA) { + return AA = pA(U, AA, S2, h2, m2), X[g2].S === void 0 ? (AA.Z = c2 - 2, X[g2] = AA) : X[g2].S[c2 - 2] = AA, []; + }), []; + }); + }, A: function(s, g2) { + g2 = hA(g2), nA(s, { name: g2, fromWireType: function(c2) { + var B = OA(c2); + return ZA(c2), B; + }, toWireType: function(c2, B) { + return PA(B); + }, argPackAdvance: 8, readValueFromPointer: XA, V: null }); + }, n: function(s, g2, c2) { + c2 = Z(c2), g2 = hA(g2), nA(s, { name: g2, fromWireType: function(B) { + return B; + }, toWireType: function(B, Q) { + return Q; + }, argPackAdvance: 8, readValueFromPointer: Se(g2, c2), V: null }); + }, e: function(s, g2, c2, B, Q) { + g2 = hA(g2), Q === -1 && (Q = 4294967295), Q = Z(c2); + var h2 = (w2) => w2; + if (B === 0) { + var m2 = 32 - 8 * c2; + h2 = (w2) => w2 << m2 >>> m2; + } + c2 = g2.includes("unsigned") ? function(w2, D) { + return D >>> 0; + } : function(w2, D) { + return D; + }, nA(s, { name: g2, fromWireType: h2, toWireType: c2, argPackAdvance: 8, readValueFromPointer: be(g2, Q, B !== 0), V: null }); + }, b: function(s, g2, c2) { + function B(h2) { + h2 >>= 2; + var m2 = v2; + return new Q(p, m2[h2 + 1], m2[h2]); + } + var Q = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array][g2]; + c2 = hA(c2), nA(s, { name: c2, fromWireType: B, argPackAdvance: 8, readValueFromPointer: B }, { ua: true }); + }, o: function(s, g2) { + g2 = hA(g2); + var c2 = g2 === "std::string"; + nA(s, { name: g2, fromWireType: function(B) { + var Q = v2[B >> 2], h2 = B + 4; + if (c2) for (var m2 = h2, w2 = 0; w2 <= Q; ++w2) { + var D = h2 + w2; + if (w2 == Q || k[D] == 0) { + if (m2 = m2 ? d2(k, m2, D - m2) : "", S2 === void 0) var S2 = m2; + else S2 += String.fromCharCode(0), S2 += m2; + m2 = D + 1; + } + } + else { + for (S2 = Array(Q), w2 = 0; w2 < Q; ++w2) S2[w2] = String.fromCharCode(k[h2 + w2]); + S2 = S2.join(""); + } + return de(B), S2; + }, toWireType: function(B, Q) { + Q instanceof ArrayBuffer && (Q = new Uint8Array(Q)); + var h2, m2 = typeof Q == "string"; + if (m2 || Q instanceof Uint8Array || Q instanceof Uint8ClampedArray || Q instanceof Int8Array || Y("Cannot pass non-string to std::string"), c2 && m2) { + var w2 = 0; + for (h2 = 0; h2 < Q.length; ++h2) { + var D = Q.charCodeAt(h2); + 127 >= D ? w2++ : 2047 >= D ? w2 += 2 : 55296 <= D && 57343 >= D ? (w2 += 4, ++h2) : w2 += 3; + } + h2 = w2; + } else h2 = Q.length; + if (w2 = ve(4 + h2 + 1), D = w2 + 4, v2[w2 >> 2] = h2, c2 && m2) { + if (m2 = D, D = h2 + 1, h2 = k, 0 < D) { + D = m2 + D - 1; + for (var S2 = 0; S2 < Q.length; ++S2) { + var N = Q.charCodeAt(S2); + if (55296 <= N && 57343 >= N) { + var U = Q.charCodeAt(++S2); + N = 65536 + ((N & 1023) << 10) | U & 1023; + } + if (127 >= N) { + if (m2 >= D) break; + h2[m2++] = N; + } else { + if (2047 >= N) { + if (m2 + 1 >= D) break; + h2[m2++] = 192 | N >> 6; + } else { + if (65535 >= N) { + if (m2 + 2 >= D) break; + h2[m2++] = 224 | N >> 12; + } else { + if (m2 + 3 >= D) break; + h2[m2++] = 240 | N >> 18, h2[m2++] = 128 | N >> 12 & 63; + } + h2[m2++] = 128 | N >> 6 & 63; + } + h2[m2++] = 128 | N & 63; + } + } + h2[m2] = 0; + } + } else if (m2) for (m2 = 0; m2 < h2; ++m2) S2 = Q.charCodeAt(m2), 255 < S2 && (de(D), Y("String has UTF-16 code units that do not fit in 8 bits")), k[D + m2] = S2; + else for (m2 = 0; m2 < h2; ++m2) k[D + m2] = Q[m2]; + return B !== null && B.push(de, w2), w2; + }, argPackAdvance: 8, readValueFromPointer: XA, V: function(B) { + de(B); + } }); + }, i: function(s, g2, c2) { + if (c2 = hA(c2), g2 === 2) var B = $A, Q = Ce, h2 = It, m2 = () => F, w2 = 1; + else g2 === 4 && (B = et, Q = wt, h2 = Dt, m2 = () => v2, w2 = 2); + nA(s, { name: c2, fromWireType: function(D) { + for (var S2 = v2[D >> 2], N = m2(), U, X = D + 4, z = 0; z <= S2; ++z) { + var AA = D + 4 + z * g2; + (z == S2 || N[AA >> w2] == 0) && (X = B(X, AA - X), U === void 0 ? U = X : (U += String.fromCharCode(0), U += X), X = AA + g2); + } + return de(D), U; + }, toWireType: function(D, S2) { + typeof S2 != "string" && Y("Cannot pass non-string to C++ string type " + c2); + var N = h2(S2), U = ve(4 + N + g2); + return v2[U >> 2] = N >> w2, Q(S2, U + 4, N + g2), D !== null && D.push(de, U), U; + }, argPackAdvance: 8, readValueFromPointer: XA, V: function(D) { + de(D); + } }); + }, k: function(s, g2, c2, B, Q, h2) { + De[s] = { name: hA(g2), fa: YA(c2, B), W: YA(Q, h2), ia: [] }; + }, h: function(s, g2, c2, B, Q, h2, m2, w2, D, S2) { + De[s].ia.push({ oa: hA(g2), ta: c2, ra: YA(B, Q), sa: h2, za: m2, ya: YA(w2, D), Aa: S2 }); + }, C: function(s, g2) { + g2 = hA(g2), nA(s, { va: true, name: g2, argPackAdvance: 0, fromWireType: function() { + }, toWireType: function() { + } }); + }, s: function(s, g2, c2, B, Q) { + s = St[s], g2 = OA(g2), c2 = ct(c2); + var h2 = []; + return v2[B >> 2] = PA(h2), s(g2, c2, h2, Q); + }, t: function(s, g2, c2, B) { + s = St[s], g2 = OA(g2), c2 = ct(c2), s(g2, c2, null, B); + }, g: ZA, m: function(s, g2) { + var c2 = Pr(s, g2), B = c2[0]; + g2 = B.name + "_$" + c2.slice(1).map(function(m2) { + return m2.name; + }).join("_") + "$"; + var Q = _r[g2]; + if (Q !== void 0) return Q; + var h2 = Array(s - 1); + return Q = Fn((m2, w2, D, S2) => { + for (var N = 0, U = 0; U < s - 1; ++U) h2[U] = c2[U + 1].readValueFromPointer(S2 + N), N += c2[U + 1].argPackAdvance; + for (m2 = m2[w2].apply(m2, h2), U = 0; U < s - 1; ++U) c2[U + 1].ma && c2[U + 1].ma(h2[U]); + if (!B.va) return B.toWireType(D, m2); + }), _r[g2] = Q; + }, D: function(s) { + 4 < s && (q[s].ga += 1); + }, r: function(s) { + var g2 = OA(s); + KA(g2), ZA(s); + }, c: function() { + iA(""); + }, x: function(s, g2, c2) { + k.copyWithin(s, g2, g2 + c2); + }, w: function(s) { + var g2 = k.length; + if (s >>>= 0, 2147483648 < s) return false; + for (var c2 = 1; 4 >= c2; c2 *= 2) { + var B = g2 * (1 + 0.2 / c2); + B = Math.min(B, s + 100663296); + var Q = Math; + B = Math.max(s, B), Q = Q.min.call(Q, 2147483648, B + (65536 - B % 65536) % 65536); + A: { + try { + E.grow(Q - p.byteLength + 65535 >>> 16), O(); + var h2 = 1; + break A; + } catch { + } + h2 = void 0; + } + if (h2) return true; + } + return false; + }, z: function() { + return 52; + }, u: function() { + return 70; + }, y: function(s, g2, c2, B) { + for (var Q = 0, h2 = 0; h2 < c2; h2++) { + var m2 = v2[g2 >> 2], w2 = v2[g2 + 4 >> 2]; + g2 += 8; + for (var D = 0; D < w2; D++) { + var S2 = k[m2 + D], N = Jr[s]; + S2 === 0 || S2 === 10 ? ((s === 1 ? a : u2)(d2(N, 0)), N.length = 0) : N.push(S2); + } + Q += w2; + } + return v2[B >> 2] = Q, 0; + } }; + (function() { + function s(Q) { + t.asm = Q.exports, E = t.asm.E, O(), J = t.asm.J, CA.unshift(t.asm.F), sA--, t.monitorRunDependencies && t.monitorRunDependencies(sA), sA == 0 && (vA !== null && (clearInterval(vA), vA = null), rA && (Q = rA, rA = null, Q())); + } + function g2(Q) { + s(Q.instance); + } + function c2(Q) { + return te().then(function(h2) { + return WebAssembly.instantiate(h2, B); + }).then(function(h2) { + return h2; + }).then(Q, function(h2) { + u2("failed to asynchronously prepare wasm: " + h2), iA(h2); + }); + } + var B = { a: Wr }; + if (sA++, t.monitorRunDependencies && t.monitorRunDependencies(sA), t.instantiateWasm) try { + return t.instantiateWasm(B, s); + } catch (Q) { + u2("Module.instantiateWasm callback failed with error: " + Q), n(Q); + } + return (function() { + return l2 || typeof WebAssembly.instantiateStreaming != "function" || wA(aA) || typeof fetch != "function" ? c2(g2) : fetch(aA, { credentials: "same-origin" }).then(function(Q) { + return WebAssembly.instantiateStreaming(Q, B).then(g2, function(h2) { + return u2("wasm streaming compile failed: " + h2), u2("falling back to ArrayBuffer instantiation"), c2(g2); + }); + }); + })().catch(n), {}; + })(), t.___wasm_call_ctors = function() { + return (t.___wasm_call_ctors = t.asm.F).apply(null, arguments); + }; + var vt = t.___getTypeName = function() { + return (vt = t.___getTypeName = t.asm.G).apply(null, arguments); + }; + t.__embind_initialize_bindings = function() { + return (t.__embind_initialize_bindings = t.asm.H).apply(null, arguments); + }; + var ve = t._malloc = function() { + return (ve = t._malloc = t.asm.I).apply(null, arguments); + }, de = t._free = function() { + return (de = t._free = t.asm.K).apply(null, arguments); + }; + t.dynCall_jiji = function() { + return (t.dynCall_jiji = t.asm.L).apply(null, arguments); + }; + var Bt; + rA = function s() { + Bt || f(), Bt || (rA = s); + }; + function f() { + function s() { + if (!Bt && (Bt = true, t.calledRun = true, !C)) { + if (TA(CA), r(t), t.onRuntimeInitialized && t.onRuntimeInitialized(), t.postRun) for (typeof t.postRun == "function" && (t.postRun = [t.postRun]); t.postRun.length; ) { + var g2 = t.postRun.shift(); + MA.unshift(g2); + } + TA(MA); + } + } + if (!(0 < sA)) { + if (t.preRun) for (typeof t.preRun == "function" && (t.preRun = [t.preRun]); t.preRun.length; ) dA(); + TA(j), 0 < sA || (t.setStatus ? (t.setStatus("Running..."), setTimeout(function() { + setTimeout(function() { + t.setStatus(""); + }, 1), s(); + }, 1)) : s()); + } + } + if (t.preInit) for (typeof t.preInit == "function" && (t.preInit = [t.preInit]); 0 < t.preInit.length; ) t.preInit.pop()(); + return f(), e.ready; + }; + })(), GI = LI; + }); + Vn = Xe(() => { + Ns(); + Zr(); + }); + Zn = {}; + kt(Zn, { getYoga: () => PI, init: () => TI }); + jn = Xe(() => { + Vn(); + UI = jr, HI = new Promise((A, e) => { + Hs = A, zn = e; + }); + }); + $n = {}; + kt($n, { getYoga: () => JI }); + Ai = Xe(() => { + Vn(); + _I = jr(); + }); + wi = K2((yi) => { + "use strict"; + Object.defineProperty(yi, "__esModule", { value: true }); + Object.defineProperty(yi, "default", { enumerable: true, get: () => Ac }); + function Ac(A) { + if (A = `${A}`, A === "0") return "0"; + if (/^[+-]?(\d+|\d*\.\d+)(e[+-]?\d+)?(%|\w+)?$/.test(A)) return A.replace(/^[+-]?/, (e) => e === "-" ? "" : "-"); + if (A.includes("var(") || A.includes("calc(")) return `calc(${A} * -1)`; + } + }); + Ra = K2((Di) => { + "use strict"; + Object.defineProperty(Di, "__esModule", { value: true }); + Object.defineProperty(Di, "default", { enumerable: true, get: () => ec }); + var ec = ["preflight", "container", "accessibility", "pointerEvents", "visibility", "position", "inset", "isolation", "zIndex", "order", "gridColumn", "gridColumnStart", "gridColumnEnd", "gridRow", "gridRowStart", "gridRowEnd", "float", "clear", "margin", "boxSizing", "display", "aspectRatio", "height", "maxHeight", "minHeight", "width", "minWidth", "maxWidth", "flex", "flexShrink", "flexGrow", "flexBasis", "tableLayout", "borderCollapse", "borderSpacing", "transformOrigin", "translate", "rotate", "skew", "scale", "transform", "animation", "cursor", "touchAction", "userSelect", "resize", "scrollSnapType", "scrollSnapAlign", "scrollSnapStop", "scrollMargin", "scrollPadding", "listStylePosition", "listStyleType", "appearance", "columns", "breakBefore", "breakInside", "breakAfter", "gridAutoColumns", "gridAutoFlow", "gridAutoRows", "gridTemplateColumns", "gridTemplateRows", "flexDirection", "flexWrap", "placeContent", "placeItems", "alignContent", "alignItems", "justifyContent", "justifyItems", "gap", "space", "divideWidth", "divideStyle", "divideColor", "divideOpacity", "placeSelf", "alignSelf", "justifySelf", "overflow", "overscrollBehavior", "scrollBehavior", "textOverflow", "whitespace", "wordBreak", "borderRadius", "borderWidth", "borderStyle", "borderColor", "borderOpacity", "backgroundColor", "backgroundOpacity", "backgroundImage", "gradientColorStops", "boxDecorationBreak", "backgroundSize", "backgroundAttachment", "backgroundClip", "backgroundPosition", "backgroundRepeat", "backgroundOrigin", "fill", "stroke", "strokeWidth", "objectFit", "objectPosition", "padding", "textAlign", "textIndent", "verticalAlign", "fontFamily", "fontSize", "fontWeight", "textTransform", "fontStyle", "fontVariantNumeric", "lineHeight", "letterSpacing", "textColor", "textOpacity", "textDecoration", "textDecorationColor", "textDecorationStyle", "textDecorationThickness", "textUnderlineOffset", "fontSmoothing", "placeholderColor", "placeholderOpacity", "caretColor", "accentColor", "opacity", "backgroundBlendMode", "mixBlendMode", "boxShadow", "boxShadowColor", "outlineStyle", "outlineWidth", "outlineOffset", "outlineColor", "ringWidth", "ringColor", "ringOpacity", "ringOffsetWidth", "ringOffsetColor", "blur", "brightness", "contrast", "dropShadow", "grayscale", "hueRotate", "invert", "saturate", "sepia", "filter", "backdropBlur", "backdropBrightness", "backdropContrast", "backdropGrayscale", "backdropHueRotate", "backdropInvert", "backdropOpacity", "backdropSaturate", "backdropSepia", "backdropFilter", "transitionProperty", "transitionDelay", "transitionDuration", "transitionTimingFunction", "willChange", "content"]; + }); + Na = K2((Si) => { + "use strict"; + Object.defineProperty(Si, "__esModule", { value: true }); + Object.defineProperty(Si, "default", { enumerable: true, get: () => tc }); + function tc(A, e) { + return A === void 0 ? e : Array.isArray(A) ? A : [...new Set(e.filter((r) => A !== false && A[r] !== false).concat(Object.keys(A).filter((r) => A[r] !== false)))]; + } + }); + bi = K2((e0, Ma) => { + Ma.exports = { content: [], presets: [], darkMode: "media", theme: { screens: { sm: "640px", md: "768px", lg: "1024px", xl: "1280px", "2xl": "1536px" }, colors: ({ colors: A }) => ({ inherit: A.inherit, current: A.current, transparent: A.transparent, black: A.black, white: A.white, slate: A.slate, gray: A.gray, zinc: A.zinc, neutral: A.neutral, stone: A.stone, red: A.red, orange: A.orange, amber: A.amber, yellow: A.yellow, lime: A.lime, green: A.green, emerald: A.emerald, teal: A.teal, cyan: A.cyan, sky: A.sky, blue: A.blue, indigo: A.indigo, violet: A.violet, purple: A.purple, fuchsia: A.fuchsia, pink: A.pink, rose: A.rose }), columns: { auto: "auto", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", 10: "10", 11: "11", 12: "12", "3xs": "16rem", "2xs": "18rem", xs: "20rem", sm: "24rem", md: "28rem", lg: "32rem", xl: "36rem", "2xl": "42rem", "3xl": "48rem", "4xl": "56rem", "5xl": "64rem", "6xl": "72rem", "7xl": "80rem" }, spacing: { px: "1px", 0: "0px", 0.5: "0.125rem", 1: "0.25rem", 1.5: "0.375rem", 2: "0.5rem", 2.5: "0.625rem", 3: "0.75rem", 3.5: "0.875rem", 4: "1rem", 5: "1.25rem", 6: "1.5rem", 7: "1.75rem", 8: "2rem", 9: "2.25rem", 10: "2.5rem", 11: "2.75rem", 12: "3rem", 14: "3.5rem", 16: "4rem", 20: "5rem", 24: "6rem", 28: "7rem", 32: "8rem", 36: "9rem", 40: "10rem", 44: "11rem", 48: "12rem", 52: "13rem", 56: "14rem", 60: "15rem", 64: "16rem", 72: "18rem", 80: "20rem", 96: "24rem" }, animation: { none: "none", spin: "spin 1s linear infinite", ping: "ping 1s cubic-bezier(0, 0, 0.2, 1) infinite", pulse: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite", bounce: "bounce 1s infinite" }, aspectRatio: { auto: "auto", square: "1 / 1", video: "16 / 9" }, backdropBlur: ({ theme: A }) => A("blur"), backdropBrightness: ({ theme: A }) => A("brightness"), backdropContrast: ({ theme: A }) => A("contrast"), backdropGrayscale: ({ theme: A }) => A("grayscale"), backdropHueRotate: ({ theme: A }) => A("hueRotate"), backdropInvert: ({ theme: A }) => A("invert"), backdropOpacity: ({ theme: A }) => A("opacity"), backdropSaturate: ({ theme: A }) => A("saturate"), backdropSepia: ({ theme: A }) => A("sepia"), backgroundColor: ({ theme: A }) => A("colors"), backgroundImage: { none: "none", "gradient-to-t": "linear-gradient(to top, var(--tw-gradient-stops))", "gradient-to-tr": "linear-gradient(to top right, var(--tw-gradient-stops))", "gradient-to-r": "linear-gradient(to right, var(--tw-gradient-stops))", "gradient-to-br": "linear-gradient(to bottom right, var(--tw-gradient-stops))", "gradient-to-b": "linear-gradient(to bottom, var(--tw-gradient-stops))", "gradient-to-bl": "linear-gradient(to bottom left, var(--tw-gradient-stops))", "gradient-to-l": "linear-gradient(to left, var(--tw-gradient-stops))", "gradient-to-tl": "linear-gradient(to top left, var(--tw-gradient-stops))" }, backgroundOpacity: ({ theme: A }) => A("opacity"), backgroundPosition: { bottom: "bottom", center: "center", left: "left", "left-bottom": "left bottom", "left-top": "left top", right: "right", "right-bottom": "right bottom", "right-top": "right top", top: "top" }, backgroundSize: { auto: "auto", cover: "cover", contain: "contain" }, blur: { 0: "0", none: "0", sm: "4px", DEFAULT: "8px", md: "12px", lg: "16px", xl: "24px", "2xl": "40px", "3xl": "64px" }, brightness: { 0: "0", 50: ".5", 75: ".75", 90: ".9", 95: ".95", 100: "1", 105: "1.05", 110: "1.1", 125: "1.25", 150: "1.5", 200: "2" }, borderColor: ({ theme: A }) => ({ ...A("colors"), DEFAULT: A("colors.gray.200", "currentColor") }), borderOpacity: ({ theme: A }) => A("opacity"), borderRadius: { none: "0px", sm: "0.125rem", DEFAULT: "0.25rem", md: "0.375rem", lg: "0.5rem", xl: "0.75rem", "2xl": "1rem", "3xl": "1.5rem", full: "9999px" }, borderSpacing: ({ theme: A }) => ({ ...A("spacing") }), borderWidth: { DEFAULT: "1px", 0: "0px", 2: "2px", 4: "4px", 8: "8px" }, boxShadow: { sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)", DEFAULT: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", md: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)", lg: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)", xl: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)", "2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)", inner: "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)", none: "none" }, boxShadowColor: ({ theme: A }) => A("colors"), caretColor: ({ theme: A }) => A("colors"), accentColor: ({ theme: A }) => ({ ...A("colors"), auto: "auto" }), contrast: { 0: "0", 50: ".5", 75: ".75", 100: "1", 125: "1.25", 150: "1.5", 200: "2" }, container: {}, content: { none: "none" }, cursor: { auto: "auto", default: "default", pointer: "pointer", wait: "wait", text: "text", move: "move", help: "help", "not-allowed": "not-allowed", none: "none", "context-menu": "context-menu", progress: "progress", cell: "cell", crosshair: "crosshair", "vertical-text": "vertical-text", alias: "alias", copy: "copy", "no-drop": "no-drop", grab: "grab", grabbing: "grabbing", "all-scroll": "all-scroll", "col-resize": "col-resize", "row-resize": "row-resize", "n-resize": "n-resize", "e-resize": "e-resize", "s-resize": "s-resize", "w-resize": "w-resize", "ne-resize": "ne-resize", "nw-resize": "nw-resize", "se-resize": "se-resize", "sw-resize": "sw-resize", "ew-resize": "ew-resize", "ns-resize": "ns-resize", "nesw-resize": "nesw-resize", "nwse-resize": "nwse-resize", "zoom-in": "zoom-in", "zoom-out": "zoom-out" }, divideColor: ({ theme: A }) => A("borderColor"), divideOpacity: ({ theme: A }) => A("borderOpacity"), divideWidth: ({ theme: A }) => A("borderWidth"), dropShadow: { sm: "0 1px 1px rgb(0 0 0 / 0.05)", DEFAULT: ["0 1px 2px rgb(0 0 0 / 0.1)", "0 1px 1px rgb(0 0 0 / 0.06)"], md: ["0 4px 3px rgb(0 0 0 / 0.07)", "0 2px 2px rgb(0 0 0 / 0.06)"], lg: ["0 10px 8px rgb(0 0 0 / 0.04)", "0 4px 3px rgb(0 0 0 / 0.1)"], xl: ["0 20px 13px rgb(0 0 0 / 0.03)", "0 8px 5px rgb(0 0 0 / 0.08)"], "2xl": "0 25px 25px rgb(0 0 0 / 0.15)", none: "0 0 #0000" }, fill: ({ theme: A }) => A("colors"), grayscale: { 0: "0", DEFAULT: "100%" }, hueRotate: { 0: "0deg", 15: "15deg", 30: "30deg", 60: "60deg", 90: "90deg", 180: "180deg" }, invert: { 0: "0", DEFAULT: "100%" }, flex: { 1: "1 1 0%", auto: "1 1 auto", initial: "0 1 auto", none: "none" }, flexBasis: ({ theme: A }) => ({ auto: "auto", ...A("spacing"), "1/2": "50%", "1/3": "33.333333%", "2/3": "66.666667%", "1/4": "25%", "2/4": "50%", "3/4": "75%", "1/5": "20%", "2/5": "40%", "3/5": "60%", "4/5": "80%", "1/6": "16.666667%", "2/6": "33.333333%", "3/6": "50%", "4/6": "66.666667%", "5/6": "83.333333%", "1/12": "8.333333%", "2/12": "16.666667%", "3/12": "25%", "4/12": "33.333333%", "5/12": "41.666667%", "6/12": "50%", "7/12": "58.333333%", "8/12": "66.666667%", "9/12": "75%", "10/12": "83.333333%", "11/12": "91.666667%", full: "100%" }), flexGrow: { 0: "0", DEFAULT: "1" }, flexShrink: { 0: "0", DEFAULT: "1" }, fontFamily: { sans: ["ui-sans-serif", "system-ui", "-apple-system", "BlinkMacSystemFont", '"Segoe UI"', "Roboto", '"Helvetica Neue"', "Arial", '"Noto Sans"', "sans-serif", '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'], serif: ["ui-serif", "Georgia", "Cambria", '"Times New Roman"', "Times", "serif"], mono: ["ui-monospace", "SFMono-Regular", "Menlo", "Monaco", "Consolas", '"Liberation Mono"', '"Courier New"', "monospace"] }, fontSize: { xs: ["0.75rem", { lineHeight: "1rem" }], sm: ["0.875rem", { lineHeight: "1.25rem" }], base: ["1rem", { lineHeight: "1.5rem" }], lg: ["1.125rem", { lineHeight: "1.75rem" }], xl: ["1.25rem", { lineHeight: "1.75rem" }], "2xl": ["1.5rem", { lineHeight: "2rem" }], "3xl": ["1.875rem", { lineHeight: "2.25rem" }], "4xl": ["2.25rem", { lineHeight: "2.5rem" }], "5xl": ["3rem", { lineHeight: "1" }], "6xl": ["3.75rem", { lineHeight: "1" }], "7xl": ["4.5rem", { lineHeight: "1" }], "8xl": ["6rem", { lineHeight: "1" }], "9xl": ["8rem", { lineHeight: "1" }] }, fontWeight: { thin: "100", extralight: "200", light: "300", normal: "400", medium: "500", semibold: "600", bold: "700", extrabold: "800", black: "900" }, gap: ({ theme: A }) => A("spacing"), gradientColorStops: ({ theme: A }) => A("colors"), gridAutoColumns: { auto: "auto", min: "min-content", max: "max-content", fr: "minmax(0, 1fr)" }, gridAutoRows: { auto: "auto", min: "min-content", max: "max-content", fr: "minmax(0, 1fr)" }, gridColumn: { auto: "auto", "span-1": "span 1 / span 1", "span-2": "span 2 / span 2", "span-3": "span 3 / span 3", "span-4": "span 4 / span 4", "span-5": "span 5 / span 5", "span-6": "span 6 / span 6", "span-7": "span 7 / span 7", "span-8": "span 8 / span 8", "span-9": "span 9 / span 9", "span-10": "span 10 / span 10", "span-11": "span 11 / span 11", "span-12": "span 12 / span 12", "span-full": "1 / -1" }, gridColumnEnd: { auto: "auto", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", 10: "10", 11: "11", 12: "12", 13: "13" }, gridColumnStart: { auto: "auto", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", 10: "10", 11: "11", 12: "12", 13: "13" }, gridRow: { auto: "auto", "span-1": "span 1 / span 1", "span-2": "span 2 / span 2", "span-3": "span 3 / span 3", "span-4": "span 4 / span 4", "span-5": "span 5 / span 5", "span-6": "span 6 / span 6", "span-full": "1 / -1" }, gridRowStart: { auto: "auto", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7" }, gridRowEnd: { auto: "auto", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7" }, gridTemplateColumns: { none: "none", 1: "repeat(1, minmax(0, 1fr))", 2: "repeat(2, minmax(0, 1fr))", 3: "repeat(3, minmax(0, 1fr))", 4: "repeat(4, minmax(0, 1fr))", 5: "repeat(5, minmax(0, 1fr))", 6: "repeat(6, minmax(0, 1fr))", 7: "repeat(7, minmax(0, 1fr))", 8: "repeat(8, minmax(0, 1fr))", 9: "repeat(9, minmax(0, 1fr))", 10: "repeat(10, minmax(0, 1fr))", 11: "repeat(11, minmax(0, 1fr))", 12: "repeat(12, minmax(0, 1fr))" }, gridTemplateRows: { none: "none", 1: "repeat(1, minmax(0, 1fr))", 2: "repeat(2, minmax(0, 1fr))", 3: "repeat(3, minmax(0, 1fr))", 4: "repeat(4, minmax(0, 1fr))", 5: "repeat(5, minmax(0, 1fr))", 6: "repeat(6, minmax(0, 1fr))" }, height: ({ theme: A }) => ({ auto: "auto", ...A("spacing"), "1/2": "50%", "1/3": "33.333333%", "2/3": "66.666667%", "1/4": "25%", "2/4": "50%", "3/4": "75%", "1/5": "20%", "2/5": "40%", "3/5": "60%", "4/5": "80%", "1/6": "16.666667%", "2/6": "33.333333%", "3/6": "50%", "4/6": "66.666667%", "5/6": "83.333333%", full: "100%", screen: "100vh", min: "min-content", max: "max-content", fit: "fit-content" }), inset: ({ theme: A }) => ({ auto: "auto", ...A("spacing"), "1/2": "50%", "1/3": "33.333333%", "2/3": "66.666667%", "1/4": "25%", "2/4": "50%", "3/4": "75%", full: "100%" }), keyframes: { spin: { to: { transform: "rotate(360deg)" } }, ping: { "75%, 100%": { transform: "scale(2)", opacity: "0" } }, pulse: { "50%": { opacity: ".5" } }, bounce: { "0%, 100%": { transform: "translateY(-25%)", animationTimingFunction: "cubic-bezier(0.8,0,1,1)" }, "50%": { transform: "none", animationTimingFunction: "cubic-bezier(0,0,0.2,1)" } } }, letterSpacing: { tighter: "-0.05em", tight: "-0.025em", normal: "0em", wide: "0.025em", wider: "0.05em", widest: "0.1em" }, lineHeight: { none: "1", tight: "1.25", snug: "1.375", normal: "1.5", relaxed: "1.625", loose: "2", 3: ".75rem", 4: "1rem", 5: "1.25rem", 6: "1.5rem", 7: "1.75rem", 8: "2rem", 9: "2.25rem", 10: "2.5rem" }, listStyleType: { none: "none", disc: "disc", decimal: "decimal" }, margin: ({ theme: A }) => ({ auto: "auto", ...A("spacing") }), maxHeight: ({ theme: A }) => ({ ...A("spacing"), full: "100%", screen: "100vh", min: "min-content", max: "max-content", fit: "fit-content" }), maxWidth: ({ theme: A, breakpoints: e }) => ({ none: "none", 0: "0rem", xs: "20rem", sm: "24rem", md: "28rem", lg: "32rem", xl: "36rem", "2xl": "42rem", "3xl": "48rem", "4xl": "56rem", "5xl": "64rem", "6xl": "72rem", "7xl": "80rem", full: "100%", min: "min-content", max: "max-content", fit: "fit-content", prose: "65ch", ...e(A("screens")) }), minHeight: { 0: "0px", full: "100%", screen: "100vh", min: "min-content", max: "max-content", fit: "fit-content" }, minWidth: { 0: "0px", full: "100%", min: "min-content", max: "max-content", fit: "fit-content" }, objectPosition: { bottom: "bottom", center: "center", left: "left", "left-bottom": "left bottom", "left-top": "left top", right: "right", "right-bottom": "right bottom", "right-top": "right top", top: "top" }, opacity: { 0: "0", 5: "0.05", 10: "0.1", 20: "0.2", 25: "0.25", 30: "0.3", 40: "0.4", 50: "0.5", 60: "0.6", 70: "0.7", 75: "0.75", 80: "0.8", 90: "0.9", 95: "0.95", 100: "1" }, order: { first: "-9999", last: "9999", none: "0", 1: "1", 2: "2", 3: "3", 4: "4", 5: "5", 6: "6", 7: "7", 8: "8", 9: "9", 10: "10", 11: "11", 12: "12" }, padding: ({ theme: A }) => A("spacing"), placeholderColor: ({ theme: A }) => A("colors"), placeholderOpacity: ({ theme: A }) => A("opacity"), outlineColor: ({ theme: A }) => A("colors"), outlineOffset: { 0: "0px", 1: "1px", 2: "2px", 4: "4px", 8: "8px" }, outlineWidth: { 0: "0px", 1: "1px", 2: "2px", 4: "4px", 8: "8px" }, ringColor: ({ theme: A }) => ({ DEFAULT: A("colors.blue.500", "#3b82f6"), ...A("colors") }), ringOffsetColor: ({ theme: A }) => A("colors"), ringOffsetWidth: { 0: "0px", 1: "1px", 2: "2px", 4: "4px", 8: "8px" }, ringOpacity: ({ theme: A }) => ({ DEFAULT: "0.5", ...A("opacity") }), ringWidth: { DEFAULT: "3px", 0: "0px", 1: "1px", 2: "2px", 4: "4px", 8: "8px" }, rotate: { 0: "0deg", 1: "1deg", 2: "2deg", 3: "3deg", 6: "6deg", 12: "12deg", 45: "45deg", 90: "90deg", 180: "180deg" }, saturate: { 0: "0", 50: ".5", 100: "1", 150: "1.5", 200: "2" }, scale: { 0: "0", 50: ".5", 75: ".75", 90: ".9", 95: ".95", 100: "1", 105: "1.05", 110: "1.1", 125: "1.25", 150: "1.5" }, scrollMargin: ({ theme: A }) => ({ ...A("spacing") }), scrollPadding: ({ theme: A }) => A("spacing"), sepia: { 0: "0", DEFAULT: "100%" }, skew: { 0: "0deg", 1: "1deg", 2: "2deg", 3: "3deg", 6: "6deg", 12: "12deg" }, space: ({ theme: A }) => ({ ...A("spacing") }), stroke: ({ theme: A }) => A("colors"), strokeWidth: { 0: "0", 1: "1", 2: "2" }, textColor: ({ theme: A }) => A("colors"), textDecorationColor: ({ theme: A }) => A("colors"), textDecorationThickness: { auto: "auto", "from-font": "from-font", 0: "0px", 1: "1px", 2: "2px", 4: "4px", 8: "8px" }, textUnderlineOffset: { auto: "auto", 0: "0px", 1: "1px", 2: "2px", 4: "4px", 8: "8px" }, textIndent: ({ theme: A }) => ({ ...A("spacing") }), textOpacity: ({ theme: A }) => A("opacity"), transformOrigin: { center: "center", top: "top", "top-right": "top right", right: "right", "bottom-right": "bottom right", bottom: "bottom", "bottom-left": "bottom left", left: "left", "top-left": "top left" }, transitionDelay: { 75: "75ms", 100: "100ms", 150: "150ms", 200: "200ms", 300: "300ms", 500: "500ms", 700: "700ms", 1e3: "1000ms" }, transitionDuration: { DEFAULT: "150ms", 75: "75ms", 100: "100ms", 150: "150ms", 200: "200ms", 300: "300ms", 500: "500ms", 700: "700ms", 1e3: "1000ms" }, transitionProperty: { none: "none", all: "all", DEFAULT: "color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter", colors: "color, background-color, border-color, text-decoration-color, fill, stroke", opacity: "opacity", shadow: "box-shadow", transform: "transform" }, transitionTimingFunction: { DEFAULT: "cubic-bezier(0.4, 0, 0.2, 1)", linear: "linear", in: "cubic-bezier(0.4, 0, 1, 1)", out: "cubic-bezier(0, 0, 0.2, 1)", "in-out": "cubic-bezier(0.4, 0, 0.2, 1)" }, translate: ({ theme: A }) => ({ ...A("spacing"), "1/2": "50%", "1/3": "33.333333%", "2/3": "66.666667%", "1/4": "25%", "2/4": "50%", "3/4": "75%", full: "100%" }), width: ({ theme: A }) => ({ auto: "auto", ...A("spacing"), "1/2": "50%", "1/3": "33.333333%", "2/3": "66.666667%", "1/4": "25%", "2/4": "50%", "3/4": "75%", "1/5": "20%", "2/5": "40%", "3/5": "60%", "4/5": "80%", "1/6": "16.666667%", "2/6": "33.333333%", "3/6": "50%", "4/6": "66.666667%", "5/6": "83.333333%", "1/12": "8.333333%", "2/12": "16.666667%", "3/12": "25%", "4/12": "33.333333%", "5/12": "41.666667%", "6/12": "50%", "7/12": "58.333333%", "8/12": "66.666667%", "9/12": "75%", "10/12": "83.333333%", "11/12": "91.666667%", full: "100%", screen: "100vw", min: "min-content", max: "max-content", fit: "fit-content" }), willChange: { auto: "auto", scroll: "scroll-position", contents: "contents", transform: "transform" }, zIndex: { auto: "auto", 0: "0", 10: "10", 20: "20", 30: "30", 40: "40", 50: "50" } }, variantOrder: ["first", "last", "odd", "even", "visited", "checked", "empty", "read-only", "group-hover", "group-focus", "focus-within", "hover", "focus", "focus-visible", "active", "disabled"], plugins: [] }; + }); + fn = {}; + kt(fn, { default: () => rc }); + Bn = Xe(() => { + rc = { info(A, e) { + console.info(...Array.isArray(A) ? [A] : [e, A]); + }, warn(A, e) { + console.warn(...Array.isArray(A) ? [A] : [e, A]); + }, risk(A, e) { + console.error(...Array.isArray(A) ? [A] : [e, A]); + } }; + }); + Fa = K2((vi) => { + "use strict"; + Object.defineProperty(vi, "__esModule", { value: true }); + Object.defineProperty(vi, "default", { enumerable: true, get: () => oc }); + var nc = ic((Bn(), Yr(fn))); + function ic(A) { + return A && A.__esModule ? A : { default: A }; + } + function or({ version: A, from: e, to: t }) { + nc.default.warn(`${e}-color-renamed`, [`As of Tailwind CSS ${A}, \`${e}\` has been renamed to \`${t}\`.`, "Update your configuration file to silence this warning."]); + } + var oc = { inherit: "inherit", current: "currentColor", transparent: "transparent", black: "#000", white: "#fff", slate: { 50: "#f8fafc", 100: "#f1f5f9", 200: "#e2e8f0", 300: "#cbd5e1", 400: "#94a3b8", 500: "#64748b", 600: "#475569", 700: "#334155", 800: "#1e293b", 900: "#0f172a" }, gray: { 50: "#f9fafb", 100: "#f3f4f6", 200: "#e5e7eb", 300: "#d1d5db", 400: "#9ca3af", 500: "#6b7280", 600: "#4b5563", 700: "#374151", 800: "#1f2937", 900: "#111827" }, zinc: { 50: "#fafafa", 100: "#f4f4f5", 200: "#e4e4e7", 300: "#d4d4d8", 400: "#a1a1aa", 500: "#71717a", 600: "#52525b", 700: "#3f3f46", 800: "#27272a", 900: "#18181b" }, neutral: { 50: "#fafafa", 100: "#f5f5f5", 200: "#e5e5e5", 300: "#d4d4d4", 400: "#a3a3a3", 500: "#737373", 600: "#525252", 700: "#404040", 800: "#262626", 900: "#171717" }, stone: { 50: "#fafaf9", 100: "#f5f5f4", 200: "#e7e5e4", 300: "#d6d3d1", 400: "#a8a29e", 500: "#78716c", 600: "#57534e", 700: "#44403c", 800: "#292524", 900: "#1c1917" }, red: { 50: "#fef2f2", 100: "#fee2e2", 200: "#fecaca", 300: "#fca5a5", 400: "#f87171", 500: "#ef4444", 600: "#dc2626", 700: "#b91c1c", 800: "#991b1b", 900: "#7f1d1d" }, orange: { 50: "#fff7ed", 100: "#ffedd5", 200: "#fed7aa", 300: "#fdba74", 400: "#fb923c", 500: "#f97316", 600: "#ea580c", 700: "#c2410c", 800: "#9a3412", 900: "#7c2d12" }, amber: { 50: "#fffbeb", 100: "#fef3c7", 200: "#fde68a", 300: "#fcd34d", 400: "#fbbf24", 500: "#f59e0b", 600: "#d97706", 700: "#b45309", 800: "#92400e", 900: "#78350f" }, yellow: { 50: "#fefce8", 100: "#fef9c3", 200: "#fef08a", 300: "#fde047", 400: "#facc15", 500: "#eab308", 600: "#ca8a04", 700: "#a16207", 800: "#854d0e", 900: "#713f12" }, lime: { 50: "#f7fee7", 100: "#ecfccb", 200: "#d9f99d", 300: "#bef264", 400: "#a3e635", 500: "#84cc16", 600: "#65a30d", 700: "#4d7c0f", 800: "#3f6212", 900: "#365314" }, green: { 50: "#f0fdf4", 100: "#dcfce7", 200: "#bbf7d0", 300: "#86efac", 400: "#4ade80", 500: "#22c55e", 600: "#16a34a", 700: "#15803d", 800: "#166534", 900: "#14532d" }, emerald: { 50: "#ecfdf5", 100: "#d1fae5", 200: "#a7f3d0", 300: "#6ee7b7", 400: "#34d399", 500: "#10b981", 600: "#059669", 700: "#047857", 800: "#065f46", 900: "#064e3b" }, teal: { 50: "#f0fdfa", 100: "#ccfbf1", 200: "#99f6e4", 300: "#5eead4", 400: "#2dd4bf", 500: "#14b8a6", 600: "#0d9488", 700: "#0f766e", 800: "#115e59", 900: "#134e4a" }, cyan: { 50: "#ecfeff", 100: "#cffafe", 200: "#a5f3fc", 300: "#67e8f9", 400: "#22d3ee", 500: "#06b6d4", 600: "#0891b2", 700: "#0e7490", 800: "#155e75", 900: "#164e63" }, sky: { 50: "#f0f9ff", 100: "#e0f2fe", 200: "#bae6fd", 300: "#7dd3fc", 400: "#38bdf8", 500: "#0ea5e9", 600: "#0284c7", 700: "#0369a1", 800: "#075985", 900: "#0c4a6e" }, blue: { 50: "#eff6ff", 100: "#dbeafe", 200: "#bfdbfe", 300: "#93c5fd", 400: "#60a5fa", 500: "#3b82f6", 600: "#2563eb", 700: "#1d4ed8", 800: "#1e40af", 900: "#1e3a8a" }, indigo: { 50: "#eef2ff", 100: "#e0e7ff", 200: "#c7d2fe", 300: "#a5b4fc", 400: "#818cf8", 500: "#6366f1", 600: "#4f46e5", 700: "#4338ca", 800: "#3730a3", 900: "#312e81" }, violet: { 50: "#f5f3ff", 100: "#ede9fe", 200: "#ddd6fe", 300: "#c4b5fd", 400: "#a78bfa", 500: "#8b5cf6", 600: "#7c3aed", 700: "#6d28d9", 800: "#5b21b6", 900: "#4c1d95" }, purple: { 50: "#faf5ff", 100: "#f3e8ff", 200: "#e9d5ff", 300: "#d8b4fe", 400: "#c084fc", 500: "#a855f7", 600: "#9333ea", 700: "#7e22ce", 800: "#6b21a8", 900: "#581c87" }, fuchsia: { 50: "#fdf4ff", 100: "#fae8ff", 200: "#f5d0fe", 300: "#f0abfc", 400: "#e879f9", 500: "#d946ef", 600: "#c026d3", 700: "#a21caf", 800: "#86198f", 900: "#701a75" }, pink: { 50: "#fdf2f8", 100: "#fce7f3", 200: "#fbcfe8", 300: "#f9a8d4", 400: "#f472b6", 500: "#ec4899", 600: "#db2777", 700: "#be185d", 800: "#9d174d", 900: "#831843" }, rose: { 50: "#fff1f2", 100: "#ffe4e6", 200: "#fecdd3", 300: "#fda4af", 400: "#fb7185", 500: "#f43f5e", 600: "#e11d48", 700: "#be123c", 800: "#9f1239", 900: "#881337" }, get lightBlue() { + return or({ version: "v2.2", from: "lightBlue", to: "sky" }), this.sky; + }, get warmGray() { + return or({ version: "v3.0", from: "warmGray", to: "stone" }), this.stone; + }, get trueGray() { + return or({ version: "v3.0", from: "trueGray", to: "neutral" }), this.neutral; + }, get coolGray() { + return or({ version: "v3.0", from: "coolGray", to: "gray" }), this.gray; + }, get blueGray() { + return or({ version: "v3.0", from: "blueGray", to: "slate" }), this.slate; + } }; + }); + La = K2((ki) => { + "use strict"; + Object.defineProperty(ki, "__esModule", { value: true }); + Object.defineProperty(ki, "defaults", { enumerable: true, get: () => sc }); + function sc(A, ...e) { + for (let n of e) { + for (let i in n) { + var t; + !(A == null || (t = A.hasOwnProperty) === null || t === void 0) && t.call(A, i) || (A[i] = n[i]); + } + for (let i of Object.getOwnPropertySymbols(n)) { + var r; + !(A == null || (r = A.hasOwnProperty) === null || r === void 0) && r.call(A, i) || (A[i] = n[i]); + } + } + return A; + } + }); + Ga = K2((xi) => { + "use strict"; + Object.defineProperty(xi, "__esModule", { value: true }); + Object.defineProperty(xi, "toPath", { enumerable: true, get: () => ac }); + function ac(A) { + if (Array.isArray(A)) return A; + let e = A.split("[").length - 1, t = A.split("]").length - 1; + if (e !== t) throw new Error(`Path is invalid. Has unbalanced brackets: ${A}`); + return A.split(/\.(?![^\[]*\])|[\[\]]/g).filter(Boolean); + } + }); + Ha = K2((Ri) => { + "use strict"; + Object.defineProperty(Ri, "__esModule", { value: true }); + Object.defineProperty(Ri, "normalizeConfig", { enumerable: true, get: () => uc }); + var sr = gc((Bn(), Yr(fn))); + function Ua(A) { + if (typeof WeakMap != "function") return null; + var e = /* @__PURE__ */ new WeakMap(), t = /* @__PURE__ */ new WeakMap(); + return (Ua = function(r) { + return r ? t : e; + })(A); + } + function gc(A, e) { + if (!e && A && A.__esModule) return A; + if (A === null || typeof A != "object" && typeof A != "function") return { default: A }; + var t = Ua(e); + if (t && t.has(A)) return t.get(A); + var r = {}, n = Object.defineProperty && Object.getOwnPropertyDescriptor; + for (var i in A) if (i !== "default" && Object.prototype.hasOwnProperty.call(A, i)) { + var o = n ? Object.getOwnPropertyDescriptor(A, i) : null; + o && (o.get || o.set) ? Object.defineProperty(r, i, o) : r[i] = A[i]; + } + return r.default = A, t && t.set(A, r), r; + } + function uc(A) { + if ((() => { + if (A.purge || !A.content || !Array.isArray(A.content) && !(typeof A.content == "object" && A.content !== null)) return false; + if (Array.isArray(A.content)) return A.content.every((r) => typeof r == "string" ? true : !(typeof (r == null ? void 0 : r.raw) != "string" || r != null && r.extension && typeof (r == null ? void 0 : r.extension) != "string")); + if (typeof A.content == "object" && A.content !== null) { + if (Object.keys(A.content).some((r) => !["files", "extract", "transform"].includes(r))) return false; + if (Array.isArray(A.content.files)) { + if (!A.content.files.every((r) => typeof r == "string" ? true : !(typeof (r == null ? void 0 : r.raw) != "string" || r != null && r.extension && typeof (r == null ? void 0 : r.extension) != "string"))) return false; + if (typeof A.content.extract == "object") { + for (let r of Object.values(A.content.extract)) if (typeof r != "function") return false; + } else if (!(A.content.extract === void 0 || typeof A.content.extract == "function")) return false; + if (typeof A.content.transform == "object") { + for (let r of Object.values(A.content.transform)) if (typeof r != "function") return false; + } else if (!(A.content.transform === void 0 || typeof A.content.transform == "function")) return false; + } + return true; + } + return false; + })() || sr.default.warn("purge-deprecation", ["The `purge`/`content` options have changed in Tailwind CSS v3.0.", "Update your configuration file to eliminate this warning.", "https://tailwindcss.com/docs/upgrade-guide#configure-content-sources"]), A.safelist = (() => { + var r; + let { content: n, purge: i, safelist: o } = A; + return Array.isArray(o) ? o : Array.isArray(n == null ? void 0 : n.safelist) ? n.safelist : Array.isArray(i == null ? void 0 : i.safelist) ? i.safelist : Array.isArray(i == null || (r = i.options) === null || r === void 0 ? void 0 : r.safelist) ? i.options.safelist : []; + })(), typeof A.prefix == "function") sr.default.warn("prefix-function", ["As of Tailwind CSS v3.0, `prefix` cannot be a function.", "Update `prefix` in your configuration to be a string to eliminate this warning.", "https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function"]), A.prefix = ""; + else { + var t; + A.prefix = (t = A.prefix) !== null && t !== void 0 ? t : ""; + } + A.content = { files: (() => { + let { content: r, purge: n } = A; + return Array.isArray(n) ? n : Array.isArray(n == null ? void 0 : n.content) ? n.content : Array.isArray(r) ? r : Array.isArray(r == null ? void 0 : r.content) ? r.content : Array.isArray(r == null ? void 0 : r.files) ? r.files : []; + })(), extract: (() => { + let r = (() => { + var o, a, u2, l2, I, E, C, d2, p, y; + return !((o = A.purge) === null || o === void 0) && o.extract ? A.purge.extract : !((a = A.content) === null || a === void 0) && a.extract ? A.content.extract : !((u2 = A.purge) === null || u2 === void 0 || (l2 = u2.extract) === null || l2 === void 0) && l2.DEFAULT ? A.purge.extract.DEFAULT : !((I = A.content) === null || I === void 0 || (E = I.extract) === null || E === void 0) && E.DEFAULT ? A.content.extract.DEFAULT : !((C = A.purge) === null || C === void 0 || (d2 = C.options) === null || d2 === void 0) && d2.extractors ? A.purge.options.extractors : !((p = A.content) === null || p === void 0 || (y = p.options) === null || y === void 0) && y.extractors ? A.content.options.extractors : {}; + })(), n = {}, i = (() => { + var o, a, u2, l2; + if (!((o = A.purge) === null || o === void 0 || (a = o.options) === null || a === void 0) && a.defaultExtractor) return A.purge.options.defaultExtractor; + if (!((u2 = A.content) === null || u2 === void 0 || (l2 = u2.options) === null || l2 === void 0) && l2.defaultExtractor) return A.content.options.defaultExtractor; + })(); + if (i !== void 0 && (n.DEFAULT = i), typeof r == "function") n.DEFAULT = r; + else if (Array.isArray(r)) for (let { extensions: o, extractor: a } of r ?? []) for (let u2 of o) n[u2] = a; + else typeof r == "object" && r !== null && Object.assign(n, r); + return n; + })(), transform: (() => { + let r = (() => { + var i, o, a, u2, l2, I; + return !((i = A.purge) === null || i === void 0) && i.transform ? A.purge.transform : !((o = A.content) === null || o === void 0) && o.transform ? A.content.transform : !((a = A.purge) === null || a === void 0 || (u2 = a.transform) === null || u2 === void 0) && u2.DEFAULT ? A.purge.transform.DEFAULT : !((l2 = A.content) === null || l2 === void 0 || (I = l2.transform) === null || I === void 0) && I.DEFAULT ? A.content.transform.DEFAULT : {}; + })(), n = {}; + return typeof r == "function" && (n.DEFAULT = r), typeof r == "object" && r !== null && Object.assign(n, r), n; + })() }; + for (let r of A.content.files) if (typeof r == "string" && /{([^,]*?)}/g.test(r)) { + sr.default.warn("invalid-glob-braces", [`The glob pattern ${(0, sr.dim)(r)} in your Tailwind CSS configuration is invalid.`, `Update it to ${(0, sr.dim)(r.replace(/{([^,]*?)}/g, "$1"))} to silence this warning.`]); + break; + } + return A; + } + }); + Oa = K2((Ni) => { + "use strict"; + Object.defineProperty(Ni, "__esModule", { value: true }); + Object.defineProperty(Ni, "default", { enumerable: true, get: () => Ic }); + function Ic(A) { + if (Object.prototype.toString.call(A) !== "[object Object]") return false; + let e = Object.getPrototypeOf(A); + return e === null || e === Object.prototype; + } + }); + Ta = K2((Fi) => { + "use strict"; + Object.defineProperty(Fi, "__esModule", { value: true }); + Object.defineProperty(Fi, "cloneDeep", { enumerable: true, get: () => Mi }); + function Mi(A) { + return Array.isArray(A) ? A.map((e) => Mi(e)) : typeof A == "object" && A !== null ? Object.fromEntries(Object.entries(A).map(([e, t]) => [e, Mi(t)])) : A; + } + }); + Li = K2((En, Pa) => { + "use strict"; + En.__esModule = true; + En.default = fc; + function lc(A) { + for (var e = A.toLowerCase(), t = "", r = false, n = 0; n < 6 && e[n] !== void 0; n++) { + var i = e.charCodeAt(n), o = i >= 97 && i <= 102 || i >= 48 && i <= 57; + if (r = i === 32, !o) break; + t += e[n]; + } + if (t.length !== 0) { + var a = parseInt(t, 16), u2 = a >= 55296 && a <= 57343; + return u2 || a === 0 || a > 1114111 ? ["\uFFFD", t.length + (r ? 1 : 0)] : [String.fromCodePoint(a), t.length + (r ? 1 : 0)]; + } + } + var cc = /\\/; + function fc(A) { + var e = cc.test(A); + if (!e) return A; + for (var t = "", r = 0; r < A.length; r++) { + if (A[r] === "\\") { + var n = lc(A.slice(r + 1, r + 7)); + if (n !== void 0) { + t += n[0], r += n[1]; + continue; + } + if (A[r + 1] === "\\") { + t += "\\", r++; + continue; + } + A.length === r + 1 && (t += A[r]); + continue; + } + t += A[r]; + } + return t; + } + Pa.exports = En.default; + }); + Ja = K2((Qn, _a) => { + "use strict"; + Qn.__esModule = true; + Qn.default = Bc; + function Bc(A) { + for (var e = arguments.length, t = new Array(e > 1 ? e - 1 : 0), r = 1; r < e; r++) t[r - 1] = arguments[r]; + for (; t.length > 0; ) { + var n = t.shift(); + if (!A[n]) return; + A = A[n]; + } + return A; + } + _a.exports = Qn.default; + }); + Ka = K2((Cn, Wa) => { + "use strict"; + Cn.__esModule = true; + Cn.default = Ec; + function Ec(A) { + for (var e = arguments.length, t = new Array(e > 1 ? e - 1 : 0), r = 1; r < e; r++) t[r - 1] = arguments[r]; + for (; t.length > 0; ) { + var n = t.shift(); + A[n] || (A[n] = {}), A = A[n]; + } + } + Wa.exports = Cn.default; + }); + qa = K2((dn, Ya) => { + "use strict"; + dn.__esModule = true; + dn.default = Qc; + function Qc(A) { + for (var e = "", t = A.indexOf("/*"), r = 0; t >= 0; ) { + e = e + A.slice(r, t); + var n = A.indexOf("*/", t + 2); + if (n < 0) return e; + r = n + 2, t = A.indexOf("/*", r); + } + return e = e + A.slice(r), e; + } + Ya.exports = dn.default; + }); + ar = K2((Je) => { + "use strict"; + Je.__esModule = true; + Je.stripComments = Je.ensureObject = Je.getProp = Je.unesc = void 0; + var Cc = hn(Li()); + Je.unesc = Cc.default; + var dc = hn(Ja()); + Je.getProp = dc.default; + var hc = hn(Ka()); + Je.ensureObject = hc.default; + var pc = hn(qa()); + Je.stripComments = pc.default; + function hn(A) { + return A && A.__esModule ? A : { default: A }; + } + }); + je = K2((gr, za) => { + "use strict"; + gr.__esModule = true; + gr.default = void 0; + var Xa = ar(); + function Va(A, e) { + for (var t = 0; t < e.length; t++) { + var r = e[t]; + r.enumerable = r.enumerable || false, r.configurable = true, "value" in r && (r.writable = true), Object.defineProperty(A, r.key, r); + } + } + function mc(A, e, t) { + return e && Va(A.prototype, e), t && Va(A, t), A; + } + var yc = function A(e, t) { + if (typeof e != "object" || e === null) return e; + var r = new e.constructor(); + for (var n in e) if (e.hasOwnProperty(n)) { + var i = e[n], o = typeof i; + n === "parent" && o === "object" ? t && (r[n] = t) : i instanceof Array ? r[n] = i.map(function(a) { + return A(a, r); + }) : r[n] = A(i, r); + } + return r; + }, wc = (function() { + function A(t) { + t === void 0 && (t = {}), Object.assign(this, t), this.spaces = this.spaces || {}, this.spaces.before = this.spaces.before || "", this.spaces.after = this.spaces.after || ""; + } + var e = A.prototype; + return e.remove = function() { + return this.parent && this.parent.removeChild(this), this.parent = void 0, this; + }, e.replaceWith = function() { + if (this.parent) { + for (var r in arguments) this.parent.insertBefore(this, arguments[r]); + this.remove(); + } + return this; + }, e.next = function() { + return this.parent.at(this.parent.index(this) + 1); + }, e.prev = function() { + return this.parent.at(this.parent.index(this) - 1); + }, e.clone = function(r) { + r === void 0 && (r = {}); + var n = yc(this); + for (var i in r) n[i] = r[i]; + return n; + }, e.appendToPropertyAndEscape = function(r, n, i) { + this.raws || (this.raws = {}); + var o = this[r], a = this.raws[r]; + this[r] = o + n, a || i !== n ? this.raws[r] = (a || o) + i : delete this.raws[r]; + }, e.setPropertyAndEscape = function(r, n, i) { + this.raws || (this.raws = {}), this[r] = n, this.raws[r] = i; + }, e.setPropertyWithoutEscape = function(r, n) { + this[r] = n, this.raws && delete this.raws[r]; + }, e.isAtPosition = function(r, n) { + if (this.source && this.source.start && this.source.end) return !(this.source.start.line > r || this.source.end.line < r || this.source.start.line === r && this.source.start.column > n || this.source.end.line === r && this.source.end.column < n); + }, e.stringifyProperty = function(r) { + return this.raws && this.raws[r] || this[r]; + }, e.valueToString = function() { + return String(this.stringifyProperty("value")); + }, e.toString = function() { + return [this.rawSpaceBefore, this.valueToString(), this.rawSpaceAfter].join(""); + }, mc(A, [{ key: "rawSpaceBefore", get: function() { + var r = this.raws && this.raws.spaces && this.raws.spaces.before; + return r === void 0 && (r = this.spaces && this.spaces.before), r || ""; + }, set: function(r) { + (0, Xa.ensureObject)(this, "raws", "spaces"), this.raws.spaces.before = r; + } }, { key: "rawSpaceAfter", get: function() { + var r = this.raws && this.raws.spaces && this.raws.spaces.after; + return r === void 0 && (r = this.spaces.after), r || ""; + }, set: function(r) { + (0, Xa.ensureObject)(this, "raws", "spaces"), this.raws.spaces.after = r; + } }]), A; + })(); + gr.default = wc; + za.exports = gr.default; + }); + ee = K2((SA) => { + "use strict"; + SA.__esModule = true; + SA.UNIVERSAL = SA.ATTRIBUTE = SA.CLASS = SA.COMBINATOR = SA.COMMENT = SA.ID = SA.NESTING = SA.PSEUDO = SA.ROOT = SA.SELECTOR = SA.STRING = SA.TAG = void 0; + var Dc = "tag"; + SA.TAG = Dc; + var Sc = "string"; + SA.STRING = Sc; + var bc = "selector"; + SA.SELECTOR = bc; + var vc = "root"; + SA.ROOT = vc; + var kc = "pseudo"; + SA.PSEUDO = kc; + var xc = "nesting"; + SA.NESTING = xc; + var Rc = "id"; + SA.ID = Rc; + var Nc = "comment"; + SA.COMMENT = Nc; + var Mc = "combinator"; + SA.COMBINATOR = Mc; + var Fc = "class"; + SA.CLASS = Fc; + var Lc = "attribute"; + SA.ATTRIBUTE = Lc; + var Gc = "universal"; + SA.UNIVERSAL = Gc; + }); + pn = K2((ur, Ag) => { + "use strict"; + ur.__esModule = true; + ur.default = void 0; + var Uc = Oc(je()), $e = Hc(ee()); + function $a() { + if (typeof WeakMap != "function") return null; + var A = /* @__PURE__ */ new WeakMap(); + return $a = function() { + return A; + }, A; + } + function Hc(A) { + if (A && A.__esModule) return A; + if (A === null || typeof A != "object" && typeof A != "function") return { default: A }; + var e = $a(); + if (e && e.has(A)) return e.get(A); + var t = {}, r = Object.defineProperty && Object.getOwnPropertyDescriptor; + for (var n in A) if (Object.prototype.hasOwnProperty.call(A, n)) { + var i = r ? Object.getOwnPropertyDescriptor(A, n) : null; + i && (i.get || i.set) ? Object.defineProperty(t, n, i) : t[n] = A[n]; + } + return t.default = A, e && e.set(A, t), t; + } + function Oc(A) { + return A && A.__esModule ? A : { default: A }; + } + function Tc(A, e) { + var t; + if (typeof Symbol > "u" || A[Symbol.iterator] == null) { + if (Array.isArray(A) || (t = Pc(A)) || e && A && typeof A.length == "number") { + t && (A = t); + var r = 0; + return function() { + return r >= A.length ? { done: true } : { done: false, value: A[r++] }; + }; + } + throw new TypeError(`Invalid attempt to iterate non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`); + } + return t = A[Symbol.iterator](), t.next.bind(t); + } + function Pc(A, e) { + if (A) { + if (typeof A == "string") return Za(A, e); + var t = Object.prototype.toString.call(A).slice(8, -1); + if (t === "Object" && A.constructor && (t = A.constructor.name), t === "Map" || t === "Set") return Array.from(A); + if (t === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)) return Za(A, e); + } + } + function Za(A, e) { + (e == null || e > A.length) && (e = A.length); + for (var t = 0, r = new Array(e); t < e; t++) r[t] = A[t]; + return r; + } + function ja(A, e) { + for (var t = 0; t < e.length; t++) { + var r = e[t]; + r.enumerable = r.enumerable || false, r.configurable = true, "value" in r && (r.writable = true), Object.defineProperty(A, r.key, r); + } + } + function _c(A, e, t) { + return e && ja(A.prototype, e), t && ja(A, t), A; + } + function Jc(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Gi(A, e); + } + function Gi(A, e) { + return Gi = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Gi(A, e); + } + var Wc = (function(A) { + Jc(e, A); + function e(r) { + var n; + return n = A.call(this, r) || this, n.nodes || (n.nodes = []), n; + } + var t = e.prototype; + return t.append = function(n) { + return n.parent = this, this.nodes.push(n), this; + }, t.prepend = function(n) { + return n.parent = this, this.nodes.unshift(n), this; + }, t.at = function(n) { + return this.nodes[n]; + }, t.index = function(n) { + return typeof n == "number" ? n : this.nodes.indexOf(n); + }, t.removeChild = function(n) { + n = this.index(n), this.at(n).parent = void 0, this.nodes.splice(n, 1); + var i; + for (var o in this.indexes) i = this.indexes[o], i >= n && (this.indexes[o] = i - 1); + return this; + }, t.removeAll = function() { + for (var n = Tc(this.nodes), i; !(i = n()).done; ) { + var o = i.value; + o.parent = void 0; + } + return this.nodes = [], this; + }, t.empty = function() { + return this.removeAll(); + }, t.insertAfter = function(n, i) { + i.parent = this; + var o = this.index(n); + this.nodes.splice(o + 1, 0, i), i.parent = this; + var a; + for (var u2 in this.indexes) a = this.indexes[u2], o <= a && (this.indexes[u2] = a + 1); + return this; + }, t.insertBefore = function(n, i) { + i.parent = this; + var o = this.index(n); + this.nodes.splice(o, 0, i), i.parent = this; + var a; + for (var u2 in this.indexes) a = this.indexes[u2], a <= o && (this.indexes[u2] = a + 1); + return this; + }, t._findChildAtPosition = function(n, i) { + var o = void 0; + return this.each(function(a) { + if (a.atPosition) { + var u2 = a.atPosition(n, i); + if (u2) return o = u2, false; + } else if (a.isAtPosition(n, i)) return o = a, false; + }), o; + }, t.atPosition = function(n, i) { + if (this.isAtPosition(n, i)) return this._findChildAtPosition(n, i) || this; + }, t._inferEndPosition = function() { + this.last && this.last.source && this.last.source.end && (this.source = this.source || {}, this.source.end = this.source.end || {}, Object.assign(this.source.end, this.last.source.end)); + }, t.each = function(n) { + this.lastEach || (this.lastEach = 0), this.indexes || (this.indexes = {}), this.lastEach++; + var i = this.lastEach; + if (this.indexes[i] = 0, !!this.length) { + for (var o, a; this.indexes[i] < this.length && (o = this.indexes[i], a = n(this.at(o), o), a !== false); ) this.indexes[i] += 1; + if (delete this.indexes[i], a === false) return false; + } + }, t.walk = function(n) { + return this.each(function(i, o) { + var a = n(i, o); + if (a !== false && i.length && (a = i.walk(n)), a === false) return false; + }); + }, t.walkAttributes = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.ATTRIBUTE) return n.call(i, o); + }); + }, t.walkClasses = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.CLASS) return n.call(i, o); + }); + }, t.walkCombinators = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.COMBINATOR) return n.call(i, o); + }); + }, t.walkComments = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.COMMENT) return n.call(i, o); + }); + }, t.walkIds = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.ID) return n.call(i, o); + }); + }, t.walkNesting = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.NESTING) return n.call(i, o); + }); + }, t.walkPseudos = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.PSEUDO) return n.call(i, o); + }); + }, t.walkTags = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.TAG) return n.call(i, o); + }); + }, t.walkUniversals = function(n) { + var i = this; + return this.walk(function(o) { + if (o.type === $e.UNIVERSAL) return n.call(i, o); + }); + }, t.split = function(n) { + var i = this, o = []; + return this.reduce(function(a, u2, l2) { + var I = n.call(i, u2); + return o.push(u2), I ? (a.push(o), o = []) : l2 === i.length - 1 && a.push(o), a; + }, []); + }, t.map = function(n) { + return this.nodes.map(n); + }, t.reduce = function(n, i) { + return this.nodes.reduce(n, i); + }, t.every = function(n) { + return this.nodes.every(n); + }, t.some = function(n) { + return this.nodes.some(n); + }, t.filter = function(n) { + return this.nodes.filter(n); + }, t.sort = function(n) { + return this.nodes.sort(n); + }, t.toString = function() { + return this.map(String).join(""); + }, _c(e, [{ key: "first", get: function() { + return this.at(0); + } }, { key: "last", get: function() { + return this.at(this.length - 1); + } }, { key: "length", get: function() { + return this.nodes.length; + } }]), e; + })(Uc.default); + ur.default = Wc; + Ag.exports = ur.default; + }); + Hi = K2((Ir, tg) => { + "use strict"; + Ir.__esModule = true; + Ir.default = void 0; + var Kc = qc(pn()), Yc = ee(); + function qc(A) { + return A && A.__esModule ? A : { default: A }; + } + function eg(A, e) { + for (var t = 0; t < e.length; t++) { + var r = e[t]; + r.enumerable = r.enumerable || false, r.configurable = true, "value" in r && (r.writable = true), Object.defineProperty(A, r.key, r); + } + } + function Xc(A, e, t) { + return e && eg(A.prototype, e), t && eg(A, t), A; + } + function Vc(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Ui(A, e); + } + function Ui(A, e) { + return Ui = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Ui(A, e); + } + var zc = (function(A) { + Vc(e, A); + function e(r) { + var n; + return n = A.call(this, r) || this, n.type = Yc.ROOT, n; + } + var t = e.prototype; + return t.toString = function() { + var n = this.reduce(function(i, o) { + return i.push(String(o)), i; + }, []).join(","); + return this.trailingComma ? n + "," : n; + }, t.error = function(n, i) { + return this._error ? this._error(n, i) : new Error(n); + }, Xc(e, [{ key: "errorGenerator", set: function(n) { + this._error = n; + } }]), e; + })(Kc.default); + Ir.default = zc; + tg.exports = Ir.default; + }); + Ti = K2((lr, rg) => { + "use strict"; + lr.__esModule = true; + lr.default = void 0; + var Zc = $c(pn()), jc = ee(); + function $c(A) { + return A && A.__esModule ? A : { default: A }; + } + function Af(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Oi(A, e); + } + function Oi(A, e) { + return Oi = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Oi(A, e); + } + var ef = (function(A) { + Af(e, A); + function e(t) { + var r; + return r = A.call(this, t) || this, r.type = jc.SELECTOR, r; + } + return e; + })(Zc.default); + lr.default = ef; + rg.exports = lr.default; + }); + mn = K2((u0, ng) => { + "use strict"; + var tf = {}, rf = tf.hasOwnProperty, nf = function(e, t) { + if (!e) return t; + var r = {}; + for (var n in t) r[n] = rf.call(e, n) ? e[n] : t[n]; + return r; + }, of = /[ -,\.\/:-@\[-\^`\{-~]/, sf = /[ -,\.\/:-@\[\]\^`\{-~]/, af = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g, Pi = function A(e, t) { + t = nf(t, A.options), t.quotes != "single" && t.quotes != "double" && (t.quotes = "single"); + for (var r = t.quotes == "double" ? '"' : "'", n = t.isIdentifier, i = e.charAt(0), o = "", a = 0, u2 = e.length; a < u2; ) { + var l2 = e.charAt(a++), I = l2.charCodeAt(), E = void 0; + if (I < 32 || I > 126) { + if (I >= 55296 && I <= 56319 && a < u2) { + var C = e.charCodeAt(a++); + (C & 64512) == 56320 ? I = ((I & 1023) << 10) + (C & 1023) + 65536 : a--; + } + E = "\\" + I.toString(16).toUpperCase() + " "; + } else t.escapeEverything ? of.test(l2) ? E = "\\" + l2 : E = "\\" + I.toString(16).toUpperCase() + " " : /[\t\n\f\r\x0B]/.test(l2) ? E = "\\" + I.toString(16).toUpperCase() + " " : l2 == "\\" || !n && (l2 == '"' && r == l2 || l2 == "'" && r == l2) || n && sf.test(l2) ? E = "\\" + l2 : E = l2; + o += E; + } + return n && (/^-[-\d]/.test(o) ? o = "\\-" + o.slice(1) : /\d/.test(i) && (o = "\\3" + i + " " + o.slice(1))), o = o.replace(af, function(d2, p, y) { + return p && p.length % 2 ? d2 : (p || "") + y; + }), !n && t.wrap ? r + o + r : o; + }; + Pi.options = { escapeEverything: false, isIdentifier: false, quotes: "single", wrap: false }; + Pi.version = "3.0.0"; + ng.exports = Pi; + }); + Ji = K2((cr, sg) => { + "use strict"; + cr.__esModule = true; + cr.default = void 0; + var gf = og(mn()), uf = ar(), If = og(je()), lf = ee(); + function og(A) { + return A && A.__esModule ? A : { default: A }; + } + function ig(A, e) { + for (var t = 0; t < e.length; t++) { + var r = e[t]; + r.enumerable = r.enumerable || false, r.configurable = true, "value" in r && (r.writable = true), Object.defineProperty(A, r.key, r); + } + } + function cf(A, e, t) { + return e && ig(A.prototype, e), t && ig(A, t), A; + } + function ff(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, _i(A, e); + } + function _i(A, e) { + return _i = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, _i(A, e); + } + var Bf = (function(A) { + ff(e, A); + function e(r) { + var n; + return n = A.call(this, r) || this, n.type = lf.CLASS, n._constructed = true, n; + } + var t = e.prototype; + return t.valueToString = function() { + return "." + A.prototype.valueToString.call(this); + }, cf(e, [{ key: "value", get: function() { + return this._value; + }, set: function(n) { + if (this._constructed) { + var i = (0, gf.default)(n, { isIdentifier: true }); + i !== n ? ((0, uf.ensureObject)(this, "raws"), this.raws.value = i) : this.raws && delete this.raws.value; + } + this._value = n; + } }]), e; + })(If.default); + cr.default = Bf; + sg.exports = cr.default; + }); + Ki = K2((fr, ag) => { + "use strict"; + fr.__esModule = true; + fr.default = void 0; + var Ef = Cf(je()), Qf = ee(); + function Cf(A) { + return A && A.__esModule ? A : { default: A }; + } + function df(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Wi(A, e); + } + function Wi(A, e) { + return Wi = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Wi(A, e); + } + var hf = (function(A) { + df(e, A); + function e(t) { + var r; + return r = A.call(this, t) || this, r.type = Qf.COMMENT, r; + } + return e; + })(Ef.default); + fr.default = hf; + ag.exports = fr.default; + }); + qi = K2((Br, gg) => { + "use strict"; + Br.__esModule = true; + Br.default = void 0; + var pf = yf(je()), mf = ee(); + function yf(A) { + return A && A.__esModule ? A : { default: A }; + } + function wf(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Yi(A, e); + } + function Yi(A, e) { + return Yi = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Yi(A, e); + } + var Df = (function(A) { + wf(e, A); + function e(r) { + var n; + return n = A.call(this, r) || this, n.type = mf.ID, n; + } + var t = e.prototype; + return t.valueToString = function() { + return "#" + A.prototype.valueToString.call(this); + }, e; + })(pf.default); + Br.default = Df; + gg.exports = Br.default; + }); + yn = K2((Er, lg) => { + "use strict"; + Er.__esModule = true; + Er.default = void 0; + var Sf = Ig(mn()), bf = ar(), vf = Ig(je()); + function Ig(A) { + return A && A.__esModule ? A : { default: A }; + } + function ug(A, e) { + for (var t = 0; t < e.length; t++) { + var r = e[t]; + r.enumerable = r.enumerable || false, r.configurable = true, "value" in r && (r.writable = true), Object.defineProperty(A, r.key, r); + } + } + function kf(A, e, t) { + return e && ug(A.prototype, e), t && ug(A, t), A; + } + function xf(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Xi(A, e); + } + function Xi(A, e) { + return Xi = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Xi(A, e); + } + var Rf = (function(A) { + xf(e, A); + function e() { + return A.apply(this, arguments) || this; + } + var t = e.prototype; + return t.qualifiedName = function(n) { + return this.namespace ? this.namespaceString + "|" + n : n; + }, t.valueToString = function() { + return this.qualifiedName(A.prototype.valueToString.call(this)); + }, kf(e, [{ key: "namespace", get: function() { + return this._namespace; + }, set: function(n) { + if (n === true || n === "*" || n === "&") { + this._namespace = n, this.raws && delete this.raws.namespace; + return; + } + var i = (0, Sf.default)(n, { isIdentifier: true }); + this._namespace = n, i !== n ? ((0, bf.ensureObject)(this, "raws"), this.raws.namespace = i) : this.raws && delete this.raws.namespace; + } }, { key: "ns", get: function() { + return this._namespace; + }, set: function(n) { + this.namespace = n; + } }, { key: "namespaceString", get: function() { + if (this.namespace) { + var n = this.stringifyProperty("namespace"); + return n === true ? "" : n; + } else return ""; + } }]), e; + })(vf.default); + Er.default = Rf; + lg.exports = Er.default; + }); + zi = K2((Qr, cg) => { + "use strict"; + Qr.__esModule = true; + Qr.default = void 0; + var Nf = Ff(yn()), Mf = ee(); + function Ff(A) { + return A && A.__esModule ? A : { default: A }; + } + function Lf(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Vi(A, e); + } + function Vi(A, e) { + return Vi = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Vi(A, e); + } + var Gf = (function(A) { + Lf(e, A); + function e(t) { + var r; + return r = A.call(this, t) || this, r.type = Mf.TAG, r; + } + return e; + })(Nf.default); + Qr.default = Gf; + cg.exports = Qr.default; + }); + ji = K2((Cr, fg) => { + "use strict"; + Cr.__esModule = true; + Cr.default = void 0; + var Uf = Of(je()), Hf = ee(); + function Of(A) { + return A && A.__esModule ? A : { default: A }; + } + function Tf(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Zi(A, e); + } + function Zi(A, e) { + return Zi = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Zi(A, e); + } + var Pf = (function(A) { + Tf(e, A); + function e(t) { + var r; + return r = A.call(this, t) || this, r.type = Hf.STRING, r; + } + return e; + })(Uf.default); + Cr.default = Pf; + fg.exports = Cr.default; + }); + Ao = K2((dr, Bg) => { + "use strict"; + dr.__esModule = true; + dr.default = void 0; + var _f = Wf(pn()), Jf = ee(); + function Wf(A) { + return A && A.__esModule ? A : { default: A }; + } + function Kf(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, $i(A, e); + } + function $i(A, e) { + return $i = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, $i(A, e); + } + var Yf = (function(A) { + Kf(e, A); + function e(r) { + var n; + return n = A.call(this, r) || this, n.type = Jf.PSEUDO, n; + } + var t = e.prototype; + return t.toString = function() { + var n = this.length ? "(" + this.map(String).join(",") + ")" : ""; + return [this.rawSpaceBefore, this.stringifyProperty("value"), n, this.rawSpaceAfter].join(""); + }, e; + })(_f.default); + dr.default = Yf; + Bg.exports = dr.default; + }); + Qg = K2((I0, Eg) => { + Eg.exports = function(e, t) { + return function(...r) { + return console.warn(t), e(...r); + }; + }; + }); + oo = K2((mr) => { + "use strict"; + mr.__esModule = true; + mr.unescapeValue = io; + mr.default = void 0; + var hr = no(mn()), qf = no(Li()), Xf = no(yn()), Vf = ee(), eo; + function no(A) { + return A && A.__esModule ? A : { default: A }; + } + function Cg(A, e) { + for (var t = 0; t < e.length; t++) { + var r = e[t]; + r.enumerable = r.enumerable || false, r.configurable = true, "value" in r && (r.writable = true), Object.defineProperty(A, r.key, r); + } + } + function zf(A, e, t) { + return e && Cg(A.prototype, e), t && Cg(A, t), A; + } + function Zf(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, ro(A, e); + } + function ro(A, e) { + return ro = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, ro(A, e); + } + var pr = Qg(), jf = /^('|")([^]*)\1$/, $f = pr(function() { + }, "Assigning an attribute a value containing characters that might need to be escaped is deprecated. Call attribute.setValue() instead."), AB = pr(function() { + }, "Assigning attr.quoted is deprecated and has no effect. Assign to attr.quoteMark instead."), eB = pr(function() { + }, "Constructing an Attribute selector with a value without specifying quoteMark is deprecated. Note: The value should be unescaped now."); + function io(A) { + var e = false, t = null, r = A, n = r.match(jf); + return n && (t = n[1], r = n[2]), r = (0, qf.default)(r), r !== A && (e = true), { deprecatedUsage: e, unescaped: r, quoteMark: t }; + } + function tB(A) { + if (A.quoteMark !== void 0 || A.value === void 0) return A; + eB(); + var e = io(A.value), t = e.quoteMark, r = e.unescaped; + return A.raws || (A.raws = {}), A.raws.value === void 0 && (A.raws.value = A.value), A.value = r, A.quoteMark = t, A; + } + var wn = (function(A) { + Zf(e, A); + function e(r) { + var n; + return r === void 0 && (r = {}), n = A.call(this, tB(r)) || this, n.type = Vf.ATTRIBUTE, n.raws = n.raws || {}, Object.defineProperty(n.raws, "unquoted", { get: pr(function() { + return n.value; + }, "attr.raws.unquoted is deprecated. Call attr.value instead."), set: pr(function() { + return n.value; + }, "Setting attr.raws.unquoted is deprecated and has no effect. attr.value is unescaped by default now.") }), n._constructed = true, n; + } + var t = e.prototype; + return t.getQuotedValue = function(n) { + n === void 0 && (n = {}); + var i = this._determineQuoteMark(n), o = to[i], a = (0, hr.default)(this._value, o); + return a; + }, t._determineQuoteMark = function(n) { + return n.smart ? this.smartQuoteMark(n) : this.preferredQuoteMark(n); + }, t.setValue = function(n, i) { + i === void 0 && (i = {}), this._value = n, this._quoteMark = this._determineQuoteMark(i), this._syncRawValue(); + }, t.smartQuoteMark = function(n) { + var i = this.value, o = i.replace(/[^']/g, "").length, a = i.replace(/[^"]/g, "").length; + if (o + a === 0) { + var u2 = (0, hr.default)(i, { isIdentifier: true }); + if (u2 === i) return e.NO_QUOTE; + var l2 = this.preferredQuoteMark(n); + if (l2 === e.NO_QUOTE) { + var I = this.quoteMark || n.quoteMark || e.DOUBLE_QUOTE, E = to[I], C = (0, hr.default)(i, E); + if (C.length < u2.length) return I; + } + return l2; + } else return a === o ? this.preferredQuoteMark(n) : a < o ? e.DOUBLE_QUOTE : e.SINGLE_QUOTE; + }, t.preferredQuoteMark = function(n) { + var i = n.preferCurrentQuoteMark ? this.quoteMark : n.quoteMark; + return i === void 0 && (i = n.preferCurrentQuoteMark ? n.quoteMark : this.quoteMark), i === void 0 && (i = e.DOUBLE_QUOTE), i; + }, t._syncRawValue = function() { + var n = (0, hr.default)(this._value, to[this.quoteMark]); + n === this._value ? this.raws && delete this.raws.value : this.raws.value = n; + }, t._handleEscapes = function(n, i) { + if (this._constructed) { + var o = (0, hr.default)(i, { isIdentifier: true }); + o !== i ? this.raws[n] = o : delete this.raws[n]; + } + }, t._spacesFor = function(n) { + var i = { before: "", after: "" }, o = this.spaces[n] || {}, a = this.raws.spaces && this.raws.spaces[n] || {}; + return Object.assign(i, o, a); + }, t._stringFor = function(n, i, o) { + i === void 0 && (i = n), o === void 0 && (o = dg); + var a = this._spacesFor(i); + return o(this.stringifyProperty(n), a); + }, t.offsetOf = function(n) { + var i = 1, o = this._spacesFor("attribute"); + if (i += o.before.length, n === "namespace" || n === "ns") return this.namespace ? i : -1; + if (n === "attributeNS" || (i += this.namespaceString.length, this.namespace && (i += 1), n === "attribute")) return i; + i += this.stringifyProperty("attribute").length, i += o.after.length; + var a = this._spacesFor("operator"); + i += a.before.length; + var u2 = this.stringifyProperty("operator"); + if (n === "operator") return u2 ? i : -1; + i += u2.length, i += a.after.length; + var l2 = this._spacesFor("value"); + i += l2.before.length; + var I = this.stringifyProperty("value"); + if (n === "value") return I ? i : -1; + i += I.length, i += l2.after.length; + var E = this._spacesFor("insensitive"); + return i += E.before.length, n === "insensitive" && this.insensitive ? i : -1; + }, t.toString = function() { + var n = this, i = [this.rawSpaceBefore, "["]; + return i.push(this._stringFor("qualifiedAttribute", "attribute")), this.operator && (this.value || this.value === "") && (i.push(this._stringFor("operator")), i.push(this._stringFor("value")), i.push(this._stringFor("insensitiveFlag", "insensitive", function(o, a) { + return o.length > 0 && !n.quoted && a.before.length === 0 && !(n.spaces.value && n.spaces.value.after) && (a.before = " "), dg(o, a); + }))), i.push("]"), i.push(this.rawSpaceAfter), i.join(""); + }, zf(e, [{ key: "quoted", get: function() { + var n = this.quoteMark; + return n === "'" || n === '"'; + }, set: function(n) { + AB(); + } }, { key: "quoteMark", get: function() { + return this._quoteMark; + }, set: function(n) { + if (!this._constructed) { + this._quoteMark = n; + return; + } + this._quoteMark !== n && (this._quoteMark = n, this._syncRawValue()); + } }, { key: "qualifiedAttribute", get: function() { + return this.qualifiedName(this.raws.attribute || this.attribute); + } }, { key: "insensitiveFlag", get: function() { + return this.insensitive ? "i" : ""; + } }, { key: "value", get: function() { + return this._value; + }, set: function(n) { + if (this._constructed) { + var i = io(n), o = i.deprecatedUsage, a = i.unescaped, u2 = i.quoteMark; + if (o && $f(), a === this._value && u2 === this._quoteMark) return; + this._value = a, this._quoteMark = u2, this._syncRawValue(); + } else this._value = n; + } }, { key: "attribute", get: function() { + return this._attribute; + }, set: function(n) { + this._handleEscapes("attribute", n), this._attribute = n; + } }]), e; + })(Xf.default); + mr.default = wn; + wn.NO_QUOTE = null; + wn.SINGLE_QUOTE = "'"; + wn.DOUBLE_QUOTE = '"'; + var to = (eo = { "'": { quotes: "single", wrap: true }, '"': { quotes: "double", wrap: true } }, eo[null] = { isIdentifier: true }, eo); + function dg(A, e) { + return "" + e.before + A + e.after; + } + }); + ao = K2((yr, hg) => { + "use strict"; + yr.__esModule = true; + yr.default = void 0; + var rB = iB(yn()), nB = ee(); + function iB(A) { + return A && A.__esModule ? A : { default: A }; + } + function oB(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, so(A, e); + } + function so(A, e) { + return so = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, so(A, e); + } + var sB = (function(A) { + oB(e, A); + function e(t) { + var r; + return r = A.call(this, t) || this, r.type = nB.UNIVERSAL, r.value = "*", r; + } + return e; + })(rB.default); + yr.default = sB; + hg.exports = yr.default; + }); + uo = K2((wr, pg) => { + "use strict"; + wr.__esModule = true; + wr.default = void 0; + var aB = uB(je()), gB = ee(); + function uB(A) { + return A && A.__esModule ? A : { default: A }; + } + function IB(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, go(A, e); + } + function go(A, e) { + return go = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, go(A, e); + } + var lB = (function(A) { + IB(e, A); + function e(t) { + var r; + return r = A.call(this, t) || this, r.type = gB.COMBINATOR, r; + } + return e; + })(aB.default); + wr.default = lB; + pg.exports = wr.default; + }); + lo = K2((Dr, mg) => { + "use strict"; + Dr.__esModule = true; + Dr.default = void 0; + var cB = BB(je()), fB = ee(); + function BB(A) { + return A && A.__esModule ? A : { default: A }; + } + function EB(A, e) { + A.prototype = Object.create(e.prototype), A.prototype.constructor = A, Io(A, e); + } + function Io(A, e) { + return Io = Object.setPrototypeOf || function(r, n) { + return r.__proto__ = n, r; + }, Io(A, e); + } + var QB = (function(A) { + EB(e, A); + function e(t) { + var r; + return r = A.call(this, t) || this, r.type = fB.NESTING, r.value = "&", r; + } + return e; + })(cB.default); + Dr.default = QB; + mg.exports = Dr.default; + }); + wg = K2((Dn, yg) => { + "use strict"; + Dn.__esModule = true; + Dn.default = CB; + function CB(A) { + return A.sort(function(e, t) { + return e - t; + }); + } + yg.exports = Dn.default; + }); + co = K2((_) => { + "use strict"; + _.__esModule = true; + _.combinator = _.word = _.comment = _.str = _.tab = _.newline = _.feed = _.cr = _.backslash = _.bang = _.slash = _.doubleQuote = _.singleQuote = _.space = _.greaterThan = _.pipe = _.equals = _.plus = _.caret = _.tilde = _.dollar = _.closeSquare = _.openSquare = _.closeParenthesis = _.openParenthesis = _.semicolon = _.colon = _.comma = _.at = _.asterisk = _.ampersand = void 0; + var dB = 38; + _.ampersand = dB; + var hB = 42; + _.asterisk = hB; + var pB = 64; + _.at = pB; + var mB = 44; + _.comma = mB; + var yB = 58; + _.colon = yB; + var wB = 59; + _.semicolon = wB; + var DB = 40; + _.openParenthesis = DB; + var SB = 41; + _.closeParenthesis = SB; + var bB = 91; + _.openSquare = bB; + var vB = 93; + _.closeSquare = vB; + var kB = 36; + _.dollar = kB; + var xB = 126; + _.tilde = xB; + var RB = 94; + _.caret = RB; + var NB = 43; + _.plus = NB; + var MB = 61; + _.equals = MB; + var FB = 124; + _.pipe = FB; + var LB = 62; + _.greaterThan = LB; + var GB = 32; + _.space = GB; + var Dg = 39; + _.singleQuote = Dg; + var UB = 34; + _.doubleQuote = UB; + var HB = 47; + _.slash = HB; + var OB = 33; + _.bang = OB; + var TB = 92; + _.backslash = TB; + var PB = 13; + _.cr = PB; + var _B = 12; + _.feed = _B; + var JB = 10; + _.newline = JB; + var WB = 9; + _.tab = WB; + var KB = Dg; + _.str = KB; + var YB = -1; + _.comment = YB; + var qB = -2; + _.word = qB; + var XB = -3; + _.combinator = XB; + }); + vg = K2((Sr) => { + "use strict"; + Sr.__esModule = true; + Sr.default = eE; + Sr.FIELDS = void 0; + var T = VB(co()), Ht, yA; + function bg() { + if (typeof WeakMap != "function") return null; + var A = /* @__PURE__ */ new WeakMap(); + return bg = function() { + return A; + }, A; + } + function VB(A) { + if (A && A.__esModule) return A; + if (A === null || typeof A != "object" && typeof A != "function") return { default: A }; + var e = bg(); + if (e && e.has(A)) return e.get(A); + var t = {}, r = Object.defineProperty && Object.getOwnPropertyDescriptor; + for (var n in A) if (Object.prototype.hasOwnProperty.call(A, n)) { + var i = r ? Object.getOwnPropertyDescriptor(A, n) : null; + i && (i.get || i.set) ? Object.defineProperty(t, n, i) : t[n] = A[n]; + } + return t.default = A, e && e.set(A, t), t; + } + var zB = (Ht = {}, Ht[T.tab] = true, Ht[T.newline] = true, Ht[T.cr] = true, Ht[T.feed] = true, Ht), ZB = (yA = {}, yA[T.space] = true, yA[T.tab] = true, yA[T.newline] = true, yA[T.cr] = true, yA[T.feed] = true, yA[T.ampersand] = true, yA[T.asterisk] = true, yA[T.bang] = true, yA[T.comma] = true, yA[T.colon] = true, yA[T.semicolon] = true, yA[T.openParenthesis] = true, yA[T.closeParenthesis] = true, yA[T.openSquare] = true, yA[T.closeSquare] = true, yA[T.singleQuote] = true, yA[T.doubleQuote] = true, yA[T.plus] = true, yA[T.pipe] = true, yA[T.tilde] = true, yA[T.greaterThan] = true, yA[T.equals] = true, yA[T.dollar] = true, yA[T.caret] = true, yA[T.slash] = true, yA), fo = {}, Sg = "0123456789abcdefABCDEF"; + for (Sn = 0; Sn < Sg.length; Sn++) fo[Sg.charCodeAt(Sn)] = true; + var Sn; + function jB(A, e) { + var t = e, r; + do { + if (r = A.charCodeAt(t), ZB[r]) return t - 1; + r === T.backslash ? t = $B(A, t) + 1 : t++; + } while (t < A.length); + return t - 1; + } + function $B(A, e) { + var t = e, r = A.charCodeAt(t + 1); + if (!zB[r]) if (fo[r]) { + var n = 0; + do + t++, n++, r = A.charCodeAt(t + 1); + while (fo[r] && n < 6); + n < 6 && r === T.space && t++; + } else t++; + return t; + } + var AE = { TYPE: 0, START_LINE: 1, START_COL: 2, END_LINE: 3, END_COL: 4, START_POS: 5, END_POS: 6 }; + Sr.FIELDS = AE; + function eE(A) { + var e = [], t = A.css.valueOf(), r = t, n = r.length, i = -1, o = 1, a = 0, u2 = 0, l2, I, E, C, d2, p, y, k, x2, F, b, v2, M; + function L(O, J) { + if (A.safe) t += J, x2 = t.length - 1; + else throw A.error("Unclosed " + O, o, a - i, a); + } + for (; a < n; ) { + switch (l2 = t.charCodeAt(a), l2 === T.newline && (i = a, o += 1), l2) { + case T.space: + case T.tab: + case T.newline: + case T.cr: + case T.feed: + x2 = a; + do + x2 += 1, l2 = t.charCodeAt(x2), l2 === T.newline && (i = x2, o += 1); + while (l2 === T.space || l2 === T.newline || l2 === T.tab || l2 === T.cr || l2 === T.feed); + M = T.space, C = o, E = x2 - i - 1, u2 = x2; + break; + case T.plus: + case T.greaterThan: + case T.tilde: + case T.pipe: + x2 = a; + do + x2 += 1, l2 = t.charCodeAt(x2); + while (l2 === T.plus || l2 === T.greaterThan || l2 === T.tilde || l2 === T.pipe); + M = T.combinator, C = o, E = a - i, u2 = x2; + break; + case T.asterisk: + case T.ampersand: + case T.bang: + case T.comma: + case T.equals: + case T.dollar: + case T.caret: + case T.openSquare: + case T.closeSquare: + case T.colon: + case T.semicolon: + case T.openParenthesis: + case T.closeParenthesis: + x2 = a, M = l2, C = o, E = a - i, u2 = x2 + 1; + break; + case T.singleQuote: + case T.doubleQuote: + v2 = l2 === T.singleQuote ? "'" : '"', x2 = a; + do + for (d2 = false, x2 = t.indexOf(v2, x2 + 1), x2 === -1 && L("quote", v2), p = x2; t.charCodeAt(p - 1) === T.backslash; ) p -= 1, d2 = !d2; + while (d2); + M = T.str, C = o, E = a - i, u2 = x2 + 1; + break; + default: + l2 === T.slash && t.charCodeAt(a + 1) === T.asterisk ? (x2 = t.indexOf("*/", a + 2) + 1, x2 === 0 && L("comment", "*/"), I = t.slice(a, x2 + 1), k = I.split(` +`), y = k.length - 1, y > 0 ? (F = o + y, b = x2 - k[y].length) : (F = o, b = i), M = T.comment, o = F, C = F, E = x2 - b) : l2 === T.slash ? (x2 = a, M = l2, C = o, E = a - i, u2 = x2 + 1) : (x2 = jB(t, a), M = T.word, C = o, E = x2 - i), u2 = x2 + 1; + break; + } + e.push([M, o, a - i, C, E, a, u2]), b && (i = b, b = null), a = u2; + } + return e; + } + }); + Gg = K2((br, Lg) => { + "use strict"; + br.__esModule = true; + br.default = void 0; + var tE = Re(Hi()), Bo = Re(Ti()), rE = Re(Ji()), kg = Re(Ki()), nE = Re(qi()), iE = Re(zi()), Eo = Re(ji()), oE = Re(Ao()), xg = bn(oo()), sE = Re(ao()), Qo = Re(uo()), aE = Re(lo()), gE = Re(wg()), G = bn(vg()), P2 = bn(co()), uE = bn(ee()), UA = ar(), Ct, Co; + function Fg() { + if (typeof WeakMap != "function") return null; + var A = /* @__PURE__ */ new WeakMap(); + return Fg = function() { + return A; + }, A; + } + function bn(A) { + if (A && A.__esModule) return A; + if (A === null || typeof A != "object" && typeof A != "function") return { default: A }; + var e = Fg(); + if (e && e.has(A)) return e.get(A); + var t = {}, r = Object.defineProperty && Object.getOwnPropertyDescriptor; + for (var n in A) if (Object.prototype.hasOwnProperty.call(A, n)) { + var i = r ? Object.getOwnPropertyDescriptor(A, n) : null; + i && (i.get || i.set) ? Object.defineProperty(t, n, i) : t[n] = A[n]; + } + return t.default = A, e && e.set(A, t), t; + } + function Re(A) { + return A && A.__esModule ? A : { default: A }; + } + function Rg(A, e) { + for (var t = 0; t < e.length; t++) { + var r = e[t]; + r.enumerable = r.enumerable || false, r.configurable = true, "value" in r && (r.writable = true), Object.defineProperty(A, r.key, r); + } + } + function IE(A, e, t) { + return e && Rg(A.prototype, e), t && Rg(A, t), A; + } + var mo = (Ct = {}, Ct[P2.space] = true, Ct[P2.cr] = true, Ct[P2.feed] = true, Ct[P2.newline] = true, Ct[P2.tab] = true, Ct), lE = Object.assign({}, mo, (Co = {}, Co[P2.comment] = true, Co)); + function Ng(A) { + return { line: A[G.FIELDS.START_LINE], column: A[G.FIELDS.START_COL] }; + } + function Mg(A) { + return { line: A[G.FIELDS.END_LINE], column: A[G.FIELDS.END_COL] }; + } + function dt(A, e, t, r) { + return { start: { line: A, column: e }, end: { line: t, column: r } }; + } + function Ot(A) { + return dt(A[G.FIELDS.START_LINE], A[G.FIELDS.START_COL], A[G.FIELDS.END_LINE], A[G.FIELDS.END_COL]); + } + function ho(A, e) { + if (A) return dt(A[G.FIELDS.START_LINE], A[G.FIELDS.START_COL], e[G.FIELDS.END_LINE], e[G.FIELDS.END_COL]); + } + function Tt(A, e) { + var t = A[e]; + if (typeof t == "string") return t.indexOf("\\") !== -1 && ((0, UA.ensureObject)(A, "raws"), A[e] = (0, UA.unesc)(t), A.raws[e] === void 0 && (A.raws[e] = t)), A; + } + function po(A, e) { + for (var t = -1, r = []; (t = A.indexOf(e, t + 1)) !== -1; ) r.push(t); + return r; + } + function cE() { + var A = Array.prototype.concat.apply([], arguments); + return A.filter(function(e, t) { + return t === A.indexOf(e); + }); + } + var fE = (function() { + function A(t, r) { + r === void 0 && (r = {}), this.rule = t, this.options = Object.assign({ lossy: false, safe: false }, r), this.position = 0, this.css = typeof this.rule == "string" ? this.rule : this.rule.selector, this.tokens = (0, G.default)({ css: this.css, error: this._errorGenerator(), safe: this.options.safe }); + var n = ho(this.tokens[0], this.tokens[this.tokens.length - 1]); + this.root = new tE.default({ source: n }), this.root.errorGenerator = this._errorGenerator(); + var i = new Bo.default({ source: { start: { line: 1, column: 1 } } }); + this.root.append(i), this.current = i, this.loop(); + } + var e = A.prototype; + return e._errorGenerator = function() { + var r = this; + return function(n, i) { + return typeof r.rule == "string" ? new Error(n) : r.rule.error(n, i); + }; + }, e.attribute = function() { + var r = [], n = this.currToken; + for (this.position++; this.position < this.tokens.length && this.currToken[G.FIELDS.TYPE] !== P2.closeSquare; ) r.push(this.currToken), this.position++; + if (this.currToken[G.FIELDS.TYPE] !== P2.closeSquare) return this.expected("closing square bracket", this.currToken[G.FIELDS.START_POS]); + var i = r.length, o = { source: dt(n[1], n[2], this.currToken[3], this.currToken[4]), sourceIndex: n[G.FIELDS.START_POS] }; + if (i === 1 && !~[P2.word].indexOf(r[0][G.FIELDS.TYPE])) return this.expected("attribute", r[0][G.FIELDS.START_POS]); + for (var a = 0, u2 = "", l2 = "", I = null, E = false; a < i; ) { + var C = r[a], d2 = this.content(C), p = r[a + 1]; + switch (C[G.FIELDS.TYPE]) { + case P2.space: + if (E = true, this.options.lossy) break; + if (I) { + (0, UA.ensureObject)(o, "spaces", I); + var y = o.spaces[I].after || ""; + o.spaces[I].after = y + d2; + var k = (0, UA.getProp)(o, "raws", "spaces", I, "after") || null; + k && (o.raws.spaces[I].after = k + d2); + } else u2 = u2 + d2, l2 = l2 + d2; + break; + case P2.asterisk: + if (p[G.FIELDS.TYPE] === P2.equals) o.operator = d2, I = "operator"; + else if ((!o.namespace || I === "namespace" && !E) && p) { + u2 && ((0, UA.ensureObject)(o, "spaces", "attribute"), o.spaces.attribute.before = u2, u2 = ""), l2 && ((0, UA.ensureObject)(o, "raws", "spaces", "attribute"), o.raws.spaces.attribute.before = u2, l2 = ""), o.namespace = (o.namespace || "") + d2; + var x2 = (0, UA.getProp)(o, "raws", "namespace") || null; + x2 && (o.raws.namespace += d2), I = "namespace"; + } + E = false; + break; + case P2.dollar: + if (I === "value") { + var F = (0, UA.getProp)(o, "raws", "value"); + o.value += "$", F && (o.raws.value = F + "$"); + break; + } + case P2.caret: + p[G.FIELDS.TYPE] === P2.equals && (o.operator = d2, I = "operator"), E = false; + break; + case P2.combinator: + if (d2 === "~" && p[G.FIELDS.TYPE] === P2.equals && (o.operator = d2, I = "operator"), d2 !== "|") { + E = false; + break; + } + p[G.FIELDS.TYPE] === P2.equals ? (o.operator = d2, I = "operator") : !o.namespace && !o.attribute && (o.namespace = true), E = false; + break; + case P2.word: + if (p && this.content(p) === "|" && r[a + 2] && r[a + 2][G.FIELDS.TYPE] !== P2.equals && !o.operator && !o.namespace) o.namespace = d2, I = "namespace"; + else if (!o.attribute || I === "attribute" && !E) { + u2 && ((0, UA.ensureObject)(o, "spaces", "attribute"), o.spaces.attribute.before = u2, u2 = ""), l2 && ((0, UA.ensureObject)(o, "raws", "spaces", "attribute"), o.raws.spaces.attribute.before = l2, l2 = ""), o.attribute = (o.attribute || "") + d2; + var b = (0, UA.getProp)(o, "raws", "attribute") || null; + b && (o.raws.attribute += d2), I = "attribute"; + } else if (!o.value && o.value !== "" || I === "value" && !E) { + var v2 = (0, UA.unesc)(d2), M = (0, UA.getProp)(o, "raws", "value") || "", L = o.value || ""; + o.value = L + v2, o.quoteMark = null, (v2 !== d2 || M) && ((0, UA.ensureObject)(o, "raws"), o.raws.value = (M || L) + d2), I = "value"; + } else { + var O = d2 === "i" || d2 === "I"; + (o.value || o.value === "") && (o.quoteMark || E) ? (o.insensitive = O, (!O || d2 === "I") && ((0, UA.ensureObject)(o, "raws"), o.raws.insensitiveFlag = d2), I = "insensitive", u2 && ((0, UA.ensureObject)(o, "spaces", "insensitive"), o.spaces.insensitive.before = u2, u2 = ""), l2 && ((0, UA.ensureObject)(o, "raws", "spaces", "insensitive"), o.raws.spaces.insensitive.before = l2, l2 = "")) : (o.value || o.value === "") && (I = "value", o.value += d2, o.raws.value && (o.raws.value += d2)); + } + E = false; + break; + case P2.str: + if (!o.attribute || !o.operator) return this.error("Expected an attribute followed by an operator preceding the string.", { index: C[G.FIELDS.START_POS] }); + var J = (0, xg.unescapeValue)(d2), j = J.unescaped, CA = J.quoteMark; + o.value = j, o.quoteMark = CA, I = "value", (0, UA.ensureObject)(o, "raws"), o.raws.value = d2, E = false; + break; + case P2.equals: + if (!o.attribute) return this.expected("attribute", C[G.FIELDS.START_POS], d2); + if (o.value) return this.error('Unexpected "=" found; an operator was already defined.', { index: C[G.FIELDS.START_POS] }); + o.operator = o.operator ? o.operator + d2 : d2, I = "operator", E = false; + break; + case P2.comment: + if (I) if (E || p && p[G.FIELDS.TYPE] === P2.space || I === "insensitive") { + var MA = (0, UA.getProp)(o, "spaces", I, "after") || "", dA = (0, UA.getProp)(o, "raws", "spaces", I, "after") || MA; + (0, UA.ensureObject)(o, "raws", "spaces", I), o.raws.spaces[I].after = dA + d2; + } else { + var sA = o[I] || "", vA = (0, UA.getProp)(o, "raws", I) || sA; + (0, UA.ensureObject)(o, "raws"), o.raws[I] = vA + d2; + } + else l2 = l2 + d2; + break; + default: + return this.error('Unexpected "' + d2 + '" found.', { index: C[G.FIELDS.START_POS] }); + } + a++; + } + Tt(o, "attribute"), Tt(o, "namespace"), this.newNode(new xg.default(o)), this.position++; + }, e.parseWhitespaceEquivalentTokens = function(r) { + r < 0 && (r = this.tokens.length); + var n = this.position, i = [], o = "", a = void 0; + do + if (mo[this.currToken[G.FIELDS.TYPE]]) this.options.lossy || (o += this.content()); + else if (this.currToken[G.FIELDS.TYPE] === P2.comment) { + var u2 = {}; + o && (u2.before = o, o = ""), a = new kg.default({ value: this.content(), source: Ot(this.currToken), sourceIndex: this.currToken[G.FIELDS.START_POS], spaces: u2 }), i.push(a); + } + while (++this.position < r); + if (o) { + if (a) a.spaces.after = o; + else if (!this.options.lossy) { + var l2 = this.tokens[n], I = this.tokens[this.position - 1]; + i.push(new Eo.default({ value: "", source: dt(l2[G.FIELDS.START_LINE], l2[G.FIELDS.START_COL], I[G.FIELDS.END_LINE], I[G.FIELDS.END_COL]), sourceIndex: l2[G.FIELDS.START_POS], spaces: { before: o, after: "" } })); + } + } + return i; + }, e.convertWhitespaceNodesToSpace = function(r, n) { + var i = this; + n === void 0 && (n = false); + var o = "", a = ""; + r.forEach(function(l2) { + var I = i.lossySpace(l2.spaces.before, n), E = i.lossySpace(l2.rawSpaceBefore, n); + o += I + i.lossySpace(l2.spaces.after, n && I.length === 0), a += I + l2.value + i.lossySpace(l2.rawSpaceAfter, n && E.length === 0); + }), a === o && (a = void 0); + var u2 = { space: o, rawSpace: a }; + return u2; + }, e.isNamedCombinator = function(r) { + return r === void 0 && (r = this.position), this.tokens[r + 0] && this.tokens[r + 0][G.FIELDS.TYPE] === P2.slash && this.tokens[r + 1] && this.tokens[r + 1][G.FIELDS.TYPE] === P2.word && this.tokens[r + 2] && this.tokens[r + 2][G.FIELDS.TYPE] === P2.slash; + }, e.namedCombinator = function() { + if (this.isNamedCombinator()) { + var r = this.content(this.tokens[this.position + 1]), n = (0, UA.unesc)(r).toLowerCase(), i = {}; + n !== r && (i.value = "/" + r + "/"); + var o = new Qo.default({ value: "/" + n + "/", source: dt(this.currToken[G.FIELDS.START_LINE], this.currToken[G.FIELDS.START_COL], this.tokens[this.position + 2][G.FIELDS.END_LINE], this.tokens[this.position + 2][G.FIELDS.END_COL]), sourceIndex: this.currToken[G.FIELDS.START_POS], raws: i }); + return this.position = this.position + 3, o; + } else this.unexpected(); + }, e.combinator = function() { + var r = this; + if (this.content() === "|") return this.namespace(); + var n = this.locateNextMeaningfulToken(this.position); + if (n < 0 || this.tokens[n][G.FIELDS.TYPE] === P2.comma) { + var i = this.parseWhitespaceEquivalentTokens(n); + if (i.length > 0) { + var o = this.current.last; + if (o) { + var a = this.convertWhitespaceNodesToSpace(i), u2 = a.space, l2 = a.rawSpace; + l2 !== void 0 && (o.rawSpaceAfter += l2), o.spaces.after += u2; + } else i.forEach(function(M) { + return r.newNode(M); + }); + } + return; + } + var I = this.currToken, E = void 0; + n > this.position && (E = this.parseWhitespaceEquivalentTokens(n)); + var C; + if (this.isNamedCombinator() ? C = this.namedCombinator() : this.currToken[G.FIELDS.TYPE] === P2.combinator ? (C = new Qo.default({ value: this.content(), source: Ot(this.currToken), sourceIndex: this.currToken[G.FIELDS.START_POS] }), this.position++) : mo[this.currToken[G.FIELDS.TYPE]] || E || this.unexpected(), C) { + if (E) { + var d2 = this.convertWhitespaceNodesToSpace(E), p = d2.space, y = d2.rawSpace; + C.spaces.before = p, C.rawSpaceBefore = y; + } + } else { + var k = this.convertWhitespaceNodesToSpace(E, true), x2 = k.space, F = k.rawSpace; + F || (F = x2); + var b = {}, v2 = { spaces: {} }; + x2.endsWith(" ") && F.endsWith(" ") ? (b.before = x2.slice(0, x2.length - 1), v2.spaces.before = F.slice(0, F.length - 1)) : x2.startsWith(" ") && F.startsWith(" ") ? (b.after = x2.slice(1), v2.spaces.after = F.slice(1)) : v2.value = F, C = new Qo.default({ value: " ", source: ho(I, this.tokens[this.position - 1]), sourceIndex: I[G.FIELDS.START_POS], spaces: b, raws: v2 }); + } + return this.currToken && this.currToken[G.FIELDS.TYPE] === P2.space && (C.spaces.after = this.optionalSpace(this.content()), this.position++), this.newNode(C); + }, e.comma = function() { + if (this.position === this.tokens.length - 1) { + this.root.trailingComma = true, this.position++; + return; + } + this.current._inferEndPosition(); + var r = new Bo.default({ source: { start: Ng(this.tokens[this.position + 1]) } }); + this.current.parent.append(r), this.current = r, this.position++; + }, e.comment = function() { + var r = this.currToken; + this.newNode(new kg.default({ value: this.content(), source: Ot(r), sourceIndex: r[G.FIELDS.START_POS] })), this.position++; + }, e.error = function(r, n) { + throw this.root.error(r, n); + }, e.missingBackslash = function() { + return this.error("Expected a backslash preceding the semicolon.", { index: this.currToken[G.FIELDS.START_POS] }); + }, e.missingParenthesis = function() { + return this.expected("opening parenthesis", this.currToken[G.FIELDS.START_POS]); + }, e.missingSquareBracket = function() { + return this.expected("opening square bracket", this.currToken[G.FIELDS.START_POS]); + }, e.unexpected = function() { + return this.error("Unexpected '" + this.content() + "'. Escaping special characters with \\ may help.", this.currToken[G.FIELDS.START_POS]); + }, e.namespace = function() { + var r = this.prevToken && this.content(this.prevToken) || true; + if (this.nextToken[G.FIELDS.TYPE] === P2.word) return this.position++, this.word(r); + if (this.nextToken[G.FIELDS.TYPE] === P2.asterisk) return this.position++, this.universal(r); + }, e.nesting = function() { + if (this.nextToken) { + var r = this.content(this.nextToken); + if (r === "|") { + this.position++; + return; + } + } + var n = this.currToken; + this.newNode(new aE.default({ value: this.content(), source: Ot(n), sourceIndex: n[G.FIELDS.START_POS] })), this.position++; + }, e.parentheses = function() { + var r = this.current.last, n = 1; + if (this.position++, r && r.type === uE.PSEUDO) { + var i = new Bo.default({ source: { start: Ng(this.tokens[this.position - 1]) } }), o = this.current; + for (r.append(i), this.current = i; this.position < this.tokens.length && n; ) this.currToken[G.FIELDS.TYPE] === P2.openParenthesis && n++, this.currToken[G.FIELDS.TYPE] === P2.closeParenthesis && n--, n ? this.parse() : (this.current.source.end = Mg(this.currToken), this.current.parent.source.end = Mg(this.currToken), this.position++); + this.current = o; + } else { + for (var a = this.currToken, u2 = "(", l2; this.position < this.tokens.length && n; ) this.currToken[G.FIELDS.TYPE] === P2.openParenthesis && n++, this.currToken[G.FIELDS.TYPE] === P2.closeParenthesis && n--, l2 = this.currToken, u2 += this.parseParenthesisToken(this.currToken), this.position++; + r ? r.appendToPropertyAndEscape("value", u2, u2) : this.newNode(new Eo.default({ value: u2, source: dt(a[G.FIELDS.START_LINE], a[G.FIELDS.START_COL], l2[G.FIELDS.END_LINE], l2[G.FIELDS.END_COL]), sourceIndex: a[G.FIELDS.START_POS] })); + } + if (n) return this.expected("closing parenthesis", this.currToken[G.FIELDS.START_POS]); + }, e.pseudo = function() { + for (var r = this, n = "", i = this.currToken; this.currToken && this.currToken[G.FIELDS.TYPE] === P2.colon; ) n += this.content(), this.position++; + if (!this.currToken) return this.expected(["pseudo-class", "pseudo-element"], this.position - 1); + if (this.currToken[G.FIELDS.TYPE] === P2.word) this.splitWord(false, function(o, a) { + n += o, r.newNode(new oE.default({ value: n, source: ho(i, r.currToken), sourceIndex: i[G.FIELDS.START_POS] })), a > 1 && r.nextToken && r.nextToken[G.FIELDS.TYPE] === P2.openParenthesis && r.error("Misplaced parenthesis.", { index: r.nextToken[G.FIELDS.START_POS] }); + }); + else return this.expected(["pseudo-class", "pseudo-element"], this.currToken[G.FIELDS.START_POS]); + }, e.space = function() { + var r = this.content(); + this.position === 0 || this.prevToken[G.FIELDS.TYPE] === P2.comma || this.prevToken[G.FIELDS.TYPE] === P2.openParenthesis || this.current.nodes.every(function(n) { + return n.type === "comment"; + }) ? (this.spaces = this.optionalSpace(r), this.position++) : this.position === this.tokens.length - 1 || this.nextToken[G.FIELDS.TYPE] === P2.comma || this.nextToken[G.FIELDS.TYPE] === P2.closeParenthesis ? (this.current.last.spaces.after = this.optionalSpace(r), this.position++) : this.combinator(); + }, e.string = function() { + var r = this.currToken; + this.newNode(new Eo.default({ value: this.content(), source: Ot(r), sourceIndex: r[G.FIELDS.START_POS] })), this.position++; + }, e.universal = function(r) { + var n = this.nextToken; + if (n && this.content(n) === "|") return this.position++, this.namespace(); + var i = this.currToken; + this.newNode(new sE.default({ value: this.content(), source: Ot(i), sourceIndex: i[G.FIELDS.START_POS] }), r), this.position++; + }, e.splitWord = function(r, n) { + for (var i = this, o = this.nextToken, a = this.content(); o && ~[P2.dollar, P2.caret, P2.equals, P2.word].indexOf(o[G.FIELDS.TYPE]); ) { + this.position++; + var u2 = this.content(); + if (a += u2, u2.lastIndexOf("\\") === u2.length - 1) { + var l2 = this.nextToken; + l2 && l2[G.FIELDS.TYPE] === P2.space && (a += this.requiredSpace(this.content(l2)), this.position++); + } + o = this.nextToken; + } + var I = po(a, ".").filter(function(p) { + var y = a[p - 1] === "\\", k = /^\d+\.\d+%$/.test(a); + return !y && !k; + }), E = po(a, "#").filter(function(p) { + return a[p - 1] !== "\\"; + }), C = po(a, "#{"); + C.length && (E = E.filter(function(p) { + return !~C.indexOf(p); + })); + var d2 = (0, gE.default)(cE([0].concat(I, E))); + d2.forEach(function(p, y) { + var k = d2[y + 1] || a.length, x2 = a.slice(p, k); + if (y === 0 && n) return n.call(i, x2, d2.length); + var F, b = i.currToken, v2 = b[G.FIELDS.START_POS] + d2[y], M = dt(b[1], b[2] + p, b[3], b[2] + (k - 1)); + if (~I.indexOf(p)) { + var L = { value: x2.slice(1), source: M, sourceIndex: v2 }; + F = new rE.default(Tt(L, "value")); + } else if (~E.indexOf(p)) { + var O = { value: x2.slice(1), source: M, sourceIndex: v2 }; + F = new nE.default(Tt(O, "value")); + } else { + var J = { value: x2, source: M, sourceIndex: v2 }; + Tt(J, "value"), F = new iE.default(J); + } + i.newNode(F, r), r = null; + }), this.position++; + }, e.word = function(r) { + var n = this.nextToken; + return n && this.content(n) === "|" ? (this.position++, this.namespace()) : this.splitWord(r); + }, e.loop = function() { + for (; this.position < this.tokens.length; ) this.parse(true); + return this.current._inferEndPosition(), this.root; + }, e.parse = function(r) { + switch (this.currToken[G.FIELDS.TYPE]) { + case P2.space: + this.space(); + break; + case P2.comment: + this.comment(); + break; + case P2.openParenthesis: + this.parentheses(); + break; + case P2.closeParenthesis: + r && this.missingParenthesis(); + break; + case P2.openSquare: + this.attribute(); + break; + case P2.dollar: + case P2.caret: + case P2.equals: + case P2.word: + this.word(); + break; + case P2.colon: + this.pseudo(); + break; + case P2.comma: + this.comma(); + break; + case P2.asterisk: + this.universal(); + break; + case P2.ampersand: + this.nesting(); + break; + case P2.slash: + case P2.combinator: + this.combinator(); + break; + case P2.str: + this.string(); + break; + case P2.closeSquare: + this.missingSquareBracket(); + case P2.semicolon: + this.missingBackslash(); + default: + this.unexpected(); + } + }, e.expected = function(r, n, i) { + if (Array.isArray(r)) { + var o = r.pop(); + r = r.join(", ") + " or " + o; + } + var a = /^[aeiou]/.test(r[0]) ? "an" : "a"; + return i ? this.error("Expected " + a + " " + r + ', found "' + i + '" instead.', { index: n }) : this.error("Expected " + a + " " + r + ".", { index: n }); + }, e.requiredSpace = function(r) { + return this.options.lossy ? " " : r; + }, e.optionalSpace = function(r) { + return this.options.lossy ? "" : r; + }, e.lossySpace = function(r, n) { + return this.options.lossy ? n ? " " : "" : r; + }, e.parseParenthesisToken = function(r) { + var n = this.content(r); + return r[G.FIELDS.TYPE] === P2.space ? this.requiredSpace(n) : n; + }, e.newNode = function(r, n) { + return n && (/^ +$/.test(n) && (this.options.lossy || (this.spaces = (this.spaces || "") + n), n = true), r.namespace = n, Tt(r, "namespace")), this.spaces && (r.spaces.before = this.spaces, this.spaces = ""), this.current.append(r); + }, e.content = function(r) { + return r === void 0 && (r = this.currToken), this.css.slice(r[G.FIELDS.START_POS], r[G.FIELDS.END_POS]); + }, e.locateNextMeaningfulToken = function(r) { + r === void 0 && (r = this.position + 1); + for (var n = r; n < this.tokens.length; ) if (lE[this.tokens[n][G.FIELDS.TYPE]]) { + n++; + continue; + } else return n; + return -1; + }, IE(A, [{ key: "currToken", get: function() { + return this.tokens[this.position]; + } }, { key: "nextToken", get: function() { + return this.tokens[this.position + 1]; + } }, { key: "prevToken", get: function() { + return this.tokens[this.position - 1]; + } }]), A; + })(); + br.default = fE; + Lg.exports = br.default; + }); + Hg = K2((vr, Ug) => { + "use strict"; + vr.__esModule = true; + vr.default = void 0; + var BE = EE(Gg()); + function EE(A) { + return A && A.__esModule ? A : { default: A }; + } + var QE = (function() { + function A(t, r) { + this.func = t || function() { + }, this.funcRes = null, this.options = r; + } + var e = A.prototype; + return e._shouldUpdateSelector = function(r, n) { + n === void 0 && (n = {}); + var i = Object.assign({}, this.options, n); + return i.updateSelector === false ? false : typeof r != "string"; + }, e._isLossy = function(r) { + r === void 0 && (r = {}); + var n = Object.assign({}, this.options, r); + return n.lossless === false; + }, e._root = function(r, n) { + n === void 0 && (n = {}); + var i = new BE.default(r, this._parseOptions(n)); + return i.root; + }, e._parseOptions = function(r) { + return { lossy: this._isLossy(r) }; + }, e._run = function(r, n) { + var i = this; + return n === void 0 && (n = {}), new Promise(function(o, a) { + try { + var u2 = i._root(r, n); + Promise.resolve(i.func(u2)).then(function(l2) { + var I = void 0; + return i._shouldUpdateSelector(r, n) && (I = u2.toString(), r.selector = I), { transform: l2, root: u2, string: I }; + }).then(o, a); + } catch (l2) { + a(l2); + return; + } + }); + }, e._runSync = function(r, n) { + n === void 0 && (n = {}); + var i = this._root(r, n), o = this.func(i); + if (o && typeof o.then == "function") throw new Error("Selector processor returned a promise to a synchronous call."); + var a = void 0; + return n.updateSelector && typeof r != "string" && (a = i.toString(), r.selector = a), { transform: o, root: i, string: a }; + }, e.ast = function(r, n) { + return this._run(r, n).then(function(i) { + return i.root; + }); + }, e.astSync = function(r, n) { + return this._runSync(r, n).root; + }, e.transform = function(r, n) { + return this._run(r, n).then(function(i) { + return i.transform; + }); + }, e.transformSync = function(r, n) { + return this._runSync(r, n).transform; + }, e.process = function(r, n) { + return this._run(r, n).then(function(i) { + return i.string || i.root.toString(); + }); + }, e.processSync = function(r, n) { + var i = this._runSync(r, n); + return i.string || i.root.toString(); + }, A; + })(); + vr.default = QE; + Ug.exports = vr.default; + }); + Og = K2((bA) => { + "use strict"; + bA.__esModule = true; + bA.universal = bA.tag = bA.string = bA.selector = bA.root = bA.pseudo = bA.nesting = bA.id = bA.comment = bA.combinator = bA.className = bA.attribute = void 0; + var CE = Ne(oo()), dE = Ne(Ji()), hE = Ne(uo()), pE = Ne(Ki()), mE = Ne(qi()), yE = Ne(lo()), wE = Ne(Ao()), DE = Ne(Hi()), SE = Ne(Ti()), bE = Ne(ji()), vE = Ne(zi()), kE = Ne(ao()); + function Ne(A) { + return A && A.__esModule ? A : { default: A }; + } + var xE = function(e) { + return new CE.default(e); + }; + bA.attribute = xE; + var RE = function(e) { + return new dE.default(e); + }; + bA.className = RE; + var NE = function(e) { + return new hE.default(e); + }; + bA.combinator = NE; + var ME = function(e) { + return new pE.default(e); + }; + bA.comment = ME; + var FE = function(e) { + return new mE.default(e); + }; + bA.id = FE; + var LE = function(e) { + return new yE.default(e); + }; + bA.nesting = LE; + var GE = function(e) { + return new wE.default(e); + }; + bA.pseudo = GE; + var UE = function(e) { + return new DE.default(e); + }; + bA.root = UE; + var HE = function(e) { + return new SE.default(e); + }; + bA.selector = HE; + var OE = function(e) { + return new bE.default(e); + }; + bA.string = OE; + var TE = function(e) { + return new vE.default(e); + }; + bA.tag = TE; + var PE = function(e) { + return new kE.default(e); + }; + bA.universal = PE; + }); + Jg = K2((EA) => { + "use strict"; + EA.__esModule = true; + EA.isNode = yo; + EA.isPseudoElement = _g; + EA.isPseudoClass = jE; + EA.isContainer = $E; + EA.isNamespace = AQ; + EA.isUniversal = EA.isTag = EA.isString = EA.isSelector = EA.isRoot = EA.isPseudo = EA.isNesting = EA.isIdentifier = EA.isComment = EA.isCombinator = EA.isClassName = EA.isAttribute = void 0; + var HA = ee(), he, _E = (he = {}, he[HA.ATTRIBUTE] = true, he[HA.CLASS] = true, he[HA.COMBINATOR] = true, he[HA.COMMENT] = true, he[HA.ID] = true, he[HA.NESTING] = true, he[HA.PSEUDO] = true, he[HA.ROOT] = true, he[HA.SELECTOR] = true, he[HA.STRING] = true, he[HA.TAG] = true, he[HA.UNIVERSAL] = true, he); + function yo(A) { + return typeof A == "object" && _E[A.type]; + } + function Me(A, e) { + return yo(e) && e.type === A; + } + var Tg = Me.bind(null, HA.ATTRIBUTE); + EA.isAttribute = Tg; + var JE = Me.bind(null, HA.CLASS); + EA.isClassName = JE; + var WE = Me.bind(null, HA.COMBINATOR); + EA.isCombinator = WE; + var KE = Me.bind(null, HA.COMMENT); + EA.isComment = KE; + var YE = Me.bind(null, HA.ID); + EA.isIdentifier = YE; + var qE = Me.bind(null, HA.NESTING); + EA.isNesting = qE; + var wo = Me.bind(null, HA.PSEUDO); + EA.isPseudo = wo; + var XE = Me.bind(null, HA.ROOT); + EA.isRoot = XE; + var VE = Me.bind(null, HA.SELECTOR); + EA.isSelector = VE; + var zE = Me.bind(null, HA.STRING); + EA.isString = zE; + var Pg = Me.bind(null, HA.TAG); + EA.isTag = Pg; + var ZE = Me.bind(null, HA.UNIVERSAL); + EA.isUniversal = ZE; + function _g(A) { + return wo(A) && A.value && (A.value.startsWith("::") || A.value.toLowerCase() === ":before" || A.value.toLowerCase() === ":after" || A.value.toLowerCase() === ":first-letter" || A.value.toLowerCase() === ":first-line"); + } + function jE(A) { + return wo(A) && !_g(A); + } + function $E(A) { + return !!(yo(A) && A.walk); + } + function AQ(A) { + return Tg(A) || Pg(A); + } + }); + Wg = K2((Te) => { + "use strict"; + Te.__esModule = true; + var Do = ee(); + Object.keys(Do).forEach(function(A) { + A === "default" || A === "__esModule" || A in Te && Te[A] === Do[A] || (Te[A] = Do[A]); + }); + var So = Og(); + Object.keys(So).forEach(function(A) { + A === "default" || A === "__esModule" || A in Te && Te[A] === So[A] || (Te[A] = So[A]); + }); + var bo = Jg(); + Object.keys(bo).forEach(function(A) { + A === "default" || A === "__esModule" || A in Te && Te[A] === bo[A] || (Te[A] = bo[A]); + }); + }); + qg = K2((kr, Yg) => { + "use strict"; + kr.__esModule = true; + kr.default = void 0; + var eQ = nQ(Hg()), tQ = rQ(Wg()); + function Kg() { + if (typeof WeakMap != "function") return null; + var A = /* @__PURE__ */ new WeakMap(); + return Kg = function() { + return A; + }, A; + } + function rQ(A) { + if (A && A.__esModule) return A; + if (A === null || typeof A != "object" && typeof A != "function") return { default: A }; + var e = Kg(); + if (e && e.has(A)) return e.get(A); + var t = {}, r = Object.defineProperty && Object.getOwnPropertyDescriptor; + for (var n in A) if (Object.prototype.hasOwnProperty.call(A, n)) { + var i = r ? Object.getOwnPropertyDescriptor(A, n) : null; + i && (i.get || i.set) ? Object.defineProperty(t, n, i) : t[n] = A[n]; + } + return t.default = A, e && e.set(A, t), t; + } + function nQ(A) { + return A && A.__esModule ? A : { default: A }; + } + var vo = function(e) { + return new eQ.default(e); + }; + Object.assign(vo, tQ); + delete vo.__esModule; + var iQ = vo; + kr.default = iQ; + Yg.exports = kr.default; + }); + Xg = K2((ko) => { + "use strict"; + Object.defineProperty(ko, "__esModule", { value: true }); + Object.defineProperty(ko, "default", { enumerable: true, get: () => oQ }); + function oQ(A) { + return A.replace(/\\,/g, "\\2c "); + } + }); + zg = K2((d0, Vg) => { + "use strict"; + Vg.exports = { aliceblue: [240, 248, 255], antiquewhite: [250, 235, 215], aqua: [0, 255, 255], aquamarine: [127, 255, 212], azure: [240, 255, 255], beige: [245, 245, 220], bisque: [255, 228, 196], black: [0, 0, 0], blanchedalmond: [255, 235, 205], blue: [0, 0, 255], blueviolet: [138, 43, 226], brown: [165, 42, 42], burlywood: [222, 184, 135], cadetblue: [95, 158, 160], chartreuse: [127, 255, 0], chocolate: [210, 105, 30], coral: [255, 127, 80], cornflowerblue: [100, 149, 237], cornsilk: [255, 248, 220], crimson: [220, 20, 60], cyan: [0, 255, 255], darkblue: [0, 0, 139], darkcyan: [0, 139, 139], darkgoldenrod: [184, 134, 11], darkgray: [169, 169, 169], darkgreen: [0, 100, 0], darkgrey: [169, 169, 169], darkkhaki: [189, 183, 107], darkmagenta: [139, 0, 139], darkolivegreen: [85, 107, 47], darkorange: [255, 140, 0], darkorchid: [153, 50, 204], darkred: [139, 0, 0], darksalmon: [233, 150, 122], darkseagreen: [143, 188, 143], darkslateblue: [72, 61, 139], darkslategray: [47, 79, 79], darkslategrey: [47, 79, 79], darkturquoise: [0, 206, 209], darkviolet: [148, 0, 211], deeppink: [255, 20, 147], deepskyblue: [0, 191, 255], dimgray: [105, 105, 105], dimgrey: [105, 105, 105], dodgerblue: [30, 144, 255], firebrick: [178, 34, 34], floralwhite: [255, 250, 240], forestgreen: [34, 139, 34], fuchsia: [255, 0, 255], gainsboro: [220, 220, 220], ghostwhite: [248, 248, 255], gold: [255, 215, 0], goldenrod: [218, 165, 32], gray: [128, 128, 128], green: [0, 128, 0], greenyellow: [173, 255, 47], grey: [128, 128, 128], honeydew: [240, 255, 240], hotpink: [255, 105, 180], indianred: [205, 92, 92], indigo: [75, 0, 130], ivory: [255, 255, 240], khaki: [240, 230, 140], lavender: [230, 230, 250], lavenderblush: [255, 240, 245], lawngreen: [124, 252, 0], lemonchiffon: [255, 250, 205], lightblue: [173, 216, 230], lightcoral: [240, 128, 128], lightcyan: [224, 255, 255], lightgoldenrodyellow: [250, 250, 210], lightgray: [211, 211, 211], lightgreen: [144, 238, 144], lightgrey: [211, 211, 211], lightpink: [255, 182, 193], lightsalmon: [255, 160, 122], lightseagreen: [32, 178, 170], lightskyblue: [135, 206, 250], lightslategray: [119, 136, 153], lightslategrey: [119, 136, 153], lightsteelblue: [176, 196, 222], lightyellow: [255, 255, 224], lime: [0, 255, 0], limegreen: [50, 205, 50], linen: [250, 240, 230], magenta: [255, 0, 255], maroon: [128, 0, 0], mediumaquamarine: [102, 205, 170], mediumblue: [0, 0, 205], mediumorchid: [186, 85, 211], mediumpurple: [147, 112, 219], mediumseagreen: [60, 179, 113], mediumslateblue: [123, 104, 238], mediumspringgreen: [0, 250, 154], mediumturquoise: [72, 209, 204], mediumvioletred: [199, 21, 133], midnightblue: [25, 25, 112], mintcream: [245, 255, 250], mistyrose: [255, 228, 225], moccasin: [255, 228, 181], navajowhite: [255, 222, 173], navy: [0, 0, 128], oldlace: [253, 245, 230], olive: [128, 128, 0], olivedrab: [107, 142, 35], orange: [255, 165, 0], orangered: [255, 69, 0], orchid: [218, 112, 214], palegoldenrod: [238, 232, 170], palegreen: [152, 251, 152], paleturquoise: [175, 238, 238], palevioletred: [219, 112, 147], papayawhip: [255, 239, 213], peachpuff: [255, 218, 185], peru: [205, 133, 63], pink: [255, 192, 203], plum: [221, 160, 221], powderblue: [176, 224, 230], purple: [128, 0, 128], rebeccapurple: [102, 51, 153], red: [255, 0, 0], rosybrown: [188, 143, 143], royalblue: [65, 105, 225], saddlebrown: [139, 69, 19], salmon: [250, 128, 114], sandybrown: [244, 164, 96], seagreen: [46, 139, 87], seashell: [255, 245, 238], sienna: [160, 82, 45], silver: [192, 192, 192], skyblue: [135, 206, 235], slateblue: [106, 90, 205], slategray: [112, 128, 144], slategrey: [112, 128, 144], snow: [255, 250, 250], springgreen: [0, 255, 127], steelblue: [70, 130, 180], tan: [210, 180, 140], teal: [0, 128, 128], thistle: [216, 191, 216], tomato: [255, 99, 71], turquoise: [64, 224, 208], violet: [238, 130, 238], wheat: [245, 222, 179], white: [255, 255, 255], whitesmoke: [245, 245, 245], yellow: [255, 255, 0], yellowgreen: [154, 205, 50] }; + }); + Ro = K2((xo) => { + "use strict"; + Object.defineProperty(xo, "__esModule", { value: true }); + function sQ(A, e) { + for (var t in e) Object.defineProperty(A, t, { enumerable: true, get: e[t] }); + } + sQ(xo, { parseColor: () => cQ, formatColor: () => fQ }); + var Zg = aQ(zg()); + function aQ(A) { + return A && A.__esModule ? A : { default: A }; + } + var gQ = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i, uQ = /^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i, ot = /(?:\d+|\d*\.\d+)%?/, vn = /(?:\s*,\s*|\s+)/, jg = /\s*[,/]\s*/, st = /var\(--(?:[^ )]*?)\)/, IQ = new RegExp(`^(rgb)a?\\(\\s*(${ot.source}|${st.source})(?:${vn.source}(${ot.source}|${st.source}))?(?:${vn.source}(${ot.source}|${st.source}))?(?:${jg.source}(${ot.source}|${st.source}))?\\s*\\)$`), lQ = new RegExp(`^(hsl)a?\\(\\s*((?:${ot.source})(?:deg|rad|grad|turn)?|${st.source})(?:${vn.source}(${ot.source}|${st.source}))?(?:${vn.source}(${ot.source}|${st.source}))?(?:${jg.source}(${ot.source}|${st.source}))?\\s*\\)$`); + function cQ(A, { loose: e = false } = {}) { + var t, r; + if (typeof A != "string") return null; + if (A = A.trim(), A === "transparent") return { mode: "rgb", color: ["0", "0", "0"], alpha: "0" }; + if (A in Zg.default) return { mode: "rgb", color: Zg.default[A].map((u2) => u2.toString()) }; + let n = A.replace(uQ, (u2, l2, I, E, C) => ["#", l2, l2, I, I, E, E, C ? C + C : ""].join("")).match(gQ); + if (n !== null) return { mode: "rgb", color: [parseInt(n[1], 16), parseInt(n[2], 16), parseInt(n[3], 16)].map((u2) => u2.toString()), alpha: n[4] ? (parseInt(n[4], 16) / 255).toString() : void 0 }; + var i; + let o = (i = A.match(IQ)) !== null && i !== void 0 ? i : A.match(lQ); + if (o === null) return null; + let a = [o[2], o[3], o[4]].filter(Boolean).map((u2) => u2.toString()); + return !e && a.length !== 3 || a.length < 3 && !a.some((u2) => /^var\(.*?\)$/.test(u2)) ? null : { mode: o[1], color: a, alpha: (t = o[5]) === null || t === void 0 || (r = t.toString) === null || r === void 0 ? void 0 : r.call(t) }; + } + function fQ({ mode: A, color: e, alpha: t }) { + let r = t !== void 0; + return `${A}(${e.join(" ")}${r ? ` / ${t}` : ""})`; + } + }); + Mo = K2((No) => { + "use strict"; + Object.defineProperty(No, "__esModule", { value: true }); + function BQ(A, e) { + for (var t in e) Object.defineProperty(A, t, { enumerable: true, get: e[t] }); + } + BQ(No, { withAlphaValue: () => EQ, default: () => QQ }); + var kn = Ro(); + function EQ(A, e, t) { + if (typeof A == "function") return A({ opacityValue: e }); + let r = (0, kn.parseColor)(A, { loose: true }); + return r === null ? t : (0, kn.formatColor)({ ...r, alpha: e }); + } + function QQ({ color: A, property: e, variable: t }) { + let r = [].concat(e); + if (typeof A == "function") return { [t]: "1", ...Object.fromEntries(r.map((i) => [i, A({ opacityVariable: t, opacityValue: `var(${t})` })])) }; + let n = (0, kn.parseColor)(A); + return n === null ? Object.fromEntries(r.map((i) => [i, A])) : n.alpha !== void 0 ? Object.fromEntries(r.map((i) => [i, A])) : { [t]: "1", ...Object.fromEntries(r.map((i) => [i, (0, kn.formatColor)({ ...n, alpha: `var(${t})` })])) }; + } + }); + ru = K2((Fo) => { + "use strict"; + Object.defineProperty(Fo, "__esModule", { value: true }); + function CQ(A, e) { + for (var t in e) Object.defineProperty(A, t, { enumerable: true, get: e[t] }); + } + CQ(Fo, { pattern: () => hQ, withoutCapturing: () => Au, any: () => eu, optional: () => pQ, zeroOrMore: () => mQ, nestedBrackets: () => tu, escape: () => ht }); + var $g = /[\\^$.*+?()[\]{}|]/g, dQ = RegExp($g.source); + function xr(A) { + return A = Array.isArray(A) ? A : [A], A = A.map((e) => e instanceof RegExp ? e.source : e), A.join(""); + } + function hQ(A) { + return new RegExp(xr(A), "g"); + } + function Au(A) { + return new RegExp(`(?:${xr(A)})`, "g"); + } + function eu(A) { + return `(?:${A.map(xr).join("|")})`; + } + function pQ(A) { + return `(?:${xr(A)})?`; + } + function mQ(A) { + return `(?:${xr(A)})*`; + } + function tu(A, e, t = 1) { + return Au([ht(A), /[^\s]*/, t === 1 ? `[^${ht(A)}${ht(e)}s]*` : eu([`[^${ht(A)}${ht(e)}s]*`, tu(A, e, t - 1)]), /[^\s]*/, ht(e)]); + } + function ht(A) { + return A && dQ.test(A) ? A.replace($g, "\\$&") : A || ""; + } + }); + iu = K2((Lo) => { + "use strict"; + Object.defineProperty(Lo, "__esModule", { value: true }); + Object.defineProperty(Lo, "splitAtTopLevelOnly", { enumerable: true, get: () => DQ }); + var yQ = wQ(ru()); + function nu(A) { + if (typeof WeakMap != "function") return null; + var e = /* @__PURE__ */ new WeakMap(), t = /* @__PURE__ */ new WeakMap(); + return (nu = function(r) { + return r ? t : e; + })(A); + } + function wQ(A, e) { + if (!e && A && A.__esModule) return A; + if (A === null || typeof A != "object" && typeof A != "function") return { default: A }; + var t = nu(e); + if (t && t.has(A)) return t.get(A); + var r = {}, n = Object.defineProperty && Object.getOwnPropertyDescriptor; + for (var i in A) if (i !== "default" && Object.prototype.hasOwnProperty.call(A, i)) { + var o = n ? Object.getOwnPropertyDescriptor(A, i) : null; + o && (o.get || o.set) ? Object.defineProperty(r, i, o) : r[i] = A[i]; + } + return r.default = A, t && t.set(A, r), r; + } + function* DQ(A, e) { + let t = new RegExp(`[(){}\\[\\]${yQ.escape(e)}]`, "g"), r = 0, n = 0, i = false, o = 0, a = 0, u2 = e.length; + for (let l2 of A.matchAll(t)) { + let I = l2[0] === e[o], E = o === u2 - 1, C = I && E; + l2[0] === "(" && r++, l2[0] === ")" && r--, l2[0] === "[" && r++, l2[0] === "]" && r--, l2[0] === "{" && r++, l2[0] === "}" && r--, I && r === 0 && (a === 0 && (a = l2.index), o++), C && r === 0 && (i = true, yield A.substring(n, a), n = a + u2), o === u2 && (o = 0, a = 0); + } + i ? yield A.substring(n) : yield A; + } + }); + su = K2((Go) => { + "use strict"; + Object.defineProperty(Go, "__esModule", { value: true }); + function SQ(A, e) { + for (var t in e) Object.defineProperty(A, t, { enumerable: true, get: e[t] }); + } + SQ(Go, { parseBoxShadowValue: () => xQ, formatBoxShadowValue: () => RQ }); + var bQ = iu(), vQ = /* @__PURE__ */ new Set(["inset", "inherit", "initial", "revert", "unset"]), kQ = /\ +(?![^(]*\))/g, ou = /^-?(\d+|\.\d+)(.*?)$/g; + function xQ(A) { + return Array.from((0, bQ.splitAtTopLevelOnly)(A, ",")).map((t) => { + let r = t.trim(), n = { raw: r }, i = r.split(kQ), o = /* @__PURE__ */ new Set(); + for (let a of i) ou.lastIndex = 0, !o.has("KEYWORD") && vQ.has(a) ? (n.keyword = a, o.add("KEYWORD")) : ou.test(a) ? o.has("X") ? o.has("Y") ? o.has("BLUR") ? o.has("SPREAD") || (n.spread = a, o.add("SPREAD")) : (n.blur = a, o.add("BLUR")) : (n.y = a, o.add("Y")) : (n.x = a, o.add("X")) : n.color ? (n.unknown || (n.unknown = []), n.unknown.push(a)) : n.color = a; + return n.valid = n.x !== void 0 && n.y !== void 0, n; + }); + } + function RQ(A) { + return A.map((e) => e.valid ? [e.keyword, e.x, e.y, e.blur, e.spread, e.color].filter(Boolean).join(" ") : e.raw).join(", "); + } + }); + fu = K2((Ho) => { + "use strict"; + Object.defineProperty(Ho, "__esModule", { value: true }); + function NQ(A, e) { + for (var t in e) Object.defineProperty(A, t, { enumerable: true, get: e[t] }); + } + NQ(Ho, { normalize: () => at, url: () => uu, number: () => LQ, percentage: () => Iu, length: () => lu, lineWidth: () => HQ, shadow: () => OQ, color: () => TQ, image: () => PQ, gradient: () => cu, position: () => WQ, familyName: () => KQ, genericName: () => qQ, absoluteSize: () => VQ, relativeSize: () => ZQ }); + var MQ = Ro(), FQ = su(), Uo = ["min", "max", "clamp", "calc"], gu = /,(?![^(]*\))/g, xn = /_(?![^(]*\))/g; + function at(A, e = true) { + return A.includes("url(") ? A.split(/(url\(.*?\))/g).filter(Boolean).map((t) => /^url\(.*?\)$/.test(t) ? t : at(t, false)).join("") : (A = A.replace(/([^\\])_+/g, (t, r) => r + " ".repeat(t.length - 1)).replace(/^_/g, " ").replace(/\\_/g, "_"), e && (A = A.trim()), A = A.replace(/(calc|min|max|clamp)\(.+\)/g, (t) => t.replace(/(-?\d*\.?\d(?!\b-.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, "$1 $2 ")), A); + } + function uu(A) { + return A.startsWith("url("); + } + function LQ(A) { + return !isNaN(Number(A)) || Uo.some((e) => new RegExp(`^${e}\\(.+?`).test(A)); + } + function Iu(A) { + return A.split(xn).every((e) => /%$/g.test(e) || Uo.some((t) => new RegExp(`^${t}\\(.+?%`).test(e))); + } + var GQ = ["cm", "mm", "Q", "in", "pc", "pt", "px", "em", "ex", "ch", "rem", "lh", "vw", "vh", "vmin", "vmax"], au = `(?:${GQ.join("|")})`; + function lu(A) { + return A.split(xn).every((e) => e === "0" || new RegExp(`${au}$`).test(e) || Uo.some((t) => new RegExp(`^${t}\\(.+?${au}`).test(e))); + } + var UQ = /* @__PURE__ */ new Set(["thin", "medium", "thick"]); + function HQ(A) { + return UQ.has(A); + } + function OQ(A) { + let e = (0, FQ.parseBoxShadowValue)(at(A)); + for (let t of e) if (!t.valid) return false; + return true; + } + function TQ(A) { + let e = 0; + return A.split(xn).every((r) => (r = at(r), r.startsWith("var(") ? true : (0, MQ.parseColor)(r, { loose: true }) !== null ? (e++, true) : false)) ? e > 0 : false; + } + function PQ(A) { + let e = 0; + return A.split(gu).every((r) => (r = at(r), r.startsWith("var(") ? true : uu(r) || cu(r) || ["element(", "image(", "cross-fade(", "image-set("].some((n) => r.startsWith(n)) ? (e++, true) : false)) ? e > 0 : false; + } + var _Q = /* @__PURE__ */ new Set(["linear-gradient", "radial-gradient", "repeating-linear-gradient", "repeating-radial-gradient", "conic-gradient"]); + function cu(A) { + A = at(A); + for (let e of _Q) if (A.startsWith(`${e}(`)) return true; + return false; + } + var JQ = /* @__PURE__ */ new Set(["center", "top", "right", "bottom", "left"]); + function WQ(A) { + let e = 0; + return A.split(xn).every((r) => (r = at(r), r.startsWith("var(") ? true : JQ.has(r) || lu(r) || Iu(r) ? (e++, true) : false)) ? e > 0 : false; + } + function KQ(A) { + let e = 0; + return A.split(gu).every((r) => (r = at(r), r.startsWith("var(") ? true : r.includes(" ") && !/(['"])([^"']+)\1/g.test(r) || /^\d/g.test(r) ? false : (e++, true))) ? e > 0 : false; + } + var YQ = /* @__PURE__ */ new Set(["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded", "math", "emoji", "fangsong"]); + function qQ(A) { + return YQ.has(A); + } + var XQ = /* @__PURE__ */ new Set(["xx-small", "x-small", "small", "medium", "large", "x-large", "x-large", "xxx-large"]); + function VQ(A) { + return XQ.has(A); + } + var zQ = /* @__PURE__ */ new Set(["larger", "smaller"]); + function ZQ(A) { + return zQ.has(A); + } + }); + mu = K2((Po) => { + "use strict"; + Object.defineProperty(Po, "__esModule", { value: true }); + function jQ(A, e) { + for (var t in e) Object.defineProperty(A, t, { enumerable: true, get: e[t] }); + } + jQ(Po, { updateAllClasses: () => eC, asValue: () => Nr, parseColorFormat: () => Oo, asColor: () => du, asLookupValue: () => hu, coerceValue: () => iC }); + var $Q = To(qg()), AC = To(Xg()), Bu = Mo(), pe = fu(), Eu = To(wi()); + function To(A) { + return A && A.__esModule ? A : { default: A }; + } + function eC(A, e) { + return (0, $Q.default)((n) => { + n.walkClasses((i) => { + let o = e(i.value); + i.value = o, i.raws && i.raws.value && (i.raws.value = (0, AC.default)(i.raws.value)); + }); + }).processSync(A); + } + function Cu(A, e) { + if (!Rr(A)) return; + let t = A.slice(1, -1); + if (e(t)) return (0, pe.normalize)(t); + } + function tC(A, e = {}, t) { + let r = e[A]; + if (r !== void 0) return (0, Eu.default)(r); + if (Rr(A)) { + let n = Cu(A, t); + return n === void 0 ? void 0 : (0, Eu.default)(n); + } + } + function Nr(A, e = {}, { validate: t = () => true } = {}) { + var r; + let n = (r = e.values) === null || r === void 0 ? void 0 : r[A]; + return n !== void 0 ? n : e.supportsNegativeValues && A.startsWith("-") ? tC(A.slice(1), e.values, t) : Cu(A, t); + } + function Rr(A) { + return A.startsWith("[") && A.endsWith("]"); + } + function rC(A) { + let e = A.lastIndexOf("/"); + return e === -1 || e === A.length - 1 ? [A] : [A.slice(0, e), A.slice(e + 1)]; + } + function Oo(A) { + if (typeof A == "string" && A.includes("")) { + let e = A; + return ({ opacityValue: t = 1 }) => e.replace("", t); + } + return A; + } + function du(A, e = {}, { tailwindConfig: t = {} } = {}) { + var r; + if (((r = e.values) === null || r === void 0 ? void 0 : r[A]) !== void 0) { + var n; + return Oo((n = e.values) === null || n === void 0 ? void 0 : n[A]); + } + let [i, o] = rC(A); + if (o !== void 0) { + var a, u2, l2, I; + let E = (I = (a = e.values) === null || a === void 0 ? void 0 : a[i]) !== null && I !== void 0 ? I : Rr(i) ? i.slice(1, -1) : void 0; + return E === void 0 ? void 0 : (E = Oo(E), Rr(o) ? (0, Bu.withAlphaValue)(E, o.slice(1, -1)) : ((u2 = t.theme) === null || u2 === void 0 || (l2 = u2.opacity) === null || l2 === void 0 ? void 0 : l2[o]) === void 0 ? void 0 : (0, Bu.withAlphaValue)(E, t.theme.opacity[o])); + } + return Nr(A, e, { validate: pe.color }); + } + function hu(A, e = {}) { + var t; + return (t = e.values) === null || t === void 0 ? void 0 : t[A]; + } + function Fe(A) { + return (e, t) => Nr(e, t, { validate: A }); + } + var pu = { any: Nr, color: du, url: Fe(pe.url), image: Fe(pe.image), length: Fe(pe.length), percentage: Fe(pe.percentage), position: Fe(pe.position), lookup: hu, "generic-name": Fe(pe.genericName), "family-name": Fe(pe.familyName), number: Fe(pe.number), "line-width": Fe(pe.lineWidth), "absolute-size": Fe(pe.absoluteSize), "relative-size": Fe(pe.relativeSize), shadow: Fe(pe.shadow) }, Qu = Object.keys(pu); + function nC(A, e) { + let t = A.indexOf(e); + return t === -1 ? [void 0, A] : [A.slice(0, t), A.slice(t + 1)]; + } + function iC(A, e, t, r) { + if (Rr(e)) { + let n = e.slice(1, -1), [i, o] = nC(n, ":"); + if (!/^[\w-_]+$/g.test(i)) o = n; + else if (i !== void 0 && !Qu.includes(i)) return []; + if (o.length > 0 && Qu.includes(i)) return [Nr(`[${o}]`, t), i]; + } + for (let n of [].concat(A)) { + let i = pu[n](e, t, { tailwindConfig: r }); + if (i !== void 0) return [i, n]; + } + return []; + } + }); + yu = K2((_o) => { + "use strict"; + Object.defineProperty(_o, "__esModule", { value: true }); + Object.defineProperty(_o, "default", { enumerable: true, get: () => oC }); + function oC(A) { + return typeof A == "function" ? A({}) : A; + } + }); + vu = K2((Wo) => { + "use strict"; + Object.defineProperty(Wo, "__esModule", { value: true }); + Object.defineProperty(Wo, "default", { enumerable: true, get: () => SC }); + var sC = pt(wi()), aC = pt(Ra()), gC = pt(Na()), uC = pt(bi()), IC = pt(Fa()), Su = La(), wu = Ga(), lC = Ha(), cC = pt(Oa()), fC = Ta(), BC = mu(), EC = Mo(), QC = pt(yu()); + function pt(A) { + return A && A.__esModule ? A : { default: A }; + } + function Pt(A) { + return typeof A == "function"; + } + function Mr(A) { + return typeof A == "object" && A !== null; + } + function Fr(A, ...e) { + let t = e.pop(); + for (let r of e) for (let n in r) { + let i = t(A[n], r[n]); + i === void 0 ? Mr(A[n]) && Mr(r[n]) ? A[n] = Fr(A[n], r[n], t) : A[n] = r[n] : A[n] = i; + } + return A; + } + var Jo = { colors: IC.default, negative(A) { + return Object.keys(A).filter((e) => A[e] !== "0").reduce((e, t) => { + let r = (0, sC.default)(A[t]); + return r !== void 0 && (e[`-${t}`] = r), e; + }, {}); + }, breakpoints(A) { + return Object.keys(A).filter((e) => typeof A[e] == "string").reduce((e, t) => ({ ...e, [`screen-${t}`]: A[t] }), {}); + } }; + function CC(A, ...e) { + return Pt(A) ? A(...e) : A; + } + function dC(A) { + return A.reduce((e, { extend: t }) => Fr(e, t, (r, n) => r === void 0 ? [n] : Array.isArray(r) ? [n, ...r] : [n, r]), {}); + } + function hC(A) { + return { ...A.reduce((e, t) => (0, Su.defaults)(e, t), {}), extend: dC(A) }; + } + function Du(A, e) { + if (Array.isArray(A) && Mr(A[0])) return A.concat(e); + if (Array.isArray(e) && Mr(e[0]) && Mr(A)) return [A, ...e]; + if (Array.isArray(e)) return e; + } + function pC({ extend: A, ...e }) { + return Fr(e, A, (t, r) => !Pt(t) && !r.some(Pt) ? Fr({}, t, ...r, Du) : (n, i) => Fr({}, ...[t, ...r].map((o) => CC(o, n, i)), Du)); + } + function* mC(A) { + let e = (0, wu.toPath)(A); + if (e.length === 0 || (yield e, Array.isArray(A))) return; + let t = /^(.*?)\s*\/\s*([^/]+)$/, r = A.match(t); + if (r !== null) { + let [, n, i] = r, o = (0, wu.toPath)(n); + o.alpha = i, yield o; + } + } + function yC(A) { + let e = (t, r) => { + for (let n of mC(t)) { + let i = 0, o = A; + for (; o != null && i < n.length; ) o = o[n[i++]], o = Pt(o) && (n.alpha === void 0 || i <= n.length - 1) ? o(e, Jo) : o; + if (o !== void 0) { + if (n.alpha !== void 0) { + let a = (0, BC.parseColorFormat)(o); + return (0, EC.withAlphaValue)(a, n.alpha, (0, QC.default)(a)); + } + return (0, cC.default)(o) ? (0, fC.cloneDeep)(o) : o; + } + } + return r; + }; + return Object.assign(e, { theme: e, ...Jo }), Object.keys(A).reduce((t, r) => (t[r] = Pt(A[r]) ? A[r](e, Jo) : A[r], t), {}); + } + function bu(A) { + let e = []; + return A.forEach((t) => { + e = [...e, t]; + var r; + let n = (r = t == null ? void 0 : t.plugins) !== null && r !== void 0 ? r : []; + n.length !== 0 && n.forEach((i) => { + i.__isOptionsFunction && (i = i()); + var o; + e = [...e, ...bu([(o = i == null ? void 0 : i.config) !== null && o !== void 0 ? o : {}])]; + }); + }), e; + } + function wC(A) { + return [...A].reduceRight((t, r) => Pt(r) ? r({ corePlugins: t }) : (0, gC.default)(r, t), aC.default); + } + function DC(A) { + return [...A].reduceRight((t, r) => [...t, ...r], []); + } + function SC(A) { + let e = [...bu(A), { prefix: "", important: false, separator: ":", variantOrder: uC.default.variantOrder }]; + var t, r; + return (0, lC.normalizeConfig)((0, Su.defaults)({ theme: yC(pC(hC(e.map((n) => (t = n == null ? void 0 : n.theme) !== null && t !== void 0 ? t : {})))), corePlugins: wC(e.map((n) => n.corePlugins)), plugins: DC(A.map((n) => (r = n == null ? void 0 : n.plugins) !== null && r !== void 0 ? r : [])) }, ...e)); + } + }); + ku = {}; + kt(ku, { default: () => bC }); + xu = Xe(() => { + bC = { yellow: (A) => A }; + }); + Fu = K2((Ko) => { + "use strict"; + Object.defineProperty(Ko, "__esModule", { value: true }); + function vC(A, e) { + for (var t in e) Object.defineProperty(A, t, { enumerable: true, get: e[t] }); + } + vC(Ko, { flagEnabled: () => RC, issueFlagNotices: () => NC, default: () => MC }); + var kC = Mu((xu(), Yr(ku))), xC = Mu((Bn(), Yr(fn))); + function Mu(A) { + return A && A.__esModule ? A : { default: A }; + } + var Ru = { optimizeUniversalDefaults: false }, Lr = { future: ["hoverOnlyWhenSupported", "respectDefaultRingColorOpacity"], experimental: ["optimizeUniversalDefaults", "matchVariant"] }; + function RC(A, e) { + if (Lr.future.includes(e)) { + var t, r, n; + return A.future === "all" || ((n = (r = A == null || (t = A.future) === null || t === void 0 ? void 0 : t[e]) !== null && r !== void 0 ? r : Ru[e]) !== null && n !== void 0 ? n : false); + } + if (Lr.experimental.includes(e)) { + var i, o, a; + return A.experimental === "all" || ((a = (o = A == null || (i = A.experimental) === null || i === void 0 ? void 0 : i[e]) !== null && o !== void 0 ? o : Ru[e]) !== null && a !== void 0 ? a : false); + } + return false; + } + function Nu(A) { + if (A.experimental === "all") return Lr.experimental; + var e; + return Object.keys((e = A == null ? void 0 : A.experimental) !== null && e !== void 0 ? e : {}).filter((t) => Lr.experimental.includes(t) && A.experimental[t]); + } + function NC(A) { + if (process.env.JEST_WORKER_ID === void 0 && Nu(A).length > 0) { + let e = Nu(A).map((t) => kC.default.yellow(t)).join(", "); + xC.default.warn("experimental-flags-enabled", [`You have enabled experimental features: ${e}`, "Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time."]); + } + } + var MC = Lr; + }); + Gu = K2((Yo) => { + "use strict"; + Object.defineProperty(Yo, "__esModule", { value: true }); + Object.defineProperty(Yo, "default", { enumerable: true, get: () => Lu }); + var FC = GC(bi()), LC = Fu(); + function GC(A) { + return A && A.__esModule ? A : { default: A }; + } + function Lu(A) { + var e; + let t = ((e = A == null ? void 0 : A.presets) !== null && e !== void 0 ? e : [FC.default]).slice().reverse().flatMap((i) => Lu(typeof i == "function" ? i() : i)), r = { respectDefaultRingColorOpacity: { theme: { ringColor: { DEFAULT: "#3b82f67f" } } } }, n = Object.keys(r).filter((i) => (0, LC.flagEnabled)(A, i)).map((i) => r[i]); + return [A, ...n, ...t]; + } + }); + Hu = K2((qo) => { + "use strict"; + Object.defineProperty(qo, "__esModule", { value: true }); + Object.defineProperty(qo, "default", { enumerable: true, get: () => OC }); + var UC = Uu(vu()), HC = Uu(Gu()); + function Uu(A) { + return A && A.__esModule ? A : { default: A }; + } + function OC(...A) { + let [, ...e] = (0, HC.default)(A[0]); + return (0, UC.default)([...A, ...e]); + } + }); + Tu = K2((N0, Ou) => { + var Xo = Hu(); + Ou.exports = (Xo.__esModule ? Xo : { default: Xo }).default; + }); + Kt = (A, e) => () => (e || A((e = { exports: {} }).exports, e), e.exports); + dI = Kt((A, e) => { + e.exports = ["em", "ex", "ch", "rem", "vh", "vw", "vmin", "vmax", "px", "mm", "cm", "in", "pt", "pc", "mozmm"]; + }); + hI = Kt((A, e) => { + e.exports = ["deg", "grad", "rad", "turn"]; + }); + pI = Kt((A, e) => { + e.exports = ["dpi", "dpcm", "dppx"]; + }); + mI = Kt((A, e) => { + e.exports = ["Hz", "kHz"]; + }); + yI = Kt((A, e) => { + e.exports = ["s", "ms"]; + }); + wI = dI(); + fs = hI(); + Bs = pI(); + Es = mI(); + Qs = yI(); + Un.prototype.valueOf = function() { + return this.value; + }; + Un.prototype.toString = function() { + return this.value + (this.unit || ""); + }; + SI = [].concat(fs, Es, wI, Bs, Qs); + vI = Object.assign(qr(fs, "angle"), qr(Es, "frequency"), qr(Bs, "resolution"), qr(Qs, "time")); + ms = [32, 160, 4961, 65792, 65793, 4153, 4241, 10].map((A) => String.fromCodePoint(A)); + xt = /* @__PURE__ */ new Map(); + RI = 500; + bs = (A) => A.replaceAll(/([A-Z])/g, (e, t) => `-${t.toLowerCase()}`); + process.env.SATORI_STANDALONE !== "1" && Promise.resolve().then(() => (Ai(), $n)); + KI = "image/avif"; + YI = "image/webp"; + $r = "image/apng"; + An = "image/png"; + en = "image/jpeg"; + tn = "image/gif"; + ei = "image/svg+xml"; + _e = ys(500); + Xt = /* @__PURE__ */ new Map(); + qI = [An, $r, en, tn, ei]; + XI = /]*>/i; + VI = /viewBox=['"]([^'"]+)['"]/; + zI = /width=['"](\d*\.?\d+)['"]/; + ZI = /height=['"](\d*\.?\d+)['"]/; + ti = { accentHeight: "accent-height", alignmentBaseline: "alignment-baseline", arabicForm: "arabic-form", baselineShift: "baseline-shift", capHeight: "cap-height", clipPath: "clip-path", clipRule: "clip-rule", colorInterpolation: "color-interpolation", colorInterpolationFilters: "color-interpolation-filters", colorProfile: "color-profile", colorRendering: "color-rendering", dominantBaseline: "dominant-baseline", enableBackground: "enable-background", fillOpacity: "fill-opacity", fillRule: "fill-rule", floodColor: "flood-color", floodOpacity: "flood-opacity", fontFamily: "font-family", fontSize: "font-size", fontSizeAdjust: "font-size-adjust", fontStretch: "font-stretch", fontStyle: "font-style", fontVariant: "font-variant", fontWeight: "font-weight", glyphName: "glyph-name", glyphOrientationHorizontal: "glyph-orientation-horizontal", glyphOrientationVertical: "glyph-orientation-vertical", horizAdvX: "horiz-adv-x", horizOriginX: "horiz-origin-x", href: "href", imageRendering: "image-rendering", letterSpacing: "letter-spacing", lightingColor: "lighting-color", markerEnd: "marker-end", markerMid: "marker-mid", markerStart: "marker-start", overlinePosition: "overline-position", overlineThickness: "overline-thickness", paintOrder: "paint-order", panose1: "panose-1", pointerEvents: "pointer-events", renderingIntent: "rendering-intent", shapeRendering: "shape-rendering", stopColor: "stop-color", stopOpacity: "stop-opacity", strikethroughPosition: "strikethrough-position", strikethroughThickness: "strikethrough-thickness", strokeDasharray: "stroke-dasharray", strokeDashoffset: "stroke-dashoffset", strokeLinecap: "stroke-linecap", strokeLinejoin: "stroke-linejoin", strokeMiterlimit: "stroke-miterlimit", strokeOpacity: "stroke-opacity", strokeWidth: "stroke-width", textAnchor: "text-anchor", textDecoration: "text-decoration", textRendering: "text-rendering", underlinePosition: "underline-position", underlineThickness: "underline-thickness", unicodeBidi: "unicode-bidi", unicodeRange: "unicode-range", unitsPerEm: "units-per-em", vAlphabetic: "v-alphabetic", vHanging: "v-hanging", vIdeographic: "v-ideographic", vMathematical: "v-mathematical", vectorEffect: "vector-effect", vertAdvY: "vert-adv-y", vertOriginX: "vert-origin-x", vertOriginY: "vert-origin-y", wordSpacing: "word-spacing", writingMode: "writing-mode", xHeight: "x-height", xlinkActuate: "xlink:actuate", xlinkArcrole: "xlink:arcrole", xlinkHref: "xlink:href", xlinkRole: "xlink:role", xlinkShow: "xlink:show", xlinkTitle: "xlink:title", xlinkType: "xlink:type", xmlBase: "xml:base", xmlLang: "xml:lang", xmlSpace: "xml:space", xmlnsXlink: "xmlns:xlink" }; + tl = /[\r\n%#()<>?[\\\]^`{|}"']/g; + xe = "flex"; + Ys = { p: { display: xe, marginTop: "1em", marginBottom: "1em" }, div: { display: xe }, blockquote: { display: xe, marginTop: "1em", marginBottom: "1em", marginLeft: 40, marginRight: 40 }, center: { display: xe, textAlign: "center" }, hr: { display: xe, marginTop: "0.5em", marginBottom: "0.5em", marginLeft: "auto", marginRight: "auto", borderWidth: 1, borderStyle: "solid" }, h1: { display: xe, fontSize: "2em", marginTop: "0.67em", marginBottom: "0.67em", marginLeft: 0, marginRight: 0, fontWeight: "bold" }, h2: { display: xe, fontSize: "1.5em", marginTop: "0.83em", marginBottom: "0.83em", marginLeft: 0, marginRight: 0, fontWeight: "bold" }, h3: { display: xe, fontSize: "1.17em", marginTop: "1em", marginBottom: "1em", marginLeft: 0, marginRight: 0, fontWeight: "bold" }, h4: { display: xe, marginTop: "1.33em", marginBottom: "1.33em", marginLeft: 0, marginRight: 0, fontWeight: "bold" }, h5: { display: xe, fontSize: "0.83em", marginTop: "1.67em", marginBottom: "1.67em", marginLeft: 0, marginRight: 0, fontWeight: "bold" }, h6: { display: xe, fontSize: "0.67em", marginTop: "2.33em", marginBottom: "2.33em", marginLeft: 0, marginRight: 0, fontWeight: "bold" }, u: { textDecoration: "underline" }, strong: { fontWeight: "bold" }, b: { fontWeight: "bold" }, i: { fontStyle: "italic" }, em: { fontStyle: "italic" }, code: { fontFamily: "monospace" }, kbd: { fontFamily: "monospace" }, pre: { display: xe, fontFamily: "monospace", whiteSpace: "pre", marginTop: "1em", marginBottom: "1em" }, mark: { backgroundColor: "yellow", color: "black" }, big: { fontSize: "larger" }, small: { fontSize: "smaller" }, s: { textDecoration: "line-through" } }; + rl = /* @__PURE__ */ new Set(["color", "font", "fontFamily", "fontSize", "fontStyle", "fontWeight", "letterSpacing", "lineHeight", "textAlign", "textIndent", "textTransform", "textShadowOffset", "textShadowColor", "textShadowRadius", "WebkitTextStrokeWidth", "WebkitTextStrokeColor", "textDecorationLine", "textDecorationStyle", "textDecorationColor", "textDecorationSkipInk", "whiteSpace", "transform", "wordBreak", "tabSize", "opacity", "filter", "_viewportWidth", "_viewportHeight", "_inheritedClipPathId", "_inheritedMaskId", "_inheritedBackgroundClipTextPath", "_inheritedBackgroundClipTextHasBackground"]); + ll = /* @__PURE__ */ new Set(["flex", "flexGrow", "flexShrink", "flexBasis", "fontWeight", "lineHeight", "opacity", "scale", "scaleX", "scaleY"]); + cl = /* @__PURE__ */ new Set(["lineHeight"]); + js = /rgb\((\d+)\s+(\d+)\s+(\d+)\s*\/\s*([\.\d]+)\)/; + ea = [1, 0, 0, 1, 0, 0]; + nn = 1.1; + Qt = ui("U+0020"); + Ii = ui("U+0009"); + Ut = ui("U+2026"); + vl = /* @__PURE__ */ new Set([Ii]); + In = (A) => A && A[0] !== 0 && A[1] !== 0; + er = { circle: /circle\((.+)\)/, ellipse: /ellipse\((.+)\)/, path: /path\((.+)\)/, polygon: /polygon\((.+)\)/, inset: /inset\((.+)\)/ }; + Yl = (A) => `satori_mi-${A}`; + Da = String.raw; + wa = Da`\p{Emoji}(?:\p{EMod}|[\u{E0020}-\u{E007E}]+\u{E007F}|\uFE0F?\u20E3?)`; + Sa = () => new RegExp(Da`\p{RI}{2}|(?![#*\d](?!\uFE0F?\u20E3))${wa}(?:\u200D${wa})*`, "gu"); + Xl = new RegExp(Sa(), "u"); + di = { emoji: Xl, symbol: new RegExp("\\p{Symbol}", "u"), math: new RegExp("\\p{Math}", "u") }; + hi = { "ja-JP": new RegExp("\\p{scx=Hira}|\\p{scx=Kana}|\\p{scx=Han}|[\\u3000]|[\\uFF00-\\uFFEF]", "u"), "ko-KR": new RegExp("\\p{scx=Hangul}", "u"), "zh-CN": new RegExp("\\p{scx=Han}", "u"), "zh-TW": new RegExp("\\p{scx=Han}", "u"), "zh-HK": new RegExp("\\p{scx=Han}", "u"), "th-TH": new RegExp("\\p{scx=Thai}", "u"), "bn-IN": new RegExp("\\p{scx=Bengali}", "u"), "ar-AR": new RegExp("\\p{scx=Arabic}", "u"), "ta-IN": new RegExp("\\p{scx=Tamil}", "u"), "ml-IN": new RegExp("\\p{scx=Malayalam}", "u"), "he-IL": new RegExp("\\p{scx=Hebrew}", "u"), "te-IN": new RegExp("\\p{scx=Telugu}", "u"), devanagari: new RegExp("\\p{scx=Devanagari}", "u"), kannada: new RegExp("\\p{scx=Kannada}", "u") }; + cn = Object.keys({ ...hi, ...di }); + xa = "unknown"; + pi = /* @__PURE__ */ new WeakMap(); + ir = class { + constructor(e) { + this.fonts = /* @__PURE__ */ new Map(); + this.addFonts(e); + } + get({ name: e, weight: t, style: r }) { + if (!this.fonts.has(e)) return null; + t === "normal" && (t = 400), t === "bold" && (t = 700), typeof t == "string" && (t = Number.parseInt(t, 10)); + let n = [...this.fonts.get(e)], i = n[0]; + for (let o = 1; o < n.length; o++) { + let [, a, u2] = i, [, l2, I] = n[o]; + jl(t, r, [a, u2], [l2, I]) > 0 && (i = n[o]); + } + return i[0]; + } + addFonts(e) { + for (let t of e) { + let { name: r, data: n, lang: i } = t; + if (i && !ba(i)) throw new Error(`Invalid value for props \`lang\`: "${i}". The value must be one of the following: ${cn.join(", ")}.`); + let o = i ?? xa, a; + if (pi.has(n)) a = pi.get(n); + else { + a = import_opentype.default.parse("buffer" in n ? n.buffer.slice(n.byteOffset, n.byteOffset + n.byteLength) : n, { lowMemory: true }); + let l2 = a.charToGlyphIndex; + a.charToGlyphIndex = (I) => { + let E = l2.call(a, I); + return E === 0 && a._trackBrokenChars && a._trackBrokenChars.push(I), E; + }, pi.set(n, a); + } + this.defaultFont || (this.defaultFont = a); + let u2 = `${r.toLowerCase()}_${o}`; + this.fonts.has(u2) || this.fonts.set(u2, []), this.fonts.get(u2).push([a, t.weight, t.style]); + } + } + getEngine(e = 16, t = "normal", { fontFamily: r = "sans-serif", fontWeight: n = 400, fontStyle: i = "normal" }, o) { + if (!this.fonts.size) throw new Error("No fonts are loaded. At least one font is required to calculate the layout."); + r = (Array.isArray(r) ? r : [r]).map((b) => b.toLowerCase()); + let a = []; + r.forEach((b) => { + let v2 = this.get({ name: b, weight: n, style: i }); + if (v2) { + a.push(v2); + return; + } + let M = this.get({ name: b + "_unknown", weight: n, style: i }); + if (M) { + a.push(M); + return; + } + }); + let u2 = Array.from(this.fonts.keys()), l2 = [], I = [], E = []; + for (let b of u2) if (!r.includes(b)) if (o) { + let v2 = $l(b); + v2 ? v2 === o ? l2.push(this.get({ name: b, weight: n, style: i })) : I.push(this.get({ name: b, weight: n, style: i })) : E.push(this.get({ name: b, weight: n, style: i })); + } else E.push(this.get({ name: b, weight: n, style: i })); + let C = /* @__PURE__ */ new Map(), d2 = (b, v2 = true) => { + let M = [...a, ...E, ...l2, ...v2 ? I : []]; + if (typeof b > "u") return v2 ? M[M.length - 1] : void 0; + let L = b.charCodeAt(0); + if (C.has(L)) return C.get(L); + let O = M.find((J, j) => !!J.charToGlyphIndex(b) || v2 && j === M.length - 1); + return O && C.set(L, O), O; + }, p = (b, v2 = false) => { + var L, O; + return ((v2 ? (O = (L = b.tables) == null ? void 0 : L.os2) == null ? void 0 : O.sTypoAscender : 0) || b.ascender) / b.unitsPerEm * e; + }, y = (b, v2 = false) => { + var L, O; + return ((v2 ? (O = (L = b.tables) == null ? void 0 : L.os2) == null ? void 0 : O.sTypoDescender : 0) || b.descender) / b.unitsPerEm * e; + }, k = (b, v2 = false) => { + var M, L; + if (typeof t == "string" && t === "normal") { + let O = (v2 ? (L = (M = b.tables) == null ? void 0 : M.os2) == null ? void 0 : L.sTypoLineGap : 0) || 0; + return p(b, v2) - y(b, v2) + O / b.unitsPerEm * e; + } else if (typeof t == "number") return e * t; + }, x2 = (b) => d2(b, false); + return { has: (b) => { + if (b === ` +`) return true; + let v2 = x2(b); + return v2 ? (v2._trackBrokenChars = [], v2.stringToGlyphs(b), v2._trackBrokenChars.length ? (v2._trackBrokenChars = void 0, false) : true) : false; + }, baseline: (b, v2 = typeof b > "u" ? a[0] : d2(b)) => { + let M = p(v2), L = y(v2), O = M - L; + return M + (k(v2) - O) / 2; + }, height: (b, v2 = typeof b > "u" ? a[0] : d2(b)) => k(v2), measure: (b, v2) => this.measure(d2, b, v2), getSVG: (b, v2, M) => this.getSVG(d2, b, v2, M) }; + } + patchFontFallbackResolver(e, t) { + let r = []; + e._trackBrokenChars = r; + let n = e.stringToGlyphs; + return e.stringToGlyphs = (i, ...o) => { + let a = n.call(e, i, ...o); + for (let u2 = 0; u2 < a.length; u2++) if (a[u2].unicode === void 0) { + let l2 = r.shift(), I = t(l2); + if (I !== e) { + let E = I.charToGlyph(l2), C = e.unitsPerEm / I.unitsPerEm, d2 = new import_opentype.default.Path(); + d2.unitsPerEm = e.unitsPerEm, d2.commands = E.path.commands.map((y) => { + let k = { ...y }; + for (let x2 in k) typeof k[x2] == "number" && (k[x2] *= C); + return k; + }); + let p = new import_opentype.default.Glyph({ ...E, advanceWidth: E.advanceWidth * C, xMin: E.xMin * C, xMax: E.xMax * C, yMin: E.yMin * C, yMax: E.yMax * C, path: d2 }); + a[u2] = p; + } + } + return a; + }, () => { + e.stringToGlyphs = n, e._trackBrokenChars = void 0; + }; + } + measure(e, t, { fontSize: r, letterSpacing: n = 0 }) { + let i = e(t), o = this.patchFontFallbackResolver(i, e); + try { + return i.getAdvanceWidth(t, r, { letterSpacing: n / r }); + } finally { + o(); + } + } + getSVG(e, t, { fontSize: r, top: n, left: i, letterSpacing: o = 0 }, a) { + let u2 = e(t), l2 = this.patchFontFallbackResolver(u2, e); + try { + if (r === 0) return { path: "", boxes: [] }; + let I = new import_opentype.default.Path(), E = [], C = { letterSpacing: o / r }, d2 = /* @__PURE__ */ new WeakMap(); + return u2.forEachGlyph(t.replace(/\n/g, ""), i, n, r, C, function(p, y, k, x2) { + let F; + if (!d2.has(p)) F = p.getPath(y, k, x2, C), d2.set(p, [y, k, F]); + else { + let [v2, M, L] = d2.get(p); + F = new import_opentype.default.Path(), F.commands = L.commands.map((O) => { + let J = { ...O }; + for (let j in J) typeof J[j] == "number" && ((j === "x" || j === "x1" || j === "x2") && (J[j] += y - v2), (j === "y" || j === "y1" || j === "y2") && (J[j] += k - M)); + return J; + }); + } + let b = a ? Zl(F.commands, a) : []; + b.length && E.push(...b), I.extend(F); + }), { path: I.toPathData(1), boxes: E }; + } finally { + l2(); + } + } + }; + gI = CI(Tu()); + TC = ["ios", "android", "windows", "macos", "web"]; + PC = ["portrait", "landscape"]; + (function(A) { + A.fontSize = "fontSize", A.lineHeight = "lineHeight"; + })(Pu || (Pu = {})); + (function(A) { + A.rem = "rem", A.em = "em", A.px = "px", A.percent = "%", A.vw = "vw", A.vh = "vh", A.none = ""; + })(QA || (QA = {})); + _C = { t: "Top", tr: "TopRight", tl: "TopLeft", b: "Bottom", br: "BottomRight", bl: "BottomLeft", l: "Left", r: "Right", x: "Horizontal", y: "Vertical" }; + me = typeof process > "u" || ((Zo = process == null ? void 0 : process.env) === null || Zo === void 0 ? void 0 : Zo.JEST_WORKER_ID) === void 0 ? JC : WC; + KC = [["aspect-square", R2({ aspectRatio: 1 })], ["aspect-video", R2({ aspectRatio: 16 / 9 })], ["items-center", R2({ alignItems: "center" })], ["items-start", R2({ alignItems: "flex-start" })], ["items-end", R2({ alignItems: "flex-end" })], ["items-baseline", R2({ alignItems: "baseline" })], ["items-stretch", R2({ alignItems: "stretch" })], ["justify-start", R2({ justifyContent: "flex-start" })], ["justify-end", R2({ justifyContent: "flex-end" })], ["justify-center", R2({ justifyContent: "center" })], ["justify-between", R2({ justifyContent: "space-between" })], ["justify-around", R2({ justifyContent: "space-around" })], ["justify-evenly", R2({ justifyContent: "space-evenly" })], ["content-start", R2({ alignContent: "flex-start" })], ["content-end", R2({ alignContent: "flex-end" })], ["content-between", R2({ alignContent: "space-between" })], ["content-around", R2({ alignContent: "space-around" })], ["content-stretch", R2({ alignContent: "stretch" })], ["content-center", R2({ alignContent: "center" })], ["self-auto", R2({ alignSelf: "auto" })], ["self-start", R2({ alignSelf: "flex-start" })], ["self-end", R2({ alignSelf: "flex-end" })], ["self-center", R2({ alignSelf: "center" })], ["self-stretch", R2({ alignSelf: "stretch" })], ["self-baseline", R2({ alignSelf: "baseline" })], ["direction-inherit", R2({ direction: "inherit" })], ["direction-ltr", R2({ direction: "ltr" })], ["direction-rtl", R2({ direction: "rtl" })], ["hidden", R2({ display: "none" })], ["flex", R2({ display: "flex" })], ["flex-row", R2({ flexDirection: "row" })], ["flex-row-reverse", R2({ flexDirection: "row-reverse" })], ["flex-col", R2({ flexDirection: "column" })], ["flex-col-reverse", R2({ flexDirection: "column-reverse" })], ["flex-wrap", R2({ flexWrap: "wrap" })], ["flex-wrap-reverse", R2({ flexWrap: "wrap-reverse" })], ["flex-nowrap", R2({ flexWrap: "nowrap" })], ["flex-auto", R2({ flexGrow: 1, flexShrink: 1, flexBasis: "auto" })], ["flex-initial", R2({ flexGrow: 0, flexShrink: 1, flexBasis: "auto" })], ["flex-none", R2({ flexGrow: 0, flexShrink: 0, flexBasis: "auto" })], ["overflow-hidden", R2({ overflow: "hidden" })], ["overflow-visible", R2({ overflow: "visible" })], ["overflow-scroll", R2({ overflow: "scroll" })], ["absolute", R2({ position: "absolute" })], ["relative", R2({ position: "relative" })], ["italic", R2({ fontStyle: "italic" })], ["not-italic", R2({ fontStyle: "normal" })], ["oldstyle-nums", Gr("oldstyle-nums")], ["small-caps", Gr("small-caps")], ["lining-nums", Gr("lining-nums")], ["tabular-nums", Gr("tabular-nums")], ["proportional-nums", Gr("proportional-nums")], ["font-thin", R2({ fontWeight: "100" })], ["font-100", R2({ fontWeight: "100" })], ["font-extralight", R2({ fontWeight: "200" })], ["font-200", R2({ fontWeight: "200" })], ["font-light", R2({ fontWeight: "300" })], ["font-300", R2({ fontWeight: "300" })], ["font-normal", R2({ fontWeight: "normal" })], ["font-400", R2({ fontWeight: "400" })], ["font-medium", R2({ fontWeight: "500" })], ["font-500", R2({ fontWeight: "500" })], ["font-semibold", R2({ fontWeight: "600" })], ["font-600", R2({ fontWeight: "600" })], ["font-bold", R2({ fontWeight: "bold" })], ["font-700", R2({ fontWeight: "700" })], ["font-extrabold", R2({ fontWeight: "800" })], ["font-800", R2({ fontWeight: "800" })], ["font-black", R2({ fontWeight: "900" })], ["font-900", R2({ fontWeight: "900" })], ["include-font-padding", R2({ includeFontPadding: true })], ["remove-font-padding", R2({ includeFontPadding: false })], ["max-w-none", R2({ maxWidth: "99999%" })], ["text-left", R2({ textAlign: "left" })], ["text-center", R2({ textAlign: "center" })], ["text-right", R2({ textAlign: "right" })], ["text-justify", R2({ textAlign: "justify" })], ["text-auto", R2({ textAlign: "auto" })], ["underline", R2({ textDecorationLine: "underline" })], ["line-through", R2({ textDecorationLine: "line-through" })], ["no-underline", R2({ textDecorationLine: "none" })], ["uppercase", R2({ textTransform: "uppercase" })], ["lowercase", R2({ textTransform: "lowercase" })], ["capitalize", R2({ textTransform: "capitalize" })], ["normal-case", R2({ textTransform: "none" })], ["w-auto", R2({ width: "auto" })], ["h-auto", R2({ height: "auto" })], ["shadow-sm", R2({ shadowOffset: { width: 1, height: 1 }, shadowColor: "#000", shadowRadius: 1, shadowOpacity: 0.025, elevation: 1 })], ["shadow", R2({ shadowOffset: { width: 1, height: 1 }, shadowColor: "#000", shadowRadius: 1, shadowOpacity: 0.075, elevation: 2 })], ["shadow-md", R2({ shadowOffset: { width: 1, height: 1 }, shadowColor: "#000", shadowRadius: 3, shadowOpacity: 0.125, elevation: 3 })], ["shadow-lg", R2({ shadowOffset: { width: 1, height: 1 }, shadowColor: "#000", shadowOpacity: 0.15, shadowRadius: 8, elevation: 8 })], ["shadow-xl", R2({ shadowOffset: { width: 1, height: 1 }, shadowColor: "#000", shadowOpacity: 0.19, shadowRadius: 20, elevation: 12 })], ["shadow-2xl", R2({ shadowOffset: { width: 1, height: 1 }, shadowColor: "#000", shadowOpacity: 0.25, shadowRadius: 30, elevation: 16 })], ["shadow-none", R2({ shadowOffset: { width: 0, height: 0 }, shadowColor: "#000", shadowRadius: 0, shadowOpacity: 0, elevation: 0 })]]; + es = KC; + Ur = class { + constructor(e) { + this.ir = new Map(es), this.styles = /* @__PURE__ */ new Map(), this.prefixes = /* @__PURE__ */ new Map(), this.ir = new Map([...es, ...e ?? []]); + } + getStyle(e) { + return this.styles.get(e); + } + setStyle(e, t) { + this.styles.set(e, t); + } + getIr(e) { + return this.ir.get(e); + } + setIr(e, t) { + this.ir.set(e, t); + } + getPrefixMatch(e) { + return this.prefixes.get(e); + } + setPrefixMatch(e, t) { + this.prefixes.set(e, t); + } + }; + Nn = { bg: { opacity: "__opacity_bg", color: "backgroundColor" }, text: { opacity: "__opacity_text", color: "color" }, border: { opacity: "__opacity_border", color: "borderColor" }, borderTop: { opacity: "__opacity_border", color: "borderTopColor" }, borderBottom: { opacity: "__opacity_border", color: "borderBottomColor" }, borderLeft: { opacity: "__opacity_border", color: "borderLeftColor" }, borderRight: { opacity: "__opacity_border", color: "borderRightColor" }, shadow: { opacity: "__opacity_shadow", color: "shadowColor" }, tint: { opacity: "__opacity_tint", color: "tintColor" } }; + qC = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + XC = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; + Jt = class { + constructor(e, t = {}, r, n, i) { + var o, a, u2, l2, I, E; + this.config = t, this.cache = r, this.position = 0, this.isNull = false, this.isNegative = false, this.context = {}, this.context.device = n; + let C = e.trim().split(":"), d2 = []; + C.length === 1 ? this.string = e : (this.string = (o = C.pop()) !== null && o !== void 0 ? o : "", d2 = C), this.char = this.string[0]; + let p = is((a = this.config.theme) === null || a === void 0 ? void 0 : a.screens); + for (let y of d2) if (p[y]) { + let k = (u2 = p[y]) === null || u2 === void 0 ? void 0 : u2[2]; + k !== void 0 && (this.order = ((l2 = this.order) !== null && l2 !== void 0 ? l2 : 0) + k); + let x2 = (I = n.windowDimensions) === null || I === void 0 ? void 0 : I.width; + if (x2) { + let [F, b] = (E = p[y]) !== null && E !== void 0 ? E : [0, 0]; + (x2 <= F || x2 > b) && (this.isNull = true); + } else this.isNull = true; + } else _u(y) ? this.isNull = y !== i : Ju(y) ? n.windowDimensions ? (n.windowDimensions.width > n.windowDimensions.height ? "landscape" : "portrait") !== y ? this.isNull = true : this.incrementOrder() : this.isNull = true : y === "retina" ? n.pixelDensity === 2 ? this.incrementOrder() : this.isNull = true : y === "dark" ? n.colorScheme !== "dark" ? this.isNull = true : this.incrementOrder() : this.handlePossibleArbitraryBreakpointPrefix(y) || (this.isNull = true); + } + parse() { + if (this.isNull) return { kind: "null" }; + let e = this.cache.getIr(this.rest); + if (e) return e; + this.parseIsNegative(); + let t = this.parseUtility(); + return t ? this.order !== void 0 ? { kind: "ordered", order: this.order, styleIr: t } : t : { kind: "null" }; + } + parseUtility() { + var e, t, r, n, i; + let o = this.config.theme, a = null; + switch (this.char) { + case "m": + case "p": { + let u2 = this.peekSlice(1, 3).match(/^(t|b|r|l|x|y)?-/); + if (u2) { + let l2 = this.char === "m" ? "margin" : "padding"; + this.advance(((t = (e = u2[0]) === null || e === void 0 ? void 0 : e.length) !== null && t !== void 0 ? t : 0) + 1); + let I = $o(u2[1]), E = ns(l2, I, this.isNegative, this.rest, (r = this.config.theme) === null || r === void 0 ? void 0 : r[l2]); + if (E) return E; + } + } + } + if (this.consumePeeked("h-") && (a = ss("height", this.rest, this.context, o == null ? void 0 : o.height), a) || this.consumePeeked("w-") && (a = ss("width", this.rest, this.context, o == null ? void 0 : o.width), a) || this.consumePeeked("min-w-") && (a = Tr("minWidth", this.rest, this.context, o == null ? void 0 : o.minWidth), a) || this.consumePeeked("min-h-") && (a = Tr("minHeight", this.rest, this.context, o == null ? void 0 : o.minHeight), a) || this.consumePeeked("max-w-") && (a = Tr("maxWidth", this.rest, this.context, o == null ? void 0 : o.maxWidth), a) || this.consumePeeked("max-h-") && (a = Tr("maxHeight", this.rest, this.context, o == null ? void 0 : o.maxHeight), a) || this.consumePeeked("leading-") && (a = rs(this.rest, o == null ? void 0 : o.lineHeight), a) || this.consumePeeked("text-") && (a = ts(this.rest, o == null ? void 0 : o.fontSize, this.context), a || (a = yt("text", this.rest, o == null ? void 0 : o.textColor), a) || this.consumePeeked("opacity-") && (a = Hr("text", this.rest), a)) || this.consumePeeked("font-") && (a = os(this.rest, o == null ? void 0 : o.fontFamily), a) || this.consumePeeked("aspect-") && (this.consumePeeked("ratio-") && me("`aspect-ratio-{ratio}` is deprecated, use `aspect-{ratio}` instead"), a = At("aspectRatio", this.rest, { fractions: true }), a) || this.consumePeeked("tint-") && (a = yt("tint", this.rest, o == null ? void 0 : o.colors), a) || this.consumePeeked("bg-") && (a = yt("bg", this.rest, o == null ? void 0 : o.backgroundColor), a || this.consumePeeked("opacity-") && (a = Hr("bg", this.rest), a)) || this.consumePeeked("border") && (a = $u(this.rest, o), a || this.consumePeeked("-opacity-") && (a = Hr("border", this.rest), a)) || this.consumePeeked("rounded") && (a = AI(this.rest, o == null ? void 0 : o.borderRadius), a) || this.consumePeeked("bottom-") && (a = _t("bottom", this.rest, this.isNegative, o == null ? void 0 : o.inset), a) || this.consumePeeked("top-") && (a = _t("top", this.rest, this.isNegative, o == null ? void 0 : o.inset), a) || this.consumePeeked("left-") && (a = _t("left", this.rest, this.isNegative, o == null ? void 0 : o.inset), a) || this.consumePeeked("right-") && (a = _t("right", this.rest, this.isNegative, o == null ? void 0 : o.inset), a) || this.consumePeeked("inset-") && (a = _t("inset", this.rest, this.isNegative, o == null ? void 0 : o.inset), a) || this.consumePeeked("flex-") && (this.consumePeeked("grow") ? a = Or("Grow", this.rest, o == null ? void 0 : o.flexGrow) : this.consumePeeked("shrink") ? a = Or("Shrink", this.rest, o == null ? void 0 : o.flexShrink) : a = tI(this.rest, o == null ? void 0 : o.flex), a) || this.consumePeeked("grow") && (a = Or("Grow", this.rest, o == null ? void 0 : o.flexGrow), a) || this.consumePeeked("shrink") && (a = Or("Shrink", this.rest, o == null ? void 0 : o.flexShrink), a) || this.consumePeeked("shadow-color-opacity-") && (a = Hr("shadow", this.rest), a) || this.consumePeeked("shadow-opacity-") && (a = iI(this.rest), a) || this.consumePeeked("shadow-offset-") && (a = oI(this.rest), a) || this.consumePeeked("shadow-radius-") && (a = Le("shadowRadius", this.rest), a) || this.consumePeeked("shadow-") && (a = yt("shadow", this.rest, o == null ? void 0 : o.colors), a)) return a; + if (this.consumePeeked("elevation-")) { + let u2 = parseInt(this.rest, 10); + if (!Number.isNaN(u2)) return R2({ elevation: u2 }); + } + if (this.consumePeeked("opacity-") && (a = nI(this.rest, o == null ? void 0 : o.opacity), a) || this.consumePeeked("tracking-") && (a = rI(this.rest, this.isNegative, o == null ? void 0 : o.letterSpacing), a)) return a; + if (this.consumePeeked("z-")) { + let u2 = Number((i = (n = o == null ? void 0 : o.zIndex) === null || n === void 0 ? void 0 : n[this.rest]) !== null && i !== void 0 ? i : this.rest); + if (!Number.isNaN(u2)) return R2({ zIndex: u2 }); + } + return me(`\`${this.rest}\` unknown or invalid utility`), null; + } + handlePossibleArbitraryBreakpointPrefix(e) { + var t; + if (e[0] !== "m") return false; + let r = e.match(/^(min|max)-(w|h)-\[([^\]]+)\]$/); + if (!r) return false; + if (!(!((t = this.context.device) === null || t === void 0) && t.windowDimensions)) return this.isNull = true, true; + let n = this.context.device.windowDimensions, [, i = "", o = "", a = ""] = r, u2 = o === "w" ? n.width : n.height, l2 = VA(a, this.context); + if (l2 === null) return this.isNull = true, true; + let [I, E] = l2; + return E !== "px" && (this.isNull = true), (i === "min" ? u2 >= I : u2 <= I) ? this.incrementOrder() : this.isNull = true, true; + } + advance(e = 1) { + this.position += e, this.char = this.string[this.position]; + } + get rest() { + return this.peekSlice(0, this.string.length); + } + peekSlice(e, t) { + return this.string.slice(this.position + e, this.position + t); + } + consumePeeked(e) { + return this.peekSlice(0, e.length) === e ? (this.advance(e.length), true) : false; + } + parseIsNegative() { + this.char === "-" && (this.advance(), this.isNegative = true, this.context.isNegative = true); + } + incrementOrder() { + var e; + this.order = ((e = this.order) !== null && e !== void 0 ? e : 0) + 1; + } + }; + $C = { addComponents: Ke, addBase: Ke, addVariant: Ke, e: Ke, prefix: Ke, theme: Ke, variants: Ke, config: Ke, corePlugins: Ke, matchUtilities: Ke, postcss: null }; + td = { handler: ({ addUtilities: A }) => { + A({ "shadow-sm": { boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)" }, shadow: { boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)" }, "shadow-md": { boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)" }, "shadow-lg": { boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)" }, "shadow-xl": { boxShadow: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)" }, "shadow-2xl": { boxShadow: "0 25px 50px -12px rgb(0 0 0 / 0.25)" }, "shadow-inner": { boxShadow: "inset 0 2px 4px 0 rgb(0 0 0 / 0.05)" }, "shadow-none": { boxShadow: "0 0 #0000" } }); + } }; + Is = /* @__PURE__ */ new WeakMap(); + } +}); + +// render.mjs +import fs2 from "node:fs/promises"; +import path from "node:path"; +import process2 from "node:process"; + +// components/primitives.mjs +function node(type, style, children) { + return { type, props: { style, children } }; +} +function box(style, children = []) { + return node("div", { display: "flex", boxSizing: "border-box", ...style }, children); +} +function TextBlock(value, style = {}) { + return node( + "div", + { + display: "flex", + boxSizing: "border-box", + whiteSpace: "normal", + ...style + }, + value + ); +} +function Title(value, style = {}) { + return TextBlock(value, { + fontSize: 58, + fontWeight: 800, + lineHeight: 1.05, + ...style + }); +} +function Subtitle(value, style = {}) { + return TextBlock(value, { + fontSize: 24, + fontWeight: 500, + lineHeight: 1.25, + ...style + }); +} +function Badge(value, style = {}) { + return TextBlock(value, { + fontSize: 18, + fontWeight: 700, + ...style + }); +} +function Chip(value, style = {}) { + return TextBlock(value, { + minWidth: 92, + height: 40, + padding: "8px 15px", + fontSize: 17, + fontWeight: 600, + ...style + }); +} +function StatCard({ index, label, color, textColor, panelColor, style = {} }) { + return box( + { + width: 250, + minHeight: 126, + flexDirection: "column", + backgroundColor: panelColor, + padding: 22, + ...style + }, + [ + TextBlock(String(index).padStart(2, "0"), { + color, + fontSize: 18, + fontWeight: 800, + marginBottom: 12 + }), + TextBlock(label, { + color: textColor, + fontSize: 21, + fontWeight: 700, + lineHeight: 1.18 + }) + ] + ); +} + +// templates/p0-templates.mjs +var CANVAS = { width: 960, height: 540 }; +var DEFAULT_FONT_FAMILY = "SVGlideDefault"; +function colors(spec) { + const source = spec.theme?.colors || {}; + return { + background: source.background || "#0F172A", + panel: source.panel || "#111827", + primary: source.primary || "#38BDF8", + accent: source.accent || "#A78BFA", + text: source.text || "#F8FAFC", + muted: source.muted || "#CBD5E1" + }; +} +function text(spec, key, fallback = "") { + const value = spec.content?.[key]; + return typeof value === "string" && value.trim() ? value.trim() : fallback; +} +function list(spec, key) { + const value = spec.content?.[key]; + return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim()) : []; +} +function firstList(spec, keys, fallback = []) { + for (const key of keys) { + const values = list(spec, key); + if (values.length) return values; + } + return fallback; +} +function themeSize(spec, key, fallback) { + const value = spec.theme?.typography?.[key]; + return typeof value === "number" ? value : fallback; +} +function pageShell(spec, children) { + const theme = colors(spec); + return box( + { + width: CANVAS.width, + height: CANVAS.height, + position: "relative", + flexDirection: "column", + backgroundColor: theme.background, + color: theme.text, + fontFamily: DEFAULT_FONT_FAMILY, + padding: 56 + }, + children + ); +} +function pageHeader(spec, { titleWidth = 720, titleSize = null, subtitleKey = "subtitle" } = {}) { + const theme = colors(spec); + return box({ flexDirection: "column", marginBottom: 28 }, [ + Badge(text(spec, "eyebrow", "").toUpperCase(), { + color: theme.primary, + fontSize: 16, + fontWeight: 800, + marginBottom: 12 + }), + Title(text(spec, "title", "Untitled"), { + width: titleWidth, + color: theme.text, + fontSize: titleSize || themeSize(spec, "title", 42), + fontWeight: 850, + lineHeight: 1.08, + marginBottom: 14 + }), + Subtitle(text(spec, subtitleKey, ""), { + width: Math.min(titleWidth, 700), + color: theme.muted, + fontSize: themeSize(spec, "subtitle", 21), + lineHeight: 1.22 + }) + ]); +} +function numberedRows(items, theme, { start = 1, max = 6 } = {}) { + return items.slice(0, max).map( + (item, index) => box( + { + width: "100%", + minHeight: 46, + flexDirection: "row", + alignItems: "center", + marginBottom: 12, + backgroundColor: theme.panel, + padding: "11px 14px" + }, + [ + TextBlock(String(index + start).padStart(2, "0"), { + width: 48, + color: theme.primary, + fontSize: 18, + fontWeight: 850 + }), + TextBlock(item, { + flex: 1, + color: theme.text, + fontSize: 20, + fontWeight: 650, + lineHeight: 1.15 + }) + ] + ) + ); +} +function smallCard(label, value, theme, style = {}) { + return box( + { + width: 184, + minHeight: 112, + flexDirection: "column", + backgroundColor: theme.panel, + padding: 18, + ...style + }, + [ + TextBlock(label, { color: theme.muted, fontSize: 15, fontWeight: 700, marginBottom: 14 }), + TextBlock(value, { color: theme.text, fontSize: 25, fontWeight: 850, lineHeight: 1.05 }) + ] + ); +} +function coverHero(spec) { + const theme = colors(spec); + const chips = list(spec, "chips").slice(0, 4); + return box( + { + width: CANVAS.width, + height: CANVAS.height, + position: "relative", + flexDirection: "column", + backgroundColor: theme.background, + color: theme.text, + fontFamily: DEFAULT_FONT_FAMILY, + padding: 72 + }, + [ + box({ + position: "absolute", + left: 724, + top: 36, + width: 192, + height: 192, + borderRadius: 96, + backgroundColor: theme.accent, + opacity: 0.28 + }), + box({ + width: 704, + minHeight: 356, + flexDirection: "column", + backgroundColor: theme.panel, + opacity: 0.96, + padding: 28 + }, [ + Badge(text(spec, "eyebrow", "SVGLIDE ARTBOARD"), { + color: theme.primary, + marginBottom: 18 + }), + Title(text(spec, "title", "Untitled"), { + color: theme.text, + fontSize: 58, + fontWeight: 800, + lineHeight: 1.05, + marginBottom: 20 + }), + Subtitle(text(spec, "subtitle", ""), { + color: theme.muted, + fontSize: 24, + fontWeight: 500, + lineHeight: 1.25 + }) + ]), + box( + { + position: "absolute", + left: 84, + top: 444, + flexDirection: "row", + gap: 14 + }, + chips.map( + (chip) => Chip(chip, { + backgroundColor: theme.primary, + color: theme.text, + opacity: 0.86 + }) + ) + ) + ] + ); +} +function comparisonCards(spec) { + const theme = colors(spec); + const leftPoints = list(spec, "left_points").slice(0, 3); + const rightPoints = list(spec, "right_points").slice(0, 3); + const point = (value, color) => box({ flexDirection: "row", alignItems: "center", marginBottom: 18 }, [ + box({ width: 10, height: 10, borderRadius: 5, backgroundColor: color, marginRight: 14 }), + TextBlock(value, { color: theme.muted, fontSize: 20, fontWeight: 500, lineHeight: 1.2 }) + ]); + return box( + { + width: CANVAS.width, + height: CANVAS.height, + position: "relative", + flexDirection: "column", + backgroundColor: theme.background, + color: theme.text, + fontFamily: DEFAULT_FONT_FAMILY, + padding: "52px 64px" + }, + [ + Title(text(spec, "title", "Comparison"), { color: theme.text, fontSize: 40, lineHeight: 1.1, marginBottom: 44 }), + box({ flexDirection: "row", gap: 52 }, [ + box({ width: 390, height: 250, flexDirection: "column", backgroundColor: theme.panel, padding: 28 }, [ + Title(text(spec, "left_title", "Before"), { color: theme.primary, fontSize: 24, lineHeight: 1.1, marginBottom: 28 }), + ...leftPoints.map((item) => point(item, theme.primary)) + ]), + box({ width: 390, height: 250, flexDirection: "column", backgroundColor: theme.panel, padding: 28 }, [ + Title(text(spec, "right_title", "After"), { color: theme.accent, fontSize: 24, lineHeight: 1.1, marginBottom: 28 }), + ...rightPoints.map((item) => point(item, theme.accent)) + ]) + ]), + TextBlock(text(spec, "conclusion", ""), { + position: "absolute", + left: 64, + top: 414, + width: 832, + height: 66, + padding: "20px 22px", + backgroundColor: theme.primary, + color: theme.text, + opacity: 0.88, + fontSize: 22, + fontWeight: 700 + }) + ] + ); +} +function summaryFinal(spec) { + const theme = colors(spec); + const takeaways = list(spec, "takeaways").slice(0, 3); + return box( + { + width: CANVAS.width, + height: CANVAS.height, + position: "relative", + flexDirection: "column", + backgroundColor: theme.background, + color: theme.text, + fontFamily: DEFAULT_FONT_FAMILY, + padding: "64px 72px" + }, + [ + box({ position: "absolute", left: 704, top: 54, width: 164, height: 164, borderRadius: 82, backgroundColor: theme.accent, opacity: 0.22 }), + box({ position: "absolute", left: 712, top: 286, flexDirection: "row", alignItems: "flex-end", gap: 12 }, [ + box({ width: 18, height: 30, backgroundColor: theme.primary, opacity: 0.72 }), + box({ width: 18, height: 48, backgroundColor: theme.primary, opacity: 0.86 }), + box({ width: 18, height: 66, backgroundColor: theme.accent, opacity: 0.92 }) + ]), + Badge(text(spec, "eyebrow", "SUMMARY"), { color: theme.primary, fontSize: 18, fontWeight: 800, marginBottom: 24 }), + Title(text(spec, "title", "Summary"), { width: 700, color: theme.text, fontSize: 50, fontWeight: 850, lineHeight: 1.08, marginBottom: 24 }), + Subtitle(text(spec, "subtitle", ""), { width: 640, color: theme.muted, fontSize: 23, marginBottom: 34 }), + box( + { flexDirection: "row", gap: 18 }, + takeaways.map( + (item, index) => StatCard({ + index: index + 1, + label: item, + color: theme.primary, + textColor: theme.text, + panelColor: theme.panel + }) + ) + ) + ] + ); +} +function sectionTitle(spec) { + const theme = colors(spec); + return pageShell(spec, [ + box({ position: "absolute", left: 72, top: 116, width: 8, height: 258, backgroundColor: theme.primary }), + box({ position: "absolute", left: 734, top: 74, width: 148, height: 148, backgroundColor: theme.accent, opacity: 0.2 }), + box({ position: "absolute", left: 734, top: 242, width: 148, height: 12, backgroundColor: theme.primary }), + box({ marginLeft: 52, marginTop: 64 }, [pageHeader(spec, { titleWidth: 690, titleSize: 56 })]) + ]); +} +function agendaList(spec) { + const theme = colors(spec); + const items = firstList(spec, ["items", "takeaways"], ["Context", "Evidence", "Decision"]).slice(0, 6); + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 760, titleSize: 42 }), + box({ width: 724, flexDirection: "column" }, numberedRows(items, theme, { max: 6 })), + box({ position: "absolute", right: 56, top: 126, width: 112, height: 310, backgroundColor: theme.primary, opacity: 0.12 }) + ]); +} +function timelineSteps(spec) { + const theme = colors(spec); + const events = firstList(spec, ["events", "steps", "items"], ["Discover", "Design", "Deliver", "Measure"]).slice(0, 5); + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 760, titleSize: 40 }), + box({ position: "absolute", left: 110, top: 330, width: 740, height: 4, backgroundColor: theme.primary, opacity: 0.55 }), + box( + { position: "absolute", left: 96, top: 254, flexDirection: "row", gap: 22 }, + events.map( + (event, index) => box({ width: 130, flexDirection: "column", alignItems: "center" }, [ + TextBlock(String(index + 1).padStart(2, "0"), { + width: 52, + height: 52, + color: theme.text, + backgroundColor: index % 2 ? theme.accent : theme.primary, + fontSize: 20, + fontWeight: 850, + padding: "14px 0", + textAlign: "center", + marginBottom: 18 + }), + TextBlock(event, { color: theme.text, fontSize: 18, fontWeight: 700, textAlign: "center", lineHeight: 1.18 }) + ]) + ) + ) + ]); +} +function processFlow(spec) { + const theme = colors(spec); + const steps = firstList(spec, ["steps", "items"], ["Input", "Normalize", "Render", "Verify"]).slice(0, 5); + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 730, titleSize: 40 }), + box( + { flexDirection: "row", gap: 18, marginTop: 26 }, + steps.map( + (step, index) => box({ width: 154, height: 172, flexDirection: "column", backgroundColor: theme.panel, padding: 18 }, [ + TextBlock(String(index + 1), { color: theme.primary, fontSize: 28, fontWeight: 900, marginBottom: 20 }), + TextBlock(step, { color: theme.text, fontSize: 21, fontWeight: 750, lineHeight: 1.15 }), + box({ width: 48, height: 5, backgroundColor: index % 2 ? theme.accent : theme.primary, marginTop: "auto" }) + ]) + ) + ), + TextBlock(text(spec, "conclusion", ""), { + position: "absolute", + left: 74, + bottom: 50, + width: 812, + minHeight: 48, + color: theme.text, + backgroundColor: theme.primary, + opacity: 0.18, + fontSize: 20, + fontWeight: 750, + padding: 14 + }) + ]); +} +function metricDashboard(spec) { + const theme = colors(spec); + const metrics = firstList(spec, ["metrics", "items"], ["Velocity +32%", "Cost -18%", "Quality 96%", "Reach 4.2x"]).slice(0, 6); + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 710, titleSize: 38 }), + box( + { flexDirection: "row", flexWrap: "wrap", gap: 18, marginTop: 6 }, + metrics.map((metric, index) => smallCard(`METRIC ${index + 1}`, metric, theme)) + ) + ]); +} +function quoteFocus(spec) { + const theme = colors(spec); + return pageShell(spec, [ + TextBlock("\u201C", { position: "absolute", left: 60, top: 36, color: theme.primary, fontSize: 132, fontWeight: 900, opacity: 0.7 }), + TextBlock(text(spec, "quote", text(spec, "title", "A strong point belongs on a quiet page.")), { + width: 720, + marginTop: 116, + marginLeft: 72, + color: theme.text, + fontSize: 42, + fontWeight: 850, + lineHeight: 1.13 + }), + TextBlock(text(spec, "attribution", ""), { + marginLeft: 76, + marginTop: 34, + color: theme.muted, + fontSize: 22, + fontWeight: 700 + }), + box({ position: "absolute", right: 80, bottom: 72, width: 150, height: 10, backgroundColor: theme.accent }) + ]); +} +function imageFeature(spec) { + const theme = colors(spec); + const points = firstList(spec, ["points", "items"], ["Primary visual anchor", "Caption explains evidence", "Text stays out of the image"]).slice(0, 3); + return pageShell(spec, [ + box({ position: "absolute", left: 56, top: 56, width: 452, height: 428, backgroundColor: theme.panel }), + box({ position: "absolute", left: 86, top: 86, width: 392, height: 268, backgroundColor: theme.primary, opacity: 0.18 }), + TextBlock(text(spec, "image_label", "IMAGE"), { position: "absolute", left: 226, top: 204, color: theme.primary, fontSize: 28, fontWeight: 900 }), + TextBlock(text(spec, "caption", ""), { position: "absolute", left: 86, top: 386, width: 388, color: theme.muted, fontSize: 19, fontWeight: 650 }), + box({ position: "absolute", left: 548, top: 72, width: 330 }, [pageHeader(spec, { titleWidth: 330, titleSize: 38 })]), + box({ position: "absolute", left: 552, top: 280, width: 324, flexDirection: "column" }, numberedRows(points, theme, { max: 3 })) + ]); +} +function researchPoster(spec) { + const theme = colors(spec); + const sections = firstList(spec, ["sections", "items"], ["Context", "Method", "Result", "Implication"]).slice(0, 6); + return pageShell(spec, [ + box({ position: "absolute", left: 56, top: 42, width: 588 }, [pageHeader(spec, { titleWidth: 588, titleSize: 34, subtitleKey: "authors" })]), + box({ position: "absolute", right: 70, top: 54, width: 140, height: 96, backgroundColor: theme.primary, opacity: 0.18 }), + box( + { position: "absolute", left: 58, top: 194, flexDirection: "row", gap: 20 }, + [0, 1, 2].map( + (column) => box( + { width: 268, flexDirection: "column", gap: 14 }, + sections.slice(column * 2, column * 2 + 2).map( + (section, index) => box({ height: 120, flexDirection: "column", backgroundColor: theme.panel, padding: 16 }, [ + TextBlock(section, { color: theme.primary, fontSize: 20, fontWeight: 850, marginBottom: 12 }), + TextBlock(column === 1 && index === 0 ? text(spec, "key_visual", "key visual") : "Evidence block", { + color: theme.muted, + fontSize: 17, + fontWeight: 600 + }) + ]) + ) + ) + ) + ) + ]); +} +function dataStory(spec) { + const theme = colors(spec); + const metrics = firstList(spec, ["metrics", "items"], ["North 42", "South 35", "West 28", "East 19"]).slice(0, 4); + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 600, titleSize: 38 }), + box({ position: "absolute", left: 86, top: 260, flexDirection: "row", alignItems: "flex-end", gap: 34 }, metrics.map( + (metric, index) => box({ width: 112, flexDirection: "column", alignItems: "center" }, [ + box({ width: 64, height: 82 + index * 28, backgroundColor: index % 2 ? theme.accent : theme.primary, marginBottom: 18 }), + TextBlock(metric, { color: theme.text, fontSize: 18, fontWeight: 750, textAlign: "center" }) + ]) + )), + TextBlock(text(spec, "callout", ""), { position: "absolute", right: 72, top: 184, width: 260, color: theme.text, backgroundColor: theme.panel, fontSize: 24, fontWeight: 850, lineHeight: 1.14, padding: 22 }) + ]); +} +function riskAlert(spec) { + const theme = colors(spec); + const risks = firstList(spec, ["risks", "items"], ["Scope drift", "Dependency delay", "Insufficient evidence"]).slice(0, 4); + return pageShell(spec, [ + TextBlock(text(spec, "severity", "L2"), { position: "absolute", right: 70, top: 54, color: theme.text, backgroundColor: theme.primary, fontSize: 28, fontWeight: 900, padding: "14px 22px" }), + pageHeader(spec, { titleWidth: 690, titleSize: 40 }), + box({ width: 800, flexDirection: "column", marginTop: 16 }, risks.map( + (risk, index) => box({ height: 58, flexDirection: "row", alignItems: "center", backgroundColor: theme.panel, marginBottom: 14, padding: 16 }, [ + box({ width: 12, height: 34, backgroundColor: index === 0 ? theme.accent : theme.primary, marginRight: 16 }), + TextBlock(risk, { color: theme.text, fontSize: 22, fontWeight: 760 }) + ]) + )), + TextBlock(text(spec, "summary", ""), { color: theme.muted, fontSize: 18, fontWeight: 650, marginTop: 6 }) + ]); +} +function roadmapLanes(spec) { + const theme = colors(spec); + const lanes = firstList(spec, ["lanes", "items"], ["Now", "Next", "Later"]).slice(0, 4); + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 700, titleSize: 38 }), + box({ flexDirection: "column", gap: 16, marginTop: 16 }, lanes.map( + (lane, index) => box({ width: 820, height: 62, flexDirection: "row", alignItems: "center", backgroundColor: theme.panel, padding: "0 18px" }, [ + TextBlock(lane, { width: 132, color: theme.primary, fontSize: 21, fontWeight: 850 }), + box({ flex: 1, height: 12, backgroundColor: index % 2 ? theme.accent : theme.primary, opacity: 0.38 }), + TextBlock(`Q${index + 1}`, { width: 54, color: theme.text, fontSize: 18, fontWeight: 800, textAlign: "right" }) + ]) + )) + ]); +} +function architectureBlueprint(spec) { + const theme = colors(spec); + const nodes = firstList(spec, ["nodes", "items"], ["Planner", "CanvasSpec", "Renderer", "SVGlide"]).slice(0, 6); + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 630, titleSize: 36 }), + box( + { position: "absolute", left: 86, top: 240, flexDirection: "row", flexWrap: "wrap", gap: 24, width: 780 }, + nodes.map( + (item, index) => box({ width: 236, height: 72, backgroundColor: theme.panel, borderWidth: 2, borderColor: index % 2 ? theme.accent : theme.primary, padding: 16 }, [ + TextBlock(item, { color: theme.text, fontSize: 20, fontWeight: 800 }) + ]) + ) + ) + ]); +} +function renderTree(spec) { + if (spec.template_id === "cover-hero") return coverHero(spec); + if (spec.template_id === "comparison-cards") return comparisonCards(spec); + if (spec.template_id === "summary-final") return summaryFinal(spec); + if (spec.template_id === "section-title") return sectionTitle(spec); + if (spec.template_id === "agenda-list") return agendaList(spec); + if (spec.template_id === "timeline-steps") return timelineSteps(spec); + if (spec.template_id === "process-flow") return processFlow(spec); + if (spec.template_id === "metric-dashboard") return metricDashboard(spec); + if (spec.template_id === "quote-focus") return quoteFocus(spec); + if (spec.template_id === "image-feature") return imageFeature(spec); + if (spec.template_id === "research-poster") return researchPoster(spec); + if (spec.template_id === "data-story") return dataStory(spec); + if (spec.template_id === "risk-alert") return riskAlert(spec); + if (spec.template_id === "roadmap-lanes") return roadmapLanes(spec); + if (spec.template_id === "architecture-blueprint") return architectureBlueprint(spec); + throw new Error(`unsupported template_id for Satori adapter: ${spec.template_id}`); +} + +// render.mjs +var SATORI_VERSION = "0.26.0"; +var RESVG_VERSION = "2.6.2"; +var DEFAULT_FONT_FAMILY2 = "SVGlideDefault"; +var DEFAULT_FONT_CANDIDATES = [ + "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "/System/Library/Fonts/Supplemental/Arial.ttf", + "/System/Library/Fonts/Supplemental/Verdana.ttf", + "/System/Library/Fonts/Supplemental/Trebuchet MS.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", + "C:\\Windows\\Fonts\\arial.ttf" +]; +async function pathExists(candidate) { + try { + await fs2.access(candidate); + return true; + } catch { + return false; + } +} +async function resolveFontPath() { + if (process2.env.SVGLIDE_SATORI_FONT_PATH) { + return process2.env.SVGLIDE_SATORI_FONT_PATH; + } + for (const candidate of DEFAULT_FONT_CANDIDATES) { + if (await pathExists(candidate)) { + return candidate; + } + } + throw new Error( + "no usable Satori font found; set SVGLIDE_SATORI_FONT_PATH to a .ttf/.otf font available on this machine" + ); +} +async function loadFont() { + const fontPath = await resolveFontPath(); + const data = await fs2.readFile(fontPath); + return { name: DEFAULT_FONT_FAMILY2, data, weight: 400, style: "normal", path: fontPath }; +} +async function loadSatori() { + try { + return (await Promise.resolve().then(() => (init_dist2(), dist_exports))).default; + } catch (error) { + console.error("satori dependency is not available in this adapter runtime"); + console.error("development fix: run pnpm install --frozen-lockfile in skills/lark-slides/scripts/artboard_renderer"); + console.error("release fix: publish dist/render.mjs built by pnpm run build, not the unbundled source entry"); + console.error(String(error?.message || error)); + process2.exit(3); + } +} +async function loadResvg() { + try { + return (await import("@resvg/resvg-js")).Resvg; + } catch (error) { + console.error("@resvg/resvg-js native dependency is not available in this adapter runtime"); + console.error("fix: run pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile"); + console.error("release fix: install the platform-native @resvg/resvg-js package before running dist/render.mjs --check-runtime"); + console.error(String(error?.message || error)); + process2.exit(4); + } +} +async function checkRuntime() { + await loadSatori(); + const Resvg = await loadResvg(); + const font = await loadFont(); + const probe = ''; + new Resvg(probe).render().asPng(); + console.log(JSON.stringify({ ok: true, renderer: "satori-resvg", satori_version: SATORI_VERSION, resvg_version: RESVG_VERSION, font_path: font.path })); +} +async function main() { + const [, , inputPath, outputPath, pngPath, metadataPath] = process2.argv; + if (inputPath === "--check-runtime") { + await checkRuntime(); + return; + } + if (!inputPath || !outputPath) { + console.error("usage: node render.mjs [output.png] [metadata.json]"); + process2.exit(2); + } + const satori = await loadSatori(); + const Resvg = await loadResvg(); + const spec = JSON.parse(await fs2.readFile(inputPath, "utf8")); + const font = await loadFont(); + const svg = await satori(renderTree(spec), { + width: 960, + height: 540, + embedFont: false, + fonts: [font] + }); + await fs2.mkdir(path.dirname(outputPath), { recursive: true }); + await fs2.writeFile(outputPath, svg); + let pngBytes = null; + if (pngPath) { + pngBytes = new Resvg(svg, { + fitTo: { mode: "width", value: 960 }, + font: { loadSystemFonts: true } + }).render().asPng(); + await fs2.mkdir(path.dirname(pngPath), { recursive: true }); + await fs2.writeFile(pngPath, pngBytes); + } + if (metadataPath) { + await fs2.mkdir(path.dirname(metadataPath), { recursive: true }); + await fs2.writeFile( + metadataPath, + JSON.stringify( + { + node_version: process2.version, + satori_version: SATORI_VERSION, + resvg_version: RESVG_VERSION, + font_path: font.path, + png_bytes: pngBytes ? pngBytes.length : null + }, + null, + 2 + ) + "\n" + ); + } +} +main(); +/*! Bundled license information: + +css-background-parser/index.js: + (*! + * https://github.com/gilmoreorless/css-background-parser + * Copyright © 2015 Gilmore Davidson under the MIT license: http://gilmoreorless.mit-license.org/ + *) + +parse-css-color/dist/index.cjs.js: + (** + * parse-css-color + * @version v0.2.1 + * @link http://github.com/noeldelgado/parse-css-color/ + * @license MIT + *) + +escape-html/index.js: + (*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + *) +*/ diff --git a/skills/lark-slides/scripts/artboard_renderer/package.json b/skills/lark-slides/scripts/artboard_renderer/package.json new file mode 100644 index 00000000..a2ea339b --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/package.json @@ -0,0 +1,21 @@ +{ + "name": "@larksuite/svglide-artboard-renderer", + "version": "0.0.0-p0", + "private": true, + "type": "module", + "scripts": { + "render": "node render.mjs", + "build": "esbuild render.mjs --bundle --platform=node --format=esm --target=node18 --external:@resvg/resvg-js --outfile=dist/render.mjs", + "check": "node render.mjs --check-runtime" + }, + "engines": { + "node": ">=18" + }, + "dependencies": { + "@resvg/resvg-js": "2.6.2", + "satori": "0.26.0" + }, + "devDependencies": { + "esbuild": "0.28.1" + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/pnpm-lock.yaml b/skills/lark-slides/scripts/artboard_renderer/pnpm-lock.yaml new file mode 100644 index 00000000..8578375b --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/pnpm-lock.yaml @@ -0,0 +1,569 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@resvg/resvg-js': + specifier: 2.6.2 + version: 2.6.2 + satori: + specifier: 0.26.0 + version: 0.26.0 + devDependencies: + esbuild: + specifier: 0.28.1 + version: 0.28.1 + +packages: + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@resvg/resvg-js-android-arm-eabi@2.6.2': + resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@resvg/resvg-js-android-arm64@2.6.2': + resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@resvg/resvg-js-darwin-arm64@2.6.2': + resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@resvg/resvg-js-darwin-x64@2.6.2': + resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': + resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': + resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@resvg/resvg-js-linux-arm64-musl@2.6.2': + resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@resvg/resvg-js-linux-x64-gnu@2.6.2': + resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@resvg/resvg-js-linux-x64-musl@2.6.2': + resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': + resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': + resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@resvg/resvg-js-win32-x64-msvc@2.6.2': + resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@resvg/resvg-js@2.6.2': + resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==} + engines: {node: '>= 10'} + + '@shuding/opentype.js@1.4.0-beta.0': + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} + engines: {node: '>= 8.0.0'} + hasBin: true + + base64-js@0.0.8: + resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==} + engines: {node: '>= 0.4'} + + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + css-background-parser@0.1.0: + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} + + css-box-shadow@1.0.0-3: + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-gradient-parser@0.0.17: + resolution: {integrity: sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg==} + engines: {node: '>=16'} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + + emoji-regex-xs@2.0.1: + resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} + engines: {node: '>=10.0.0'} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + fflate@0.7.4: + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + + hex-rgb@4.3.0: + resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} + engines: {node: '>=6'} + + linebreak@1.1.0: + resolution: {integrity: sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + parse-css-color@0.2.1: + resolution: {integrity: sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + satori@0.26.0: + resolution: {integrity: sha512-tkMFrfIs3l2mQ2JEcyW0ADTy3zGggFRFzi6Ef8YozQSFsFKEqaSO1Y8F9wJg4//PJGQauMalHGTUEkPrFwhVPA==} + engines: {node: '>=16'} + + string.prototype.codepointat@0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + + yoga-layout@3.2.1: + resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==} + +snapshots: + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@resvg/resvg-js-android-arm-eabi@2.6.2': + optional: true + + '@resvg/resvg-js-android-arm64@2.6.2': + optional: true + + '@resvg/resvg-js-darwin-arm64@2.6.2': + optional: true + + '@resvg/resvg-js-darwin-x64@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm64-gnu@2.6.2': + optional: true + + '@resvg/resvg-js-linux-arm64-musl@2.6.2': + optional: true + + '@resvg/resvg-js-linux-x64-gnu@2.6.2': + optional: true + + '@resvg/resvg-js-linux-x64-musl@2.6.2': + optional: true + + '@resvg/resvg-js-win32-arm64-msvc@2.6.2': + optional: true + + '@resvg/resvg-js-win32-ia32-msvc@2.6.2': + optional: true + + '@resvg/resvg-js-win32-x64-msvc@2.6.2': + optional: true + + '@resvg/resvg-js@2.6.2': + optionalDependencies: + '@resvg/resvg-js-android-arm-eabi': 2.6.2 + '@resvg/resvg-js-android-arm64': 2.6.2 + '@resvg/resvg-js-darwin-arm64': 2.6.2 + '@resvg/resvg-js-darwin-x64': 2.6.2 + '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2 + '@resvg/resvg-js-linux-arm64-gnu': 2.6.2 + '@resvg/resvg-js-linux-arm64-musl': 2.6.2 + '@resvg/resvg-js-linux-x64-gnu': 2.6.2 + '@resvg/resvg-js-linux-x64-musl': 2.6.2 + '@resvg/resvg-js-win32-arm64-msvc': 2.6.2 + '@resvg/resvg-js-win32-ia32-msvc': 2.6.2 + '@resvg/resvg-js-win32-x64-msvc': 2.6.2 + + '@shuding/opentype.js@1.4.0-beta.0': + dependencies: + fflate: 0.7.4 + string.prototype.codepointat: 0.2.1 + + base64-js@0.0.8: {} + + camelize@1.0.1: {} + + color-name@1.1.4: {} + + css-background-parser@0.1.0: {} + + css-box-shadow@1.0.0-3: {} + + css-color-keywords@1.0.0: {} + + css-gradient-parser@0.0.17: {} + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + + emoji-regex-xs@2.0.1: {} + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + escape-html@1.0.3: {} + + fflate@0.7.4: {} + + hex-rgb@4.3.0: {} + + linebreak@1.1.0: + dependencies: + base64-js: 0.0.8 + unicode-trie: 2.0.0 + + pako@0.2.9: {} + + parse-css-color@0.2.1: + dependencies: + color-name: 1.1.4 + hex-rgb: 4.3.0 + + postcss-value-parser@4.2.0: {} + + satori@0.26.0: + dependencies: + '@shuding/opentype.js': 1.4.0-beta.0 + css-background-parser: 0.1.0 + css-box-shadow: 1.0.0-3 + css-gradient-parser: 0.0.17 + css-to-react-native: 3.2.0 + emoji-regex-xs: 2.0.1 + escape-html: 1.0.3 + linebreak: 1.1.0 + parse-css-color: 0.2.1 + postcss-value-parser: 4.2.0 + yoga-layout: 3.2.1 + + string.prototype.codepointat@0.2.1: {} + + tiny-inflate@1.0.3: {} + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + + yoga-layout@3.2.1: {} diff --git a/skills/lark-slides/scripts/artboard_renderer/render.mjs b/skills/lark-slides/scripts/artboard_renderer/render.mjs new file mode 100644 index 00000000..f78e8e34 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/render.mjs @@ -0,0 +1,132 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import process from 'node:process' +import { renderTree } from './templates/p0-templates.mjs' + +const SATORI_VERSION = '0.26.0' +const RESVG_VERSION = '2.6.2' +const DEFAULT_FONT_FAMILY = 'SVGlideDefault' +const DEFAULT_FONT_CANDIDATES = [ + '/System/Library/Fonts/Supplemental/Arial Unicode.ttf', + '/System/Library/Fonts/Supplemental/Arial.ttf', + '/System/Library/Fonts/Supplemental/Verdana.ttf', + '/System/Library/Fonts/Supplemental/Trebuchet MS.ttf', + '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', + '/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf', + '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', + 'C:\\Windows\\Fonts\\arial.ttf' +] + +async function pathExists(candidate) { + try { + await fs.access(candidate) + return true + } catch { + return false + } +} + +async function resolveFontPath() { + if (process.env.SVGLIDE_SATORI_FONT_PATH) { + return process.env.SVGLIDE_SATORI_FONT_PATH + } + for (const candidate of DEFAULT_FONT_CANDIDATES) { + if (await pathExists(candidate)) { + return candidate + } + } + throw new Error( + 'no usable Satori font found; set SVGLIDE_SATORI_FONT_PATH to a .ttf/.otf font available on this machine' + ) +} + +async function loadFont() { + const fontPath = await resolveFontPath() + const data = await fs.readFile(fontPath) + return { name: DEFAULT_FONT_FAMILY, data, weight: 400, style: 'normal', path: fontPath } +} + +async function loadSatori() { + try { + return (await import('satori')).default + } catch (error) { + console.error('satori dependency is not available in this adapter runtime') + console.error('development fix: run pnpm install --frozen-lockfile in skills/lark-slides/scripts/artboard_renderer') + console.error('release fix: publish dist/render.mjs built by pnpm run build, not the unbundled source entry') + console.error(String(error?.message || error)) + process.exit(3) + } +} + +async function loadResvg() { + try { + return (await import('@resvg/resvg-js')).Resvg + } catch (error) { + console.error('@resvg/resvg-js native dependency is not available in this adapter runtime') + console.error('fix: run pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile') + console.error('release fix: install the platform-native @resvg/resvg-js package before running dist/render.mjs --check-runtime') + console.error(String(error?.message || error)) + process.exit(4) + } +} + +async function checkRuntime() { + await loadSatori() + const Resvg = await loadResvg() + const font = await loadFont() + const probe = '' + new Resvg(probe).render().asPng() + console.log(JSON.stringify({ ok: true, renderer: 'satori-resvg', satori_version: SATORI_VERSION, resvg_version: RESVG_VERSION, font_path: font.path })) +} + +async function main() { + const [, , inputPath, outputPath, pngPath, metadataPath] = process.argv + if (inputPath === '--check-runtime') { + await checkRuntime() + return + } + if (!inputPath || !outputPath) { + console.error('usage: node render.mjs [output.png] [metadata.json]') + process.exit(2) + } + const satori = await loadSatori() + const Resvg = await loadResvg() + const spec = JSON.parse(await fs.readFile(inputPath, 'utf8')) + const font = await loadFont() + const svg = await satori(renderTree(spec), { + width: 960, + height: 540, + embedFont: false, + fonts: [font] + }) + await fs.mkdir(path.dirname(outputPath), { recursive: true }) + await fs.writeFile(outputPath, svg) + let pngBytes = null + if (pngPath) { + pngBytes = new Resvg(svg, { + fitTo: { mode: 'width', value: 960 }, + font: { loadSystemFonts: true } + }).render().asPng() + await fs.mkdir(path.dirname(pngPath), { recursive: true }) + await fs.writeFile(pngPath, pngBytes) + } + if (metadataPath) { + await fs.mkdir(path.dirname(metadataPath), { recursive: true }) + await fs.writeFile( + metadataPath, + JSON.stringify( + { + node_version: process.version, + satori_version: SATORI_VERSION, + resvg_version: RESVG_VERSION, + font_path: font.path, + png_bytes: pngBytes ? pngBytes.length : null + }, + null, + 2 + ) + '\n' + ) + } +} + +main() diff --git a/skills/lark-slides/scripts/artboard_renderer/templates/README.md b/skills/lark-slides/scripts/artboard_renderer/templates/README.md new file mode 100644 index 00000000..ad3f3f3c --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/templates/README.md @@ -0,0 +1,39 @@ +# Artboard Templates + +P0 keeps live SVGlide output in `svglide_artboard_renderer.py`. + +The Node renderer in this folder is the isolated Satori adapter. It stays in a skill-local subpackage so the root Go CLI does not absorb Node dependencies directly. + +Supported P0 templates: + +- `cover_hero` +- `section_title` +- `comparison` + +The final SVGlide SVG is compiled from the Python CanvasSpec template artifact. The Node adapter is still required for the `artboard_satori` preview path because it renders Satori SVG and the resvg PNG used by preview and quality evidence. + +To enable true Satori source rendering: + +1. Install the adapter dependencies from `skills/lark-slides/scripts/artboard_renderer`. +2. Build and publish `dist/render.mjs` with the `lark-slides` skill machine resources. +3. Install the platform-native `@resvg/resvg-js` package for the target host; do not commit or embed `node_modules`. +4. Optionally set `SVGLIDE_SATORI_FONT_PATH` to a readable TTF/OTF font when the host has no usable system font. +5. Run the project with `SVGLIDE_ARTBOARD_USE_NODE_SATORI=1`. + +Build command: + +```bash +pnpm install --frozen-lockfile +pnpm run build +node dist/render.mjs --check-runtime +``` + +Gate 11 package validation command: + +```bash +python3 skills/lark-slides/scripts/svglide_artboard_package_check.py --pretty +``` + +The published runtime must not require a sibling Satori source checkout. It does require the package-managed native `@resvg/resvg-js` runtime dependency; missing Node/resvg dependencies must fail fast before live create with the install command above. + +The final SVG passed downstream is generated by parsing controlled Satori SVG and adding private SVGlide markers. Arbitrary HTML/CSS is still not accepted. diff --git a/skills/lark-slides/scripts/artboard_renderer/templates/p0-templates.mjs b/skills/lark-slides/scripts/artboard_renderer/templates/p0-templates.mjs new file mode 100644 index 00000000..bcb86231 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/templates/p0-templates.mjs @@ -0,0 +1,526 @@ +import { Badge, Chip, StatCard, Subtitle, TextBlock, Title, box } from '../components/primitives.mjs' + +const CANVAS = { width: 960, height: 540 } +const DEFAULT_FONT_FAMILY = 'SVGlideDefault' + +function colors(spec) { + const source = spec.theme?.colors || {} + return { + background: source.background || '#0F172A', + panel: source.panel || '#111827', + primary: source.primary || '#38BDF8', + accent: source.accent || '#A78BFA', + text: source.text || '#F8FAFC', + muted: source.muted || '#CBD5E1' + } +} + +function text(spec, key, fallback = '') { + const value = spec.content?.[key] + return typeof value === 'string' && value.trim() ? value.trim() : fallback +} + +function list(spec, key) { + const value = spec.content?.[key] + return Array.isArray(value) ? value.filter((item) => typeof item === 'string' && item.trim()).map((item) => item.trim()) : [] +} + +function firstList(spec, keys, fallback = []) { + for (const key of keys) { + const values = list(spec, key) + if (values.length) return values + } + return fallback +} + +function themeSize(spec, key, fallback) { + const value = spec.theme?.typography?.[key] + return typeof value === 'number' ? value : fallback +} + +function pageShell(spec, children) { + const theme = colors(spec) + return box( + { + width: CANVAS.width, + height: CANVAS.height, + position: 'relative', + flexDirection: 'column', + backgroundColor: theme.background, + color: theme.text, + fontFamily: DEFAULT_FONT_FAMILY, + padding: 56 + }, + children + ) +} + +function pageHeader(spec, { titleWidth = 720, titleSize = null, subtitleKey = 'subtitle' } = {}) { + const theme = colors(spec) + return box({ flexDirection: 'column', marginBottom: 28 }, [ + Badge(text(spec, 'eyebrow', '').toUpperCase(), { + color: theme.primary, + fontSize: 16, + fontWeight: 800, + marginBottom: 12 + }), + Title(text(spec, 'title', 'Untitled'), { + width: titleWidth, + color: theme.text, + fontSize: titleSize || themeSize(spec, 'title', 42), + fontWeight: 850, + lineHeight: 1.08, + marginBottom: 14 + }), + Subtitle(text(spec, subtitleKey, ''), { + width: Math.min(titleWidth, 700), + color: theme.muted, + fontSize: themeSize(spec, 'subtitle', 21), + lineHeight: 1.22 + }) + ]) +} + +function numberedRows(items, theme, { start = 1, max = 6 } = {}) { + return items.slice(0, max).map((item, index) => + box( + { + width: '100%', + minHeight: 46, + flexDirection: 'row', + alignItems: 'center', + marginBottom: 12, + backgroundColor: theme.panel, + padding: '11px 14px' + }, + [ + TextBlock(String(index + start).padStart(2, '0'), { + width: 48, + color: theme.primary, + fontSize: 18, + fontWeight: 850 + }), + TextBlock(item, { + flex: 1, + color: theme.text, + fontSize: 20, + fontWeight: 650, + lineHeight: 1.15 + }) + ] + ) + ) +} + +function smallCard(label, value, theme, style = {}) { + return box( + { + width: 184, + minHeight: 112, + flexDirection: 'column', + backgroundColor: theme.panel, + padding: 18, + ...style + }, + [ + TextBlock(label, { color: theme.muted, fontSize: 15, fontWeight: 700, marginBottom: 14 }), + TextBlock(value, { color: theme.text, fontSize: 25, fontWeight: 850, lineHeight: 1.05 }) + ] + ) +} + +function coverHero(spec) { + const theme = colors(spec) + const chips = list(spec, 'chips').slice(0, 4) + return box( + { + width: CANVAS.width, + height: CANVAS.height, + position: 'relative', + flexDirection: 'column', + backgroundColor: theme.background, + color: theme.text, + fontFamily: DEFAULT_FONT_FAMILY, + padding: 72 + }, + [ + box({ + position: 'absolute', + left: 724, + top: 36, + width: 192, + height: 192, + borderRadius: 96, + backgroundColor: theme.accent, + opacity: 0.28 + }), + box({ + width: 704, + minHeight: 356, + flexDirection: 'column', + backgroundColor: theme.panel, + opacity: 0.96, + padding: 28 + }, [ + Badge(text(spec, 'eyebrow', 'SVGLIDE ARTBOARD'), { + color: theme.primary, + marginBottom: 18 + }), + Title(text(spec, 'title', 'Untitled'), { + color: theme.text, + fontSize: 58, + fontWeight: 800, + lineHeight: 1.05, + marginBottom: 20 + }), + Subtitle(text(spec, 'subtitle', ''), { + color: theme.muted, + fontSize: 24, + fontWeight: 500, + lineHeight: 1.25 + }) + ]), + box( + { + position: 'absolute', + left: 84, + top: 444, + flexDirection: 'row', + gap: 14 + }, + chips.map((chip) => + Chip(chip, { + backgroundColor: theme.primary, + color: theme.text, + opacity: 0.86 + }) + ) + ) + ] + ) +} + +function comparisonCards(spec) { + const theme = colors(spec) + const leftPoints = list(spec, 'left_points').slice(0, 3) + const rightPoints = list(spec, 'right_points').slice(0, 3) + const point = (value, color) => + box({ flexDirection: 'row', alignItems: 'center', marginBottom: 18 }, [ + box({ width: 10, height: 10, borderRadius: 5, backgroundColor: color, marginRight: 14 }), + TextBlock(value, { color: theme.muted, fontSize: 20, fontWeight: 500, lineHeight: 1.2 }) + ]) + return box( + { + width: CANVAS.width, + height: CANVAS.height, + position: 'relative', + flexDirection: 'column', + backgroundColor: theme.background, + color: theme.text, + fontFamily: DEFAULT_FONT_FAMILY, + padding: '52px 64px' + }, + [ + Title(text(spec, 'title', 'Comparison'), { color: theme.text, fontSize: 40, lineHeight: 1.1, marginBottom: 44 }), + box({ flexDirection: 'row', gap: 52 }, [ + box({ width: 390, height: 250, flexDirection: 'column', backgroundColor: theme.panel, padding: 28 }, [ + Title(text(spec, 'left_title', 'Before'), { color: theme.primary, fontSize: 24, lineHeight: 1.1, marginBottom: 28 }), + ...leftPoints.map((item) => point(item, theme.primary)) + ]), + box({ width: 390, height: 250, flexDirection: 'column', backgroundColor: theme.panel, padding: 28 }, [ + Title(text(spec, 'right_title', 'After'), { color: theme.accent, fontSize: 24, lineHeight: 1.1, marginBottom: 28 }), + ...rightPoints.map((item) => point(item, theme.accent)) + ]) + ]), + TextBlock(text(spec, 'conclusion', ''), { + position: 'absolute', + left: 64, + top: 414, + width: 832, + height: 66, + padding: '20px 22px', + backgroundColor: theme.primary, + color: theme.text, + opacity: 0.88, + fontSize: 22, + fontWeight: 700 + }) + ] + ) +} + +function summaryFinal(spec) { + const theme = colors(spec) + const takeaways = list(spec, 'takeaways').slice(0, 3) + return box( + { + width: CANVAS.width, + height: CANVAS.height, + position: 'relative', + flexDirection: 'column', + backgroundColor: theme.background, + color: theme.text, + fontFamily: DEFAULT_FONT_FAMILY, + padding: '64px 72px' + }, + [ + box({ position: 'absolute', left: 704, top: 54, width: 164, height: 164, borderRadius: 82, backgroundColor: theme.accent, opacity: 0.22 }), + box({ position: 'absolute', left: 712, top: 286, flexDirection: 'row', alignItems: 'flex-end', gap: 12 }, [ + box({ width: 18, height: 30, backgroundColor: theme.primary, opacity: 0.72 }), + box({ width: 18, height: 48, backgroundColor: theme.primary, opacity: 0.86 }), + box({ width: 18, height: 66, backgroundColor: theme.accent, opacity: 0.92 }) + ]), + Badge(text(spec, 'eyebrow', 'SUMMARY'), { color: theme.primary, fontSize: 18, fontWeight: 800, marginBottom: 24 }), + Title(text(spec, 'title', 'Summary'), { width: 700, color: theme.text, fontSize: 50, fontWeight: 850, lineHeight: 1.08, marginBottom: 24 }), + Subtitle(text(spec, 'subtitle', ''), { width: 640, color: theme.muted, fontSize: 23, marginBottom: 34 }), + box( + { flexDirection: 'row', gap: 18 }, + takeaways.map((item, index) => + StatCard({ + index: index + 1, + label: item, + color: theme.primary, + textColor: theme.text, + panelColor: theme.panel + }) + ) + ) + ] + ) +} + +function sectionTitle(spec) { + const theme = colors(spec) + return pageShell(spec, [ + box({ position: 'absolute', left: 72, top: 116, width: 8, height: 258, backgroundColor: theme.primary }), + box({ position: 'absolute', left: 734, top: 74, width: 148, height: 148, backgroundColor: theme.accent, opacity: 0.2 }), + box({ position: 'absolute', left: 734, top: 242, width: 148, height: 12, backgroundColor: theme.primary }), + box({ marginLeft: 52, marginTop: 64 }, [pageHeader(spec, { titleWidth: 690, titleSize: 56 })]) + ]) +} + +function agendaList(spec) { + const theme = colors(spec) + const items = firstList(spec, ['items', 'takeaways'], ['Context', 'Evidence', 'Decision']).slice(0, 6) + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 760, titleSize: 42 }), + box({ width: 724, flexDirection: 'column' }, numberedRows(items, theme, { max: 6 })), + box({ position: 'absolute', right: 56, top: 126, width: 112, height: 310, backgroundColor: theme.primary, opacity: 0.12 }) + ]) +} + +function timelineSteps(spec) { + const theme = colors(spec) + const events = firstList(spec, ['events', 'steps', 'items'], ['Discover', 'Design', 'Deliver', 'Measure']).slice(0, 5) + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 760, titleSize: 40 }), + box({ position: 'absolute', left: 110, top: 330, width: 740, height: 4, backgroundColor: theme.primary, opacity: 0.55 }), + box( + { position: 'absolute', left: 96, top: 254, flexDirection: 'row', gap: 22 }, + events.map((event, index) => + box({ width: 130, flexDirection: 'column', alignItems: 'center' }, [ + TextBlock(String(index + 1).padStart(2, '0'), { + width: 52, + height: 52, + color: theme.text, + backgroundColor: index % 2 ? theme.accent : theme.primary, + fontSize: 20, + fontWeight: 850, + padding: '14px 0', + textAlign: 'center', + marginBottom: 18 + }), + TextBlock(event, { color: theme.text, fontSize: 18, fontWeight: 700, textAlign: 'center', lineHeight: 1.18 }) + ]) + ) + ) + ]) +} + +function processFlow(spec) { + const theme = colors(spec) + const steps = firstList(spec, ['steps', 'items'], ['Input', 'Normalize', 'Render', 'Verify']).slice(0, 5) + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 730, titleSize: 40 }), + box( + { flexDirection: 'row', gap: 18, marginTop: 26 }, + steps.map((step, index) => + box({ width: 154, height: 172, flexDirection: 'column', backgroundColor: theme.panel, padding: 18 }, [ + TextBlock(String(index + 1), { color: theme.primary, fontSize: 28, fontWeight: 900, marginBottom: 20 }), + TextBlock(step, { color: theme.text, fontSize: 21, fontWeight: 750, lineHeight: 1.15 }), + box({ width: 48, height: 5, backgroundColor: index % 2 ? theme.accent : theme.primary, marginTop: 'auto' }) + ]) + ) + ), + TextBlock(text(spec, 'conclusion', ''), { + position: 'absolute', + left: 74, + bottom: 50, + width: 812, + minHeight: 48, + color: theme.text, + backgroundColor: theme.primary, + opacity: 0.18, + fontSize: 20, + fontWeight: 750, + padding: 14 + }) + ]) +} + +function metricDashboard(spec) { + const theme = colors(spec) + const metrics = firstList(spec, ['metrics', 'items'], ['Velocity +32%', 'Cost -18%', 'Quality 96%', 'Reach 4.2x']).slice(0, 6) + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 710, titleSize: 38 }), + box( + { flexDirection: 'row', flexWrap: 'wrap', gap: 18, marginTop: 6 }, + metrics.map((metric, index) => smallCard(`METRIC ${index + 1}`, metric, theme)) + ) + ]) +} + +function quoteFocus(spec) { + const theme = colors(spec) + return pageShell(spec, [ + TextBlock('“', { position: 'absolute', left: 60, top: 36, color: theme.primary, fontSize: 132, fontWeight: 900, opacity: 0.7 }), + TextBlock(text(spec, 'quote', text(spec, 'title', 'A strong point belongs on a quiet page.')), { + width: 720, + marginTop: 116, + marginLeft: 72, + color: theme.text, + fontSize: 42, + fontWeight: 850, + lineHeight: 1.13 + }), + TextBlock(text(spec, 'attribution', ''), { + marginLeft: 76, + marginTop: 34, + color: theme.muted, + fontSize: 22, + fontWeight: 700 + }), + box({ position: 'absolute', right: 80, bottom: 72, width: 150, height: 10, backgroundColor: theme.accent }) + ]) +} + +function imageFeature(spec) { + const theme = colors(spec) + const points = firstList(spec, ['points', 'items'], ['Primary visual anchor', 'Caption explains evidence', 'Text stays out of the image']).slice(0, 3) + return pageShell(spec, [ + box({ position: 'absolute', left: 56, top: 56, width: 452, height: 428, backgroundColor: theme.panel }), + box({ position: 'absolute', left: 86, top: 86, width: 392, height: 268, backgroundColor: theme.primary, opacity: 0.18 }), + TextBlock(text(spec, 'image_label', 'IMAGE'), { position: 'absolute', left: 226, top: 204, color: theme.primary, fontSize: 28, fontWeight: 900 }), + TextBlock(text(spec, 'caption', ''), { position: 'absolute', left: 86, top: 386, width: 388, color: theme.muted, fontSize: 19, fontWeight: 650 }), + box({ position: 'absolute', left: 548, top: 72, width: 330 }, [pageHeader(spec, { titleWidth: 330, titleSize: 38 })]), + box({ position: 'absolute', left: 552, top: 280, width: 324, flexDirection: 'column' }, numberedRows(points, theme, { max: 3 })) + ]) +} + +function researchPoster(spec) { + const theme = colors(spec) + const sections = firstList(spec, ['sections', 'items'], ['Context', 'Method', 'Result', 'Implication']).slice(0, 6) + return pageShell(spec, [ + box({ position: 'absolute', left: 56, top: 42, width: 588 }, [pageHeader(spec, { titleWidth: 588, titleSize: 34, subtitleKey: 'authors' })]), + box({ position: 'absolute', right: 70, top: 54, width: 140, height: 96, backgroundColor: theme.primary, opacity: 0.18 }), + box( + { position: 'absolute', left: 58, top: 194, flexDirection: 'row', gap: 20 }, + [0, 1, 2].map((column) => + box( + { width: 268, flexDirection: 'column', gap: 14 }, + sections.slice(column * 2, column * 2 + 2).map((section, index) => + box({ height: 120, flexDirection: 'column', backgroundColor: theme.panel, padding: 16 }, [ + TextBlock(section, { color: theme.primary, fontSize: 20, fontWeight: 850, marginBottom: 12 }), + TextBlock(column === 1 && index === 0 ? text(spec, 'key_visual', 'key visual') : 'Evidence block', { + color: theme.muted, + fontSize: 17, + fontWeight: 600 + }) + ]) + ) + ) + ) + ) + ]) +} + +function dataStory(spec) { + const theme = colors(spec) + const metrics = firstList(spec, ['metrics', 'items'], ['North 42', 'South 35', 'West 28', 'East 19']).slice(0, 4) + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 600, titleSize: 38 }), + box({ position: 'absolute', left: 86, top: 260, flexDirection: 'row', alignItems: 'flex-end', gap: 34 }, metrics.map((metric, index) => + box({ width: 112, flexDirection: 'column', alignItems: 'center' }, [ + box({ width: 64, height: 82 + index * 28, backgroundColor: index % 2 ? theme.accent : theme.primary, marginBottom: 18 }), + TextBlock(metric, { color: theme.text, fontSize: 18, fontWeight: 750, textAlign: 'center' }) + ]) + )), + TextBlock(text(spec, 'callout', ''), { position: 'absolute', right: 72, top: 184, width: 260, color: theme.text, backgroundColor: theme.panel, fontSize: 24, fontWeight: 850, lineHeight: 1.14, padding: 22 }) + ]) +} + +function riskAlert(spec) { + const theme = colors(spec) + const risks = firstList(spec, ['risks', 'items'], ['Scope drift', 'Dependency delay', 'Insufficient evidence']).slice(0, 4) + return pageShell(spec, [ + TextBlock(text(spec, 'severity', 'L2'), { position: 'absolute', right: 70, top: 54, color: theme.text, backgroundColor: theme.primary, fontSize: 28, fontWeight: 900, padding: '14px 22px' }), + pageHeader(spec, { titleWidth: 690, titleSize: 40 }), + box({ width: 800, flexDirection: 'column', marginTop: 16 }, risks.map((risk, index) => + box({ height: 58, flexDirection: 'row', alignItems: 'center', backgroundColor: theme.panel, marginBottom: 14, padding: 16 }, [ + box({ width: 12, height: 34, backgroundColor: index === 0 ? theme.accent : theme.primary, marginRight: 16 }), + TextBlock(risk, { color: theme.text, fontSize: 22, fontWeight: 760 }) + ]) + )), + TextBlock(text(spec, 'summary', ''), { color: theme.muted, fontSize: 18, fontWeight: 650, marginTop: 6 }) + ]) +} + +function roadmapLanes(spec) { + const theme = colors(spec) + const lanes = firstList(spec, ['lanes', 'items'], ['Now', 'Next', 'Later']).slice(0, 4) + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 700, titleSize: 38 }), + box({ flexDirection: 'column', gap: 16, marginTop: 16 }, lanes.map((lane, index) => + box({ width: 820, height: 62, flexDirection: 'row', alignItems: 'center', backgroundColor: theme.panel, padding: '0 18px' }, [ + TextBlock(lane, { width: 132, color: theme.primary, fontSize: 21, fontWeight: 850 }), + box({ flex: 1, height: 12, backgroundColor: index % 2 ? theme.accent : theme.primary, opacity: 0.38 }), + TextBlock(`Q${index + 1}`, { width: 54, color: theme.text, fontSize: 18, fontWeight: 800, textAlign: 'right' }) + ]) + )) + ]) +} + +function architectureBlueprint(spec) { + const theme = colors(spec) + const nodes = firstList(spec, ['nodes', 'items'], ['Planner', 'CanvasSpec', 'Renderer', 'SVGlide']).slice(0, 6) + return pageShell(spec, [ + pageHeader(spec, { titleWidth: 630, titleSize: 36 }), + box( + { position: 'absolute', left: 86, top: 240, flexDirection: 'row', flexWrap: 'wrap', gap: 24, width: 780 }, + nodes.map((item, index) => + box({ width: 236, height: 72, backgroundColor: theme.panel, borderWidth: 2, borderColor: index % 2 ? theme.accent : theme.primary, padding: 16 }, [ + TextBlock(item, { color: theme.text, fontSize: 20, fontWeight: 800 }) + ]) + ) + ) + ]) +} + +export function renderTree(spec) { + if (spec.template_id === 'cover-hero') return coverHero(spec) + if (spec.template_id === 'comparison-cards') return comparisonCards(spec) + if (spec.template_id === 'summary-final') return summaryFinal(spec) + if (spec.template_id === 'section-title') return sectionTitle(spec) + if (spec.template_id === 'agenda-list') return agendaList(spec) + if (spec.template_id === 'timeline-steps') return timelineSteps(spec) + if (spec.template_id === 'process-flow') return processFlow(spec) + if (spec.template_id === 'metric-dashboard') return metricDashboard(spec) + if (spec.template_id === 'quote-focus') return quoteFocus(spec) + if (spec.template_id === 'image-feature') return imageFeature(spec) + if (spec.template_id === 'research-poster') return researchPoster(spec) + if (spec.template_id === 'data-story') return dataStory(spec) + if (spec.template_id === 'risk-alert') return riskAlert(spec) + if (spec.template_id === 'roadmap-lanes') return roadmapLanes(spec) + if (spec.template_id === 'architecture-blueprint') return architectureBlueprint(spec) + throw new Error(`unsupported template_id for Satori adapter: ${spec.template_id}`) +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/blueprint-technical.json b/skills/lark-slides/scripts/artboard_renderer/themes/blueprint-technical.json new file mode 100644 index 00000000..1ff6ddbc --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/blueprint-technical.json @@ -0,0 +1,22 @@ +{ + "theme_id": "blueprint-technical", + "colors": { + "background": "#071827", + "panel": "#0E2A3F", + "primary": "#5EEAD4", + "accent": "#F97316", + "text": "#EAF6FF", + "muted": "#93B4C8" + }, + "typography": { + "title": 54, + "subtitle": 23, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/cobalt-grid.json b/skills/lark-slides/scripts/artboard_renderer/themes/cobalt-grid.json new file mode 100644 index 00000000..29c3a85e --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/cobalt-grid.json @@ -0,0 +1,22 @@ +{ + "theme_id": "cobalt-grid", + "colors": { + "background": "#081C4A", + "panel": "#102C6B", + "primary": "#60A5FA", + "accent": "#FDE047", + "text": "#EEF6FF", + "muted": "#B7C8E8" + }, + "typography": { + "title": 56, + "subtitle": 24, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json b/skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json new file mode 100644 index 00000000..bc953b1f --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json @@ -0,0 +1,22 @@ +{ + "theme_id": "dark-clarity", + "colors": { + "background": "#0F172A", + "panel": "#111827", + "primary": "#38BDF8", + "accent": "#A78BFA", + "text": "#F8FAFC", + "muted": "#CBD5E1" + }, + "typography": { + "title": 58, + "subtitle": 24, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/editorial-tritone.json b/skills/lark-slides/scripts/artboard_renderer/themes/editorial-tritone.json new file mode 100644 index 00000000..38b54cd3 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/editorial-tritone.json @@ -0,0 +1,22 @@ +{ + "theme_id": "editorial-tritone", + "colors": { + "background": "#F6E7DF", + "panel": "#FFF7F0", + "primary": "#9F1239", + "accent": "#D97706", + "text": "#351A24", + "muted": "#7C5B62" + }, + "typography": { + "title": 52, + "subtitle": 23, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/finance-dark.json b/skills/lark-slides/scripts/artboard_renderer/themes/finance-dark.json new file mode 100644 index 00000000..b76c2338 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/finance-dark.json @@ -0,0 +1,22 @@ +{ + "theme_id": "finance-dark", + "colors": { + "background": "#07110E", + "panel": "#10201A", + "primary": "#22C55E", + "accent": "#F59E0B", + "text": "#ECFDF5", + "muted": "#A7C4B7" + }, + "typography": { + "title": 54, + "subtitle": 23, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/forest-signal.json b/skills/lark-slides/scripts/artboard_renderer/themes/forest-signal.json new file mode 100644 index 00000000..739b9089 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/forest-signal.json @@ -0,0 +1,22 @@ +{ + "theme_id": "forest-signal", + "colors": { + "background": "#0B1F1A", + "panel": "#123329", + "primary": "#34D399", + "accent": "#FBBF24", + "text": "#F7FCEB", + "muted": "#B7D3C6" + }, + "typography": { + "title": 56, + "subtitle": 24, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/glass-neon.json b/skills/lark-slides/scripts/artboard_renderer/themes/glass-neon.json new file mode 100644 index 00000000..888c7587 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/glass-neon.json @@ -0,0 +1,22 @@ +{ + "theme_id": "glass-neon", + "colors": { + "background": "#090B1A", + "panel": "#14172E", + "primary": "#22D3EE", + "accent": "#C084FC", + "text": "#F8FAFC", + "muted": "#BAC3D9" + }, + "typography": { + "title": 56, + "subtitle": 24, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/paper-research.json b/skills/lark-slides/scripts/artboard_renderer/themes/paper-research.json new file mode 100644 index 00000000..1dc9e9c4 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/paper-research.json @@ -0,0 +1,22 @@ +{ + "theme_id": "paper-research", + "colors": { + "background": "#F7F3E8", + "panel": "#FFFDF6", + "primary": "#1E3A8A", + "accent": "#B45309", + "text": "#1F2937", + "muted": "#6B7280" + }, + "typography": { + "title": 52, + "subtitle": 22, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/registry.json b/skills/lark-slides/scripts/artboard_renderer/themes/registry.json new file mode 100644 index 00000000..367950e3 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/registry.json @@ -0,0 +1,55 @@ +{ + "schema_version": "svglide-theme-registry/v1", + "themes": [ + { + "id": "dark-clarity", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json" + }, + { + "id": "forest-signal", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/forest-signal.json" + }, + { + "id": "warm-editorial", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/warm-editorial.json" + }, + { + "id": "blueprint-technical", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/blueprint-technical.json" + }, + { + "id": "editorial-tritone", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/editorial-tritone.json" + }, + { + "id": "cobalt-grid", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/cobalt-grid.json" + }, + { + "id": "finance-dark", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/finance-dark.json" + }, + { + "id": "swiss-red", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/swiss-red.json" + }, + { + "id": "glass-neon", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/glass-neon.json" + }, + { + "id": "paper-research", + "status": "active", + "path": "skills/lark-slides/scripts/artboard_renderer/themes/paper-research.json" + } + ] +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/swiss-red.json b/skills/lark-slides/scripts/artboard_renderer/themes/swiss-red.json new file mode 100644 index 00000000..8bde2011 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/swiss-red.json @@ -0,0 +1,22 @@ +{ + "theme_id": "swiss-red", + "colors": { + "background": "#F8F8F4", + "panel": "#FFFFFF", + "primary": "#E11D48", + "accent": "#111111", + "text": "#111111", + "muted": "#666666" + }, + "typography": { + "title": 52, + "subtitle": 22, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/artboard_renderer/themes/warm-editorial.json b/skills/lark-slides/scripts/artboard_renderer/themes/warm-editorial.json new file mode 100644 index 00000000..43b7c1f1 --- /dev/null +++ b/skills/lark-slides/scripts/artboard_renderer/themes/warm-editorial.json @@ -0,0 +1,22 @@ +{ + "theme_id": "warm-editorial", + "colors": { + "background": "#27130F", + "panel": "#3A211B", + "primary": "#F97316", + "accent": "#22D3EE", + "text": "#FFF7ED", + "muted": "#F4C9A8" + }, + "typography": { + "title": 54, + "subtitle": 23, + "body": 20, + "label": 18 + }, + "spacing": { + "page": 64, + "card": 28, + "gap": 20 + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/deck-plan.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/deck-plan.json new file mode 100644 index 00000000..1ec7d0d6 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/deck-plan.json @@ -0,0 +1,47 @@ +{ + "schema_version": "svglide-deck-plan/v1", + "topic": "冰岛火山研究", + "audience": "地学研究汇报听众", + "objective": "用三页说明研究背景、证据对比和下一步观测重点。", + "target_slide_count": 3, + "narrative_arc": ["建立研究对象", "比较观测证据", "收束行动重点"], + "theme_direction": { + "preferred_theme_ids": ["blueprint-technical", "paper-research", "warm-editorial"], + "visual_identity": "冷色科研蓝图结合火山暖色强调。", + "tone": "克制、研究型、可审查" + }, + "constraints": { + "generation_mode": "artboard_satori", + "source_policy": "只使用用户给定主题,不编造具体数值。", + "forbidden_outputs": ["free_html", "free_css", "free_svg", "markdown_fence", "base64_image_data"] + }, + "slides": [ + { + "page": 1, + "title": "冰岛火山研究", + "role": "cover", + "key_message": "研究从地貌、活动迹象和观测路线展开。", + "content_goal": "建立研究对象和语境。", + "visual_goal": "使用封面模板给出冷色科研气质。", + "allowed_template_ids": ["cover-hero", "section-title"] + }, + { + "page": 2, + "title": "观测证据对比", + "role": "evidence", + "key_message": "不同观测来源共同约束火山活动判断。", + "content_goal": "把地表和地下信号分开比较。", + "visual_goal": "使用左右对比保持信息可扫读。", + "allowed_template_ids": ["comparison-cards"] + }, + { + "page": 3, + "title": "下一步观测", + "role": "closing", + "key_message": "后续重点是连续监测和风险沟通。", + "content_goal": "收束为三个行动方向。", + "visual_goal": "使用总结页形成清晰结尾。", + "allowed_template_ids": ["summary-final"] + } + ] +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/repair-plan.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/repair-plan.json new file mode 100644 index 00000000..82e7d7c2 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/repair-plan.json @@ -0,0 +1,13 @@ +{ + "schema_version": "svglide-repair-plan/v1", + "target_plan_path": "02-plan/slide_plan.json", + "change_reason": "示例修复标题预算风险,只修改单个 CanvasSpec 字段。", + "patches": [ + { + "op": "replace", + "path": "/slides/1/canvas_spec/content/conclusion", + "value": "多源观测共同降低单一证据偏差。", + "reason": "收短结论文案以满足 comparison-cards text_budget。" + } + ] +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/slide-plan.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/slide-plan.json new file mode 100644 index 00000000..d3846217 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/slide-plan.json @@ -0,0 +1,37 @@ +{ + "schema_version": "svglide-slide-plan/v1", + "deck_plan_ref": {"path": "02-plan/deck-plan.json"}, + "generation_mode": "artboard_satori", + "slides": [ + { + "page": 1, + "title": "冰岛火山研究", + "key_message": "研究从地貌、活动迹象和观测路线展开。", + "template_id": "cover-hero", + "theme_id": "blueprint-technical", + "content_requirements": {"eyebrow": "ICELAND VOLCANO", "subtitle": "地貌证据、活动迹象与连续观测。", "chips": ["地貌", "监测", "风险"]}, + "visual_role": "research cover", + "source_policy": "topic-provided; no invented metrics" + }, + { + "page": 2, + "title": "观测证据对比", + "key_message": "不同观测来源共同约束火山活动判断。", + "template_id": "comparison-cards", + "theme_id": "paper-research", + "content_requirements": {"left_title": "地表信号", "right_title": "地下信号", "left_points": ["熔岩地貌", "热异常", "气体羽流"], "right_points": ["地震活动", "形变趋势", "岩浆补给"]}, + "visual_role": "evidence comparison", + "source_policy": "topic-derived categories; no numeric claims" + }, + { + "page": 3, + "title": "下一步观测", + "key_message": "后续重点是连续监测和风险沟通。", + "template_id": "summary-final", + "theme_id": "warm-editorial", + "content_requirements": {"eyebrow": "NEXT", "subtitle": "把研究结果转成可执行的观测闭环。", "takeaways": ["连续监测", "现场复核", "风险沟通"]}, + "visual_role": "closing summary", + "source_policy": "topic-derived actions" + } + ] +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/slide_plan.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/slide_plan.json new file mode 100644 index 00000000..d240f193 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/02-plan/slide_plan.json @@ -0,0 +1,131 @@ +{ + "schema_version": "svglide-canvas-plan/v1", + "route": "svglide-svg", + "generation_mode": "artboard_satori", + "page_count": 3, + "target_slide_count": 3, + "plan_path": "02-plan/slide_plan.json", + "style_preset": "cobalt_bloom", + "style_selection_reason": "冷色研究基调适合冰岛火山主题,暖色 accent 对应火山活动。", + "style_system": { + "palette": {"background": "#071827", "text": "#EAF6FF", "accent": "#F97316"}, + "typography": "Satori-compatible static text hierarchy", + "background_strategy": "研究页使用深色蓝图背景,结尾页允许暖色强调。", + "motif": "科研蓝图线索和火山暖色标记" + }, + "loaded_rule_set": [ + "skills/lark-slides/references/svglide-planning-layer.md", + "skills/lark-slides/references/svglide-canvas-spec.schema.json", + "skills/lark-slides/references/svglide-template-registry.json" + ], + "quality_gates": {"no_text_overflow": true, "no_debug_guides": true, "no_xml_like_pages": true}, + "art_direction": { + "cover_treatment": "深色研究封面,用简洁标题和 chips 建立主题。", + "section_divider_treatment": "不设独立章节页,使用第二页证据对比作为节奏转换。", + "closing_treatment": "暖色总结页收束为三个行动方向。", + "deck_motif": "冰岛火山研究蓝图", + "svg_native_moments": ["封面 chips", "左右证据卡片", "总结 stat cards"] + }, + "business_claims": [ + {"claim": "不展示具体数值,避免编造研究数据。", "source_type": "derived", "derivation": "derived from the no-invented-metrics source policy"} + ], + "slides": [ + { + "page": 1, + "title": "冰岛火山研究", + "key_message": "研究从地貌、活动迹象和观测路线展开。", + "renderer_id": "artboard_satori.cover-hero", + "layout_family": "cover", + "visual_recipe": "hero_typography", + "visual_intent": "建立研究主题和冷色科研气质。", + "visual_focal_point": "大标题和研究标签。", + "visual_signature": "blueprint cover panel", + "svg_effects": ["typography"], + "required_primitives": ["typography", "geometric_shape"], + "svg_primitives": ["typography", "rect", "circle"], + "xml_like_risk": "普通标题页会丢失科研蓝图气质。", + "content_density_contract": "cover title plus 3 chips", + "risk_flags": [], + "source_policy": "topic-provided; no invented metrics", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "cover-hero", + "theme_id": "blueprint-technical", + "theme": {"colors": {"background": "#071827", "panel": "#0E2A3F", "primary": "#5EEAD4", "accent": "#F97316", "text": "#EAF6FF", "muted": "#93B4C8"}}, + "content": {"eyebrow": "ICELAND VOLCANO", "title": "冰岛火山研究", "subtitle": "地貌证据、活动迹象与连续观测。", "chips": ["地貌", "监测", "风险"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 84, "y": 142, "width": 628, "height": 142}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + }, + { + "page": 2, + "title": "观测证据对比", + "key_message": "不同观测来源共同约束火山活动判断。", + "renderer_id": "artboard_satori.comparison-cards", + "layout_family": "comparison", + "visual_recipe": "two_column_comparison", + "visual_intent": "区分地表和地下观测证据。", + "visual_focal_point": "左右证据卡片。", + "visual_signature": "research evidence cards", + "svg_effects": ["typography"], + "required_primitives": ["typography", "geometric_shape"], + "svg_primitives": ["typography", "rect", "circle"], + "xml_like_risk": "普通项目符号会弱化证据对照。", + "content_density_contract": "two cards with 3 points each", + "risk_flags": [], + "source_policy": "topic-derived categories; no numeric claims", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "comparison-cards", + "theme_id": "paper-research", + "theme": {"colors": {"background": "#F7F3E8", "panel": "#FFFDF6", "primary": "#1E3A8A", "accent": "#B45309", "text": "#1F2937", "muted": "#6B7280"}}, + "content": { + "title": "观测证据对比", + "left_title": "地表信号", + "right_title": "地下信号", + "left_points": ["熔岩地貌", "热异常", "气体羽流"], + "right_points": ["地震活动", "形变趋势", "岩浆补给"], + "conclusion": "多源观测共同降低单一证据偏差。" + }, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 64, "y": 52, "width": 760, "height": 64}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + }, + { + "page": 3, + "title": "下一步观测", + "key_message": "后续重点是连续监测和风险沟通。", + "renderer_id": "artboard_satori.summary-final", + "layout_family": "closing", + "visual_recipe": "summary_cards", + "visual_intent": "把研究结果收束成行动闭环。", + "visual_focal_point": "三个行动卡片。", + "visual_signature": "warm volcanic closing", + "svg_effects": ["typography"], + "required_primitives": ["typography", "geometric_shape"], + "svg_primitives": ["typography", "rect", "circle"], + "xml_like_risk": "普通总结页会缺少行动优先级。", + "content_density_contract": "3 takeaway cards", + "risk_flags": [], + "source_policy": "topic-derived actions", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "summary-final", + "theme_id": "warm-editorial", + "theme": {"colors": {"background": "#27130F", "panel": "#3A211B", "primary": "#F97316", "accent": "#22D3EE", "text": "#FFF7ED", "muted": "#F4C9A8"}}, + "content": {"eyebrow": "NEXT", "title": "下一步观测", "subtitle": "把研究结果转成可执行的观测闭环。", "takeaways": ["连续监测", "现场复核", "风险沟通"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 72, "y": 110, "width": 700, "height": 110}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + } + ] +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.canvas-spec.json new file mode 100644 index 00000000..d1c12df8 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.canvas-spec.json @@ -0,0 +1,72 @@ +{ + "canvas": { + "height": 540, + "viewBox": "0 0 960 540", + "width": 960 + }, + "content": { + "chips": [ + "地貌", + "监测", + "风险" + ], + "eyebrow": "ICELAND VOLCANO", + "subtitle": "地貌证据、活动迹象与连续观测。", + "title": "冰岛火山研究" + }, + "quality_constraints": { + "max_title_lines": 2, + "min_font_size": 18, + "safe_area": { + "height": 460, + "width": 864, + "x": 48, + "y": 40 + } + }, + "safe_area": { + "height": 460, + "width": 864, + "x": 48, + "y": 40 + }, + "semantic_elements": [ + { + "bbox": { + "height": 142, + "width": 628, + "x": 84, + "y": 142 + }, + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title" + } + ], + "template_id": "cover-hero", + "theme": { + "colors": { + "accent": "#F97316", + "background": "#071827", + "muted": "#93B4C8", + "panel": "#0E2A3F", + "primary": "#5EEAD4", + "text": "#EAF6FF" + }, + "spacing": { + "card": 28, + "gap": 20, + "page": 64 + }, + "theme_id": "blueprint-technical", + "typography": { + "body": 20, + "label": 18, + "subtitle": 23, + "title": 54 + } + }, + "theme_id": "blueprint-technical", + "version": "svglide-canvas-spec/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.canvas-template.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.canvas-template.svg new file mode 100644 index 00000000..ec848215 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.canvas-template.svg @@ -0,0 +1,15 @@ + + + + + +ICELAND VOLCANO +冰岛火山研究 +地貌证据、活动迹象与连续观测。 + +地貌 + +监测 + +风险 + diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.node-layout-map.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.node-layout-map.json new file mode 100644 index 00000000..86e46686 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.node-layout-map.json @@ -0,0 +1,89 @@ +{ + "drift": { + "max_px": 0, + "status": "not_measured_in_p0" + }, + "nodes": [ + { + "height": 540, + "id": "background", + "kind": "rect", + "width": 960, + "x": 0, + "y": 0 + }, + { + "height": 192, + "id": "accent-orbit", + "kind": "circle", + "width": 192, + "x": 724, + "y": 36 + }, + { + "height": 356, + "id": "panel", + "kind": "rect", + "width": 704, + "x": 56, + "y": 64 + }, + { + "height": 32, + "id": "eyebrow", + "kind": "text", + "text": "ICELAND VOLCANO", + "width": 420, + "x": 84, + "y": 98 + }, + { + "height": 142, + "id": "title", + "kind": "text", + "text": "冰岛火山研究", + "width": 628, + "x": 84, + "y": 142 + }, + { + "height": 74, + "id": "subtitle", + "kind": "text", + "text": "地貌证据、活动迹象与连续观测。", + "width": 610, + "x": 88, + "y": 302 + }, + { + "height": 40, + "id": "chip-1", + "kind": "text", + "text": "地貌", + "width": 92, + "x": 84, + "y": 444 + }, + { + "height": 40, + "id": "chip-2", + "kind": "text", + "text": "监测", + "width": 92, + "x": 190, + "y": 444 + }, + { + "height": 40, + "id": "chip-3", + "kind": "text", + "text": "风险", + "width": 92, + "x": 296, + "y": 444 + } + ], + "page": 1, + "source": "template-layout-map", + "version": "svglide-node-layout-map/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.png b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.png new file mode 100644 index 00000000..09af6aca Binary files /dev/null and b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.png differ diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.receipt.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.receipt.json new file mode 100644 index 00000000..5316f693 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.receipt.json @@ -0,0 +1,104 @@ +{ + "canvas_spec_path": "02-plan/slide_plan.json#/slides/0/canvas_spec", + "canvas_spec_sha256": "c217ffcfb547080e01de408d01933ed81f2ed1f9dc06de845e8a0dad7a19af02", + "canvas_template_svg": "04-svg/artboard/page-001.canvas-template.svg", + "canvas_template_svg_sha256": "a21538ac26cd5ce67d306ec05f45e7c6ee98b4ae5a954c6bb92414994df2abc2", + "compiler": { + "compiler_input": "CanvasSpecTemplateSVG", + "fail_fast": [ + "clipPath", + "defs", + "filter", + "foreignObject", + "image", + "linearGradient", + "pattern", + "radialGradient", + "use" + ], + "native_mapped": [ + "rect", + "circle", + "circle", + "rect", + "text->foreignObject", + "text->foreignObject", + "text->foreignObject", + "rect", + "text->foreignObject", + "rect", + "text->foreignObject", + "rect", + "text->foreignObject" + ], + "satori_svg_usage": "preview_only", + "semantic_source": "CanvasSpec" + }, + "compiler_input": "04-svg/artboard/page-001.canvas-template.svg", + "compiler_input_sha256": "a21538ac26cd5ce67d306ec05f45e7c6ee98b4ae5a954c6bb92414994df2abc2", + "created_at": "2026-06-21T13:59:15+08:00", + "font_hashes": [ + { + "path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "sha256": "876af2cd4854644e7f3e7feb2f688997fdb3343c6df6693611209c9dfb47ccec" + } + ], + "node_layout_map": "04-svg/artboard/page-001.node-layout-map.json", + "node_layout_map_sha256": "895b6d98ab57c2e5633b92513c4b06047e26411c4bf75bf8f248be5358752c86", + "node_version": "v20.18.1", + "page": 1, + "png": "04-svg/artboard/page-001.png", + "png_sha256": "83d25aa4149a7ea9131f459dd3446bdf5aacfebea5d3d83e57638a5ad97f7e11", + "render_metadata": "04-svg/artboard/page-001.render-metadata.json", + "render_metadata_sha256": "050b8900bfe64f3e7e08007a4f695096976000215233e0a5bfe9743ee4f7acf4", + "renderer": { + "actual_satori_package": true, + "adapter": "skills/lark-slides/scripts/artboard_renderer/dist/render.mjs", + "engine": "satori-node", + "name": "satori-resvg-p0" + }, + "resvg_version": "2.6.2", + "satori_preview": { + "element_counts": { + "g": 8, + "mask": 10, + "path": 1, + "rect": 15, + "svg": 1, + "text": 30 + }, + "fail_fast": [ + "clipPath", + "defs", + "filter", + "foreignObject", + "image", + "linearGradient", + "pattern", + "radialGradient", + "use" + ], + "issues": [], + "status": "passed", + "strict": false + }, + "satori_svg": "04-svg/artboard/raw/page-001.satori.svg", + "satori_svg_sha256": "700e2b829a7a00354739512182916d4566877165748e9e1fa8b3aeeafcb21540", + "satori_version": "0.26.0", + "semantic_map": "04-svg/artboard/page-001.semantic-map.json", + "semantic_map_sha256": "0ce8309c016ff337d9dd72c322e928f2541a66b2acaa097c321979265ca06df7", + "stage": "generate_svg", + "status": "passed", + "svglide_svg": "04-svg/page-001.svg", + "svglide_svg_sha256": "0bec84addb5a7b9319b71a573e0bd9de7890dcbe6985b61e3456e54950850fb3", + "template_id": "cover-hero", + "template_registry": "skills/lark-slides/references/svglide-template-registry.json", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_files": [ + "skills/lark-slides/scripts/artboard_renderer/themes/blueprint-technical.json" + ], + "theme_id": "blueprint-technical", + "theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json", + "theme_registry_sha256": "7ae629f17d38922702489ad3a23da26d465c3e6f5c284b409d4ab10a53ef6ed0", + "version": "svglide-artboard-receipt/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.render-metadata.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.render-metadata.json new file mode 100644 index 00000000..ed3ac670 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.render-metadata.json @@ -0,0 +1,7 @@ +{ + "node_version": "v20.18.1", + "satori_version": "0.26.0", + "resvg_version": "2.6.2", + "font_path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "png_bytes": 26106 +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.semantic-map.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.semantic-map.json new file mode 100644 index 00000000..4252e87b --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-001.semantic-map.json @@ -0,0 +1,132 @@ +{ + "content_keys": [ + "chips", + "eyebrow", + "subtitle", + "title" + ], + "elements": [ + { + "bbox": { + "height": 540.0, + "width": 960.0, + "x": 0.0, + "y": 0.0 + }, + "element_id": "background", + "kind": "rect", + "role": "background", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 192.0, + "width": 192.0, + "x": 724.0, + "y": 36.0 + }, + "element_id": "accent-orbit", + "kind": "circle", + "role": "decorative", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 356.0, + "width": 704.0, + "x": 56.0, + "y": 64.0 + }, + "element_id": "panel", + "kind": "rect", + "role": "container", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 32.0, + "width": 420.0, + "x": 84.0, + "y": 98.0 + }, + "element_id": "eyebrow", + "kind": "text", + "role": "eyebrow", + "source_ref": "canvas_spec.content.eyebrow", + "text": "ICELAND VOLCANO" + }, + { + "bbox": { + "height": 142.0, + "width": 628.0, + "x": 84.0, + "y": 142.0 + }, + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title", + "text": "冰岛火山研究" + }, + { + "bbox": { + "height": 74.0, + "width": 610.0, + "x": 88.0, + "y": 302.0 + }, + "element_id": "subtitle", + "kind": "text", + "role": "subtitle", + "source_ref": "canvas_spec.content.subtitle", + "text": "地貌证据、活动迹象与连续观测。" + }, + { + "bbox": { + "height": 40.0, + "width": 92.0, + "x": 84.0, + "y": 444.0 + }, + "element_id": "chip-1", + "kind": "text", + "role": "badge", + "source_ref": "canvas_spec.content.chips[]", + "text": "地貌" + }, + { + "bbox": { + "height": 40.0, + "width": 92.0, + "x": 190.0, + "y": 444.0 + }, + "element_id": "chip-2", + "kind": "text", + "role": "badge", + "source_ref": "canvas_spec.content.chips[]", + "text": "监测" + }, + { + "bbox": { + "height": 40.0, + "width": 92.0, + "x": 296.0, + "y": 444.0 + }, + "element_id": "chip-3", + "kind": "text", + "role": "badge", + "source_ref": "canvas_spec.content.chips[]", + "text": "风险" + } + ], + "page": 1, + "semantic_source": "CanvasSpec", + "template_id": "cover-hero", + "theme_id": "blueprint-technical", + "version": "svglide-semantic-map/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.canvas-spec.json new file mode 100644 index 00000000..ae957704 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.canvas-spec.json @@ -0,0 +1,78 @@ +{ + "canvas": { + "height": 540, + "viewBox": "0 0 960 540", + "width": 960 + }, + "content": { + "conclusion": "多源观测共同降低单一证据偏差。", + "left_points": [ + "熔岩地貌", + "热异常", + "气体羽流" + ], + "left_title": "地表信号", + "right_points": [ + "地震活动", + "形变趋势", + "岩浆补给" + ], + "right_title": "地下信号", + "title": "观测证据对比" + }, + "quality_constraints": { + "max_title_lines": 2, + "min_font_size": 18, + "safe_area": { + "height": 460, + "width": 864, + "x": 48, + "y": 40 + } + }, + "safe_area": { + "height": 460, + "width": 864, + "x": 48, + "y": 40 + }, + "semantic_elements": [ + { + "bbox": { + "height": 64, + "width": 760, + "x": 64, + "y": 52 + }, + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title" + } + ], + "template_id": "comparison-cards", + "theme": { + "colors": { + "accent": "#B45309", + "background": "#F7F3E8", + "muted": "#6B7280", + "panel": "#FFFDF6", + "primary": "#1E3A8A", + "text": "#1F2937" + }, + "spacing": { + "card": 28, + "gap": 20, + "page": 64 + }, + "theme_id": "paper-research", + "typography": { + "body": 20, + "label": 18, + "subtitle": 22, + "title": 52 + } + }, + "theme_id": "paper-research", + "version": "svglide-canvas-spec/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.canvas-template.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.canvas-template.svg new file mode 100644 index 00000000..4427f32f --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.canvas-template.svg @@ -0,0 +1,22 @@ + + +观测证据对比 + + +地表信号 +地下信号 + +熔岩地貌 + +热异常 + +气体羽流 + +地震活动 + +形变趋势 + +岩浆补给 + +多源观测共同降低单一证据偏差。 + diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.node-layout-map.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.node-layout-map.json new file mode 100644 index 00000000..ef71b993 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.node-layout-map.json @@ -0,0 +1,125 @@ +{ + "drift": { + "max_px": 0, + "status": "not_measured_in_p0" + }, + "nodes": [ + { + "height": 540, + "id": "background", + "kind": "rect", + "width": 960, + "x": 0, + "y": 0 + }, + { + "height": 64, + "id": "title", + "kind": "text", + "text": "观测证据对比", + "width": 760, + "x": 64, + "y": 52 + }, + { + "height": 250, + "id": "left-card", + "kind": "rect", + "width": 390, + "x": 64, + "y": 140 + }, + { + "height": 250, + "id": "right-card", + "kind": "rect", + "width": 390, + "x": 506, + "y": 140 + }, + { + "height": 34, + "id": "left-title", + "kind": "text", + "text": "地表信号", + "width": 320, + "x": 92, + "y": 168 + }, + { + "height": 36, + "id": "left-point-1", + "kind": "text", + "text": "熔岩地貌", + "width": 296, + "x": 116, + "y": 222 + }, + { + "height": 36, + "id": "left-point-2", + "kind": "text", + "text": "热异常", + "width": 296, + "x": 116, + "y": 270 + }, + { + "height": 36, + "id": "left-point-3", + "kind": "text", + "text": "气体羽流", + "width": 296, + "x": 116, + "y": 318 + }, + { + "height": 34, + "id": "right-title", + "kind": "text", + "text": "地下信号", + "width": 320, + "x": 534, + "y": 168 + }, + { + "height": 36, + "id": "right-point-1", + "kind": "text", + "text": "地震活动", + "width": 296, + "x": 558, + "y": 222 + }, + { + "height": 36, + "id": "right-point-2", + "kind": "text", + "text": "形变趋势", + "width": 296, + "x": 558, + "y": 270 + }, + { + "height": 36, + "id": "right-point-3", + "kind": "text", + "text": "岩浆补给", + "width": 296, + "x": 558, + "y": 318 + }, + { + "height": 42, + "id": "conclusion", + "kind": "text", + "text": "多源观测共同降低单一证据偏差。", + "width": 788, + "x": 86, + "y": 426 + } + ], + "page": 2, + "source": "template-layout-map", + "version": "svglide-node-layout-map/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.png b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.png new file mode 100644 index 00000000..bf308d2d Binary files /dev/null and b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.png differ diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.receipt.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.receipt.json new file mode 100644 index 00000000..e9400c9d --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.receipt.json @@ -0,0 +1,111 @@ +{ + "canvas_spec_path": "02-plan/slide_plan.json#/slides/1/canvas_spec", + "canvas_spec_sha256": "71c31d9e39a828f37a38c95912be4ec79343a4101072afede90384d5454d2270", + "canvas_template_svg": "04-svg/artboard/page-002.canvas-template.svg", + "canvas_template_svg_sha256": "acb7c3b4563b96ff052ca745c2ee43de958c2025cee815cc49b92611b3fbd82b", + "compiler": { + "compiler_input": "CanvasSpecTemplateSVG", + "fail_fast": [ + "clipPath", + "defs", + "filter", + "foreignObject", + "image", + "linearGradient", + "pattern", + "radialGradient", + "use" + ], + "native_mapped": [ + "rect", + "text->foreignObject", + "rect", + "rect", + "text->foreignObject", + "text->foreignObject", + "circle", + "text->foreignObject", + "circle", + "text->foreignObject", + "circle", + "text->foreignObject", + "circle", + "text->foreignObject", + "circle", + "text->foreignObject", + "circle", + "text->foreignObject", + "rect", + "text->foreignObject" + ], + "satori_svg_usage": "preview_only", + "semantic_source": "CanvasSpec" + }, + "compiler_input": "04-svg/artboard/page-002.canvas-template.svg", + "compiler_input_sha256": "acb7c3b4563b96ff052ca745c2ee43de958c2025cee815cc49b92611b3fbd82b", + "created_at": "2026-06-21T13:59:15+08:00", + "font_hashes": [ + { + "path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "sha256": "876af2cd4854644e7f3e7feb2f688997fdb3343c6df6693611209c9dfb47ccec" + } + ], + "node_layout_map": "04-svg/artboard/page-002.node-layout-map.json", + "node_layout_map_sha256": "1ad67ee48e5a22981c1432d858587ff346ac6069b4376a52990f16bce3e16c5e", + "node_version": "v20.18.1", + "page": 2, + "png": "04-svg/artboard/page-002.png", + "png_sha256": "ba705a55dc18aaef73144e01197fd75dba6fe37d27eb36897df95683f13161a7", + "render_metadata": "04-svg/artboard/page-002.render-metadata.json", + "render_metadata_sha256": "cc88b7d0489199a4aee08a29316b247fdd6cccb1df4fdec5f4e7be6dcbbb5a34", + "renderer": { + "actual_satori_package": true, + "adapter": "skills/lark-slides/scripts/artboard_renderer/dist/render.mjs", + "engine": "satori-node", + "name": "satori-resvg-p0" + }, + "resvg_version": "2.6.2", + "satori_preview": { + "element_counts": { + "g": 1, + "mask": 26, + "path": 6, + "rect": 30, + "svg": 1, + "text": 52 + }, + "fail_fast": [ + "clipPath", + "defs", + "filter", + "foreignObject", + "image", + "linearGradient", + "pattern", + "radialGradient", + "use" + ], + "issues": [], + "status": "passed", + "strict": false + }, + "satori_svg": "04-svg/artboard/raw/page-002.satori.svg", + "satori_svg_sha256": "5979d2dd93d09936d216d871929aacb5d06eecab43d34a2e0d8070991d1ed88a", + "satori_version": "0.26.0", + "semantic_map": "04-svg/artboard/page-002.semantic-map.json", + "semantic_map_sha256": "61fb537e82510e1fcada54f9f043b4746c1aeb10553391e4778cd6b76aeca520", + "stage": "generate_svg", + "status": "passed", + "svglide_svg": "04-svg/page-002.svg", + "svglide_svg_sha256": "3c539aa631b9240c89e3d36411a2f74f0007eba48f02aaa9bda1fff98b87481d", + "template_id": "comparison-cards", + "template_registry": "skills/lark-slides/references/svglide-template-registry.json", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_files": [ + "skills/lark-slides/scripts/artboard_renderer/themes/paper-research.json" + ], + "theme_id": "paper-research", + "theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json", + "theme_registry_sha256": "85be63912ba33fd278b2bff4471ac7a1e6761d67292dc1a1a374b4fd7bd5edfd", + "version": "svglide-artboard-receipt/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.render-metadata.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.render-metadata.json new file mode 100644 index 00000000..37a9d893 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.render-metadata.json @@ -0,0 +1,7 @@ +{ + "node_version": "v20.18.1", + "satori_version": "0.26.0", + "resvg_version": "2.6.2", + "font_path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "png_bytes": 30539 +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.semantic-map.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.semantic-map.json new file mode 100644 index 00000000..0dc3d453 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-002.semantic-map.json @@ -0,0 +1,186 @@ +{ + "content_keys": [ + "conclusion", + "left_points", + "left_title", + "right_points", + "right_title", + "title" + ], + "elements": [ + { + "bbox": { + "height": 540.0, + "width": 960.0, + "x": 0.0, + "y": 0.0 + }, + "element_id": "background", + "kind": "rect", + "role": "background", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 64.0, + "width": 760.0, + "x": 64.0, + "y": 52.0 + }, + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title", + "text": "观测证据对比" + }, + { + "bbox": { + "height": 250.0, + "width": 390.0, + "x": 64.0, + "y": 140.0 + }, + "element_id": "left-card", + "kind": "rect", + "role": "container", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 250.0, + "width": 390.0, + "x": 506.0, + "y": 140.0 + }, + "element_id": "right-card", + "kind": "rect", + "role": "container", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 34.0, + "width": 320.0, + "x": 92.0, + "y": 168.0 + }, + "element_id": "left-title", + "kind": "text", + "role": "left-title", + "source_ref": "canvas_spec.content.left_title", + "text": "地表信号" + }, + { + "bbox": { + "height": 36.0, + "width": 296.0, + "x": 116.0, + "y": 222.0 + }, + "element_id": "left-point-1", + "kind": "text", + "role": "body", + "source_ref": "canvas_spec.content.left_points[]", + "text": "熔岩地貌" + }, + { + "bbox": { + "height": 36.0, + "width": 296.0, + "x": 116.0, + "y": 270.0 + }, + "element_id": "left-point-2", + "kind": "text", + "role": "body", + "source_ref": "canvas_spec.content.left_points[]", + "text": "热异常" + }, + { + "bbox": { + "height": 36.0, + "width": 296.0, + "x": 116.0, + "y": 318.0 + }, + "element_id": "left-point-3", + "kind": "text", + "role": "body", + "source_ref": "canvas_spec.content.left_points[]", + "text": "气体羽流" + }, + { + "bbox": { + "height": 34.0, + "width": 320.0, + "x": 534.0, + "y": 168.0 + }, + "element_id": "right-title", + "kind": "text", + "role": "right-title", + "source_ref": "canvas_spec.content.right_title", + "text": "地下信号" + }, + { + "bbox": { + "height": 36.0, + "width": 296.0, + "x": 558.0, + "y": 222.0 + }, + "element_id": "right-point-1", + "kind": "text", + "role": "body", + "source_ref": "canvas_spec.content.right_points[]", + "text": "地震活动" + }, + { + "bbox": { + "height": 36.0, + "width": 296.0, + "x": 558.0, + "y": 270.0 + }, + "element_id": "right-point-2", + "kind": "text", + "role": "body", + "source_ref": "canvas_spec.content.right_points[]", + "text": "形变趋势" + }, + { + "bbox": { + "height": 36.0, + "width": 296.0, + "x": 558.0, + "y": 318.0 + }, + "element_id": "right-point-3", + "kind": "text", + "role": "body", + "source_ref": "canvas_spec.content.right_points[]", + "text": "岩浆补给" + }, + { + "bbox": { + "height": 42.0, + "width": 788.0, + "x": 86.0, + "y": 426.0 + }, + "element_id": "conclusion", + "kind": "text", + "role": "conclusion", + "source_ref": "canvas_spec.content.conclusion", + "text": "多源观测共同降低单一证据偏差。" + } + ], + "page": 2, + "semantic_source": "CanvasSpec", + "template_id": "comparison-cards", + "theme_id": "paper-research", + "version": "svglide-semantic-map/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.canvas-spec.json new file mode 100644 index 00000000..b54d7f22 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.canvas-spec.json @@ -0,0 +1,72 @@ +{ + "canvas": { + "height": 540, + "viewBox": "0 0 960 540", + "width": 960 + }, + "content": { + "eyebrow": "NEXT", + "subtitle": "把研究结果转成可执行的观测闭环。", + "takeaways": [ + "连续监测", + "现场复核", + "风险沟通" + ], + "title": "下一步观测" + }, + "quality_constraints": { + "max_title_lines": 2, + "min_font_size": 18, + "safe_area": { + "height": 460, + "width": 864, + "x": 48, + "y": 40 + } + }, + "safe_area": { + "height": 460, + "width": 864, + "x": 48, + "y": 40 + }, + "semantic_elements": [ + { + "bbox": { + "height": 110, + "width": 700, + "x": 72, + "y": 110 + }, + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title" + } + ], + "template_id": "summary-final", + "theme": { + "colors": { + "accent": "#22D3EE", + "background": "#27130F", + "muted": "#F4C9A8", + "panel": "#3A211B", + "primary": "#F97316", + "text": "#FFF7ED" + }, + "spacing": { + "card": 28, + "gap": 20, + "page": 64 + }, + "theme_id": "warm-editorial", + "typography": { + "body": 20, + "label": 18, + "subtitle": 23, + "title": 54 + } + }, + "theme_id": "warm-editorial", + "version": "svglide-canvas-spec/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.canvas-template.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.canvas-template.svg new file mode 100644 index 00000000..d9c88b79 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.canvas-template.svg @@ -0,0 +1,19 @@ + + + + + + +NEXT +下一步观测 +把研究结果转成可执行的观测闭环。 + +01 +连续监测 + +02 +现场复核 + +03 +风险沟通 + diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.node-layout-map.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.node-layout-map.json new file mode 100644 index 00000000..d69319ac --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.node-layout-map.json @@ -0,0 +1,148 @@ +{ + "drift": { + "max_px": 0, + "status": "not_measured_in_p0" + }, + "nodes": [ + { + "height": 540, + "id": "background", + "kind": "rect", + "width": 960, + "x": 0, + "y": 0 + }, + { + "height": 30, + "id": "metric-bar-1", + "kind": "rect", + "width": 18, + "x": 712, + "y": 322 + }, + { + "height": 48, + "id": "metric-bar-2", + "kind": "rect", + "width": 18, + "x": 742, + "y": 304 + }, + { + "height": 66, + "id": "metric-bar-3", + "kind": "rect", + "width": 18, + "x": 772, + "y": 286 + }, + { + "height": 32, + "id": "eyebrow", + "kind": "text", + "text": "NEXT", + "width": 420, + "x": 72, + "y": 64 + }, + { + "height": 110, + "id": "title", + "kind": "text", + "text": "下一步观测", + "width": 700, + "x": 72, + "y": 110 + }, + { + "height": 66, + "id": "subtitle", + "kind": "text", + "text": "把研究结果转成可执行的观测闭环。", + "width": 640, + "x": 72, + "y": 244 + }, + { + "height": 126, + "id": "takeaway-card-1", + "kind": "rect", + "width": 250, + "x": 72, + "y": 344 + }, + { + "height": 30, + "id": "takeaway-index-1", + "kind": "text", + "text": "01", + "width": 64, + "x": 94, + "y": 366 + }, + { + "height": 54, + "id": "takeaway-1", + "kind": "text", + "text": "连续监测", + "width": 202, + "x": 94, + "y": 404 + }, + { + "height": 126, + "id": "takeaway-card-2", + "kind": "rect", + "width": 250, + "x": 340, + "y": 344 + }, + { + "height": 30, + "id": "takeaway-index-2", + "kind": "text", + "text": "02", + "width": 64, + "x": 362, + "y": 366 + }, + { + "height": 54, + "id": "takeaway-2", + "kind": "text", + "text": "现场复核", + "width": 202, + "x": 362, + "y": 404 + }, + { + "height": 126, + "id": "takeaway-card-3", + "kind": "rect", + "width": 250, + "x": 608, + "y": 344 + }, + { + "height": 30, + "id": "takeaway-index-3", + "kind": "text", + "text": "03", + "width": 64, + "x": 630, + "y": 366 + }, + { + "height": 54, + "id": "takeaway-3", + "kind": "text", + "text": "风险沟通", + "width": 202, + "x": 630, + "y": 404 + } + ], + "page": 3, + "source": "template-layout-map", + "version": "svglide-node-layout-map/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.png b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.png new file mode 100644 index 00000000..111b0eca Binary files /dev/null and b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.png differ diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.receipt.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.receipt.json new file mode 100644 index 00000000..75eb5b97 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.receipt.json @@ -0,0 +1,108 @@ +{ + "canvas_spec_path": "02-plan/slide_plan.json#/slides/2/canvas_spec", + "canvas_spec_sha256": "1409994cbf70a22f5fffff357295569ef0adc9caae55ebf6bb52920b2c385615", + "canvas_template_svg": "04-svg/artboard/page-003.canvas-template.svg", + "canvas_template_svg_sha256": "81c00e0a1494c858ea821a001833825b93bf44fe328905a4fe7f822b69b6ac2e", + "compiler": { + "compiler_input": "CanvasSpecTemplateSVG", + "fail_fast": [ + "clipPath", + "defs", + "filter", + "foreignObject", + "image", + "linearGradient", + "pattern", + "radialGradient", + "use" + ], + "native_mapped": [ + "rect", + "circle", + "rect", + "rect", + "rect", + "text->foreignObject", + "text->foreignObject", + "text->foreignObject", + "rect", + "text->foreignObject", + "text->foreignObject", + "rect", + "text->foreignObject", + "text->foreignObject", + "rect", + "text->foreignObject", + "text->foreignObject" + ], + "satori_svg_usage": "preview_only", + "semantic_source": "CanvasSpec" + }, + "compiler_input": "04-svg/artboard/page-003.canvas-template.svg", + "compiler_input_sha256": "81c00e0a1494c858ea821a001833825b93bf44fe328905a4fe7f822b69b6ac2e", + "created_at": "2026-06-21T13:59:15+08:00", + "font_hashes": [ + { + "path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "sha256": "876af2cd4854644e7f3e7feb2f688997fdb3343c6df6693611209c9dfb47ccec" + } + ], + "node_layout_map": "04-svg/artboard/page-003.node-layout-map.json", + "node_layout_map_sha256": "5b0c426195a980724c7e77f0d09cc5a8f32bafb2f85ce7d5ac3a6b641cc72886", + "node_version": "v20.18.1", + "page": 3, + "png": "04-svg/artboard/page-003.png", + "png_sha256": "a1d172b3953148d4a428d7359f48f9f3b97d74a89ef07257583861271653bc6a", + "render_metadata": "04-svg/artboard/page-003.render-metadata.json", + "render_metadata_sha256": "2c3d41831d1d62dd42c2a404501a71d0c361c9b827c583bb8acfaf586fe14f0b", + "renderer": { + "actual_satori_package": true, + "adapter": "skills/lark-slides/scripts/artboard_renderer/dist/render.mjs", + "engine": "satori-node", + "name": "satori-resvg-p0" + }, + "resvg_version": "2.6.2", + "satori_preview": { + "element_counts": { + "g": 4, + "mask": 19, + "path": 1, + "rect": 26, + "svg": 1, + "text": 37 + }, + "fail_fast": [ + "clipPath", + "defs", + "filter", + "foreignObject", + "image", + "linearGradient", + "pattern", + "radialGradient", + "use" + ], + "issues": [], + "status": "passed", + "strict": false + }, + "satori_svg": "04-svg/artboard/raw/page-003.satori.svg", + "satori_svg_sha256": "07396b9b453965f332cee318eb9b8e55dbd01fd844aa69f66e5e532aec1f7ab6", + "satori_version": "0.26.0", + "semantic_map": "04-svg/artboard/page-003.semantic-map.json", + "semantic_map_sha256": "8bacd61d84cae3f0f1ca36bcf10bc65d61b847b74a02eb224c90f3039b1195ec", + "stage": "generate_svg", + "status": "passed", + "svglide_svg": "04-svg/page-003.svg", + "svglide_svg_sha256": "9318eccee9a629b10c181b3416fe30f9de32883e36b93169121c30037dc50fd6", + "template_id": "summary-final", + "template_registry": "skills/lark-slides/references/svglide-template-registry.json", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_files": [ + "skills/lark-slides/scripts/artboard_renderer/themes/warm-editorial.json" + ], + "theme_id": "warm-editorial", + "theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json", + "theme_registry_sha256": "283def187d5c69623fd43618332b3f4f212b0dad313f2e90f13f614569d797fb", + "version": "svglide-artboard-receipt/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.render-metadata.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.render-metadata.json new file mode 100644 index 00000000..094037a3 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.render-metadata.json @@ -0,0 +1,7 @@ +{ + "node_version": "v20.18.1", + "satori_version": "0.26.0", + "resvg_version": "2.6.2", + "font_path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "png_bytes": 25871 +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.semantic-map.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.semantic-map.json new file mode 100644 index 00000000..9b13f1d1 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/page-003.semantic-map.json @@ -0,0 +1,223 @@ +{ + "content_keys": [ + "eyebrow", + "subtitle", + "takeaways", + "title" + ], + "elements": [ + { + "bbox": { + "height": 540.0, + "width": 960.0, + "x": 0.0, + "y": 0.0 + }, + "element_id": "background", + "kind": "rect", + "role": "background", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 30.0, + "width": 18.0, + "x": 712.0, + "y": 322.0 + }, + "element_id": "metric-bar-1", + "kind": "rect", + "role": "decorative", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 48.0, + "width": 18.0, + "x": 742.0, + "y": 304.0 + }, + "element_id": "metric-bar-2", + "kind": "rect", + "role": "decorative", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 66.0, + "width": 18.0, + "x": 772.0, + "y": 286.0 + }, + "element_id": "metric-bar-3", + "kind": "rect", + "role": "decorative", + "source_ref": null, + "text": null + }, + { + "bbox": { + "height": 32.0, + "width": 420.0, + "x": 72.0, + "y": 64.0 + }, + "element_id": "eyebrow", + "kind": "text", + "role": "eyebrow", + "source_ref": "canvas_spec.content.eyebrow", + "text": "NEXT" + }, + { + "bbox": { + "height": 110.0, + "width": 700.0, + "x": 72.0, + "y": 110.0 + }, + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title", + "text": "下一步观测" + }, + { + "bbox": { + "height": 66.0, + "width": 640.0, + "x": 72.0, + "y": 244.0 + }, + "element_id": "subtitle", + "kind": "text", + "role": "subtitle", + "source_ref": "canvas_spec.content.subtitle", + "text": "把研究结果转成可执行的观测闭环。" + }, + { + "bbox": { + "height": 126.0, + "width": 250.0, + "x": 72.0, + "y": 344.0 + }, + "element_id": "takeaway-card-1", + "kind": "rect", + "role": "container", + "source_ref": "canvas_spec.content.takeaways[]", + "text": null + }, + { + "bbox": { + "height": 30.0, + "width": 64.0, + "x": 94.0, + "y": 366.0 + }, + "element_id": "takeaway-index-1", + "kind": "text", + "role": "takeaway-index-1", + "source_ref": "canvas_spec.content.takeaways[]", + "text": "01" + }, + { + "bbox": { + "height": 54.0, + "width": 202.0, + "x": 94.0, + "y": 404.0 + }, + "element_id": "takeaway-1", + "kind": "text", + "role": "takeaway-1", + "source_ref": "canvas_spec.content.takeaways[]", + "text": "连续监测" + }, + { + "bbox": { + "height": 126.0, + "width": 250.0, + "x": 340.0, + "y": 344.0 + }, + "element_id": "takeaway-card-2", + "kind": "rect", + "role": "container", + "source_ref": "canvas_spec.content.takeaways[]", + "text": null + }, + { + "bbox": { + "height": 30.0, + "width": 64.0, + "x": 362.0, + "y": 366.0 + }, + "element_id": "takeaway-index-2", + "kind": "text", + "role": "takeaway-index-2", + "source_ref": "canvas_spec.content.takeaways[]", + "text": "02" + }, + { + "bbox": { + "height": 54.0, + "width": 202.0, + "x": 362.0, + "y": 404.0 + }, + "element_id": "takeaway-2", + "kind": "text", + "role": "takeaway-2", + "source_ref": "canvas_spec.content.takeaways[]", + "text": "现场复核" + }, + { + "bbox": { + "height": 126.0, + "width": 250.0, + "x": 608.0, + "y": 344.0 + }, + "element_id": "takeaway-card-3", + "kind": "rect", + "role": "container", + "source_ref": "canvas_spec.content.takeaways[]", + "text": null + }, + { + "bbox": { + "height": 30.0, + "width": 64.0, + "x": 630.0, + "y": 366.0 + }, + "element_id": "takeaway-index-3", + "kind": "text", + "role": "takeaway-index-3", + "source_ref": "canvas_spec.content.takeaways[]", + "text": "03" + }, + { + "bbox": { + "height": 54.0, + "width": 202.0, + "x": 630.0, + "y": 404.0 + }, + "element_id": "takeaway-3", + "kind": "text", + "role": "takeaway-3", + "source_ref": "canvas_spec.content.takeaways[]", + "text": "风险沟通" + } + ], + "page": 3, + "semantic_source": "CanvasSpec", + "template_id": "summary-final", + "theme_id": "warm-editorial", + "version": "svglide-semantic-map/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-001.satori.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-001.satori.svg new file mode 100644 index 00000000..8ebedffb --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-001.satori.svg @@ -0,0 +1 @@ +ICELAND VOLCANO \ No newline at end of file diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-002.satori.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-002.satori.svg new file mode 100644 index 00000000..dc849610 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-002.satori.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-003.satori.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-003.satori.svg new file mode 100644 index 00000000..d2af3baa --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/artboard/raw/page-003.satori.svg @@ -0,0 +1 @@ +NEXT010203 \ No newline at end of file diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-001.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-001.svg new file mode 100644 index 00000000..a80dfb8f --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-001.svg @@ -0,0 +1,14 @@ + + + + + +
ICELAND VOLCANO
+
冰岛火山研究
+
地貌证据、活动迹象与连续观测。
+ + +
监测
+ +
风险
+
diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-002.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-002.svg new file mode 100644 index 00000000..1eaa49f3 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-002.svg @@ -0,0 +1,22 @@ + + +
观测证据对比
+ + +
地表信号
+
地下信号
+ +
熔岩地貌
+ +
热异常
+ +
气体羽流
+ +
地震活动
+ +
形变趋势
+ +
岩浆补给
+ +
多源观测共同降低单一证据偏差。
+
diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-003.svg b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-003.svg new file mode 100644 index 00000000..324cdd78 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/04-svg/page-003.svg @@ -0,0 +1,19 @@ + + + + + + +
NEXT
+
下一步观测
+
把研究结果转成可执行的观测闭环。
+ +
01
+
连续监测
+ +
02
+
现场复核
+ +
03
+
风险沟通
+
diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/05-preview/contact-sheet.png b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/05-preview/contact-sheet.png new file mode 100644 index 00000000..894c5f02 Binary files /dev/null and b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/05-preview/contact-sheet.png differ diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/06-check/canvas-spec-validate.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/06-check/canvas-spec-validate.json new file mode 100644 index 00000000..e3692e5d --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/06-check/canvas-spec-validate.json @@ -0,0 +1,53 @@ +{ + "action": "create_live", + "inputs": { + "plan_sha256": "9c5f68a5f3536a91e07486e29ccd6961e95738de450e526727577960be672f1e", + "slide_plan": "02-plan/slide_plan.json", + "template_registry": "skills/lark-slides/references/svglide-template-registry.json", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_files": [ + "skills/lark-slides/scripts/artboard_renderer/themes/blueprint-technical.json" + ], + "theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json", + "theme_registry_sha256": "7ae629f17d38922702489ad3a23da26d465c3e6f5c284b409d4ab10a53ef6ed0" + }, + "issues": [], + "output_path": "06-check/canvas-spec-validate.json", + "pages": [ + { + "canvas_spec_sha256": "d94438cc689e2893a96ec0240051750920a855b0560eb4a594e8e9c4d4f9162b", + "error_count": 0, + "page": 1, + "template_id": "cover-hero", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_id": "blueprint-technical", + "theme_registry_sha256": "7ae629f17d38922702489ad3a23da26d465c3e6f5c284b409d4ab10a53ef6ed0" + }, + { + "canvas_spec_sha256": "4997cabbcda2823c3603a4f87993bb83b8cd8c91b0954653d611beb4e6b5a584", + "error_count": 0, + "page": 2, + "template_id": "comparison-cards", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_id": "paper-research", + "theme_registry_sha256": "85be63912ba33fd278b2bff4471ac7a1e6761d67292dc1a1a374b4fd7bd5edfd" + }, + { + "canvas_spec_sha256": "2e8f25a423a181bfe2b4462ec4fe3561a245688992dbf5c37fd26967f56ee9da", + "error_count": 0, + "page": 3, + "template_id": "summary-final", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_id": "warm-editorial", + "theme_registry_sha256": "283def187d5c69623fd43618332b3f4f212b0dad313f2e90f13f614569d797fb" + } + ], + "schema_version": "svglide-canvas-spec-validate/v1", + "stage": "canvas-spec-validate", + "status": "passed", + "summary": { + "error_count": 0, + "page_count": 3, + "warning_count": 0 + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/06-check/planner-contract-check.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/06-check/planner-contract-check.json new file mode 100644 index 00000000..0dfc0c94 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/06-check/planner-contract-check.json @@ -0,0 +1,80 @@ +{ + "action": "continue_to_generate_svg", + "inputs": { + "project": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner", + "prompt_contracts": "skills/lark-slides/references/svglide-planner-prompt-contracts.json", + "prompt_contracts_sha256": "a04fbc7ccef868a0f37aebc735a710933f3a23ad58a2208ae1bbe93cc2a65d63" + }, + "issues": [], + "output_path": "06-check/planner-contract-check.json", + "planner_outputs": [ + { + "error_count": 0, + "output_path": "02-plan/deck-plan.json", + "output_sha256": "64895dd3daa421e2467b4bd51711f4a486ffbe25ec65dcbe22115c9bcfbe7387", + "prompt_id": "deck-planner", + "schema": "skills/lark-slides/references/svglide-deck-plan.schema.json" + }, + { + "error_count": 0, + "output_path": "02-plan/slide-plan.json", + "output_sha256": "cc73612a48528e17b832efbdc124f5be2d8948d620b80803a652f6738054e475", + "prompt_id": "slide-planner", + "schema": "skills/lark-slides/references/svglide-slide-plan.schema.json" + }, + { + "error_count": 0, + "output_path": "02-plan/slide_plan.json", + "output_sha256": "9c5f68a5f3536a91e07486e29ccd6961e95738de450e526727577960be672f1e", + "prompt_id": "canvas-planner", + "schema": "skills/lark-slides/references/svglide-canvas-plan.schema.json" + }, + { + "error_count": 0, + "output_path": "02-plan/repair-plan.json", + "output_sha256": "938bed59c49da6eaeb1dd36c35143102022713e33fdc15ab9253fbbcbe26f736", + "prompt_id": "repair-planner", + "schema": "skills/lark-slides/references/svglide-repair-plan.schema.json" + } + ], + "prompt_contracts": [ + { + "id": "deck-planner", + "output_path": "02-plan/deck-plan.json", + "output_schema": "skills/lark-slides/references/svglide-deck-plan.schema.json", + "prompt_path": "skills/lark-slides/prompts/svglide/deck-planner.prompt.md", + "prompt_sha256": "3959317b1f62019cdc8f5a37d787f794119a9309384de6f3f6d1256afe7545ed" + }, + { + "id": "slide-planner", + "output_path": "02-plan/slide-plan.json", + "output_schema": "skills/lark-slides/references/svglide-slide-plan.schema.json", + "prompt_path": "skills/lark-slides/prompts/svglide/slide-planner.prompt.md", + "prompt_sha256": "27f52eabb8433c0808811c6ab4056b2d19b9f60144eec49c8c9add553330aabd" + }, + { + "id": "canvas-planner", + "output_path": "02-plan/slide_plan.json", + "output_schema": "skills/lark-slides/references/svglide-canvas-plan.schema.json", + "prompt_path": "skills/lark-slides/prompts/svglide/canvas-planner.prompt.md", + "prompt_sha256": "192897b85ad5af6bec5a0e646496b6e35214c54cda467f748d077aee2dde6de6" + }, + { + "id": "repair-planner", + "output_path": "02-plan/repair-plan.json", + "output_schema": "skills/lark-slides/references/svglide-repair-plan.schema.json", + "prompt_path": "skills/lark-slides/prompts/svglide/repair-planner.prompt.md", + "prompt_sha256": "7cb893ed2fb133161b91232706fa80d507eadc87eb62a7806f0b0f6df8f42f50" + } + ], + "receipt_path": "receipts/planner-contract-check.json", + "schema_version": "svglide-planner-contract-check/v1", + "stage": "planner-contract-check", + "status": "passed", + "summary": { + "error_count": 0, + "planner_output_count": 4, + "prompt_contract_count": 4, + "warning_count": 0 + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/artboard-render.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/artboard-render.json new file mode 100644 index 00000000..67784638 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/artboard-render.json @@ -0,0 +1,111 @@ +{ + "contact_sheet": { + "path": "05-preview/contact-sheet.png", + "sha256": "2c4c66c09ded55972bac90f9586eeb71c89fd67c20d0af781cbcc7cf9aec0a2b", + "source_pngs": [ + "04-svg/artboard/page-001.png", + "04-svg/artboard/page-002.png", + "04-svg/artboard/page-003.png" + ] + }, + "created_at": "2026-06-21T13:59:15+08:00", + "inputs": { + "canvas_spec_validate": "receipts/canvas-spec-validate.json", + "canvas_spec_validate_sha256": "c6e93921b849a96a9dbbe1449d8e9de52096bc563313ff9a744be506a0254363", + "plan_sha256": "9c5f68a5f3536a91e07486e29ccd6961e95738de450e526727577960be672f1e", + "slide_plan": "02-plan/slide_plan.json", + "template_registry": "skills/lark-slides/references/svglide-template-registry.json", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_files": [ + "skills/lark-slides/scripts/artboard_renderer/themes/blueprint-technical.json" + ], + "theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json", + "theme_registry_sha256": "7ae629f17d38922702489ad3a23da26d465c3e6f5c284b409d4ab10a53ef6ed0" + }, + "pages": [ + { + "canvas_spec_sha256": "c217ffcfb547080e01de408d01933ed81f2ed1f9dc06de845e8a0dad7a19af02", + "canvas_template_svg": "04-svg/artboard/page-001.canvas-template.svg", + "canvas_template_svg_sha256": "a21538ac26cd5ce67d306ec05f45e7c6ee98b4ae5a954c6bb92414994df2abc2", + "font_hashes": [ + { + "path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "sha256": "876af2cd4854644e7f3e7feb2f688997fdb3343c6df6693611209c9dfb47ccec" + } + ], + "node_layout_map": "04-svg/artboard/page-001.node-layout-map.json", + "node_layout_map_sha256": "895b6d98ab57c2e5633b92513c4b06047e26411c4bf75bf8f248be5358752c86", + "node_version": "v20.18.1", + "page": 1, + "png": "04-svg/artboard/page-001.png", + "png_sha256": "83d25aa4149a7ea9131f459dd3446bdf5aacfebea5d3d83e57638a5ad97f7e11", + "render_metadata": "04-svg/artboard/page-001.render-metadata.json", + "render_metadata_sha256": "050b8900bfe64f3e7e08007a4f695096976000215233e0a5bfe9743ee4f7acf4", + "resvg_version": "2.6.2", + "satori_svg": "04-svg/artboard/raw/page-001.satori.svg", + "satori_svg_sha256": "700e2b829a7a00354739512182916d4566877165748e9e1fa8b3aeeafcb21540", + "satori_version": "0.26.0", + "template_id": "cover-hero", + "theme_id": "blueprint-technical" + }, + { + "canvas_spec_sha256": "71c31d9e39a828f37a38c95912be4ec79343a4101072afede90384d5454d2270", + "canvas_template_svg": "04-svg/artboard/page-002.canvas-template.svg", + "canvas_template_svg_sha256": "acb7c3b4563b96ff052ca745c2ee43de958c2025cee815cc49b92611b3fbd82b", + "font_hashes": [ + { + "path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "sha256": "876af2cd4854644e7f3e7feb2f688997fdb3343c6df6693611209c9dfb47ccec" + } + ], + "node_layout_map": "04-svg/artboard/page-002.node-layout-map.json", + "node_layout_map_sha256": "1ad67ee48e5a22981c1432d858587ff346ac6069b4376a52990f16bce3e16c5e", + "node_version": "v20.18.1", + "page": 2, + "png": "04-svg/artboard/page-002.png", + "png_sha256": "ba705a55dc18aaef73144e01197fd75dba6fe37d27eb36897df95683f13161a7", + "render_metadata": "04-svg/artboard/page-002.render-metadata.json", + "render_metadata_sha256": "cc88b7d0489199a4aee08a29316b247fdd6cccb1df4fdec5f4e7be6dcbbb5a34", + "resvg_version": "2.6.2", + "satori_svg": "04-svg/artboard/raw/page-002.satori.svg", + "satori_svg_sha256": "5979d2dd93d09936d216d871929aacb5d06eecab43d34a2e0d8070991d1ed88a", + "satori_version": "0.26.0", + "template_id": "comparison-cards", + "theme_id": "paper-research" + }, + { + "canvas_spec_sha256": "1409994cbf70a22f5fffff357295569ef0adc9caae55ebf6bb52920b2c385615", + "canvas_template_svg": "04-svg/artboard/page-003.canvas-template.svg", + "canvas_template_svg_sha256": "81c00e0a1494c858ea821a001833825b93bf44fe328905a4fe7f822b69b6ac2e", + "font_hashes": [ + { + "path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "sha256": "876af2cd4854644e7f3e7feb2f688997fdb3343c6df6693611209c9dfb47ccec" + } + ], + "node_layout_map": "04-svg/artboard/page-003.node-layout-map.json", + "node_layout_map_sha256": "5b0c426195a980724c7e77f0d09cc5a8f32bafb2f85ce7d5ac3a6b641cc72886", + "node_version": "v20.18.1", + "page": 3, + "png": "04-svg/artboard/page-003.png", + "png_sha256": "a1d172b3953148d4a428d7359f48f9f3b97d74a89ef07257583861271653bc6a", + "render_metadata": "04-svg/artboard/page-003.render-metadata.json", + "render_metadata_sha256": "2c3d41831d1d62dd42c2a404501a71d0c361c9b827c583bb8acfaf586fe14f0b", + "resvg_version": "2.6.2", + "satori_svg": "04-svg/artboard/raw/page-003.satori.svg", + "satori_svg_sha256": "07396b9b453965f332cee318eb9b8e55dbd01fd844aa69f66e5e532aec1f7ab6", + "satori_version": "0.26.0", + "template_id": "summary-final", + "theme_id": "warm-editorial" + } + ], + "stage": "artboard-render", + "status": "passed", + "summary": { + "error_count": 0, + "max_workers": 3, + "page_count": 3, + "warning_count": 0 + }, + "version": "svglide-artboard-render/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/canvas-spec-validate.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/canvas-spec-validate.json new file mode 100644 index 00000000..e3692e5d --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/canvas-spec-validate.json @@ -0,0 +1,53 @@ +{ + "action": "create_live", + "inputs": { + "plan_sha256": "9c5f68a5f3536a91e07486e29ccd6961e95738de450e526727577960be672f1e", + "slide_plan": "02-plan/slide_plan.json", + "template_registry": "skills/lark-slides/references/svglide-template-registry.json", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_files": [ + "skills/lark-slides/scripts/artboard_renderer/themes/blueprint-technical.json" + ], + "theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json", + "theme_registry_sha256": "7ae629f17d38922702489ad3a23da26d465c3e6f5c284b409d4ab10a53ef6ed0" + }, + "issues": [], + "output_path": "06-check/canvas-spec-validate.json", + "pages": [ + { + "canvas_spec_sha256": "d94438cc689e2893a96ec0240051750920a855b0560eb4a594e8e9c4d4f9162b", + "error_count": 0, + "page": 1, + "template_id": "cover-hero", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_id": "blueprint-technical", + "theme_registry_sha256": "7ae629f17d38922702489ad3a23da26d465c3e6f5c284b409d4ab10a53ef6ed0" + }, + { + "canvas_spec_sha256": "4997cabbcda2823c3603a4f87993bb83b8cd8c91b0954653d611beb4e6b5a584", + "error_count": 0, + "page": 2, + "template_id": "comparison-cards", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_id": "paper-research", + "theme_registry_sha256": "85be63912ba33fd278b2bff4471ac7a1e6761d67292dc1a1a374b4fd7bd5edfd" + }, + { + "canvas_spec_sha256": "2e8f25a423a181bfe2b4462ec4fe3561a245688992dbf5c37fd26967f56ee9da", + "error_count": 0, + "page": 3, + "template_id": "summary-final", + "template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3", + "theme_id": "warm-editorial", + "theme_registry_sha256": "283def187d5c69623fd43618332b3f4f212b0dad313f2e90f13f614569d797fb" + } + ], + "schema_version": "svglide-canvas-spec-validate/v1", + "stage": "canvas-spec-validate", + "status": "passed", + "summary": { + "error_count": 0, + "page_count": 3, + "warning_count": 0 + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/planner-contract-check.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/planner-contract-check.json new file mode 100644 index 00000000..0dfc0c94 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/planner-contract-check.json @@ -0,0 +1,80 @@ +{ + "action": "continue_to_generate_svg", + "inputs": { + "project": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner", + "prompt_contracts": "skills/lark-slides/references/svglide-planner-prompt-contracts.json", + "prompt_contracts_sha256": "a04fbc7ccef868a0f37aebc735a710933f3a23ad58a2208ae1bbe93cc2a65d63" + }, + "issues": [], + "output_path": "06-check/planner-contract-check.json", + "planner_outputs": [ + { + "error_count": 0, + "output_path": "02-plan/deck-plan.json", + "output_sha256": "64895dd3daa421e2467b4bd51711f4a486ffbe25ec65dcbe22115c9bcfbe7387", + "prompt_id": "deck-planner", + "schema": "skills/lark-slides/references/svglide-deck-plan.schema.json" + }, + { + "error_count": 0, + "output_path": "02-plan/slide-plan.json", + "output_sha256": "cc73612a48528e17b832efbdc124f5be2d8948d620b80803a652f6738054e475", + "prompt_id": "slide-planner", + "schema": "skills/lark-slides/references/svglide-slide-plan.schema.json" + }, + { + "error_count": 0, + "output_path": "02-plan/slide_plan.json", + "output_sha256": "9c5f68a5f3536a91e07486e29ccd6961e95738de450e526727577960be672f1e", + "prompt_id": "canvas-planner", + "schema": "skills/lark-slides/references/svglide-canvas-plan.schema.json" + }, + { + "error_count": 0, + "output_path": "02-plan/repair-plan.json", + "output_sha256": "938bed59c49da6eaeb1dd36c35143102022713e33fdc15ab9253fbbcbe26f736", + "prompt_id": "repair-planner", + "schema": "skills/lark-slides/references/svglide-repair-plan.schema.json" + } + ], + "prompt_contracts": [ + { + "id": "deck-planner", + "output_path": "02-plan/deck-plan.json", + "output_schema": "skills/lark-slides/references/svglide-deck-plan.schema.json", + "prompt_path": "skills/lark-slides/prompts/svglide/deck-planner.prompt.md", + "prompt_sha256": "3959317b1f62019cdc8f5a37d787f794119a9309384de6f3f6d1256afe7545ed" + }, + { + "id": "slide-planner", + "output_path": "02-plan/slide-plan.json", + "output_schema": "skills/lark-slides/references/svglide-slide-plan.schema.json", + "prompt_path": "skills/lark-slides/prompts/svglide/slide-planner.prompt.md", + "prompt_sha256": "27f52eabb8433c0808811c6ab4056b2d19b9f60144eec49c8c9add553330aabd" + }, + { + "id": "canvas-planner", + "output_path": "02-plan/slide_plan.json", + "output_schema": "skills/lark-slides/references/svglide-canvas-plan.schema.json", + "prompt_path": "skills/lark-slides/prompts/svglide/canvas-planner.prompt.md", + "prompt_sha256": "192897b85ad5af6bec5a0e646496b6e35214c54cda467f748d077aee2dde6de6" + }, + { + "id": "repair-planner", + "output_path": "02-plan/repair-plan.json", + "output_schema": "skills/lark-slides/references/svglide-repair-plan.schema.json", + "prompt_path": "skills/lark-slides/prompts/svglide/repair-planner.prompt.md", + "prompt_sha256": "7cb893ed2fb133161b91232706fa80d507eadc87eb62a7806f0b0f6df8f42f50" + } + ], + "receipt_path": "receipts/planner-contract-check.json", + "schema_version": "svglide-planner-contract-check/v1", + "stage": "planner-contract-check", + "status": "passed", + "summary": { + "error_count": 0, + "planner_output_count": 4, + "prompt_contract_count": 4, + "warning_count": 0 + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/satori-bridge.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/satori-bridge.json new file mode 100644 index 00000000..4805f014 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate10_planner/receipts/satori-bridge.json @@ -0,0 +1,77 @@ +{ + "created_at": "2026-06-21T13:59:15+08:00", + "inputs": { + "artboard_render": "receipts/artboard-render.json", + "artboard_render_sha256": "82fb1e199442e5560d15e8b19d3ec85de4f0fbc69273015cf71b95027f5b2f1e", + "plan_sha256": "9c5f68a5f3536a91e07486e29ccd6961e95738de450e526727577960be672f1e", + "slide_plan": "02-plan/slide_plan.json" + }, + "pages": [ + { + "canvas_spec_sha256": "c217ffcfb547080e01de408d01933ed81f2ed1f9dc06de845e8a0dad7a19af02", + "canvas_template_svg": "04-svg/artboard/page-001.canvas-template.svg", + "canvas_template_svg_sha256": "a21538ac26cd5ce67d306ec05f45e7c6ee98b4ae5a954c6bb92414994df2abc2", + "compiler_input": "04-svg/artboard/page-001.canvas-template.svg", + "compiler_input_sha256": "a21538ac26cd5ce67d306ec05f45e7c6ee98b4ae5a954c6bb92414994df2abc2", + "compiler_input_type": "CanvasSpecTemplateSVG", + "node_layout_map": "04-svg/artboard/page-001.node-layout-map.json", + "node_layout_map_sha256": "895b6d98ab57c2e5633b92513c4b06047e26411c4bf75bf8f248be5358752c86", + "page": 1, + "satori_svg": "04-svg/artboard/raw/page-001.satori.svg", + "satori_svg_sha256": "700e2b829a7a00354739512182916d4566877165748e9e1fa8b3aeeafcb21540", + "satori_svg_usage": "preview_only", + "semantic_map": "04-svg/artboard/page-001.semantic-map.json", + "semantic_map_sha256": "0ce8309c016ff337d9dd72c322e928f2541a66b2acaa097c321979265ca06df7", + "semantic_source": "CanvasSpec", + "svglide_svg": "04-svg/page-001.svg", + "svglide_svg_sha256": "0bec84addb5a7b9319b71a573e0bd9de7890dcbe6985b61e3456e54950850fb3" + }, + { + "canvas_spec_sha256": "71c31d9e39a828f37a38c95912be4ec79343a4101072afede90384d5454d2270", + "canvas_template_svg": "04-svg/artboard/page-002.canvas-template.svg", + "canvas_template_svg_sha256": "acb7c3b4563b96ff052ca745c2ee43de958c2025cee815cc49b92611b3fbd82b", + "compiler_input": "04-svg/artboard/page-002.canvas-template.svg", + "compiler_input_sha256": "acb7c3b4563b96ff052ca745c2ee43de958c2025cee815cc49b92611b3fbd82b", + "compiler_input_type": "CanvasSpecTemplateSVG", + "node_layout_map": "04-svg/artboard/page-002.node-layout-map.json", + "node_layout_map_sha256": "1ad67ee48e5a22981c1432d858587ff346ac6069b4376a52990f16bce3e16c5e", + "page": 2, + "satori_svg": "04-svg/artboard/raw/page-002.satori.svg", + "satori_svg_sha256": "5979d2dd93d09936d216d871929aacb5d06eecab43d34a2e0d8070991d1ed88a", + "satori_svg_usage": "preview_only", + "semantic_map": "04-svg/artboard/page-002.semantic-map.json", + "semantic_map_sha256": "61fb537e82510e1fcada54f9f043b4746c1aeb10553391e4778cd6b76aeca520", + "semantic_source": "CanvasSpec", + "svglide_svg": "04-svg/page-002.svg", + "svglide_svg_sha256": "3c539aa631b9240c89e3d36411a2f74f0007eba48f02aaa9bda1fff98b87481d" + }, + { + "canvas_spec_sha256": "1409994cbf70a22f5fffff357295569ef0adc9caae55ebf6bb52920b2c385615", + "canvas_template_svg": "04-svg/artboard/page-003.canvas-template.svg", + "canvas_template_svg_sha256": "81c00e0a1494c858ea821a001833825b93bf44fe328905a4fe7f822b69b6ac2e", + "compiler_input": "04-svg/artboard/page-003.canvas-template.svg", + "compiler_input_sha256": "81c00e0a1494c858ea821a001833825b93bf44fe328905a4fe7f822b69b6ac2e", + "compiler_input_type": "CanvasSpecTemplateSVG", + "node_layout_map": "04-svg/artboard/page-003.node-layout-map.json", + "node_layout_map_sha256": "5b0c426195a980724c7e77f0d09cc5a8f32bafb2f85ce7d5ac3a6b641cc72886", + "page": 3, + "satori_svg": "04-svg/artboard/raw/page-003.satori.svg", + "satori_svg_sha256": "07396b9b453965f332cee318eb9b8e55dbd01fd844aa69f66e5e532aec1f7ab6", + "satori_svg_usage": "preview_only", + "semantic_map": "04-svg/artboard/page-003.semantic-map.json", + "semantic_map_sha256": "8bacd61d84cae3f0f1ca36bcf10bc65d61b847b74a02eb224c90f3039b1195ec", + "semantic_source": "CanvasSpec", + "svglide_svg": "04-svg/page-003.svg", + "svglide_svg_sha256": "9318eccee9a629b10c181b3416fe30f9de32883e36b93169121c30037dc50fd6" + } + ], + "stage": "satori-bridge", + "status": "passed", + "summary": { + "error_count": 0, + "max_workers": 3, + "page_count": 3, + "warning_count": 0 + }, + "version": "svglide-satori-bridge/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/06-check/artboard-package-check.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/06-check/artboard-package-check.json new file mode 100644 index 00000000..dac4f524 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/06-check/artboard-package-check.json @@ -0,0 +1,90 @@ +{ + "checked_at": "2026-06-21T14:46:05+08:00", + "dependency_policy": { + "dependencies": { + "@resvg/resvg-js": "2.6.2", + "satori": "0.26.0" + }, + "install_command": "pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile", + "manual_satori_source_checkout_required": false, + "native_dependency": "@resvg/resvg-js", + "node": ">=18", + "node_modules_embedded_in_go_binary": false, + "package_manager": "pnpm", + "runtime_check_commands": [ + "node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime", + "node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime" + ] + }, + "distribution_decision": { + "go_binary_embeds": [ + "agent-readable skill docs", + "prompts", + "flat Python scripts", + "artboard_renderer source/dist/templates/themes/components/package lock" + ], + "go_binary_excludes": [ + "node_modules", + "fixture outputs", + "runtime project artifacts" + ], + "runtime_requires_disk_skill_resources": true, + "runtime_requires_native_resvg_install": true, + "shape": "skill_subpackage_with_whitelisted_go_embed" + }, + "fallback_policy": { + "missing_node_or_resvg": "fail_fast_before_live_create", + "no_network": "do not auto-fetch; use CI/preinstalled pnpm store or a packaged platform dependency layer", + "operator_action": "run pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile, then node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime" + }, + "host": { + "machine": "arm64", + "python": "3.9.6", + "system": "Darwin" + }, + "issues": [], + "renderer": { + "dir": "skills/lark-slides/scripts/artboard_renderer", + "dist_entry": "skills/lark-slides/scripts/artboard_renderer/dist/render.mjs", + "lockfile": "skills/lark-slides/scripts/artboard_renderer/pnpm-lock.yaml", + "package_json": "skills/lark-slides/scripts/artboard_renderer/package.json", + "source_entry": "skills/lark-slides/scripts/artboard_renderer/render.mjs" + }, + "runtime_checks": [ + { + "command": "node /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime", + "cwd": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer", + "entry": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer/render.mjs", + "exit_code": 0, + "passed": true, + "payload": { + "font_path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "ok": true, + "renderer": "satori-resvg", + "resvg_version": "2.6.2", + "satori_version": "0.26.0" + }, + "stderr": "", + "stdout": "{\"ok\":true,\"renderer\":\"satori-resvg\",\"satori_version\":\"0.26.0\",\"resvg_version\":\"2.6.2\",\"font_path\":\"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\"}" + }, + { + "command": "node /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime", + "cwd": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer", + "entry": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer/dist/render.mjs", + "exit_code": 0, + "passed": true, + "payload": { + "font_path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "ok": true, + "renderer": "satori-resvg", + "resvg_version": "2.6.2", + "satori_version": "0.26.0" + }, + "stderr": "", + "stdout": "{\"ok\":true,\"renderer\":\"satori-resvg\",\"satori_version\":\"0.26.0\",\"resvg_version\":\"2.6.2\",\"font_path\":\"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\"}" + } + ], + "stage": "package_distribution", + "status": "passed", + "version": "svglide-artboard-package-check/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json new file mode 100644 index 00000000..dac4f524 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json @@ -0,0 +1,90 @@ +{ + "checked_at": "2026-06-21T14:46:05+08:00", + "dependency_policy": { + "dependencies": { + "@resvg/resvg-js": "2.6.2", + "satori": "0.26.0" + }, + "install_command": "pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile", + "manual_satori_source_checkout_required": false, + "native_dependency": "@resvg/resvg-js", + "node": ">=18", + "node_modules_embedded_in_go_binary": false, + "package_manager": "pnpm", + "runtime_check_commands": [ + "node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime", + "node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime" + ] + }, + "distribution_decision": { + "go_binary_embeds": [ + "agent-readable skill docs", + "prompts", + "flat Python scripts", + "artboard_renderer source/dist/templates/themes/components/package lock" + ], + "go_binary_excludes": [ + "node_modules", + "fixture outputs", + "runtime project artifacts" + ], + "runtime_requires_disk_skill_resources": true, + "runtime_requires_native_resvg_install": true, + "shape": "skill_subpackage_with_whitelisted_go_embed" + }, + "fallback_policy": { + "missing_node_or_resvg": "fail_fast_before_live_create", + "no_network": "do not auto-fetch; use CI/preinstalled pnpm store or a packaged platform dependency layer", + "operator_action": "run pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile, then node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime" + }, + "host": { + "machine": "arm64", + "python": "3.9.6", + "system": "Darwin" + }, + "issues": [], + "renderer": { + "dir": "skills/lark-slides/scripts/artboard_renderer", + "dist_entry": "skills/lark-slides/scripts/artboard_renderer/dist/render.mjs", + "lockfile": "skills/lark-slides/scripts/artboard_renderer/pnpm-lock.yaml", + "package_json": "skills/lark-slides/scripts/artboard_renderer/package.json", + "source_entry": "skills/lark-slides/scripts/artboard_renderer/render.mjs" + }, + "runtime_checks": [ + { + "command": "node /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime", + "cwd": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer", + "entry": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer/render.mjs", + "exit_code": 0, + "passed": true, + "payload": { + "font_path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "ok": true, + "renderer": "satori-resvg", + "resvg_version": "2.6.2", + "satori_version": "0.26.0" + }, + "stderr": "", + "stdout": "{\"ok\":true,\"renderer\":\"satori-resvg\",\"satori_version\":\"0.26.0\",\"resvg_version\":\"2.6.2\",\"font_path\":\"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\"}" + }, + { + "command": "node /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime", + "cwd": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer", + "entry": "/Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private/skills/lark-slides/scripts/artboard_renderer/dist/render.mjs", + "exit_code": 0, + "passed": true, + "payload": { + "font_path": "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + "ok": true, + "renderer": "satori-resvg", + "resvg_version": "2.6.2", + "satori_version": "0.26.0" + }, + "stderr": "", + "stdout": "{\"ok\":true,\"renderer\":\"satori-resvg\",\"satori_version\":\"0.26.0\",\"resvg_version\":\"2.6.2\",\"font_path\":\"/System/Library/Fonts/Supplemental/Arial Unicode.ttf\"}" + } + ], + "stage": "package_distribution", + "status": "passed", + "version": "svglide-artboard-package-check/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final/06-check/final-acceptance-check.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final/06-check/final-acceptance-check.json new file mode 100644 index 00000000..1a371b1a --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final/06-check/final-acceptance-check.json @@ -0,0 +1,233 @@ +{ + "checked_at": "2026-06-21T14:52:12+08:00", + "evidence_files": [ + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate0-gate1-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate2-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate3-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate4-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate5-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate6-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate7-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate8-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate9-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate10-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate11-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate12a-evidence.md" + } + ], + "gate_statuses": { + "0": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "1": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "10": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "11": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "12a": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "2": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "3": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "4": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "5": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "6": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "7": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "8": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "9": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + } + }, + "instruction_adherence": { + "check_path": ".tmp/svglide-p0c-gate7-live6/06-check/instruction-adherence.json", + "hash_checks": [ + { + "exists": true, + "label": "instruction", + "matched": true, + "path": "00-input/instruction.json", + "sha256": "c292a356e2d80dade4f4237f45d94da28f55a6e2889267a54e04ccdd16b42200" + }, + { + "exists": true, + "label": "deck_plan", + "matched": true, + "path": "02-plan/deck-plan.json", + "sha256": "48fae1be097548f7b9d1284516c8c4586db69ad1bfc6f34f0c499dc3afedcd40" + }, + { + "exists": true, + "label": "slide_plan", + "matched": true, + "path": "02-plan/slide-plan.json", + "sha256": "601608e462386bf4d2bfa030bc9db6ef1ca7dc31a51f0f6c52f711cb2ea01701" + }, + { + "exists": true, + "label": "final_plan", + "matched": true, + "path": "02-plan/slide_plan.json", + "sha256": "3aa06d0877a587f8b62d0c8934fcc03ec8e34823c00c725f4e16c3d44ceb3bb7" + }, + { + "exists": true, + "label": "output_page_1", + "matched": true, + "path": "04-svg/page-001.svg", + "sha256": "6d35ebfe7ec500472a9fd5ab69af230d1a73aea40b2f5f6bc6f02f5a58a0900f" + }, + { + "exists": true, + "label": "output_page_2", + "matched": true, + "path": "04-svg/page-002.svg", + "sha256": "9c623b3138cab6f0dcf68fb3cdfc68e9747f11ef5a8ba9f048a7a2360524e799" + }, + { + "exists": true, + "label": "output_page_3", + "matched": true, + "path": "04-svg/page-003.svg", + "sha256": "13d7fc19310ac89a699f1fdf85597628b1a8428aa55468262c9a920644bf4e24" + }, + { + "exists": true, + "label": "readback_check", + "matched": true, + "path": "08-readback/readback-check.json", + "sha256": "d0975690314cdde750cc2463a35c75364720e5abfe0a65de630b2f0c9f2b67f4" + }, + { + "exists": true, + "label": "readback_raw", + "matched": true, + "path": "08-readback/xml-presentations-get.json", + "sha256": "4613b160af688aef660b1c07fdd7f9b19c676ef8d9fb0fb0cfdf7607b1eeb596" + } + ], + "project": ".tmp/svglide-p0c-gate7-live6", + "readback_binding_checks": [ + { + "binding_key": "plan_sha256", + "matched": true, + "path": "02-plan/slide_plan.json" + }, + { + "binding_key": "quality_gate_sha256", + "matched": true, + "path": "06-check/quality-gate.json" + }, + { + "binding_key": "dry_run_sha256", + "matched": true, + "path": "07-create/dry-run.json" + }, + { + "binding_key": "ppe_proof_sha256", + "matched": true, + "path": "07-create/ppe-proof.json" + }, + { + "binding_key": "live_create_sha256", + "matched": true, + "path": "07-create/live-create.json" + } + ], + "receipt_path": ".tmp/svglide-p0c-gate7-live6/receipts/instruction-adherence.json", + "receipt_status": "passed", + "status": "passed" + }, + "issues": [], + "package_receipt": { + "path": "skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json", + "status": "passed" + }, + "plan_path": "/Users/bytedance/Downloads/PLAN.md", + "scope": { + "accepted_milestone": "P0/P1 artboard_satori implementation through Gate 12a reviewer PASS", + "deferrals_path": "skills/lark-slides/references/svglide-artboard-gate12-scope.md", + "not_claimed": "complete high-quality PPT generation system with actual model-driven topic-to-deck loop" + }, + "stage": "final_acceptance", + "status": "passed", + "version": "svglide-artboard-final-acceptance/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final/receipts/final-acceptance-check.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final/receipts/final-acceptance-check.json new file mode 100644 index 00000000..1a371b1a --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate12_final/receipts/final-acceptance-check.json @@ -0,0 +1,233 @@ +{ + "checked_at": "2026-06-21T14:52:12+08:00", + "evidence_files": [ + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate0-gate1-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate2-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate3-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate4-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate5-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate6-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate7-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate8-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate9-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate10-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate11-evidence.md" + }, + { + "exists": true, + "path": "skills/lark-slides/references/svglide-artboard-gate12a-evidence.md" + } + ], + "gate_statuses": { + "0": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "1": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "10": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "11": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "12a": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "2": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "3": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "4": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "5": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "6": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "7": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "8": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + }, + "9": { + "owner": "executor", + "reviewer_verdict": "PASS", + "status": "DONE" + } + }, + "instruction_adherence": { + "check_path": ".tmp/svglide-p0c-gate7-live6/06-check/instruction-adherence.json", + "hash_checks": [ + { + "exists": true, + "label": "instruction", + "matched": true, + "path": "00-input/instruction.json", + "sha256": "c292a356e2d80dade4f4237f45d94da28f55a6e2889267a54e04ccdd16b42200" + }, + { + "exists": true, + "label": "deck_plan", + "matched": true, + "path": "02-plan/deck-plan.json", + "sha256": "48fae1be097548f7b9d1284516c8c4586db69ad1bfc6f34f0c499dc3afedcd40" + }, + { + "exists": true, + "label": "slide_plan", + "matched": true, + "path": "02-plan/slide-plan.json", + "sha256": "601608e462386bf4d2bfa030bc9db6ef1ca7dc31a51f0f6c52f711cb2ea01701" + }, + { + "exists": true, + "label": "final_plan", + "matched": true, + "path": "02-plan/slide_plan.json", + "sha256": "3aa06d0877a587f8b62d0c8934fcc03ec8e34823c00c725f4e16c3d44ceb3bb7" + }, + { + "exists": true, + "label": "output_page_1", + "matched": true, + "path": "04-svg/page-001.svg", + "sha256": "6d35ebfe7ec500472a9fd5ab69af230d1a73aea40b2f5f6bc6f02f5a58a0900f" + }, + { + "exists": true, + "label": "output_page_2", + "matched": true, + "path": "04-svg/page-002.svg", + "sha256": "9c623b3138cab6f0dcf68fb3cdfc68e9747f11ef5a8ba9f048a7a2360524e799" + }, + { + "exists": true, + "label": "output_page_3", + "matched": true, + "path": "04-svg/page-003.svg", + "sha256": "13d7fc19310ac89a699f1fdf85597628b1a8428aa55468262c9a920644bf4e24" + }, + { + "exists": true, + "label": "readback_check", + "matched": true, + "path": "08-readback/readback-check.json", + "sha256": "d0975690314cdde750cc2463a35c75364720e5abfe0a65de630b2f0c9f2b67f4" + }, + { + "exists": true, + "label": "readback_raw", + "matched": true, + "path": "08-readback/xml-presentations-get.json", + "sha256": "4613b160af688aef660b1c07fdd7f9b19c676ef8d9fb0fb0cfdf7607b1eeb596" + } + ], + "project": ".tmp/svglide-p0c-gate7-live6", + "readback_binding_checks": [ + { + "binding_key": "plan_sha256", + "matched": true, + "path": "02-plan/slide_plan.json" + }, + { + "binding_key": "quality_gate_sha256", + "matched": true, + "path": "06-check/quality-gate.json" + }, + { + "binding_key": "dry_run_sha256", + "matched": true, + "path": "07-create/dry-run.json" + }, + { + "binding_key": "ppe_proof_sha256", + "matched": true, + "path": "07-create/ppe-proof.json" + }, + { + "binding_key": "live_create_sha256", + "matched": true, + "path": "07-create/live-create.json" + } + ], + "receipt_path": ".tmp/svglide-p0c-gate7-live6/receipts/instruction-adherence.json", + "receipt_status": "passed", + "status": "passed" + }, + "issues": [], + "package_receipt": { + "path": "skills/lark-slides/scripts/fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json", + "status": "passed" + }, + "plan_path": "/Users/bytedance/Downloads/PLAN.md", + "scope": { + "accepted_milestone": "P0/P1 artboard_satori implementation through Gate 12a reviewer PASS", + "deferrals_path": "skills/lark-slides/references/svglide-artboard-gate12-scope.md", + "not_claimed": "complete high-quality PPT generation system with actual model-driven topic-to-deck loop" + }, + "stage": "final_acceptance", + "status": "passed", + "version": "svglide-artboard-final-acceptance/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate8_special_cases/chart-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate8_special_cases/chart-spec.json new file mode 100644 index 00000000..74e329f2 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate8_special_cases/chart-spec.json @@ -0,0 +1,13 @@ +{ + "version": "svglide-chart-spec/v1", + "chartType": "bar", + "data": { + "categories": ["Q1", "Q2", "Q3"], + "series": [ + { + "name": "Research index", + "values": [12, 18, 25] + } + ] + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/gate8_special_cases/unsupported-filter.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate8_special_cases/unsupported-filter.canvas-spec.json new file mode 100644 index 00000000..00dc0c8d --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/gate8_special_cases/unsupported-filter.canvas-spec.json @@ -0,0 +1,57 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": { + "width": 960, + "height": 540, + "viewBox": "0 0 960 540" + }, + "safe_area": { + "x": 48, + "y": 40, + "width": 864, + "height": 460 + }, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "theme": { + "colors": { + "background": "#0F172A", + "panel": "#111827", + "primary": "#38BDF8", + "accent": "#A78BFA", + "text": "#F8FAFC", + "muted": "#CBD5E1" + } + }, + "content": { + "eyebrow": "GATE 8", + "title": "Unsupported filter fixture", + "subtitle": "This fixture must fail before live create.", + "chips": ["filter", "fail-fast"] + }, + "unsupported_features": ["filter"], + "semantic_elements": [ + { + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title", + "bbox": { + "x": 84, + "y": 142, + "width": 628, + "height": 142 + } + } + ], + "quality_constraints": { + "max_title_lines": 2, + "min_font_size": 18, + "safe_area": { + "x": 48, + "y": 40, + "width": 864, + "height": 460 + } + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/agenda-list.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/agenda-list.canvas-spec.json new file mode 100644 index 00000000..e9e44156 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/agenda-list.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "agenda-list", + "theme_id": "swiss-red", + "theme": {"colors": {"background": "#F8F8F4", "panel": "#FFFFFF", "primary": "#E11D48", "accent": "#111111", "text": "#111111", "muted": "#666666"}}, + "content": {"eyebrow": "AGENDA", "title": "今日讨论", "subtitle": "从目标到验证逐步展开。", "items": ["目标定义", "素材抽象", "模板渲染", "质量门禁", "上线验证"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 96, "width": 760, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/architecture-blueprint.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/architecture-blueprint.canvas-spec.json new file mode 100644 index 00000000..0f24eb26 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/architecture-blueprint.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "architecture-blueprint", + "theme_id": "blueprint-technical", + "theme": {"colors": {"background": "#071827", "panel": "#0E2A3F", "primary": "#5EEAD4", "accent": "#F97316", "text": "#EAF6FF", "muted": "#93B4C8"}}, + "content": {"eyebrow": "ARCH", "title": "系统职责拆分", "subtitle": "叙事、画板和导出各自收敛。", "nodes": ["Deck Planner", "Slide Planner", "CanvasSpec", "Template", "Satori", "SVGlide"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 96, "width": 630, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/comparison-cards.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/comparison-cards.canvas-spec.json new file mode 100644 index 00000000..d3a38647 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/comparison-cards.canvas-spec.json @@ -0,0 +1,18 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "comparison-cards", + "theme_id": "forest-signal", + "theme": {"colors": {"background": "#0B1F1A", "panel": "#123329", "primary": "#34D399", "accent": "#FBBF24", "text": "#F7FCEB", "muted": "#B7D3C6"}}, + "content": { + "title": "新旧链路差异", + "left_title": "旧链路", + "right_title": "新链路", + "left_points": ["直接生成 SVG", "审美分散在 prompt", "门禁只看最终文件"], + "right_points": ["先生成 CanvasSpec", "模板统一审美", "receipt 绑定画板"], + "conclusion": "美学标准从自由生成迁移到受控设计系统。" + }, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 64, "y": 52, "width": 760, "height": 64}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/cover-hero.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/cover-hero.canvas-spec.json new file mode 100644 index 00000000..d01ab4c9 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/cover-hero.canvas-spec.json @@ -0,0 +1,16 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "theme": {"colors": {"background": "#0F172A", "panel": "#111827", "primary": "#38BDF8", "accent": "#A78BFA", "text": "#F8FAFC", "muted": "#CBD5E1"}}, + "content": { + "eyebrow": "ARTBOARD P0", + "title": "受控画板生成", + "subtitle": "CanvasSpec 驱动模板,输出可进入 SVGlide 链路的协议 SVG。", + "chips": ["CanvasSpec", "Satori", "SVGlide"] + }, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 84, "y": 142, "width": 628, "height": 142}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/data-story.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/data-story.canvas-spec.json new file mode 100644 index 00000000..23a3a65f --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/data-story.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "data-story", + "theme_id": "glass-neon", + "theme": {"colors": {"background": "#090B1A", "panel": "#14172E", "primary": "#22D3EE", "accent": "#C084FC", "text": "#F8FAFC", "muted": "#BAC3D9"}}, + "content": {"eyebrow": "DATA", "title": "活动强度变化", "subtitle": "用少量指标讲清趋势。", "metrics": ["北区 42", "南区 35", "西区 28", "东区 19"], "callout": "北区持续升高,需要重点观测。"}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 96, "width": 600, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/image-feature.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/image-feature.canvas-spec.json new file mode 100644 index 00000000..0275c825 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/image-feature.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "image-feature", + "theme_id": "editorial-tritone", + "theme": {"colors": {"background": "#F6E7DF", "panel": "#FFF7F0", "primary": "#9F1239", "accent": "#D97706", "text": "#351A24", "muted": "#7C5B62"}}, + "content": {"eyebrow": "VISUAL", "title": "地貌证据", "subtitle": "图像作为证据锚点,文字负责解释。", "image_label": "LAVA FIELD", "caption": "占位图后续由受控图片资源替换。", "points": ["明确主视觉", "保留安全边距", "说明不遮挡图片"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 548, "y": 120, "width": 330, "height": 94}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/metric-dashboard.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/metric-dashboard.canvas-spec.json new file mode 100644 index 00000000..c7a2a65a --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/metric-dashboard.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "metric-dashboard", + "theme_id": "finance-dark", + "theme": {"colors": {"background": "#07110E", "panel": "#10201A", "primary": "#22C55E", "accent": "#F59E0B", "text": "#ECFDF5", "muted": "#A7C4B7"}}, + "content": {"eyebrow": "METRICS", "title": "研究指标概览", "subtitle": "关键指标保持在同一视觉系统内。", "metrics": ["样本 128", "站点 14", "误差 6%", "时长 30d", "风险 L2", "置信 92%"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 96, "width": 710, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/process-flow.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/process-flow.canvas-spec.json new file mode 100644 index 00000000..7c4cb547 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/process-flow.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "process-flow", + "theme_id": "dark-clarity", + "theme": {"colors": {"background": "#0F172A", "panel": "#111827", "primary": "#38BDF8", "accent": "#A78BFA", "text": "#F8FAFC", "muted": "#CBD5E1"}}, + "content": {"eyebrow": "FLOW", "title": "画板生成链路", "subtitle": "让内容、模板和导出职责分离。", "steps": ["Deck Plan", "CanvasSpec", "Template", "Satori", "SVGlide"], "conclusion": "每一步都留下 receipt,便于复盘。"}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 96, "width": 730, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/quote-focus.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/quote-focus.canvas-spec.json new file mode 100644 index 00000000..d345390d --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/quote-focus.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "quote-focus", + "theme_id": "paper-research", + "theme": {"colors": {"background": "#F7F3E8", "panel": "#FFFDF6", "primary": "#1E3A8A", "accent": "#B45309", "text": "#1F2937", "muted": "#6B7280"}}, + "content": {"title": "核心判断", "quote": "稳定的输入结构,比自由发挥更接近可控的美学。", "attribution": "SVGlide Artboard Review"}, + "semantic_elements": [{"element_id": "quote", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.quote", "bbox": {"x": 132, "y": 156, "width": 720, "height": 150}}], + "quality_constraints": {"max_title_lines": 3, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/research-poster.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/research-poster.canvas-spec.json new file mode 100644 index 00000000..439d91d0 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/research-poster.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "research-poster", + "theme_id": "paper-research", + "theme": {"colors": {"background": "#F7F3E8", "panel": "#FFFDF6", "primary": "#1E3A8A", "accent": "#B45309", "text": "#1F2937", "muted": "#6B7280"}}, + "content": {"eyebrow": "POSTER", "title": "冰岛火山研究", "authors": "SVGlide Research Group", "sections": ["背景", "方法", "数据", "模型", "发现", "影响"], "key_visual": "火山剖面"}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 86, "width": 588, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/risk-alert.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/risk-alert.canvas-spec.json new file mode 100644 index 00000000..7f5ad726 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/risk-alert.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "risk-alert", + "theme_id": "warm-editorial", + "theme": {"colors": {"background": "#27130F", "panel": "#3A211B", "primary": "#F97316", "accent": "#22D3EE", "text": "#FFF7ED", "muted": "#F4C9A8"}}, + "content": {"eyebrow": "RISK", "title": "观测风险", "subtitle": "提前把不确定性放进叙事。", "severity": "L2", "risks": ["云层遮挡", "样本延迟", "模型漂移", "解释误差"], "summary": "需要在导出前完成二次核验。"}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 96, "width": 690, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/roadmap-lanes.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/roadmap-lanes.canvas-spec.json new file mode 100644 index 00000000..f5653d30 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/roadmap-lanes.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "roadmap-lanes", + "theme_id": "forest-signal", + "theme": {"colors": {"background": "#0B1F1A", "panel": "#123329", "primary": "#34D399", "accent": "#FBBF24", "text": "#F7FCEB", "muted": "#B7D3C6"}}, + "content": {"eyebrow": "ROADMAP", "title": "后续建设", "subtitle": "从 P1 资产进入可用链路。", "lanes": ["模板扩充", "质量门禁", "PPE 验证", "线上发布"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 96, "width": 700, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/section-title.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/section-title.canvas-spec.json new file mode 100644 index 00000000..1b207148 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/section-title.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "section-title", + "theme_id": "blueprint-technical", + "theme": {"colors": {"background": "#071827", "panel": "#0E2A3F", "primary": "#5EEAD4", "accent": "#F97316", "text": "#EAF6FF", "muted": "#93B4C8"}}, + "content": {"eyebrow": "SECTION 01", "title": "研究背景", "subtitle": "用一页建立后续叙事的语境。"}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 104, "y": 176, "width": 680, "height": 122}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/summary-final.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/summary-final.canvas-spec.json new file mode 100644 index 00000000..8e16051e --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/summary-final.canvas-spec.json @@ -0,0 +1,16 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "summary-final", + "theme_id": "warm-editorial", + "theme": {"colors": {"background": "#27130F", "panel": "#3A211B", "primary": "#F97316", "accent": "#22D3EE", "text": "#FFF7ED", "muted": "#F4C9A8"}}, + "content": { + "eyebrow": "NEXT", + "title": "进入真实闭环", + "subtitle": "稳定多页画板系统后,再推进 PPE、live create 和 readback。", + "takeaways": ["多页门禁稳定", "resvg 预览进主路径", "推进 live/readback"] + }, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 72, "y": 110, "width": 700, "height": 110}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/timeline-steps.canvas-spec.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/timeline-steps.canvas-spec.json new file mode 100644 index 00000000..00a5a6f2 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/golden/timeline-steps.canvas-spec.json @@ -0,0 +1,11 @@ +{ + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "timeline-steps", + "theme_id": "cobalt-grid", + "theme": {"colors": {"background": "#081C4A", "panel": "#102C6B", "primary": "#60A5FA", "accent": "#FDE047", "text": "#EEF6FF", "muted": "#B7C8E8"}}, + "content": {"eyebrow": "TIMELINE", "title": "火山监测路径", "subtitle": "从异常信号到研究结论。", "events": ["卫星观测", "地震记录", "气体采样", "模型复盘", "风险沟通"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 56, "y": 96, "width": 760, "height": 88}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/01-project/project_manifest.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/01-project/project_manifest.json new file mode 100644 index 00000000..73740528 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/01-project/project_manifest.json @@ -0,0 +1,12 @@ +{ + "artifact_root": "skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover", + "cli_git_commit": "fixture", + "created_at": "2026-06-20T00:00:00+08:00", + "created_by": "lark-cli", + "deck_id": "artboard-p0a-cover", + "route": "svglide-svg", + "runner_version": "svglide-project-runner/v0", + "stage_graph": "svglide-workflow/v1", + "title": "Artboard P0a Cover", + "version": "svglide-project/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/01-project/state.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/01-project/state.json new file mode 100644 index 00000000..99b9de8c --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/01-project/state.json @@ -0,0 +1,13 @@ +{ + "created_at": "2026-06-20T00:00:00+08:00", + "current_stage": "init", + "runner_version": "svglide-project-runner/v0", + "stages": { + "init": { + "receipt": "receipts/init.json", + "status": "passed" + } + }, + "updated_at": "2026-06-20T00:00:00+08:00", + "version": "svglide-state/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/02-plan/plan-confirmation.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/02-plan/plan-confirmation.json new file mode 100644 index 00000000..884274fb --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/02-plan/plan-confirmation.json @@ -0,0 +1,8 @@ +{ + "confirmed_at": "2026-06-20T00:00:00+08:00", + "confirmed_by": "user", + "plan_path": "02-plan/slide_plan.json", + "plan_sha256": "ce50eeed63344af3e04f59291fc049c2409f4305716dbb3cade1e82ca7693fe1", + "status": "confirmed", + "version": "svglide-plan-confirmation/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/02-plan/slide_plan.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/02-plan/slide_plan.json new file mode 100644 index 00000000..7e99df24 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/02-plan/slide_plan.json @@ -0,0 +1,178 @@ +{ + "art_direction": { + "closing_treatment": "single-slide P0a fixture", + "cover_treatment": "dark technical cover with controlled typography", + "deck_motif": "controlled artboard pipeline", + "section_divider_treatment": "not used in P0a", + "svg_native_moments": [ + "protocol SVG cover", + "semantic text boxes", + "controlled decorative orbit" + ] + }, + "audience": "SVGlide engineers", + "deck_structure": [ + "cover", + "content", + "closing" + ], + "generation_mode": "artboard_satori", + "language": "zh-CN", + "loaded_rule_set": [ + "skills/lark-slides/references/lark-slides-create-svg.md", + "skills/lark-slides/references/style-presets.md", + "skills/lark-slides/references/svg-aesthetic-review.md", + "skills/lark-slides/references/svg-protocol.md", + "skills/lark-slides/references/svg-visual-recipes.md", + "skills/lark-slides/references/svglide-artifacts.spec.md", + "skills/lark-slides/references/svglide-assets.contract.md", + "skills/lark-slides/references/svglide-checks.checklist.md", + "skills/lark-slides/references/svglide-create-svg.contract.md", + "skills/lark-slides/references/svglide-generate-svg.contract.md", + "skills/lark-slides/references/svglide-lock.contract.md", + "skills/lark-slides/references/svglide-plan.contract.md", + "skills/lark-slides/references/svglide-planning-layer.md", + "skills/lark-slides/references/svglide-ppt-master-migration.matrix.md", + "skills/lark-slides/references/svglide-preview.spec.md", + "skills/lark-slides/references/svglide-readback.contract.md", + "skills/lark-slides/references/svglide-route-admission.md", + "skills/lark-slides/references/svglide-svg-private.rules.json", + "skills/lark-slides/references/svglide-validation-checklist.md", + "skills/lark-slides/references/svglide-visual-planning.md", + "skills/lark-slides/references/svglide-workflow.spec.md" + ], + "plan_path": "02-plan/slide_plan.json", + "quality_gates": { + "no_debug_guides": true, + "no_text_overflow": true, + "no_xml_like_pages": true + }, + "route": "svglide-svg", + "slides": [ + { + "body_points": [ + "CanvasSpec 负责语义和内容结构", + "Satori-compatible preview 只作为上游预览证据", + "SVGlide protocol SVG 继续进入现有质量门禁" + ], + "canvas_spec": { + "canvas": { + "height": 540, + "viewBox": "0 0 960 540", + "width": 960 + }, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "content": { + "chips": [ + "CanvasSpec", + "Satori Preview", + "SVGlide SVG" + ], + "eyebrow": "ARTBOARD P0A", + "subtitle": "CanvasSpec 驱动模板,输出可进入 SVGlide 后续链路的协议 SVG。", + "title": "受控画板生成" + }, + "semantic_elements": [ + { + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title", + "bbox": {"x": 84, "y": 142, "width": 628, "height": 142} + } + ], + "quality_constraints": { + "max_title_lines": 2, + "min_font_size": 18, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460} + }, + "template_id": "cover-hero", + "theme": { + "colors": { + "accent": "#A78BFA", + "background": "#0F172A", + "muted": "#CBD5E1", + "panel": "#111827", + "primary": "#38BDF8", + "text": "#F8FAFC" + } + }, + "theme_id": "dark-clarity", + "version": "svglide-canvas-spec/v1" + }, + "content_density_contract": "medium", + "asset_contract": "none_required", + "key_message": "P0a 证明受控画板链路可以进入现有 SVGlide delivery stages。", + "layout_family": "cover", + "page": 1, + "page_type": "cover", + "renderer_id": "artboard_cover_hero", + "required_primitives": [ + "typography", + "geometric_shape" + ], + "risk_flags": [], + "role": "thesis", + "section": "开场", + "source_policy": "source-backed", + "source_refs": [ + "source:item-001", + "source:item-002", + "source:item-003" + ], + "svg_effects": [ + "typography" + ], + "svg_primitives": [ + "typography", + "geometric_shape", + "foreignObject", + "rect", + "circle" + ], + "title": "受控画板生成", + "visual_focal_point": "title", + "visual_intent": "validate controlled artboard rendering", + "visual_recipe": "hero_typography", + "visual_signature": "dark cover panel with orbit accent", + "xml_like_risk": "low" + } + ], + "style_preset": "cobalt_bloom", + "style_selection_reason": "single fixture for controlled artboard rendering", + "style_system": { + "background_strategy": "solid", + "motif": "orbit panel", + "palette": [ + "#0F172A", + "#38BDF8", + "#A78BFA" + ], + "typography": "Inter-compatible system" + }, + "title": "Artboard P0a Cover", + "visual_identity": { + "design_dna": { + "component_bias": "controlled cover panel, semantic text blocks, orbit accent", + "image_treatment": "no image required for P0a fixture", + "layout_motif": "controlled artboard pipeline", + "palette": "technical clarity", + "shape_language": "solid rectangles, orbit circles, clear text containers", + "theme_visual_anchors": [ + "CanvasSpec", + "Satori-compatible preview", + "SVGlide protocol SVG" + ] + }, + "distinctness_target": { + "palette_overlap_max": 0.67, + "renderer_sequence_similarity_max": 0.75 + }, + "forbidden_reuse": { + "avoid_default_skeleton": true, + "avoid_same_cover_structure": true, + "recent_decks": 5 + }, + "theme_archetype": "general_explainer" + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/03-assets/assets.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/03-assets/assets.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/03-assets/assets.json @@ -0,0 +1 @@ +{} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/receipts/init.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/receipts/init.json new file mode 100644 index 00000000..ed2b119f --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/receipts/init.json @@ -0,0 +1,17 @@ +{ + "command": [ + "init", + "--deck-id", + "artboard-p0a-cover" + ], + "ended_at": "2026-06-20T00:00:00+08:00", + "error": null, + "inputs": [], + "outputs": [ + "01-project/project_manifest.json", + "01-project/state.json" + ], + "stage": "init", + "started_at": "2026-06-20T00:00:00+08:00", + "status": "passed" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/source/evidence.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/source/evidence.json new file mode 100644 index 00000000..82783372 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/source/evidence.json @@ -0,0 +1,18 @@ +{ + "items": [ + { + "id": "source:item-001", + "text": "SVGlide Artboard P0a validates a controlled CanvasSpec input before producing protocol SVG." + }, + { + "id": "source:item-002", + "text": "The renderer keeps CanvasSpec as the semantic source of truth and records artboard receipts." + }, + { + "id": "source:item-003", + "text": "The existing prepare, preflight, quality gate, and dry-run stages remain the downstream delivery path." + } + ], + "schema_version": "svglide-evidence/v1", + "source_status": "ready" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/source/source-notes.md b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/source/source-notes.md new file mode 100644 index 00000000..faa9cbde --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0a-cover/source/source-notes.md @@ -0,0 +1,3 @@ +# Artboard P0a Fixture + +This fixture validates the `CanvasSpec -> Satori-compatible preview -> SVGlide protocol SVG` path for a one-page cover slide. diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/01-project/project_manifest.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/01-project/project_manifest.json new file mode 100644 index 00000000..d33e1a3e --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/01-project/project_manifest.json @@ -0,0 +1,12 @@ +{ + "artifact_root": "skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page", + "cli_git_commit": "fixture", + "created_at": "2026-06-20T00:00:00+08:00", + "created_by": "lark-cli", + "deck_id": "artboard-p0b-three-page", + "route": "svglide-svg", + "runner_version": "svglide-project-runner/v0", + "stage_graph": "svglide-workflow/v1", + "title": "Artboard P0b Three Page", + "version": "svglide-project/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/01-project/state.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/01-project/state.json new file mode 100644 index 00000000..99b9de8c --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/01-project/state.json @@ -0,0 +1,13 @@ +{ + "created_at": "2026-06-20T00:00:00+08:00", + "current_stage": "init", + "runner_version": "svglide-project-runner/v0", + "stages": { + "init": { + "receipt": "receipts/init.json", + "status": "passed" + } + }, + "updated_at": "2026-06-20T00:00:00+08:00", + "version": "svglide-state/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/02-plan/plan-confirmation.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/02-plan/plan-confirmation.json new file mode 100644 index 00000000..b92eaca7 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/02-plan/plan-confirmation.json @@ -0,0 +1,8 @@ +{ + "confirmed_at": "2026-06-20T00:00:00+08:00", + "confirmed_by": "user", + "plan_path": "02-plan/slide_plan.json", + "plan_sha256": "b5056c3e6887aeb01724e2af2497127bc952c696459f33fa86f51b5fef715fd3", + "status": "confirmed", + "version": "svglide-plan-confirmation/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/02-plan/slide_plan.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/02-plan/slide_plan.json new file mode 100644 index 00000000..d21457e3 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/02-plan/slide_plan.json @@ -0,0 +1,175 @@ +{ + "art_direction": { + "closing_treatment": "section title closing page", + "cover_treatment": "dark technical cover with controlled typography", + "deck_motif": "controlled artboard pipeline", + "section_divider_treatment": "comparison page as middle section", + "svg_native_moments": ["semantic cover", "comparison structure", "closing takeaway"] + }, + "audience": "SVGlide engineers", + "deck_structure": ["cover", "content", "closing"], + "generation_mode": "artboard_satori", + "language": "zh-CN", + "loaded_rule_set": [ + "skills/lark-slides/references/lark-slides-create-svg.md", + "skills/lark-slides/references/style-presets.md", + "skills/lark-slides/references/svg-aesthetic-review.md", + "skills/lark-slides/references/svg-protocol.md", + "skills/lark-slides/references/svg-visual-recipes.md", + "skills/lark-slides/references/svglide-artifacts.spec.md", + "skills/lark-slides/references/svglide-assets.contract.md", + "skills/lark-slides/references/svglide-checks.checklist.md", + "skills/lark-slides/references/svglide-create-svg.contract.md", + "skills/lark-slides/references/svglide-generate-svg.contract.md", + "skills/lark-slides/references/svglide-lock.contract.md", + "skills/lark-slides/references/svglide-plan.contract.md", + "skills/lark-slides/references/svglide-planning-layer.md", + "skills/lark-slides/references/svglide-ppt-master-migration.matrix.md", + "skills/lark-slides/references/svglide-preview.spec.md", + "skills/lark-slides/references/svglide-readback.contract.md", + "skills/lark-slides/references/svglide-route-admission.md", + "skills/lark-slides/references/svglide-svg-private.rules.json", + "skills/lark-slides/references/svglide-validation-checklist.md", + "skills/lark-slides/references/svglide-visual-planning.md", + "skills/lark-slides/references/svglide-workflow.spec.md" + ], + "plan_path": "02-plan/slide_plan.json", + "quality_gates": {"no_debug_guides": true, "no_text_overflow": true, "no_xml_like_pages": true}, + "route": "svglide-svg", + "slides": [ + { + "page": 1, + "page_type": "cover", + "section": "开场", + "role": "thesis", + "title": "画板链路 P0b", + "key_message": "P0b 验证多页面板链路、模板选择和 receipt 闭环。", + "body_points": ["三页结构验证页序", "模板输出进入现有门禁", "多页 receipt 保持新鲜"], + "source_refs": ["source:item-001", "source:item-002"], + "renderer_id": "artboard_satori.cover-hero", + "layout_family": "cover", + "visual_recipe": "hero_typography", + "visual_intent": "验证多页画板封面", + "visual_focal_point": "标题", + "visual_signature": "cover panel and orbit accent", + "svg_effects": ["typography"], + "required_primitives": ["typography", "geometric_shape"], + "svg_primitives": ["typography", "geometric_shape", "foreignObject", "rect", "circle"], + "xml_like_risk": "low", + "content_density_contract": "medium", + "risk_flags": [], + "source_policy": "source-backed", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "theme": {"colors": {"background": "#0F172A", "panel": "#111827", "primary": "#38BDF8", "accent": "#A78BFA", "text": "#F8FAFC", "muted": "#CBD5E1"}}, + "content": {"eyebrow": "ARTBOARD P0B", "title": "画板链路 P0b", "subtitle": "三页 fixture 验证模板系统、质量门禁和 dry-run 编排。", "chips": ["Cover", "Comparison", "Closing"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 84, "y": 142, "width": 628, "height": 142}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + }, + { + "page": 2, + "page_type": "content", + "section": "对比", + "role": "evidence", + "title": "新旧链路差异", + "key_message": "新链路把审美和结构放进受控模板,而不是让模型自由写 SVG。", + "body_points": ["旧链路直接产出 SVG", "新链路先产出 CanvasSpec", "质量门禁检查 artboard receipt"], + "source_refs": ["source:item-002", "source:item-003"], + "renderer_id": "artboard_satori.comparison-cards", + "layout_family": "comparison", + "visual_recipe": "icon_capability_map", + "visual_intent": "比较 direct SVG 与 artboard_satori", + "visual_focal_point": "左右对比", + "visual_signature": "two-column comparison", + "svg_effects": ["typography"], + "required_primitives": ["icon", "geometric_shape"], + "svg_primitives": ["icon", "geometric_shape", "typography", "foreignObject", "rect", "circle"], + "xml_like_risk": "low", + "content_density_contract": "medium", + "risk_flags": [], + "source_policy": "source-backed", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "comparison-cards", + "theme_id": "forest-signal", + "theme": {"colors": {"background": "#0B1F1A", "panel": "#123329", "primary": "#34D399", "accent": "#FBBF24", "text": "#F7FCEB", "muted": "#B7D3C6"}}, + "content": { + "title": "新旧链路差异", + "left_title": "旧链路", + "right_title": "新链路", + "left_points": ["直接生成 SVG", "审美分散在 prompt", "门禁只看最终文件"], + "right_points": ["先生成 CanvasSpec", "模板和主题统一审美", "receipt 绑定每页画板"], + "conclusion": "核心变化是把美学标准从自由生成迁移到受控设计系统。" + }, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 64, "y": 52, "width": 760, "height": 64}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + }, + { + "page": 3, + "page_type": "closing", + "section": "结论", + "role": "takeaway", + "title": "下一步进入真实 Satori", + "key_message": "P0b 通过后,才能安全替换底层 preview renderer。", + "body_points": ["多页门禁先稳定", "再接真实 Satori 子包", "最后推进 PPE/live/readback"], + "source_refs": ["source:item-003", "source:item-004"], + "renderer_id": "artboard_satori.summary-final", + "layout_family": "closing", + "visual_recipe": "brand_system", + "visual_intent": "总结后续推进顺序", + "visual_focal_point": "结论标题", + "visual_signature": "closing section title", + "svg_effects": ["typography"], + "required_primitives": ["typography", "geometric_shape"], + "svg_primitives": ["typography", "geometric_shape", "foreignObject", "rect", "circle"], + "xml_like_risk": "low", + "content_density_contract": "medium", + "risk_flags": [], + "source_policy": "source-backed", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "summary-final", + "theme_id": "warm-editorial", + "theme": {"colors": {"background": "#27130F", "panel": "#3A211B", "primary": "#F97316", "accent": "#22D3EE", "text": "#FFF7ED", "muted": "#F4C9A8"}}, + "content": { + "eyebrow": "NEXT", + "title": "下一步进入真实 Satori", + "subtitle": "先稳定多页画板系统,再替换底层 renderer 并推进 live/readback。", + "takeaways": ["多页门禁先稳定", "resvg 预览进入主路径", "再推进 PPE/live/readback"] + }, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 72, "y": 110, "width": 700, "height": 110}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + } + ], + "style_preset": "cobalt_bloom", + "style_selection_reason": "uses existing preset for SVGlide gate compatibility", + "style_system": {"background_strategy": "solid", "motif": "artboard pipeline", "palette": ["#0F172A", "#34D399", "#F97316"], "typography": "Inter-compatible system"}, + "title": "Artboard P0b Three Page", + "visual_identity": { + "theme_archetype": "general_explainer", + "design_dna": { + "palette": "technical clarity", + "layout_motif": "controlled artboard pipeline", + "shape_language": "solid panels, comparison cards, clear text containers", + "image_treatment": "no image required for P0b fixture", + "component_bias": "cover panel, comparison cards, section title", + "theme_visual_anchors": ["CanvasSpec", "Satori-compatible preview", "SVGlide protocol SVG"] + }, + "forbidden_reuse": {"recent_decks": 5, "avoid_default_skeleton": true, "avoid_same_cover_structure": true}, + "distinctness_target": {"palette_overlap_max": 0.67, "renderer_sequence_similarity_max": 0.75} + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/03-assets/assets.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/03-assets/assets.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/03-assets/assets.json @@ -0,0 +1 @@ +{} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/receipts/init.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/receipts/init.json new file mode 100644 index 00000000..84f26ece --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/receipts/init.json @@ -0,0 +1,10 @@ +{ + "command": ["init", "--deck-id", "artboard-p0b-three-page"], + "ended_at": "2026-06-20T00:00:00+08:00", + "error": null, + "inputs": [], + "outputs": ["01-project/project_manifest.json", "01-project/state.json"], + "stage": "init", + "started_at": "2026-06-20T00:00:00+08:00", + "status": "passed" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/source/evidence.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/source/evidence.json new file mode 100644 index 00000000..69bcd8ec --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/source/evidence.json @@ -0,0 +1,10 @@ +{ + "items": [ + {"id": "source:item-001", "text": "P0b fixture uses three pages to validate page order and receipt consistency."}, + {"id": "source:item-002", "text": "CanvasSpec keeps semantic input structured before template rendering."}, + {"id": "source:item-003", "text": "Template fit, preflight, semantic review, runtime review, and quality gate must all pass before dry-run."}, + {"id": "source:item-004", "text": "Satori-compatible preview artifacts are recorded separately from final SVGlide protocol SVG."} + ], + "schema_version": "svglide-evidence/v1", + "source_status": "ready" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/source/source-notes.md b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/source/source-notes.md new file mode 100644 index 00000000..7373f5d8 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0b-three-page/source/source-notes.md @@ -0,0 +1,3 @@ +# Artboard P0b Fixture + +This fixture validates three controlled CanvasSpec pages across cover, comparison, and closing templates. diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/01-project/project_manifest.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/01-project/project_manifest.json new file mode 100644 index 00000000..4d1959b3 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/01-project/project_manifest.json @@ -0,0 +1,12 @@ +{ + "artifact_root": "skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live", + "cli_git_commit": "fixture", + "created_at": "2026-06-20T00:00:00+08:00", + "created_by": "lark-cli", + "deck_id": "artboard-p0c-live", + "route": "svglide-svg", + "runner_version": "svglide-project-runner/v0", + "stage_graph": "svglide-workflow/v1", + "title": "Artboard P0c Live", + "version": "svglide-project/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/01-project/state.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/01-project/state.json new file mode 100644 index 00000000..99b9de8c --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/01-project/state.json @@ -0,0 +1,13 @@ +{ + "created_at": "2026-06-20T00:00:00+08:00", + "current_stage": "init", + "runner_version": "svglide-project-runner/v0", + "stages": { + "init": { + "receipt": "receipts/init.json", + "status": "passed" + } + }, + "updated_at": "2026-06-20T00:00:00+08:00", + "version": "svglide-state/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/02-plan/plan-confirmation.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/02-plan/plan-confirmation.json new file mode 100644 index 00000000..b92eaca7 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/02-plan/plan-confirmation.json @@ -0,0 +1,8 @@ +{ + "confirmed_at": "2026-06-20T00:00:00+08:00", + "confirmed_by": "user", + "plan_path": "02-plan/slide_plan.json", + "plan_sha256": "b5056c3e6887aeb01724e2af2497127bc952c696459f33fa86f51b5fef715fd3", + "status": "confirmed", + "version": "svglide-plan-confirmation/v1" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/02-plan/slide_plan.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/02-plan/slide_plan.json new file mode 100644 index 00000000..d21457e3 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/02-plan/slide_plan.json @@ -0,0 +1,175 @@ +{ + "art_direction": { + "closing_treatment": "section title closing page", + "cover_treatment": "dark technical cover with controlled typography", + "deck_motif": "controlled artboard pipeline", + "section_divider_treatment": "comparison page as middle section", + "svg_native_moments": ["semantic cover", "comparison structure", "closing takeaway"] + }, + "audience": "SVGlide engineers", + "deck_structure": ["cover", "content", "closing"], + "generation_mode": "artboard_satori", + "language": "zh-CN", + "loaded_rule_set": [ + "skills/lark-slides/references/lark-slides-create-svg.md", + "skills/lark-slides/references/style-presets.md", + "skills/lark-slides/references/svg-aesthetic-review.md", + "skills/lark-slides/references/svg-protocol.md", + "skills/lark-slides/references/svg-visual-recipes.md", + "skills/lark-slides/references/svglide-artifacts.spec.md", + "skills/lark-slides/references/svglide-assets.contract.md", + "skills/lark-slides/references/svglide-checks.checklist.md", + "skills/lark-slides/references/svglide-create-svg.contract.md", + "skills/lark-slides/references/svglide-generate-svg.contract.md", + "skills/lark-slides/references/svglide-lock.contract.md", + "skills/lark-slides/references/svglide-plan.contract.md", + "skills/lark-slides/references/svglide-planning-layer.md", + "skills/lark-slides/references/svglide-ppt-master-migration.matrix.md", + "skills/lark-slides/references/svglide-preview.spec.md", + "skills/lark-slides/references/svglide-readback.contract.md", + "skills/lark-slides/references/svglide-route-admission.md", + "skills/lark-slides/references/svglide-svg-private.rules.json", + "skills/lark-slides/references/svglide-validation-checklist.md", + "skills/lark-slides/references/svglide-visual-planning.md", + "skills/lark-slides/references/svglide-workflow.spec.md" + ], + "plan_path": "02-plan/slide_plan.json", + "quality_gates": {"no_debug_guides": true, "no_text_overflow": true, "no_xml_like_pages": true}, + "route": "svglide-svg", + "slides": [ + { + "page": 1, + "page_type": "cover", + "section": "开场", + "role": "thesis", + "title": "画板链路 P0b", + "key_message": "P0b 验证多页面板链路、模板选择和 receipt 闭环。", + "body_points": ["三页结构验证页序", "模板输出进入现有门禁", "多页 receipt 保持新鲜"], + "source_refs": ["source:item-001", "source:item-002"], + "renderer_id": "artboard_satori.cover-hero", + "layout_family": "cover", + "visual_recipe": "hero_typography", + "visual_intent": "验证多页画板封面", + "visual_focal_point": "标题", + "visual_signature": "cover panel and orbit accent", + "svg_effects": ["typography"], + "required_primitives": ["typography", "geometric_shape"], + "svg_primitives": ["typography", "geometric_shape", "foreignObject", "rect", "circle"], + "xml_like_risk": "low", + "content_density_contract": "medium", + "risk_flags": [], + "source_policy": "source-backed", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "theme": {"colors": {"background": "#0F172A", "panel": "#111827", "primary": "#38BDF8", "accent": "#A78BFA", "text": "#F8FAFC", "muted": "#CBD5E1"}}, + "content": {"eyebrow": "ARTBOARD P0B", "title": "画板链路 P0b", "subtitle": "三页 fixture 验证模板系统、质量门禁和 dry-run 编排。", "chips": ["Cover", "Comparison", "Closing"]}, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 84, "y": 142, "width": 628, "height": 142}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + }, + { + "page": 2, + "page_type": "content", + "section": "对比", + "role": "evidence", + "title": "新旧链路差异", + "key_message": "新链路把审美和结构放进受控模板,而不是让模型自由写 SVG。", + "body_points": ["旧链路直接产出 SVG", "新链路先产出 CanvasSpec", "质量门禁检查 artboard receipt"], + "source_refs": ["source:item-002", "source:item-003"], + "renderer_id": "artboard_satori.comparison-cards", + "layout_family": "comparison", + "visual_recipe": "icon_capability_map", + "visual_intent": "比较 direct SVG 与 artboard_satori", + "visual_focal_point": "左右对比", + "visual_signature": "two-column comparison", + "svg_effects": ["typography"], + "required_primitives": ["icon", "geometric_shape"], + "svg_primitives": ["icon", "geometric_shape", "typography", "foreignObject", "rect", "circle"], + "xml_like_risk": "low", + "content_density_contract": "medium", + "risk_flags": [], + "source_policy": "source-backed", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "comparison-cards", + "theme_id": "forest-signal", + "theme": {"colors": {"background": "#0B1F1A", "panel": "#123329", "primary": "#34D399", "accent": "#FBBF24", "text": "#F7FCEB", "muted": "#B7D3C6"}}, + "content": { + "title": "新旧链路差异", + "left_title": "旧链路", + "right_title": "新链路", + "left_points": ["直接生成 SVG", "审美分散在 prompt", "门禁只看最终文件"], + "right_points": ["先生成 CanvasSpec", "模板和主题统一审美", "receipt 绑定每页画板"], + "conclusion": "核心变化是把美学标准从自由生成迁移到受控设计系统。" + }, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 64, "y": 52, "width": 760, "height": 64}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + }, + { + "page": 3, + "page_type": "closing", + "section": "结论", + "role": "takeaway", + "title": "下一步进入真实 Satori", + "key_message": "P0b 通过后,才能安全替换底层 preview renderer。", + "body_points": ["多页门禁先稳定", "再接真实 Satori 子包", "最后推进 PPE/live/readback"], + "source_refs": ["source:item-003", "source:item-004"], + "renderer_id": "artboard_satori.summary-final", + "layout_family": "closing", + "visual_recipe": "brand_system", + "visual_intent": "总结后续推进顺序", + "visual_focal_point": "结论标题", + "visual_signature": "closing section title", + "svg_effects": ["typography"], + "required_primitives": ["typography", "geometric_shape"], + "svg_primitives": ["typography", "geometric_shape", "foreignObject", "rect", "circle"], + "xml_like_risk": "low", + "content_density_contract": "medium", + "risk_flags": [], + "source_policy": "source-backed", + "asset_contract": "none_required", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "summary-final", + "theme_id": "warm-editorial", + "theme": {"colors": {"background": "#27130F", "panel": "#3A211B", "primary": "#F97316", "accent": "#22D3EE", "text": "#FFF7ED", "muted": "#F4C9A8"}}, + "content": { + "eyebrow": "NEXT", + "title": "下一步进入真实 Satori", + "subtitle": "先稳定多页画板系统,再替换底层 renderer 并推进 live/readback。", + "takeaways": ["多页门禁先稳定", "resvg 预览进入主路径", "再推进 PPE/live/readback"] + }, + "semantic_elements": [{"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 72, "y": 110, "width": 700, "height": 110}}], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}} + } + } + ], + "style_preset": "cobalt_bloom", + "style_selection_reason": "uses existing preset for SVGlide gate compatibility", + "style_system": {"background_strategy": "solid", "motif": "artboard pipeline", "palette": ["#0F172A", "#34D399", "#F97316"], "typography": "Inter-compatible system"}, + "title": "Artboard P0b Three Page", + "visual_identity": { + "theme_archetype": "general_explainer", + "design_dna": { + "palette": "technical clarity", + "layout_motif": "controlled artboard pipeline", + "shape_language": "solid panels, comparison cards, clear text containers", + "image_treatment": "no image required for P0b fixture", + "component_bias": "cover panel, comparison cards, section title", + "theme_visual_anchors": ["CanvasSpec", "Satori-compatible preview", "SVGlide protocol SVG"] + }, + "forbidden_reuse": {"recent_decks": 5, "avoid_default_skeleton": true, "avoid_same_cover_structure": true}, + "distinctness_target": {"palette_overlap_max": 0.67, "renderer_sequence_similarity_max": 0.75} + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/03-assets/assets.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/03-assets/assets.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/03-assets/assets.json @@ -0,0 +1 @@ +{} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/07-create/ppe-proof.input.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/07-create/ppe-proof.input.json new file mode 100644 index 00000000..3f0b7959 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/07-create/ppe-proof.input.json @@ -0,0 +1,29 @@ +{ + "status": "passed", + "environment": { + "name": "Pre_release", + "x-tt-env": "ppe_pure_svg" + }, + "auth": { + "identity": "user" + }, + "proxy": { + "mode": "whistle", + "capture": true, + "http_proxy": "http://127.0.0.1:8899", + "https_proxy": "http://127.0.0.1:8899", + "rewrite_host": "open.feishu-pre.cn", + "rule_file": "skills/lark-slides/references/ppe-pure-svg.whistle.js", + "rule_sha256": "641d01be2b2ea6b7a57a21302ee45cf10cc60d247132f50681966b88481a8487", + "inject_headers": { + "Env": "Pre_release", + "x-tt-env": "ppe_pure_svg" + } + }, + "headers": { + "x-tt-env": "ppe_pure_svg" + }, + "route": { + "name": "slides +create-svg" + } +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/receipts/init.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/receipts/init.json new file mode 100644 index 00000000..84f26ece --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/receipts/init.json @@ -0,0 +1,10 @@ +{ + "command": ["init", "--deck-id", "artboard-p0b-three-page"], + "ended_at": "2026-06-20T00:00:00+08:00", + "error": null, + "inputs": [], + "outputs": ["01-project/project_manifest.json", "01-project/state.json"], + "stage": "init", + "started_at": "2026-06-20T00:00:00+08:00", + "status": "passed" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/source/evidence.json b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/source/evidence.json new file mode 100644 index 00000000..69bcd8ec --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/source/evidence.json @@ -0,0 +1,10 @@ +{ + "items": [ + {"id": "source:item-001", "text": "P0b fixture uses three pages to validate page order and receipt consistency."}, + {"id": "source:item-002", "text": "CanvasSpec keeps semantic input structured before template rendering."}, + {"id": "source:item-003", "text": "Template fit, preflight, semantic review, runtime review, and quality gate must all pass before dry-run."}, + {"id": "source:item-004", "text": "Satori-compatible preview artifacts are recorded separately from final SVGlide protocol SVG."} + ], + "schema_version": "svglide-evidence/v1", + "source_status": "ready" +} diff --git a/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/source/source-notes.md b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/source/source-notes.md new file mode 100644 index 00000000..7373f5d8 --- /dev/null +++ b/skills/lark-slides/scripts/fixtures/svglide_artboard/p0c-live/source/source-notes.md @@ -0,0 +1,3 @@ +# Artboard P0b Fixture + +This fixture validates three controlled CanvasSpec pages across cover, comparison, and closing templates. diff --git a/skills/lark-slides/scripts/svglide_artboard_final_acceptance.py b/skills/lark-slides/scripts/svglide_artboard_final_acceptance.py new file mode 100644 index 00000000..fbd911ae --- /dev/null +++ b/skills/lark-slides/scripts/svglide_artboard_final_acceptance.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import json +import re +import sys +from datetime import datetime +from pathlib import Path +from typing import Any + + +CHECK_VERSION = "svglide-artboard-final-acceptance/v1" +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = SCRIPT_DIR.parents[2] +REFERENCES_DIR = SCRIPT_DIR.parent / "references" +DEFAULT_PLAN = Path("/Users/bytedance/Downloads/PLAN.md") +FINAL_CHECK = Path("06-check/final-acceptance-check.json") +FINAL_RECEIPT = Path("receipts/final-acceptance-check.json") +ACTION_GUIDE = REFERENCES_DIR / "svglide-artboard-full-plan-action.md" +SCOPE_DEFERRALS = REFERENCES_DIR / "svglide-artboard-gate12-scope.md" +PACKAGE_RECEIPT = SCRIPT_DIR / "fixtures/svglide_artboard/gate11_package/receipts/artboard-package-check.json" +INSTRUCTION_PROJECT = REPO_ROOT / ".tmp/svglide-p0c-gate7-live6" +INSTRUCTION_ADHERENCE_CHECK = INSTRUCTION_PROJECT / "06-check/instruction-adherence.json" +INSTRUCTION_ADHERENCE_RECEIPT = INSTRUCTION_PROJECT / "receipts/instruction-adherence.json" + +REQUIRED_GATE_EVIDENCE = { + "0": "svglide-artboard-gate0-gate1-evidence.md", + "1": "svglide-artboard-gate0-gate1-evidence.md", + "2": "svglide-artboard-gate2-evidence.md", + "3": "svglide-artboard-gate3-evidence.md", + "4": "svglide-artboard-gate4-evidence.md", + "5": "svglide-artboard-gate5-evidence.md", + "6": "svglide-artboard-gate6-evidence.md", + "7": "svglide-artboard-gate7-evidence.md", + "8": "svglide-artboard-gate8-evidence.md", + "9": "svglide-artboard-gate9-evidence.md", + "10": "svglide-artboard-gate10-evidence.md", + "11": "svglide-artboard-gate11-evidence.md", + "12a": "svglide-artboard-gate12a-evidence.md", +} + + +class FinalAcceptanceError(Exception): + pass + + +def now_iso() -> str: + return datetime.now().astimezone().replace(microsecond=0).isoformat() + + +def read_json(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as err: + raise FinalAcceptanceError(f"missing JSON file: {path}") from err + except json.JSONDecodeError as err: + raise FinalAcceptanceError(f"invalid JSON file: {path}: {err}") from err + if not isinstance(payload, dict): + raise FinalAcceptanceError(f"expected JSON object: {path}") + return payload + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def file_sha256(path: Path) -> str: + import hashlib + + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def repo_rel(path: Path, repo_root: Path = REPO_ROOT) -> str: + try: + return path.resolve().relative_to(repo_root.resolve()).as_posix() + except ValueError: + return path.as_posix() + + +def extract_gate_statuses(action_text: str) -> dict[str, dict[str, str]]: + statuses: dict[str, dict[str, str]] = {} + pattern = re.compile(r"^\| (?P\d+[a-z]?) [^|]* \| (?P[^|]+) \| (?P[^|]+) \| (?P[^|]+) \|", re.MULTILINE) + for match in pattern.finditer(action_text): + gate = match.group("gate") + statuses[gate] = { + "status": match.group("status").strip(), + "owner": match.group("owner").strip(), + "reviewer_verdict": match.group("verdict").strip(), + } + return statuses + + +def verify_hashed_file( + issues: list[dict[str, str]], + project: Path, + label: str, + record: dict[str, Any], +) -> dict[str, Any]: + rel = record.get("path") + expected_sha = record.get("sha256") + evidence = {"label": label, "path": rel, "matched": False} + if not isinstance(rel, str) or not isinstance(expected_sha, str): + issues.append({"code": "instruction_adherence_hash_record_invalid", "message": f"{label} hash record is incomplete"}) + return evidence + path = project / rel + evidence["exists"] = path.exists() + if not path.exists(): + issues.append({"code": "instruction_adherence_hashed_file_missing", "message": f"{label} file missing: {rel}"}) + return evidence + actual_sha = file_sha256(path) + evidence["sha256"] = actual_sha + evidence["matched"] = actual_sha == expected_sha + if actual_sha != expected_sha: + issues.append({"code": "instruction_adherence_hash_stale", "message": f"{label} hash is stale for {rel}"}) + return evidence + + +def validate_instruction_adherence_current(issues: list[dict[str, str]]) -> dict[str, Any]: + evidence: dict[str, Any] = { + "project": repo_rel(INSTRUCTION_PROJECT), + "check_path": repo_rel(INSTRUCTION_ADHERENCE_CHECK), + "receipt_path": repo_rel(INSTRUCTION_ADHERENCE_RECEIPT), + "hash_checks": [], + } + if not INSTRUCTION_ADHERENCE_CHECK.exists(): + issues.append({"code": "instruction_adherence_check_missing", "message": f"missing {repo_rel(INSTRUCTION_ADHERENCE_CHECK)}"}) + return evidence + if not INSTRUCTION_ADHERENCE_RECEIPT.exists(): + issues.append({"code": "instruction_adherence_receipt_missing", "message": f"missing {repo_rel(INSTRUCTION_ADHERENCE_RECEIPT)}"}) + return evidence + + check = read_json(INSTRUCTION_ADHERENCE_CHECK) + receipt = read_json(INSTRUCTION_ADHERENCE_RECEIPT) + evidence["status"] = check.get("status") + evidence["receipt_status"] = receipt.get("status") + if check.get("status") != "passed": + issues.append({"code": "instruction_adherence_not_passed", "message": "instruction adherence check must have status=passed"}) + if receipt.get("status") != "passed": + issues.append({"code": "instruction_adherence_receipt_not_passed", "message": "instruction adherence receipt must have status=passed"}) + if file_sha256(INSTRUCTION_ADHERENCE_CHECK) != file_sha256(INSTRUCTION_ADHERENCE_RECEIPT): + issues.append({"code": "instruction_adherence_check_receipt_mismatch", "message": "instruction adherence check and receipt must match"}) + + for label, key in ( + ("instruction", "instruction"), + ("deck_plan", "deck_plan"), + ("slide_plan", "slide_plan"), + ("final_plan", "plan"), + ): + record = check.get(key) + if isinstance(record, dict): + evidence["hash_checks"].append(verify_hashed_file(issues, INSTRUCTION_PROJECT, label, record)) + else: + issues.append({"code": "instruction_adherence_hash_record_missing", "message": f"missing {key} hash record"}) + + for index, record in enumerate(check.get("output_pages") or []): + if isinstance(record, dict): + evidence["hash_checks"].append(verify_hashed_file(issues, INSTRUCTION_PROJECT, f"output_page_{index + 1}", record)) + readback = check.get("readback") if isinstance(check.get("readback"), dict) else {} + for label, path_key, sha_key in ( + ("readback_check", "check_path", "check_sha256"), + ("readback_raw", "raw_path", "raw_sha256"), + ): + evidence["hash_checks"].append(verify_hashed_file(issues, INSTRUCTION_PROJECT, label, {"path": readback.get(path_key), "sha256": readback.get(sha_key)})) + binding_checks = readback.get("binding_checks") + evidence["readback_binding_checks"] = binding_checks + if not isinstance(binding_checks, list) or not binding_checks: + issues.append({"code": "instruction_adherence_binding_missing", "message": "readback binding checks are missing from instruction adherence"}) + else: + for item in binding_checks: + if not isinstance(item, dict) or item.get("matched") is not True: + issues.append({"code": "instruction_adherence_binding_stale", "message": "all readback binding checks must be matched=true"}) + break + if check.get("issues"): + issues.append({"code": "instruction_adherence_has_issues", "message": "instruction adherence issues must be empty"}) + return evidence + + +def validate_final_acceptance(plan_path: Path = DEFAULT_PLAN) -> dict[str, Any]: + issues: list[dict[str, str]] = [] + evidence: list[dict[str, Any]] = [] + + action_text = ACTION_GUIDE.read_text(encoding="utf-8") if ACTION_GUIDE.exists() else "" + if not action_text: + issues.append({"code": "action_guide_missing", "message": f"missing {repo_rel(ACTION_GUIDE)}"}) + statuses = extract_gate_statuses(action_text) + gate_statuses: dict[str, Any] = {} + for gate in [*(str(value) for value in range(0, 12)), "12a"]: + status = statuses.get(gate) + gate_statuses[gate] = status + if status is None: + issues.append({"code": "gate_status_missing", "message": f"Gate {gate} status is missing from action guide"}) + continue + if status.get("status") != "DONE" or status.get("reviewer_verdict") != "PASS": + issues.append({"code": "gate_not_passed", "message": f"Gate {gate} must be DONE/PASS before Gate 12 final acceptance"}) + + seen_evidence: set[str] = set() + for gate, name in REQUIRED_GATE_EVIDENCE.items(): + path = REFERENCES_DIR / name + key = path.as_posix() + if key not in seen_evidence: + seen_evidence.add(key) + evidence.append({"path": repo_rel(path), "exists": path.exists()}) + if not path.exists(): + issues.append({"code": "gate_evidence_missing", "message": f"Gate {gate} evidence file missing: {repo_rel(path)}"}) + + if not PACKAGE_RECEIPT.exists(): + issues.append({"code": "package_receipt_missing", "message": f"missing {repo_rel(PACKAGE_RECEIPT)}"}) + package_status = None + else: + package_payload = read_json(PACKAGE_RECEIPT) + package_status = package_payload.get("status") + if package_status != "passed": + issues.append({"code": "package_receipt_not_passed", "message": "Gate 11 package receipt must have status=passed"}) + + if not SCOPE_DEFERRALS.exists(): + issues.append({"code": "scope_deferrals_missing", "message": f"missing {repo_rel(SCOPE_DEFERRALS)}"}) + deferral_text = "" + else: + deferral_text = SCOPE_DEFERRALS.read_text(encoding="utf-8") + for marker in ("Owner:", "Target date:", "Not claimed:"): + if marker not in deferral_text: + issues.append({"code": "scope_deferrals_incomplete", "message": f"scope deferrals must include {marker}"}) + + if not plan_path.exists(): + issues.append({"code": "plan_missing", "message": f"missing PLAN.md: {plan_path}"}) + plan_text = "" + else: + plan_text = plan_path.read_text(encoding="utf-8") + if "Gate 12 Final Acceptance" not in plan_text and "Gate 12b Final Acceptance" not in plan_text: + issues.append({"code": "plan_gate12_missing", "message": "PLAN.md must mention Gate 12/Gate 12b Final Acceptance"}) + if "不声称完整高质量 PPT 生成系统已完成" not in plan_text: + issues.append({"code": "plan_scope_claim_missing", "message": "PLAN.md must explicitly avoid claiming full high-quality PPT generation completion"}) + if "svglide-artboard-gate12-scope.md" not in plan_text: + issues.append({"code": "plan_deferral_reference_missing", "message": "PLAN.md must reference the Gate 12 explicit scope file"}) + if "Gate 12a reviewer PASS" not in plan_text and "Gate 12a Instruction/Plan/Output Adherence 已获得 reviewer PASS" not in plan_text: + issues.append({"code": "plan_gate12a_pass_missing", "message": "PLAN.md must declare Gate 12a reviewer PASS before Gate 12b acceptance"}) + + instruction_adherence = validate_instruction_adherence_current(issues) + + return { + "version": CHECK_VERSION, + "stage": "final_acceptance", + "status": "passed" if not issues else "failed", + "checked_at": now_iso(), + "plan_path": plan_path.as_posix(), + "gate_statuses": gate_statuses, + "evidence_files": evidence, + "package_receipt": { + "path": repo_rel(PACKAGE_RECEIPT), + "status": package_status, + }, + "instruction_adherence": instruction_adherence, + "scope": { + "accepted_milestone": "P0/P1 artboard_satori implementation through Gate 12a reviewer PASS", + "not_claimed": "complete high-quality PPT generation system with actual model-driven topic-to-deck loop", + "deferrals_path": repo_rel(SCOPE_DEFERRALS), + }, + "issues": issues, + } + + +def write_check_outputs(output_dir: Path, payload: dict[str, Any]) -> None: + write_json(output_dir / FINAL_CHECK, payload) + write_json(output_dir / FINAL_RECEIPT, payload) + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Validate SVGlide artboard Gate 12 final acceptance evidence.") + parser.add_argument("--plan", type=Path, default=DEFAULT_PLAN) + parser.add_argument("--output-dir", type=Path, default=None) + parser.add_argument("--pretty", action="store_true") + return parser.parse_args(argv) + + +def main(argv: list[str]) -> int: + args = parse_args(argv) + try: + payload = validate_final_acceptance(args.plan) + if args.output_dir: + write_check_outputs(args.output_dir, payload) + print(json.dumps(payload, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=True)) + return 0 if payload["status"] == "passed" else 1 + except FinalAcceptanceError as err: + payload = { + "version": CHECK_VERSION, + "stage": "final_acceptance", + "status": "failed", + "checked_at": now_iso(), + "issues": [{"code": "final_acceptance_error", "message": str(err)}], + } + if args.output_dir: + write_check_outputs(args.output_dir, payload) + print(json.dumps(payload, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=True), file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/skills/lark-slides/scripts/svglide_artboard_final_acceptance_test.py b/skills/lark-slides/scripts/svglide_artboard_final_acceptance_test.py new file mode 100644 index 00000000..e3945e78 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_artboard_final_acceptance_test.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_artboard_final_acceptance as final_acceptance + + +class ArtboardFinalAcceptanceTest(unittest.TestCase): + def test_extract_gate_statuses(self) -> None: + text = """ +| Gate | Status | Owner | Reviewer verdict | Evidence | +|---:|---|---|---|---| +| 0 Baseline | DONE | executor | PASS | ok | +| 11 Packaging | DONE | executor | PASS | ok | +| 12a Instruction adherence | DONE | executor | PASS | ok | +| 12 Final | TODO | executor | PENDING | | +""" + statuses = final_acceptance.extract_gate_statuses(text) + self.assertEqual(statuses["0"]["status"], "DONE") + self.assertEqual(statuses["11"]["reviewer_verdict"], "PASS") + self.assertEqual(statuses["12a"]["reviewer_verdict"], "PASS") + self.assertEqual(statuses["12"]["status"], "TODO") + + def test_extract_gate_statuses_rejects_missing_pass_in_validator_shape(self) -> None: + text = """ +| 0 Baseline | DONE | executor | PASS | ok | +| 1 Contract | IN_REVIEW | executor | PENDING | waiting | +""" + statuses = final_acceptance.extract_gate_statuses(text) + self.assertEqual(statuses["1"]["status"], "IN_REVIEW") + self.assertNotEqual(statuses["1"]["reviewer_verdict"], "PASS") + + def test_gate12a_is_required_for_final_acceptance(self) -> None: + text = "\n".join( + f"| {gate} Gate | DONE | executor | PASS | ok |" + for gate in range(0, 12) + ) + statuses = final_acceptance.extract_gate_statuses(text) + missing = [] + for gate in [*(str(value) for value in range(0, 12)), "12a"]: + status = statuses.get(gate) + if status is None or status.get("status") != "DONE" or status.get("reviewer_verdict") != "PASS": + missing.append(gate) + self.assertEqual(missing, ["12a"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_artboard_package_check.py b/skills/lark-slides/scripts/svglide_artboard_package_check.py new file mode 100644 index 00000000..0e2ff536 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_artboard_package_check.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import json +import platform +import subprocess +import sys +from datetime import datetime +from pathlib import Path +from typing import Any + + +CHECK_VERSION = "svglide-artboard-package-check/v1" +CHECK_STAGE = "package_check" +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = SCRIPT_DIR.parents[2] +ARTBOARD_RENDERER_DIR = SCRIPT_DIR / "artboard_renderer" +PACKAGE_CHECK = Path("06-check/artboard-package-check.json") +PACKAGE_RECEIPT = Path("receipts/artboard-package-check.json") + +REQUIRED_DEPENDENCIES = { + "satori": "0.26.0", + "@resvg/resvg-js": "2.6.2", +} +REQUIRED_EMBED_PATTERNS = { + "skills/*/prompts", + "skills/*/scripts/*.py", + "skills/*/scripts/artboard_renderer/*.mjs", + "skills/*/scripts/artboard_renderer/package.json", + "skills/*/scripts/artboard_renderer/pnpm-lock.yaml", + "skills/*/scripts/artboard_renderer/components", + "skills/*/scripts/artboard_renderer/dist", + "skills/*/scripts/artboard_renderer/templates", + "skills/*/scripts/artboard_renderer/themes", +} +FORBIDDEN_EMBED_PATTERNS = { + "skills/*/scripts", + "skills/*/scripts/artboard_renderer", + "skills/*/scripts/artboard_renderer/node_modules", +} +SCAN_PATHS = ( + "package.json", + "pnpm-lock.yaml", + "render.mjs", + "dist/render.mjs", + "templates/README.md", +) +LOCAL_SOURCE_MARKERS = ( + "/Users/", + "file:../", + "file:../../", + "SVGlide/satori", + "../satori", +) + + +class PackageCheckError(Exception): + pass + + +def now_iso() -> str: + return datetime.now().astimezone().replace(microsecond=0).isoformat() + + +def read_json(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as err: + raise PackageCheckError(f"missing JSON file: {path}") from err + except json.JSONDecodeError as err: + raise PackageCheckError(f"invalid JSON file: {path}: {err}") from err + if not isinstance(payload, dict): + raise PackageCheckError(f"expected JSON object: {path}") + return payload + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def repo_rel(path: Path, repo_root: Path) -> str: + try: + return path.resolve().relative_to(repo_root.resolve()).as_posix() + except ValueError: + return path.as_posix() + + +def exact_dependency(value: Any) -> bool: + return isinstance(value, str) and bool(value) and not value.startswith(("^", "~", "file:", "workspace:", "link:")) + + +def validate_dependency_policy(package_payload: dict[str, Any], lockfile_text: str) -> list[dict[str, str]]: + issues: list[dict[str, str]] = [] + dependencies = package_payload.get("dependencies") + if not isinstance(dependencies, dict): + return [{"code": "package_dependencies_missing", "message": "package.json must declare dependencies"}] + for name, expected in REQUIRED_DEPENDENCIES.items(): + actual = dependencies.get(name) + if actual != expected: + issues.append({"code": "package_dependency_version_mismatch", "message": f"{name} must be pinned to {expected}, got {actual!r}"}) + elif not exact_dependency(actual): + issues.append({"code": "package_dependency_not_exact", "message": f"{name} must use an exact registry version"}) + package_text = json.dumps(package_payload, ensure_ascii=False, sort_keys=True) + if "file:" in package_text or "workspace:" in package_text or "link:" in package_text: + issues.append({"code": "package_local_dependency", "message": "package.json must not use file/workspace/link dependencies"}) + for marker in ("file:../", "file:../../", "link:", "workspace:"): + if marker in lockfile_text: + issues.append({"code": "lockfile_local_dependency", "message": f"pnpm-lock.yaml must not contain {marker} dependencies"}) + for platform_dep in ("@resvg/resvg-js-darwin-arm64", "@resvg/resvg-js-darwin-x64"): + if platform_dep not in lockfile_text: + issues.append({"code": "lockfile_missing_macos_resvg", "message": f"pnpm-lock.yaml must include optional native package {platform_dep}"}) + if "satori@0.26.0" not in lockfile_text: + issues.append({"code": "lockfile_missing_satori", "message": "pnpm-lock.yaml must lock satori@0.26.0"}) + if "@resvg/resvg-js@2.6.2" not in lockfile_text: + issues.append({"code": "lockfile_missing_resvg", "message": "pnpm-lock.yaml must lock @resvg/resvg-js@2.6.2"}) + return issues + + +def validate_embed_policy(embed_text: str) -> list[dict[str, str]]: + issues: list[dict[str, str]] = [] + embed_patterns: set[str] = set() + for line in embed_text.splitlines(): + stripped = line.strip() + if not stripped.startswith("//go:embed"): + continue + embed_patterns.update(stripped.removeprefix("//go:embed").strip().split()) + for pattern in sorted(REQUIRED_EMBED_PATTERNS): + if pattern not in embed_patterns: + issues.append({"code": "go_embed_missing_pattern", "message": f"skills_embed.go must include {pattern}"}) + for pattern in sorted(FORBIDDEN_EMBED_PATTERNS): + if pattern in embed_patterns: + issues.append({"code": "go_embed_forbidden_broad_pattern", "message": f"skills_embed.go must not broadly embed {pattern}"}) + if any("node_modules" in pattern for pattern in embed_patterns): + issues.append({"code": "go_embed_node_modules", "message": "skills_embed.go must not embed node_modules"}) + return issues + + +def scan_local_source_references(renderer_dir: Path) -> list[dict[str, str]]: + issues: list[dict[str, str]] = [] + for rel in SCAN_PATHS: + path = renderer_dir / rel + if not path.exists(): + issues.append({"code": "package_file_missing", "message": f"required package file is missing: {rel}"}) + continue + text = path.read_text(encoding="utf-8", errors="replace") + for marker in LOCAL_SOURCE_MARKERS: + if marker in text: + issues.append({"code": "package_local_source_reference", "message": f"{rel} contains local source marker {marker!r}"}) + return issues + + +def run_node_runtime_check(renderer_dir: Path, entry: Path, *, timeout_seconds: int = 30) -> dict[str, Any]: + command = ["node", entry.as_posix(), "--check-runtime"] + result = subprocess.run( + command, + cwd=renderer_dir, + text=True, + capture_output=True, + timeout=timeout_seconds, + check=False, + ) + payload: dict[str, Any] | None = None + if result.stdout.strip(): + try: + parsed = json.loads(result.stdout.strip().splitlines()[-1]) + if isinstance(parsed, dict): + payload = parsed + except json.JSONDecodeError: + payload = None + return { + "command": " ".join(command), + "cwd": renderer_dir.as_posix(), + "entry": entry.as_posix(), + "exit_code": result.returncode, + "stdout": result.stdout.strip(), + "stderr": result.stderr.strip(), + "payload": payload, + "passed": result.returncode == 0 and isinstance(payload, dict) and payload.get("ok") is True, + } + + +def inspect_artboard_package( + repo_root: Path = REPO_ROOT, + renderer_dir: Path = ARTBOARD_RENDERER_DIR, + *, + run_runtime: bool = True, +) -> dict[str, Any]: + repo_root = repo_root.resolve() + renderer_dir = renderer_dir.resolve() + package_path = renderer_dir / "package.json" + lockfile_path = renderer_dir / "pnpm-lock.yaml" + dist_path = renderer_dir / "dist" / "render.mjs" + source_path = renderer_dir / "render.mjs" + embed_path = repo_root / "skills_embed.go" + gitignore_path = repo_root / ".gitignore" + + issues: list[dict[str, str]] = [] + package_payload = read_json(package_path) + lockfile_text = lockfile_path.read_text(encoding="utf-8") if lockfile_path.exists() else "" + if not lockfile_path.exists(): + issues.append({"code": "lockfile_missing", "message": f"missing {repo_rel(lockfile_path, repo_root)}"}) + for path in (source_path, dist_path): + if not path.exists(): + issues.append({"code": "renderer_entry_missing", "message": f"missing {repo_rel(path, repo_root)}"}) + issues.extend(validate_dependency_policy(package_payload, lockfile_text)) + issues.extend(scan_local_source_references(renderer_dir)) + + embed_text = embed_path.read_text(encoding="utf-8") if embed_path.exists() else "" + if not embed_path.exists(): + issues.append({"code": "go_embed_missing", "message": "skills_embed.go is missing"}) + else: + issues.extend(validate_embed_policy(embed_text)) + + gitignore_text = gitignore_path.read_text(encoding="utf-8") if gitignore_path.exists() else "" + if "node_modules/" not in gitignore_text: + issues.append({"code": "gitignore_node_modules_missing", "message": ".gitignore must ignore node_modules/"}) + if "!skills/lark-slides/scripts/artboard_renderer/dist/render.mjs" not in gitignore_text: + issues.append({"code": "gitignore_dist_unignore_missing", "message": ".gitignore must keep artboard_renderer/dist/render.mjs visible"}) + + runtime_checks: list[dict[str, Any]] = [] + if run_runtime: + for entry in (source_path, dist_path): + if entry.exists(): + try: + check = run_node_runtime_check(renderer_dir, entry) + except (subprocess.SubprocessError, OSError) as err: + check = { + "command": f"node {entry.as_posix()} --check-runtime", + "entry": entry.as_posix(), + "exit_code": None, + "stdout": "", + "stderr": str(err), + "payload": None, + "passed": False, + } + runtime_checks.append(check) + if not check.get("passed"): + issues.append({"code": "runtime_check_failed", "message": f"{repo_rel(entry, repo_root)} --check-runtime failed"}) + + status = "passed" if not issues else "failed" + return { + "version": CHECK_VERSION, + "stage": CHECK_STAGE, + "status": status, + "action": "create_live" if status == "passed" else "repair_and_rerun", + "checked_at": now_iso(), + "summary": { + "error_count": len(issues), + "warning_count": 0, + "runtime_check_count": len(runtime_checks), + }, + "host": { + "system": platform.system(), + "machine": platform.machine(), + "python": platform.python_version(), + }, + "renderer": { + "dir": repo_rel(renderer_dir, repo_root), + "source_entry": repo_rel(source_path, repo_root), + "dist_entry": repo_rel(dist_path, repo_root), + "package_json": repo_rel(package_path, repo_root), + "lockfile": repo_rel(lockfile_path, repo_root), + }, + "dependency_policy": { + "node": ">=18", + "package_manager": "pnpm", + "install_command": "pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile", + "runtime_check_commands": [ + "node skills/lark-slides/scripts/artboard_renderer/render.mjs --check-runtime", + "node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime", + ], + "dependencies": REQUIRED_DEPENDENCIES, + "native_dependency": "@resvg/resvg-js", + "manual_satori_source_checkout_required": False, + "node_modules_embedded_in_go_binary": False, + }, + "distribution_decision": { + "shape": "skill_subpackage_with_whitelisted_go_embed", + "go_binary_embeds": [ + "agent-readable skill docs", + "prompts", + "flat Python scripts", + "artboard_renderer source/dist/templates/themes/components/package lock", + ], + "go_binary_excludes": [ + "node_modules", + "fixture outputs", + "runtime project artifacts", + ], + "runtime_requires_disk_skill_resources": True, + "runtime_requires_native_resvg_install": True, + }, + "fallback_policy": { + "missing_node_or_resvg": "fail_fast_before_live_create", + "operator_action": "run pnpm --dir skills/lark-slides/scripts/artboard_renderer install --frozen-lockfile, then node skills/lark-slides/scripts/artboard_renderer/dist/render.mjs --check-runtime", + "no_network": "do not auto-fetch; use CI/preinstalled pnpm store or a packaged platform dependency layer", + }, + "runtime_checks": runtime_checks, + "issues": issues, + } + + +def write_check_outputs(output_dir: Path, payload: dict[str, Any]) -> None: + write_json(output_dir / PACKAGE_CHECK, payload) + write_json(output_dir / PACKAGE_RECEIPT, payload) + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Validate SVGlide artboard renderer packaging and runtime dependencies.") + parser.add_argument("--repo-root", type=Path, default=REPO_ROOT) + parser.add_argument("--renderer-dir", type=Path, default=ARTBOARD_RENDERER_DIR) + parser.add_argument("--output-dir", type=Path, default=None) + parser.add_argument("--skip-runtime", action="store_true", help="skip node --check-runtime probes; structural checks still run") + parser.add_argument("--pretty", action="store_true") + return parser.parse_args(argv) + + +def main(argv: list[str]) -> int: + args = parse_args(argv) + try: + payload = inspect_artboard_package(args.repo_root, args.renderer_dir, run_runtime=not args.skip_runtime) + if args.output_dir: + write_check_outputs(args.output_dir, payload) + print(json.dumps(payload, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=True)) + return 0 if payload["status"] == "passed" else 1 + except PackageCheckError as err: + payload = { + "version": CHECK_VERSION, + "stage": CHECK_STAGE, + "status": "failed", + "action": "repair_and_rerun", + "checked_at": now_iso(), + "summary": {"error_count": 1, "warning_count": 0, "runtime_check_count": 0}, + "issues": [{"code": "package_check_error", "message": str(err)}], + } + if args.output_dir: + write_check_outputs(args.output_dir, payload) + print(json.dumps(payload, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=True), file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/skills/lark-slides/scripts/svglide_artboard_package_check_test.py b/skills/lark-slides/scripts/svglide_artboard_package_check_test.py new file mode 100644 index 00000000..623107ad --- /dev/null +++ b/skills/lark-slides/scripts/svglide_artboard_package_check_test.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_artboard_package_check as package_check + + +class ArtboardPackageCheckTest(unittest.TestCase): + def test_structural_check_passes_current_repo(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + payload = package_check.inspect_artboard_package(run_runtime=False) + package_check.write_check_outputs(Path(tmp), payload) + self.assertEqual(payload["status"], "passed", payload["issues"]) + self.assertEqual(payload["stage"], "package_check") + self.assertEqual(payload["action"], "create_live") + self.assertEqual(payload["summary"]["error_count"], 0) + self.assertFalse(payload["dependency_policy"]["manual_satori_source_checkout_required"]) + self.assertTrue(payload["distribution_decision"]["runtime_requires_native_resvg_install"]) + self.assertTrue((Path(tmp) / package_check.PACKAGE_CHECK).exists()) + self.assertTrue((Path(tmp) / package_check.PACKAGE_RECEIPT).exists()) + + def test_dependency_policy_rejects_file_dependency(self) -> None: + package = { + "dependencies": { + "satori": "file:../../satori", + "@resvg/resvg-js": "2.6.2", + } + } + issues = package_check.validate_dependency_policy(package, "satori@file:../../satori\n@resvg/resvg-js@2.6.2") + codes = {issue["code"] for issue in issues} + self.assertIn("package_dependency_version_mismatch", codes) + self.assertIn("package_local_dependency", codes) + self.assertIn("lockfile_local_dependency", codes) + + def test_embed_policy_rejects_broad_scripts_directory(self) -> None: + embed_text = """ +//go:embed skills/*/SKILL.md skills/*/references +//go:embed skills/*/scripts skills/*/scripts/artboard_renderer/node_modules +var skillsEmbedFS embed.FS +""" + issues = package_check.validate_embed_policy(embed_text) + codes = {issue["code"] for issue in issues} + self.assertIn("go_embed_forbidden_broad_pattern", codes) + self.assertIn("go_embed_node_modules", codes) + self.assertIn("go_embed_missing_pattern", codes) + + def test_runtime_check_payload_shape_from_fixture(self) -> None: + payload = { + "version": package_check.CHECK_VERSION, + "stage": "package_check", + "status": "passed", + "action": "create_live", + "summary": {"error_count": 0}, + "runtime_checks": [ + { + "passed": True, + "payload": { + "ok": True, + "renderer": "satori-resvg", + "satori_version": "0.26.0", + "resvg_version": "2.6.2", + }, + } + ], + } + encoded = json.dumps(payload) + decoded = json.loads(encoded) + self.assertEqual(decoded["runtime_checks"][0]["payload"]["renderer"], "satori-resvg") + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_artboard_renderer.py b/skills/lark-slides/scripts/svglide_artboard_renderer.py new file mode 100644 index 00000000..60114e14 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_artboard_renderer.py @@ -0,0 +1,1788 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import subprocess +import sys +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime +from pathlib import Path +from typing import Any +from xml.etree import ElementTree +from xml.sax.saxutils import escape, quoteattr + + +CANVAS_SPEC_VERSION = "svglide-canvas-spec/v1" +ARTBOARD_RECEIPT_VERSION = "svglide-artboard-receipt/v1" +SEMANTIC_MAP_VERSION = "svglide-semantic-map/v1" +NODE_LAYOUT_MAP_VERSION = "svglide-node-layout-map/v1" +SLIDE_NS = "https://slides.bytedance.com/ns" +SVG_NS = "http://www.w3.org/2000/svg" +XHTML_NS = "http://www.w3.org/1999/xhtml" +CONTRACT_VERSION = "svglide-authoring-contract/v1" +SUPPORTED_TEMPLATES = { + "cover-hero", + "comparison-cards", + "summary-final", + "section-title", + "agenda-list", + "timeline-steps", + "process-flow", + "metric-dashboard", + "quote-focus", + "image-feature", + "research-poster", + "data-story", + "risk-alert", + "roadmap-lanes", + "architecture-blueprint", +} +SUPPORTED_SATORI_ELEMENTS = {"svg", "g", "mask", "rect", "circle", "ellipse", "line", "path", "text"} +FAIL_FAST_ELEMENTS = {"defs", "filter", "clipPath", "pattern", "foreignObject", "image", "use", "linearGradient", "radialGradient"} +ARTBOARD_RENDERER_DIR = Path(__file__).resolve().parent / "artboard_renderer" +SCRIPT_DIR = Path(__file__).resolve().parent +REFERENCES_DIR = SCRIPT_DIR.parent / "references" +NODE_RENDERER_DIST = ARTBOARD_RENDERER_DIR / "dist" / "render.mjs" +NODE_RENDERER_SOURCE = ARTBOARD_RENDERER_DIR / "render.mjs" +GLOBAL_TEMPLATE_REGISTRY = REFERENCES_DIR / "svglide-template-registry.json" +GLOBAL_THEME_REGISTRY = ARTBOARD_RENDERER_DIR / "themes" / "registry.json" +GLOBAL_THEME_DIR = ARTBOARD_RENDERER_DIR / "themes" +PROJECT_TEMPLATE_REGISTRY = Path("02-plan/template-registry.json") +PROJECT_THEME_REGISTRY = Path("02-plan/theme-registry.json") +CANVAS_SPEC_VALIDATE_CHECK = Path("06-check/canvas-spec-validate.json") +CANVAS_SPEC_VALIDATE_RECEIPT = Path("receipts/canvas-spec-validate.json") +ARTBOARD_RENDER_RECEIPT = Path("receipts/artboard-render.json") +SATORI_BRIDGE_RECEIPT = Path("receipts/satori-bridge.json") +CONTACT_SHEET = Path("05-preview/contact-sheet.png") + + +class ArtboardError(Exception): + pass + + +def now_iso() -> str: + return datetime.now().astimezone().replace(microsecond=0).isoformat() + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def read_json(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as err: + raise ArtboardError(f"missing required file: {path}") from err + except json.JSONDecodeError as err: + raise ArtboardError(f"invalid JSON: {path}: {err}") from err + if not isinstance(payload, dict): + raise ArtboardError(f"expected JSON object: {path}") + return payload + + +def file_sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def json_sha256(payload: Any) -> str: + data = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(data).hexdigest() + + +def strings_sha256(values: list[str]) -> str: + data = "\n".join(values).encode("utf-8") + return hashlib.sha256(data).hexdigest() + + +def relpath(path: Path, project: Path) -> str: + return path.relative_to(project).as_posix() + + +def repo_relpath(path: Path) -> str: + try: + return path.resolve().relative_to(SCRIPT_DIR.parents[2].resolve()).as_posix() + except ValueError: + return path.as_posix() + + +def optional_project_path(project: Path, rel: Path) -> Path | None: + path = project / rel + return path if path.exists() else None + + +def registry_path(project: Path, rel: Path, fallback: Path) -> Path: + project_path = optional_project_path(project, rel) + return project_path if project_path else fallback + + +def registry_record_by_id(payload: dict[str, Any], key: str) -> dict[str, dict[str, Any]]: + records = payload.get(key) + result: dict[str, dict[str, Any]] = {} + if isinstance(records, list): + for item in records: + if isinstance(item, dict) and isinstance(item.get("id"), str): + result[item["id"]] = item + return result + + +def load_template_registry(project: Path) -> tuple[Path, dict[str, Any], dict[str, dict[str, Any]]]: + path = registry_path(project, PROJECT_TEMPLATE_REGISTRY, GLOBAL_TEMPLATE_REGISTRY) + payload = read_json(path) + return path, payload, registry_record_by_id(payload, "templates") + + +def load_theme_registry(project: Path) -> tuple[Path, dict[str, Any], dict[str, dict[str, Any]]]: + path = registry_path(project, PROJECT_THEME_REGISTRY, GLOBAL_THEME_REGISTRY) + payload = read_json(path) + return path, payload, registry_record_by_id(payload, "themes") + + +def resolve_theme_payload(project: Path, theme_registry_path: Path, theme_record: dict[str, Any]) -> tuple[Path | None, dict[str, Any]]: + raw_path = theme_record.get("path") + if isinstance(raw_path, str) and raw_path: + if theme_registry_path.is_relative_to(project): + candidate = (project / raw_path).resolve() if not Path(raw_path).is_absolute() else Path(raw_path) + else: + candidate = (SCRIPT_DIR.parents[2] / raw_path).resolve() if not Path(raw_path).is_absolute() else Path(raw_path) + if candidate.exists(): + return candidate, read_json(candidate) + return None, theme_record + + +def template_registry_hash(path: Path) -> str: + return file_sha256(path) + + +def theme_registry_hash(path: Path, theme_files: list[Path]) -> str: + parts = [file_sha256(path)] + for item in sorted(theme_files): + parts.append(item.as_posix()) + parts.append(file_sha256(item)) + return strings_sha256(parts) + + +def validate_registry_bindings(project: Path, spec: dict[str, Any], *, page: int) -> tuple[list[dict[str, str]], dict[str, Any]]: + issues: list[dict[str, str]] = [] + template_path, template_payload, templates = load_template_registry(project) + theme_path, theme_payload, themes = load_theme_registry(project) + template_id = spec.get("template_id") + theme_id = spec.get("theme_id") + template_record = templates.get(template_id) if isinstance(template_id, str) else None + theme_record = themes.get(theme_id) if isinstance(theme_id, str) else None + theme_file: Path | None = None + theme_payload_for_id: dict[str, Any] = {} + if template_record is None: + issues.append({"code": "canvas_spec_template_unknown", "message": f"page {page} template_id {template_id!r} is not present in Template Registry"}) + elif template_record.get("status") != "active": + issues.append({"code": "canvas_spec_template_inactive", "message": f"page {page} template_id {template_id!r} is not active"}) + if theme_record is None: + issues.append({"code": "canvas_spec_theme_unknown", "message": f"page {page} theme_id {theme_id!r} is not present in Theme Registry"}) + elif theme_record.get("status") != "active": + issues.append({"code": "canvas_spec_theme_inactive", "message": f"page {page} theme_id {theme_id!r} is not active"}) + else: + theme_file, theme_payload_for_id = resolve_theme_payload(project, theme_path, theme_record) + if template_record and theme_id: + allowed = template_record.get("supported_theme_ids") + if isinstance(allowed, list) and theme_id not in allowed: + issues.append({"code": "canvas_spec_theme_not_allowed", "message": f"page {page} template_id {template_id!r} does not allow theme_id {theme_id!r}"}) + if template_record: + content = spec.get("content") if isinstance(spec.get("content"), dict) else {} + required = template_record.get("required_content") + if isinstance(required, list): + for key in required: + if not isinstance(key, str): + continue + value = content.get(key) + if value is None or value == "" or value == []: + issues.append({"code": "canvas_spec_template_required_content_missing", "message": f"page {page} template {template_id!r} requires content.{key}"}) + max_items = template_record.get("max_items") + if isinstance(max_items, dict): + for key, max_count in max_items.items(): + if isinstance(key, str) and isinstance(max_count, int) and isinstance(content.get(key), list) and len(content[key]) > max_count: + issues.append({"code": "canvas_spec_template_too_many_items", "message": f"page {page} content.{key} exceeds template max_items {max_count}"}) + text_budget = template_record.get("text_budget") + if isinstance(text_budget, dict): + for key, max_chars in text_budget.items(): + if not isinstance(key, str) or not isinstance(max_chars, int): + continue + value = content.get(key) + values = value if isinstance(value, list) else [value] + for index, item in enumerate(values): + if isinstance(item, str) and len(item.strip()) > max_chars: + suffix = f"[{index}]" if isinstance(value, list) else "" + issues.append({"code": "canvas_spec_text_budget_exceeded", "message": f"page {page} content.{key}{suffix} exceeds text_budget {max_chars}"}) + registry = { + "template_registry_path": repo_relpath(template_path) if not template_path.is_relative_to(project) else relpath(template_path, project), + "template_registry_sha256": template_registry_hash(template_path), + "theme_registry_path": repo_relpath(theme_path) if not theme_path.is_relative_to(project) else relpath(theme_path, project), + "theme_files": [repo_relpath(theme_file) if theme_file and not theme_file.is_relative_to(project) else relpath(theme_file, project)] if theme_file else [], + "theme_registry_sha256": theme_registry_hash(theme_path, [theme_file] if theme_file else []), + "template_record": template_record, + "theme_record": theme_record, + "theme_payload": theme_payload_for_id, + } + return issues, registry + + +def apply_registry_theme(spec: dict[str, Any], registry: dict[str, Any]) -> dict[str, Any]: + theme_payload = registry.get("theme_payload") + if isinstance(theme_payload, dict) and isinstance(theme_payload.get("colors"), dict): + merged = json.loads(json.dumps(spec, ensure_ascii=False)) + merged["theme"] = theme_payload + return merged + return spec + + +def local_name(tag: str) -> str: + return tag.rsplit("}", 1)[-1] if "}" in tag else tag + + +def attr(element: ElementTree.Element, name: str) -> str | None: + value = element.attrib.get(name) + return value if value is not None and value != "" else None + + +def number(value: Any, default: float) -> float: + if isinstance(value, bool): + return default + if isinstance(value, (int, float)): + return float(value) + if isinstance(value, str): + try: + return float(value.replace("px", "")) + except ValueError: + return default + return default + + +def style_attr(style: dict[str, Any]) -> str: + pairs: list[str] = [] + for key, value in style.items(): + if value is None: + continue + pairs.append(f"{key}:{value}") + return ";".join(pairs) + + +def svg_attrs(attrs: dict[str, Any]) -> str: + parts: list[str] = [] + for key, value in attrs.items(): + if value is None: + continue + parts.append(f"{key}={quoteattr(str(value))}") + return " ".join(parts) + + +def normalized_box(payload: Any) -> dict[str, float] | None: + if not isinstance(payload, dict): + return None + return { + "x": number(payload.get("x"), -1), + "y": number(payload.get("y"), -1), + "width": number(payload.get("width"), -1), + "height": number(payload.get("height"), -1), + } + + +def box_contains(container: dict[str, float], child: dict[str, float]) -> bool: + return ( + child["x"] >= container["x"] + and child["y"] >= container["y"] + and child["x"] + child["width"] <= container["x"] + container["width"] + and child["y"] + child["height"] <= container["y"] + container["height"] + ) + + +def validate_canvas_spec(spec: dict[str, Any], *, page: int) -> list[dict[str, str]]: + issues: list[dict[str, str]] = [] + if spec.get("version") != CANVAS_SPEC_VERSION: + issues.append({"code": "canvas_spec_version_invalid", "message": f"page {page} canvas_spec.version must be {CANVAS_SPEC_VERSION}"}) + canvas = spec.get("canvas") + if not isinstance(canvas, dict): + issues.append({"code": "canvas_spec_canvas_missing", "message": f"page {page} canvas_spec.canvas is required"}) + else: + if canvas.get("width") != 960 or canvas.get("height") != 540 or canvas.get("viewBox") != "0 0 960 540": + issues.append({"code": "canvas_spec_canvas_invalid", "message": f"page {page} must use 960x540 canvas and viewBox 0 0 960 540"}) + safe_area = normalized_box(spec.get("safe_area")) + if safe_area is None or safe_area["width"] <= 0 or safe_area["height"] <= 0: + issues.append({"code": "canvas_spec_safe_area_missing", "message": f"page {page} canvas_spec.safe_area is required"}) + safe_area = None + quality_constraints = spec.get("quality_constraints") + if not isinstance(quality_constraints, dict): + issues.append({"code": "canvas_spec_quality_constraints_missing", "message": f"page {page} canvas_spec.quality_constraints is required"}) + else: + quality_safe_area = normalized_box(quality_constraints.get("safe_area")) + if quality_safe_area is None or quality_safe_area["width"] <= 0 or quality_safe_area["height"] <= 0: + issues.append({"code": "canvas_spec_quality_safe_area_missing", "message": f"page {page} quality_constraints.safe_area is required"}) + else: + safe_area = quality_safe_area + min_font_size = quality_constraints.get("min_font_size") + if not isinstance(min_font_size, (int, float)) or isinstance(min_font_size, bool) or min_font_size < 1: + issues.append({"code": "canvas_spec_min_font_size_invalid", "message": f"page {page} quality_constraints.min_font_size must be positive"}) + template_id = spec.get("template_id") + if template_id not in SUPPORTED_TEMPLATES: + issues.append({"code": "canvas_spec_template_unsupported", "message": f"page {page} template_id {template_id!r} is not supported by the artboard renderer"}) + if not isinstance(spec.get("theme_id"), str) or not spec.get("theme_id"): + issues.append({"code": "canvas_spec_theme_id_missing", "message": f"page {page} canvas_spec.theme_id is required"}) + if not isinstance(spec.get("theme"), dict): + issues.append({"code": "canvas_spec_theme_missing", "message": f"page {page} canvas_spec.theme is required for schema compatibility"}) + content = spec.get("content") + if not isinstance(content, dict): + issues.append({"code": "canvas_spec_content_missing", "message": f"page {page} canvas_spec.content is required"}) + else: + title = content.get("title") + if not isinstance(title, str) or not title.strip(): + issues.append({"code": "canvas_spec_title_missing", "message": f"page {page} content.title is required"}) + unsupported = spec.get("unsupported_features") + if isinstance(unsupported, list) and unsupported: + issues.append({"code": "canvas_spec_unsupported_features", "message": f"page {page} declares unsupported_features: {unsupported}"}) + semantic_elements = spec.get("semantic_elements") + canvas_box = {"x": 0, "y": 0, "width": 960, "height": 540} + if not isinstance(semantic_elements, list) or not semantic_elements: + issues.append({"code": "canvas_spec_semantic_elements_missing", "message": f"page {page} semantic_elements must contain at least one element"}) + else: + for index, element in enumerate(semantic_elements): + if not isinstance(element, dict): + issues.append({"code": "canvas_spec_semantic_element_invalid", "message": f"page {page} semantic_elements[{index}] must be an object"}) + continue + element_id = element.get("element_id") or f"#{index}" + bbox = normalized_box(element.get("bbox")) + if bbox is None or bbox["width"] <= 0 or bbox["height"] <= 0: + issues.append({"code": "canvas_spec_semantic_bbox_invalid", "message": f"page {page} semantic element {element_id!r} requires positive bbox"}) + continue + if not box_contains(canvas_box, bbox): + issues.append({"code": "canvas_spec_bbox_out_of_canvas", "message": f"page {page} semantic element {element_id!r} bbox exceeds 960x540 canvas"}) + role = element.get("role") + if safe_area and role != "background" and not box_contains(safe_area, bbox): + issues.append({"code": "canvas_spec_bbox_out_of_safe_area", "message": f"page {page} semantic element {element_id!r} bbox exceeds safe_area"}) + return issues + + +def normalize_theme(spec: dict[str, Any]) -> dict[str, str]: + raw = spec.get("theme") if isinstance(spec.get("theme"), dict) else {} + colors = raw.get("colors") if isinstance(raw.get("colors"), dict) else {} + return { + "background": str(colors.get("background") or "#0F172A"), + "panel": str(colors.get("panel") or "#111827"), + "primary": str(colors.get("primary") or "#60A5FA"), + "accent": str(colors.get("accent") or "#A78BFA"), + "text": str(colors.get("text") or "#F8FAFC"), + "muted": str(colors.get("muted") or "#CBD5E1"), + } + + +def content_text(spec: dict[str, Any], key: str, default: str = "") -> str: + content = spec.get("content") if isinstance(spec.get("content"), dict) else {} + value = content.get(key) + return value.strip() if isinstance(value, str) and value.strip() else default + + +def content_list(spec: dict[str, Any], key: str) -> list[str]: + content = spec.get("content") if isinstance(spec.get("content"), dict) else {} + raw = content.get(key) + return [item.strip() for item in raw if isinstance(item, str) and item.strip()] if isinstance(raw, list) else [] + + +def content_first_list(spec: dict[str, Any], keys: list[str], default: list[str]) -> list[str]: + for key in keys: + values = content_list(spec, key) + if values: + return values + return default + + +def svg_text( + parts: list[str], + nodes: list[dict[str, Any]], + node_id: str, + value: str, + *, + x: float, + y: float, + width: float, + height: float, + fill: str, + font_size: float, + font_weight: int = 700, +) -> None: + box_height = max(height, 30) + nodes.append({"id": node_id, "kind": "text", "x": x, "y": y, "width": width, "height": box_height, "text": value}) + baseline = y + min(box_height - 4, font_size * 1.18) + parts.append( + f'{escape(value)}' + ) + + +def svg_rect( + parts: list[str], + nodes: list[dict[str, Any]], + node_id: str, + *, + x: float, + y: float, + width: float, + height: float, + fill: str, + opacity: float | None = None, + stroke: str | None = None, + stroke_width: float | None = None, +) -> None: + nodes.append({"id": node_id, "kind": "rect", "x": x, "y": y, "width": width, "height": height}) + parts.append( + f'" + ) + + +def svg_circle( + parts: list[str], + nodes: list[dict[str, Any]], + node_id: str, + *, + cx: float, + cy: float, + r: float, + fill: str, + opacity: float | None = None, + stroke: str | None = None, + stroke_width: float | None = None, +) -> None: + nodes.append({"id": node_id, "kind": "circle", "x": cx - r, "y": cy - r, "width": r * 2, "height": r * 2}) + parts.append( + f'" + ) + + +def svg_line( + parts: list[str], + nodes: list[dict[str, Any]], + node_id: str, + *, + x1: float, + y1: float, + x2: float, + y2: float, + stroke: str, + stroke_width: float = 2, + opacity: float | None = None, +) -> None: + nodes.append({"id": node_id, "kind": "line", "x": min(x1, x2), "y": min(y1, y2), "width": max(abs(x2 - x1), 1), "height": max(abs(y2 - y1), 1)}) + parts.append( + f'" + ) + + +def svg_path( + parts: list[str], + nodes: list[dict[str, Any]], + node_id: str, + *, + d: str, + x: float, + y: float, + width: float, + height: float, + fill: str = "none", + stroke: str | None = None, + stroke_width: float | None = None, + opacity: float | None = None, +) -> None: + nodes.append({"id": node_id, "kind": "path", "x": x, "y": y, "width": width, "height": height}) + parts.append( + f'" + ) + + +def begin_template_svg(theme: dict[str, str], nodes: list[dict[str, Any]]) -> list[str]: + nodes.append({"id": "background", "kind": "rect", "x": 0, "y": 0, "width": 960, "height": 540}) + return [ + f'', + f'', + ] + + +def add_template_header(parts: list[str], nodes: list[dict[str, Any]], spec: dict[str, Any], theme: dict[str, str], *, title_size: int = 40, title_width: int = 710) -> None: + eyebrow = content_text(spec, "eyebrow", str(spec.get("template_id") or "ARTBOARD").replace("-", " ").upper()) + title = content_text(spec, "title", "Untitled") + subtitle = content_text(spec, "subtitle", content_text(spec, "summary", "")) + svg_text(parts, nodes, "eyebrow", eyebrow.upper(), x=64, y=54, width=380, height=28, fill=theme["primary"], font_size=17, font_weight=850) + svg_text(parts, nodes, "title", title, x=64, y=91, width=title_width, height=76, fill=theme["text"], font_size=title_size, font_weight=850) + if subtitle: + svg_text(parts, nodes, "subtitle", subtitle, x=66, y=168, width=min(title_width, 700), height=58, fill=theme["muted"], font_size=21, font_weight=560) + + +def semantic_role_for_node(node: dict[str, Any]) -> str: + node_id = str(node.get("id") or "") + kind = str(node.get("kind") or "") + if node_id == "background": + return "background" + if kind == "text": + if node_id.startswith("chip-"): + return "badge" + if node_id.startswith("left-point-") or node_id.startswith("right-point-"): + return "body" + return node_id or "text" + if "panel" in node_id or "card" in node_id: + return "container" + return "decorative" + + +def semantic_source_ref_for_node(node: dict[str, Any]) -> str | None: + node_id = str(node.get("id") or "") + source_by_id = { + "eyebrow": "canvas_spec.content.eyebrow", + "title": "canvas_spec.content.title", + "subtitle": "canvas_spec.content.subtitle", + "left-title": "canvas_spec.content.left_title", + "right-title": "canvas_spec.content.right_title", + "conclusion": "canvas_spec.content.conclusion", + } + if node_id in source_by_id: + return source_by_id[node_id] + if node_id.startswith("chip-"): + return "canvas_spec.content.chips[]" + if node_id.startswith("left-point-"): + return "canvas_spec.content.left_points[]" + if node_id.startswith("right-point-"): + return "canvas_spec.content.right_points[]" + if node_id.startswith("takeaway-"): + return "canvas_spec.content.takeaways[]" + return None + + +def semantic_elements_from_nodes(nodes: list[dict[str, Any]]) -> list[dict[str, Any]]: + elements: list[dict[str, Any]] = [] + for node in nodes: + node_id = str(node.get("id") or "") + if not node_id: + continue + elements.append( + { + "element_id": node_id, + "kind": str(node.get("kind") or "unknown"), + "role": semantic_role_for_node(node), + "source_ref": semantic_source_ref_for_node(node), + "text": node.get("text") if isinstance(node.get("text"), str) else None, + "bbox": { + "x": number(node.get("x"), 0), + "y": number(node.get("y"), 0), + "width": number(node.get("width"), 0), + "height": number(node.get("height"), 0), + }, + } + ) + return elements + + +def template_cover_hero(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + eyebrow = content_text(spec, "eyebrow", "SVGLIDE ARTBOARD") + title = content_text(spec, "title", "Untitled") + subtitle = content_text(spec, "subtitle", "") + chips = content_list(spec, "chips")[:4] + nodes = [ + {"id": "background", "kind": "rect", "x": 0, "y": 0, "width": 960, "height": 540}, + {"id": "accent-orbit", "kind": "circle", "x": 724, "y": 36, "width": 192, "height": 192}, + {"id": "panel", "kind": "rect", "x": 56, "y": 64, "width": 704, "height": 356}, + {"id": "eyebrow", "kind": "text", "x": 84, "y": 98, "width": 420, "height": 32, "text": eyebrow}, + {"id": "title", "kind": "text", "x": 84, "y": 142, "width": 628, "height": 142, "text": title}, + {"id": "subtitle", "kind": "text", "x": 88, "y": 302, "width": 610, "height": 74, "text": subtitle}, + ] + chip_nodes = [] + chip_x = 84 + for index, chip in enumerate(chips): + width = min(188, max(92, len(chip) * 13 + 34)) + chip_nodes.append({"id": f"chip-{index + 1}", "kind": "text", "x": chip_x, "y": 444, "width": width, "height": 40, "text": chip}) + chip_x += width + 14 + nodes.extend(chip_nodes) + text_nodes = { + "eyebrow": (eyebrow, 18, 700, theme["primary"]), + "title": (title, 58, 800, theme["text"]), + "subtitle": (subtitle, 24, 500, theme["muted"]), + } + parts = [ + f'', + f'', + f'', + f'', + f'', + ] + for node_id, (text, font_size, font_weight, fill) in text_nodes.items(): + node = next(item for item in nodes if item["id"] == node_id) + y = node["y"] + font_size + parts.append( + f'{escape(text)}' + ) + for index, chip in enumerate(chips): + node_id = f"chip-{index + 1}" + node = next(item for item in chip_nodes if item["id"] == node_id) + parts.append( + f'' + ) + parts.append( + f'{escape(chip)}' + ) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_section_title(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + eyebrow = content_text(spec, "eyebrow", "SECTION") + title = content_text(spec, "title", "Untitled") + subtitle = content_text(spec, "subtitle", "") + nodes = [ + {"id": "background", "kind": "rect", "x": 0, "y": 0, "width": 960, "height": 540}, + {"id": "rule", "kind": "rect", "x": 72, "y": 124, "width": 8, "height": 250}, + {"id": "eyebrow", "kind": "text", "x": 104, "y": 126, "width": 420, "height": 32, "text": eyebrow}, + {"id": "title", "kind": "text", "x": 104, "y": 176, "width": 680, "height": 122, "text": title}, + {"id": "subtitle", "kind": "text", "x": 108, "y": 322, "width": 640, "height": 66, "text": subtitle}, + ] + parts = [ + f'', + f'', + f'', + f'', + f'{escape(eyebrow)}', + f'{escape(title)}', + f'{escape(subtitle)}', + "", + ] + return "\n".join(parts) + "\n", nodes + + +def template_summary_final(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + eyebrow = content_text(spec, "eyebrow", "SUMMARY") + title = content_text(spec, "title", "Summary") + subtitle = content_text(spec, "subtitle", "") + takeaways = content_list(spec, "takeaways")[:3] + nodes = [ + {"id": "background", "kind": "rect", "x": 0, "y": 0, "width": 960, "height": 540}, + {"id": "metric-bar-1", "kind": "rect", "x": 712, "y": 322, "width": 18, "height": 30}, + {"id": "metric-bar-2", "kind": "rect", "x": 742, "y": 304, "width": 18, "height": 48}, + {"id": "metric-bar-3", "kind": "rect", "x": 772, "y": 286, "width": 18, "height": 66}, + {"id": "eyebrow", "kind": "text", "x": 72, "y": 64, "width": 420, "height": 32, "text": eyebrow}, + {"id": "title", "kind": "text", "x": 72, "y": 110, "width": 700, "height": 110, "text": title}, + {"id": "subtitle", "kind": "text", "x": 72, "y": 244, "width": 640, "height": 66, "text": subtitle}, + ] + for index, takeaway in enumerate(takeaways): + x = 72 + index * 268 + nodes.extend( + [ + {"id": f"takeaway-card-{index + 1}", "kind": "rect", "x": x, "y": 344, "width": 250, "height": 126}, + {"id": f"takeaway-index-{index + 1}", "kind": "text", "x": x + 22, "y": 366, "width": 64, "height": 30, "text": f"{index + 1:02d}"}, + {"id": f"takeaway-{index + 1}", "kind": "text", "x": x + 22, "y": 404, "width": 202, "height": 54, "text": takeaway}, + ] + ) + parts = [ + f'', + f'', + f'', + f'', + f'', + f'', + f'{escape(eyebrow)}', + f'{escape(title)}', + f'{escape(subtitle)}', + ] + for index, takeaway in enumerate(takeaways): + x = 72 + index * 268 + parts.append(f'') + parts.append(f'{index + 1:02d}') + parts.append(f'{escape(takeaway)}') + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_comparison(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + title = content_text(spec, "title", "Comparison") + left_title = content_text(spec, "left_title", "Before") + right_title = content_text(spec, "right_title", "After") + left_points = content_list(spec, "left_points")[:3] + right_points = content_list(spec, "right_points")[:3] + conclusion = content_text(spec, "conclusion", "") + nodes = [ + {"id": "background", "kind": "rect", "x": 0, "y": 0, "width": 960, "height": 540}, + {"id": "title", "kind": "text", "x": 64, "y": 52, "width": 760, "height": 64, "text": title}, + {"id": "left-card", "kind": "rect", "x": 64, "y": 140, "width": 390, "height": 250}, + {"id": "right-card", "kind": "rect", "x": 506, "y": 140, "width": 390, "height": 250}, + {"id": "left-title", "kind": "text", "x": 92, "y": 168, "width": 320, "height": 34, "text": left_title}, + {"id": "left-point-1", "kind": "text", "x": 116, "y": 222, "width": 296, "height": 36, "text": left_points[0] if len(left_points) > 0 else ""}, + {"id": "left-point-2", "kind": "text", "x": 116, "y": 270, "width": 296, "height": 36, "text": left_points[1] if len(left_points) > 1 else ""}, + {"id": "left-point-3", "kind": "text", "x": 116, "y": 318, "width": 296, "height": 36, "text": left_points[2] if len(left_points) > 2 else ""}, + {"id": "right-title", "kind": "text", "x": 534, "y": 168, "width": 320, "height": 34, "text": right_title}, + {"id": "right-point-1", "kind": "text", "x": 558, "y": 222, "width": 296, "height": 36, "text": right_points[0] if len(right_points) > 0 else ""}, + {"id": "right-point-2", "kind": "text", "x": 558, "y": 270, "width": 296, "height": 36, "text": right_points[1] if len(right_points) > 1 else ""}, + {"id": "right-point-3", "kind": "text", "x": 558, "y": 318, "width": 296, "height": 36, "text": right_points[2] if len(right_points) > 2 else ""}, + {"id": "conclusion", "kind": "text", "x": 86, "y": 426, "width": 788, "height": 42, "text": conclusion}, + ] + parts = [ + f'', + f'', + f'{escape(title)}', + f'', + f'', + f'{escape(left_title)}', + f'{escape(right_title)}', + ] + for idx, point in enumerate(left_points): + y = 248 + idx * 48 + parts.append(f'') + parts.append(f'{escape(point)}') + for idx, point in enumerate(right_points): + y = 248 + idx * 48 + parts.append(f'') + parts.append(f'{escape(point)}') + parts.extend( + [ + f'', + f'{escape(conclusion)}', + "", + ] + ) + return "\n".join(parts) + "\n", nodes + + +def template_agenda_list(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + items = content_first_list(spec, ["items", "takeaways"], ["Context", "Evidence", "Decision"])[:6] + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + svg_path(parts, nodes, "agenda-trajectory", d="M674 54 L888 54 L842 486 L674 486 Z", x=674, y=54, width=214, height=432, fill=theme["primary"], opacity=0.08) + svg_line(parts, nodes, "agenda-rail", x1=108, y1=218, x2=108, y2=444, stroke=theme["primary"], stroke_width=3, opacity=0.65) + add_template_header(parts, nodes, spec, theme, title_size=42, title_width=740) + start_y = 238 + for index, item in enumerate(items): + y = start_y + index * 42 + svg_circle(parts, nodes, f"agenda-node-{index + 1}", cx=108, cy=y + 19, r=12, fill=theme["background"], stroke=theme["primary"], stroke_width=3) + svg_rect(parts, nodes, f"agenda-card-{index + 1}", x=148, y=y, width=586, height=38, fill=theme["panel"], opacity=0.86) + svg_text(parts, nodes, f"agenda-index-{index + 1}", f"{index + 1:02d}", x=166, y=y + 7, width=54, height=24, fill=theme["primary"], font_size=17, font_weight=850) + svg_text(parts, nodes, f"agenda-item-{index + 1}", item, x=226, y=y + 6, width=470, height=26, fill=theme["text"], font_size=19, font_weight=720) + svg_rect(parts, nodes, "agenda-stack-panel", x=780, y=220, width=88, height=214, fill=theme["panel"], opacity=0.7, stroke=theme["primary"], stroke_width=1.5) + for index, height in enumerate([26, 46, 72, 38]): + svg_rect(parts, nodes, f"agenda-stack-bar-{index + 1}", x=806 + index * 12, y=394 - height, width=8, height=height, fill=theme["accent"] if index == 2 else theme["primary"], opacity=0.75) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_timeline_steps(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + events = content_first_list(spec, ["events", "steps", "items"], ["Discover", "Design", "Deliver", "Measure"])[:5] + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + add_template_header(parts, nodes, spec, theme, title_size=40, title_width=740) + svg_path(parts, nodes, "timeline-orbit", d="M104 296 C260 216 390 408 532 318 C640 246 740 246 856 330", x=104, y=216, width=752, height=192, stroke=theme["accent"], stroke_width=2.5, opacity=0.48) + svg_line(parts, nodes, "timeline-rail", x1=104, y1=320, x2=856, y2=320, stroke=theme["primary"], stroke_width=3, opacity=0.72) + count = max(1, len(events)) + for index, event in enumerate(events): + x = 116 + index * (724 / max(1, count - 1)) + card_y = 368 if index % 2 == 0 else 244 + svg_line(parts, nodes, f"timeline-pin-{index + 1}", x1=x, y1=320, x2=x, y2=card_y + (0 if index % 2 == 0 else 74), stroke=theme["muted"], stroke_width=1.8, opacity=0.5) + svg_circle(parts, nodes, f"timeline-node-{index + 1}", cx=x, cy=320, r=18, fill=theme["primary"] if index % 2 == 0 else theme["accent"], opacity=0.92) + svg_text(parts, nodes, f"timeline-index-{index + 1}", f"{index + 1}", x=x - 16, y=306, width=32, height=30, fill=theme["text"], font_size=16, font_weight=850) + svg_rect(parts, nodes, f"timeline-card-{index + 1}", x=x - 58, y=card_y, width=116, height=74, fill=theme["panel"], opacity=0.88) + svg_text(parts, nodes, f"timeline-event-{index + 1}", event, x=x - 45, y=card_y + 18, width=90, height=42, fill=theme["text"], font_size=18, font_weight=760) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_process_flow(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + steps = content_first_list(spec, ["steps", "items"], ["Input", "Normalize", "Render", "Verify"])[:5] + conclusion = content_text(spec, "conclusion", "") + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + add_template_header(parts, nodes, spec, theme, title_size=40, title_width=720) + svg_path(parts, nodes, "process-main-flow", d="M98 332 C234 258 350 392 472 318 C594 244 692 394 836 306", x=98, y=244, width=738, height=150, stroke=theme["primary"], stroke_width=3, opacity=0.55) + for index, step in enumerate(steps): + x = 76 + index * 168 + y = 272 if index % 2 == 0 else 318 + svg_rect(parts, nodes, f"process-card-{index + 1}", x=x, y=y, width=136, height=118, fill=theme["panel"], opacity=0.9, stroke=theme["primary"] if index % 2 == 0 else theme["accent"], stroke_width=1.5) + svg_circle(parts, nodes, f"process-node-{index + 1}", cx=x + 34, cy=y + 30, r=16, fill=theme["primary"] if index % 2 == 0 else theme["accent"], opacity=0.9) + svg_text(parts, nodes, f"process-index-{index + 1}", str(index + 1), x=x + 20, y=y + 16, width=32, height=32, fill=theme["text"], font_size=18, font_weight=900) + svg_text(parts, nodes, f"process-step-{index + 1}", step, x=x + 16, y=y + 56, width=112, height=52, fill=theme["text"], font_size=19, font_weight=780) + if index < len(steps) - 1: + svg_line(parts, nodes, f"process-connector-{index + 1}", x1=x + 138, y1=y + 58, x2=x + 166, y2=(318 if index % 2 == 0 else 272) + 58, stroke=theme["muted"], stroke_width=2, opacity=0.55) + svg_path(parts, nodes, f"process-arrow-{index + 1}", d=f"M{x + 162:g} {(318 if index % 2 == 0 else 272) + 52:g} L{x + 174:g} {(318 if index % 2 == 0 else 272) + 58:g} L{x + 162:g} {(318 if index % 2 == 0 else 272) + 64:g} Z", x=x + 162, y=(318 if index % 2 == 0 else 272) + 52, width=12, height=12, fill=theme["muted"], opacity=0.7) + if conclusion: + svg_rect(parts, nodes, "process-conclusion-bg", x=72, y=458, width=816, height=44, fill=theme["primary"], opacity=0.16) + svg_text(parts, nodes, "conclusion", conclusion, x=92, y=468, width=760, height=28, fill=theme["text"], font_size=19, font_weight=760) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_metric_dashboard(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + metrics = content_first_list(spec, ["metrics", "items"], ["Velocity", "Cost", "Quality", "Reach"])[:6] + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + add_template_header(parts, nodes, spec, theme, title_size=38, title_width=690) + svg_rect(parts, nodes, "dashboard-chart-panel", x=520, y=230, width=340, height=214, fill=theme["panel"], opacity=0.86, stroke=theme["primary"], stroke_width=1.5) + for index, y in enumerate([398, 358, 318, 278]): + svg_line(parts, nodes, f"dashboard-grid-{index + 1}", x1=548, y1=y, x2=830, y2=y, stroke=theme["muted"], stroke_width=1, opacity=0.22) + svg_path(parts, nodes, "dashboard-trend", d="M552 392 C592 370 614 388 652 342 C692 300 724 284 760 314 C786 336 806 278 828 252", x=552, y=252, width=276, height=140, stroke=theme["accent"], stroke_width=4, opacity=0.92) + for index, (cx, cy) in enumerate([(552, 392), (652, 342), (760, 314), (828, 252)]): + svg_circle(parts, nodes, f"dashboard-trend-node-{index + 1}", cx=cx, cy=cy, r=7, fill=theme["primary"], stroke=theme["background"], stroke_width=2) + for index, metric in enumerate(metrics): + x = 72 + (index % 2) * 214 + y = 246 + (index // 2) * 84 + svg_rect(parts, nodes, f"metric-card-{index + 1}", x=x, y=y, width=188, height=76, fill=theme["panel"], opacity=0.88) + svg_rect(parts, nodes, f"metric-signal-{index + 1}", x=x + 14, y=y + 60, width=46 + index * 9, height=5, fill=theme["primary"] if index % 2 == 0 else theme["accent"], opacity=0.8) + svg_text(parts, nodes, f"metric-value-{index + 1}", metric, x=x + 14, y=y + 18, width=158, height=36, fill=theme["text"], font_size=19, font_weight=830) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_risk_alert(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + risks = content_first_list(spec, ["risks", "items"], ["Scope drift", "Dependency delay", "Evidence gap"])[:4] + severity = content_text(spec, "severity", "L2") + summary = content_text(spec, "summary", "") + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + svg_path(parts, nodes, "risk-warning-triangle", d="M720 126 L846 344 L594 344 Z", x=594, y=126, width=252, height=218, fill=theme["accent"], opacity=0.16, stroke=theme["accent"], stroke_width=2) + svg_circle(parts, nodes, "risk-severity-ring", cx=720, cy=276, r=54, fill=theme["background"], opacity=0.92, stroke=theme["accent"], stroke_width=5) + svg_text(parts, nodes, "risk-severity", severity, x=689, y=256, width=62, height=44, fill=theme["text"], font_size=30, font_weight=900) + add_template_header(parts, nodes, spec, theme, title_size=40, title_width=650) + svg_line(parts, nodes, "risk-gauge", x1=104, y1=250, x2=104, y2=444, stroke=theme["accent"], stroke_width=4, opacity=0.7) + for index, risk in enumerate(risks): + y = 250 + index * 50 + color = theme["accent"] if index == 0 else theme["primary"] + svg_circle(parts, nodes, f"risk-node-{index + 1}", cx=104, cy=y + 22, r=10, fill=color) + svg_rect(parts, nodes, f"risk-card-{index + 1}", x=136, y=y, width=476, height=42, fill=theme["panel"], opacity=0.88) + svg_text(parts, nodes, f"risk-item-{index + 1}", risk, x=154, y=y + 9, width=408, height=24, fill=theme["text"], font_size=20, font_weight=760) + if summary: + svg_text(parts, nodes, "risk-summary", summary, x=640, y=338, width=226, height=70, fill=theme["muted"], font_size=18, font_weight=650) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_roadmap_lanes(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + lanes = content_first_list(spec, ["lanes", "items"], ["Now", "Next", "Later"])[:4] + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + add_template_header(parts, nodes, spec, theme, title_size=38, title_width=700) + svg_line(parts, nodes, "roadmap-axis", x1=230, y1=276, x2=842, y2=276, stroke=theme["muted"], stroke_width=2, opacity=0.45) + for index, label in enumerate(["Q1", "Q2", "Q3", "Q4"]): + x = 270 + index * 154 + svg_circle(parts, nodes, f"roadmap-quarter-node-{index + 1}", cx=x, cy=276, r=6, fill=theme["primary"], opacity=0.78) + svg_text(parts, nodes, f"roadmap-quarter-{index + 1}", label, x=x - 13, y=238, width=34, height=30, fill=theme["muted"], font_size=15, font_weight=800) + for index, lane in enumerate(lanes): + y = 312 + index * 44 + svg_text(parts, nodes, f"roadmap-lane-label-{index + 1}", lane, x=72, y=y + 7, width=136, height=26, fill=theme["primary"], font_size=20, font_weight=850) + svg_rect(parts, nodes, f"roadmap-lane-bg-{index + 1}", x=226, y=y, width=620, height=34, fill=theme["panel"], opacity=0.72) + start = 246 + index * 58 + width = 210 + index * 54 + svg_rect(parts, nodes, f"roadmap-lane-bar-{index + 1}", x=start, y=y + 10, width=width, height=12, fill=theme["accent"] if index % 2 else theme["primary"], opacity=0.7) + svg_circle(parts, nodes, f"roadmap-milestone-{index + 1}", cx=start + width, cy=y + 16, r=11, fill=theme["background"], stroke=theme["accent"] if index % 2 else theme["primary"], stroke_width=3) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_architecture_blueprint(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + items = content_first_list(spec, ["nodes", "items"], ["Planner", "CanvasSpec", "Renderer", "SVGlide"])[:6] + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + add_template_header(parts, nodes, spec, theme, title_size=36, title_width=640) + positions = [(102, 268), (366, 238), (630, 268), (102, 388), (366, 358), (630, 388)] + centers = [(x + 96, y + 32) for x, y in positions[: len(items)]] + for index in range(max(0, len(centers) - 1)): + x1, y1 = centers[index] + x2, y2 = centers[index + 1] + svg_line(parts, nodes, f"blueprint-link-{index + 1}", x1=x1, y1=y1, x2=x2, y2=y2, stroke=theme["primary"], stroke_width=2, opacity=0.42) + if len(centers) >= 6: + svg_path(parts, nodes, "blueprint-loop", d="M198 300 C326 194 598 194 726 300 C584 452 340 452 198 420 Z", x=198, y=194, width=528, height=258, fill="none", stroke=theme["accent"], stroke_width=2, opacity=0.34) + for index, item in enumerate(items): + x, y = positions[index] + stroke = theme["accent"] if index in {1, 4} else theme["primary"] + svg_rect(parts, nodes, f"blueprint-node-card-{index + 1}", x=x, y=y, width=192, height=64, fill=theme["panel"], opacity=0.88, stroke=stroke, stroke_width=2) + svg_circle(parts, nodes, f"blueprint-port-{index + 1}", cx=x + 18, cy=y + 32, r=7, fill=stroke) + svg_text(parts, nodes, f"blueprint-node-text-{index + 1}", item, x=x + 36, y=y + 18, width=146, height=30, fill=theme["text"], font_size=17, font_weight=780) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_image_feature(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + points = content_first_list(spec, ["points", "items"], ["Evidence", "Signal", "Implication"])[:3] + caption = content_text(spec, "caption", "") + image_label = content_text(spec, "image_label", "VISUAL ASSET") + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + add_template_header(parts, nodes, spec, theme, title_size=38, title_width=512) + nodes.append({"id": "asset-slot-page", "kind": "rect", "x": 584, "y": 70, "width": 336, "height": 334}) + parts.append( + f'' + ) + nodes.append({"id": "image-label", "kind": "text", "x": 608, "y": 92, "width": 260, "height": 34, "text": image_label.upper()}) + parts.append( + f'{escape(image_label.upper())}' + ) + svg_line(parts, nodes, "image-rule", x1=608, y1=336, x2=870, y2=336, stroke=theme["muted"], stroke_width=1, opacity=0.45) + if caption: + svg_text(parts, nodes, "caption", caption, x=608, y=346, width=262, height=42, fill=theme["muted"], font_size=16, font_weight=600) + for index, point in enumerate(points): + y = 282 + index * 62 + svg_rect(parts, nodes, f"feature-point-bg-{index + 1}", x=72, y=y, width=430, height=44, fill=theme["panel"], opacity=0.82) + svg_circle(parts, nodes, f"feature-point-dot-{index + 1}", cx=94, cy=y + 22, r=6, fill=theme["primary"] if index != 1 else theme["accent"], opacity=0.9) + svg_text(parts, nodes, f"feature-point-{index + 1}", point, x=114, y=y + 9, width=360, height=28, fill=theme["text"], font_size=18, font_weight=720) + svg_path(parts, nodes, "feature-trajectory", d="M88 476 C220 424 310 506 498 448 C602 416 704 454 842 404", x=88, y=404, width=754, height=102, stroke=theme["accent"], stroke_width=3, opacity=0.55) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_data_story(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + metrics = content_first_list(spec, ["metrics", "items"], ["$75B", "$1.77T", "+19%", "4.3%"])[:4] + callout = content_text(spec, "callout", "") + nodes: list[dict[str, Any]] = [] + parts = begin_template_svg(theme, nodes) + add_template_header(parts, nodes, spec, theme, title_size=38, title_width=650) + svg_rect(parts, nodes, "market-terminal", x=56, y=242, width=848, height=226, fill=theme["panel"], opacity=0.82, stroke=theme["primary"], stroke_width=1.4) + for index, metric in enumerate(metrics): + x = 86 + index * 204 + accent = theme["primary"] if index % 2 == 0 else theme["accent"] + svg_text(parts, nodes, f"data-metric-{index + 1}", metric, x=x, y=272, width=164, height=46, fill=accent, font_size=28, font_weight=900) + svg_rect(parts, nodes, f"data-bar-track-{index + 1}", x=x, y=334, width=148, height=10, fill=theme["muted"], opacity=0.22) + svg_rect(parts, nodes, f"data-bar-{index + 1}", x=x, y=334, width=60 + index * 26, height=10, fill=accent, opacity=0.86) + svg_text(parts, nodes, f"data-label-{index + 1}", ["募资规模", "IPO估值", "首日涨幅", "初始流通"][index], x=x, y=354, width=156, height=28, fill=theme["muted"], font_size=15, font_weight=700) + svg_line(parts, nodes, "unlock-axis", x1=96, y1=424, x2=826, y2=424, stroke=theme["muted"], stroke_width=2, opacity=0.5) + for index, label in enumerate(["T+0", "70D", "120D", "180D"]): + x = 96 + index * 242 + svg_circle(parts, nodes, f"unlock-node-{index + 1}", cx=x, cy=424, r=8, fill=theme["primary"] if index < 2 else theme["accent"], opacity=0.92) + svg_text(parts, nodes, f"unlock-label-{index + 1}", label, x=x - 24, y=440, width=60, height=24, fill=theme["text"], font_size=14, font_weight=760) + if callout: + svg_rect(parts, nodes, "data-callout-bg", x=588, y=392, width=260, height=46, fill=theme["background"], opacity=0.72) + svg_text(parts, nodes, "data-callout", callout, x=604, y=402, width=228, height=28, fill=theme["text"], font_size=16, font_weight=760) + svg_path(parts, nodes, "data-price-curve", d="M112 396 C224 370 280 412 382 364 C472 326 544 352 624 306 C692 268 746 286 828 252", x=112, y=252, width=716, height=160, stroke=theme["accent"], stroke_width=3.5, opacity=0.78) + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def template_p1_generic(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + theme = normalize_theme(spec) + template_id = str(spec.get("template_id") or "generic") + eyebrow = content_text(spec, "eyebrow", template_id.replace("-", " ").upper()) + title = content_text(spec, "title", "Untitled") + subtitle = content_text(spec, "subtitle", content_text(spec, "summary", "")) + quote = content_text(spec, "quote", "") + attribution = content_text(spec, "attribution", "") + items = content_first_list( + spec, + ["items", "steps", "events", "metrics", "points", "sections", "risks", "lanes", "nodes", "takeaways"], + ["Context", "Evidence", "Decision"], + )[:6] + if quote: + items = [quote, attribution or "Source"] + items[:3] + nodes = [ + {"id": "background", "kind": "rect", "x": 0, "y": 0, "width": 960, "height": 540}, + {"id": "accent-rule", "kind": "rect", "x": 64, "y": 72, "width": 8, "height": 344}, + {"id": "eyebrow", "kind": "text", "x": 92, "y": 64, "width": 420, "height": 30, "text": eyebrow}, + {"id": "title", "kind": "text", "x": 92, "y": 108, "width": 700, "height": 96, "text": title}, + {"id": "subtitle", "kind": "text", "x": 94, "y": 212, "width": 660, "height": 54, "text": subtitle}, + ] + parts = [ + f'', + f'', + f'', + f'', + f'{escape(eyebrow)}', + f'{escape(title)}', + f'{escape(subtitle)}', + ] + for index, item in enumerate(items): + x = 92 + (index % 3) * 268 + y = 306 + (index // 3) * 92 + nodes.extend( + [ + {"id": f"item-card-{index + 1}", "kind": "rect", "x": x, "y": y, "width": 244, "height": 72}, + {"id": f"item-{index + 1}", "kind": "text", "x": x + 16, "y": y + 15, "width": 212, "height": 44, "text": item}, + ] + ) + parts.append(f'') + parts.append(f'{escape(item)}') + parts.append("") + return "\n".join(parts) + "\n", nodes + + +def render_satori_compatible_svg(spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + template_id = spec.get("template_id") + if template_id in {"cover-hero", "cover_hero"}: + return template_cover_hero(spec) + if template_id == "comparison-cards": + return template_comparison(spec) + if template_id == "summary-final": + return template_summary_final(spec) + if template_id in {"section-title", "section_title"}: + return template_section_title(spec) + if template_id == "comparison": + return template_comparison(spec) + if template_id == "agenda-list": + return template_agenda_list(spec) + if template_id == "timeline-steps": + return template_timeline_steps(spec) + if template_id == "process-flow": + return template_process_flow(spec) + if template_id == "metric-dashboard": + return template_metric_dashboard(spec) + if template_id == "risk-alert": + return template_risk_alert(spec) + if template_id == "roadmap-lanes": + return template_roadmap_lanes(spec) + if template_id == "architecture-blueprint": + return template_architecture_blueprint(spec) + if template_id == "image-feature": + return template_image_feature(spec) + if template_id == "data-story": + return template_data_story(spec) + if template_id in SUPPORTED_TEMPLATES: + return template_p1_generic(spec) + raise ArtboardError(f"unsupported template_id: {template_id}") + + +def use_node_satori_renderer() -> bool: + raw = os.environ.get("SVGLIDE_ARTBOARD_USE_NODE_SATORI") + if raw is None: + return True + return raw.lower() not in {"0", "false", "no"} + + +def resolve_node_renderer() -> Path: + override = os.environ.get("SVGLIDE_ARTBOARD_RENDERER") + if override: + renderer = Path(override).expanduser().resolve() + if renderer.exists(): + return renderer + raise ArtboardError(f"SVGLIDE_ARTBOARD_RENDERER points to a missing file: {renderer}") + if NODE_RENDERER_DIST.exists(): + return NODE_RENDERER_DIST + if NODE_RENDERER_SOURCE.exists(): + return NODE_RENDERER_SOURCE + raise ArtboardError( + "missing node Satori renderer: expected bundled dist/render.mjs in published skills, " + "or source render.mjs for local development" + ) + + +def renderer_receipt_path(renderer: Path) -> str: + try: + return "skills/lark-slides/scripts/" + renderer.relative_to(Path(__file__).resolve().parent).as_posix() + except ValueError: + return renderer.as_posix() + + +def render_node_satori_svg(spec_path: Path, output_path: Path, png_path: Path, metadata_path: Path) -> Path: + renderer = resolve_node_renderer() + command = ["node", renderer.as_posix(), spec_path.as_posix(), output_path.as_posix(), png_path.as_posix(), metadata_path.as_posix()] + result = subprocess.run(command, cwd=renderer.parent, text=True, capture_output=True, check=False) + if result.returncode != 0: + detail = (result.stderr or result.stdout or "").strip() + raise ArtboardError(f"node Satori renderer failed with exit {result.returncode}: {detail}") + return renderer + + +def copy_shape(element: ElementTree.Element) -> str: + name = local_name(element.tag) + attrs = {key: value for key, value in element.attrib.items() if not key.startswith("data-")} + attrs["slide:role"] = "shape" + if name == "path" and "d" in attrs and "a" in str(attrs.get("d", "")) and {"x", "y", "width", "height"}.issubset(attrs): + x = number(attrs.get("x"), 0) + y = number(attrs.get("y"), 0) + width = number(attrs.get("width"), 0) + height = number(attrs.get("height"), 0) + replacement_attrs = {key: value for key, value in attrs.items() if key not in {"x", "y", "width", "height", "d"}} + if width > 0 and height > 0 and abs(width - height) < 0.01: + replacement_attrs.update({"cx": f"{x + width / 2:g}", "cy": f"{y + height / 2:g}", "r": f"{width / 2:g}"}) + return f"" + if width > 0 and height > 0: + replacement_attrs.update({"x": f"{x:g}", "y": f"{y:g}", "width": f"{width:g}", "height": f"{height:g}"}) + return f"" + return f"<{name} {svg_attrs(attrs)} />" + + +def text_style_from_element(element: ElementTree.Element) -> dict[str, str]: + x = number(attr(element, "data-box-x") or attr(element, "x"), 0) + y = number(attr(element, "data-box-y"), number(attr(element, "y"), 0) - number(attr(element, "font-size"), 18)) + width = number(attr(element, "data-box-width"), 360) + height = number(attr(element, "data-box-height"), number(attr(element, "font-size"), 18) * 1.35) + font_size = attr(element, "font-size") or "18" + font_weight = attr(element, "font-weight") or "400" + fill = attr(element, "fill") or "#111827" + family = attr(element, "font-family") or "Inter" + style = style_attr( + { + "font-size": f"{font_size}px" if str(font_size).replace(".", "", 1).isdigit() else font_size, + "font-weight": font_weight, + "font-family": family, + "color": fill, + "line-height": "1.16", + "white-space": "normal", + } + ) + return { + "x": f"{x:g}", + "y": f"{y:g}", + "width": f"{width:g}", + "height": f"{height:g}", + "style": style, + } + + +def text_to_foreign_object(element: ElementTree.Element) -> str: + text = "".join(element.itertext()).strip() + text_style = text_style_from_element(element) + return ( + f'' + f'
{escape(text)}
' + "
" + ) + + +def text_run_key(element: ElementTree.Element) -> tuple[str, str, str, str]: + return ( + attr(element, "y") or "", + attr(element, "font-size") or "18", + attr(element, "font-weight") or "400", + attr(element, "fill") or "#111827", + ) + + +def can_group_satori_text(element: ElementTree.Element) -> bool: + return not any(attr(element, key) is not None for key in ["data-box-x", "data-box-y", "data-box-width", "data-box-height"]) + + +def has_cjk(text: str) -> bool: + return any("\u4e00" <= char <= "\u9fff" for char in text) + + +def text_run_to_foreign_object(elements: list[ElementTree.Element]) -> str: + first = elements[0] + font_size = number(attr(first, "font-size"), 18) + x_values = [number(attr(item, "x"), 0) for item in elements] + x = min(x_values) if x_values else 0 + y = number(attr(first, "y"), 0) - font_size + text_parts: list[str] = [] + max_x = x + previous_right: float | None = None + for item in elements: + item_x = number(attr(item, "x"), x) + text = "".join(item.itertext()) + measured_width = number(attr(item, "width"), 0) + estimated_width = max(measured_width, font_size * 0.45, len(text) * font_size * 0.62) + if previous_right is not None and item_x - previous_right > font_size * 0.35: + text_parts.append(" ") + text_parts.append(text) + previous_right = item_x + estimated_width + max_x = max(max_x, previous_right) + text = "".join(text_parts).strip() + conservative_width = len(text) * font_size if has_cjk(text) else 0 + box_width = min(960 - x, max(max_x - x, conservative_width, font_size * 2)) + box_height = max(number(attr(first, "height"), font_size * 1.35), font_size * 1.35, 45) + synthetic = ElementTree.Element(first.tag, dict(first.attrib)) + synthetic.text = text + synthetic.set("data-box-x", f"{x:g}") + synthetic.set("data-box-y", f"{y:g}") + synthetic.set("data-box-width", f"{box_width:g}") + synthetic.set("data-box-height", f"{box_height:g}") + return text_to_foreign_object(synthetic) + + +def scan_unsupported(root: ElementTree.Element) -> list[dict[str, str]]: + issues: list[dict[str, str]] = [] + for element in root.iter(): + name = local_name(element.tag) + if name in FAIL_FAST_ELEMENTS: + issues.append({"code": "satori_svg_element_fail_fast", "message": f"unsupported Satori SVG element in P0a: {name}"}) + elif name not in SUPPORTED_SATORI_ELEMENTS: + issues.append({"code": "satori_svg_element_unsupported", "message": f"unsupported Satori SVG element in P0a: {name}"}) + if "filter" in element.attrib or "clip-path" in element.attrib or "mask" in element.attrib: + issues.append({"code": "satori_svg_effect_fail_fast", "message": f"unsupported effect attribute on {name}"}) + return issues + + +def compile_svg_markup_to_svglide( + source_svg: str, + *, + semantic_source: str, + compiler_input: str, + satori_svg_usage: str, +) -> tuple[str, dict[str, Any]]: + try: + root = ElementTree.fromstring(source_svg) + except ElementTree.ParseError as err: + raise ArtboardError(f"invalid compiler SVG input: {err}") from err + issues = scan_unsupported(root) + if issues: + raise ArtboardError(json.dumps({"issues": issues}, ensure_ascii=False)) + native_mapped: list[str] = [] + + def compile_sequence(elements: list[ElementTree.Element]) -> list[str]: + output: list[str] = [] + index = 0 + while index < len(elements): + element = elements[index] + name = local_name(element.tag) + if name == "text" and can_group_satori_text(element): + run = [element] + index += 1 + while index < len(elements): + next_element = elements[index] + if local_name(next_element.tag) != "text" or not can_group_satori_text(next_element) or text_run_key(next_element) != text_run_key(element): + break + run.append(next_element) + index += 1 + output.append(text_run_to_foreign_object(run)) + native_mapped.append("text-run->foreignObject") + continue + output.extend(compile_element(element)) + index += 1 + return output + + def compile_element(element: ElementTree.Element) -> list[str]: + name = local_name(element.tag) + if name == "mask": + return [] + if name == "g": + group_children = compile_sequence(list(element)) + if not group_children: + return [] + attrs = {key: value for key, value in element.attrib.items() if not key.startswith("data-")} + attr_text = svg_attrs(attrs) + if attr_text: + return [f"\n" + "\n".join(f" {child}" for child in group_children) + "\n "] + return group_children + if name == "text": + native_mapped.append("text->foreignObject") + return [text_to_foreign_object(element)] + if name in {"rect", "circle", "ellipse", "line", "path"}: + native_mapped.append(name) + return [copy_shape(element)] + return [] + + children = compile_sequence(list(root)) + if not children: + raise ArtboardError("compiler produced no SVGlide nodes") + svg = ( + f'\n' + + "\n".join(f" {child}" for child in children) + + "\n\n" + ) + return svg, { + "semantic_source": semantic_source, + "compiler_input": compiler_input, + "satori_svg_usage": satori_svg_usage, + "native_mapped": native_mapped, + "fail_fast": sorted(FAIL_FAST_ELEMENTS), + } + + +def compile_satori_svg_to_svglide(satori_svg: str) -> tuple[str, dict[str, Any]]: + return compile_svg_markup_to_svglide( + satori_svg, + semantic_source="SatoriSVG", + compiler_input="RawSatoriSVG", + satori_svg_usage="compiler_input", + ) + + +def compile_canvas_template_svg_to_svglide(canvas_template_svg: str) -> tuple[str, dict[str, Any]]: + return compile_svg_markup_to_svglide( + canvas_template_svg, + semantic_source="CanvasSpec", + compiler_input="CanvasSpecTemplateSVG", + satori_svg_usage="preview_only", + ) + + +def normalize_xhtml_foreign_object(svg: str) -> str: + svg = svg.replace(f' xmlns:html="{XHTML_NS}"', "") + svg = svg.replace("", f'
') + svg = svg.replace("", "
") + return svg + + +def align_text_boxes_to_node_layout(svglide_svg: str, nodes: list[dict[str, Any]]) -> str: + text_nodes = [node for node in nodes if node.get("kind") == "text"] + if not text_nodes: + return svglide_svg + ElementTree.register_namespace("", SVG_NS) + ElementTree.register_namespace("slide", SLIDE_NS) + try: + root = ElementTree.fromstring(svglide_svg) + except ElementTree.ParseError as err: + raise ArtboardError(f"invalid compiled SVGlide SVG: {err}") from err + foreign_objects = [element for element in root.iter(f"{{{SVG_NS}}}foreignObject")] + if not foreign_objects: + return ElementTree.tostring(root, encoding="unicode") + "\n" + grouped: list[list[ElementTree.Element]] = [[] for _ in text_nodes] + for element in foreign_objects: + best_index = match_text_node_index(element, text_nodes) + grouped[best_index].append(element) + parents = {child: parent for parent in root.iter() for child in list(parent)} + for node, elements in zip(text_nodes, grouped): + if not elements: + continue + element = elements[0] + for key in ["x", "y", "width", "height"]: + value = node.get(key) + if isinstance(value, (int, float)): + element.set(key, f"{value:g}") + element.set("data-node-id", str(node.get("id") or "")) + text = str(node.get("text") or "") or join_text_fragments(["".join(item.itertext()).strip() for item in elements]) + div = next(iter(element), None) + if div is not None: + div.text = text + for extra in elements[1:]: + parent = parents.get(extra) + if parent is not None: + parent.remove(extra) + return normalize_xhtml_foreign_object(ElementTree.tostring(root, encoding="unicode") + "\n") + + +def normalize_match_text(value: str) -> str: + return "".join(value.split()).lower() + + +def match_text_node_index(element: ElementTree.Element, text_nodes: list[dict[str, Any]]) -> int: + fragment = normalize_match_text("".join(element.itertext()).strip()) + if fragment: + for index, node in enumerate(text_nodes): + target = normalize_match_text(str(node.get("text") or "")) + if target and fragment == target: + return index + for index, node in enumerate(text_nodes): + target = normalize_match_text(str(node.get("text") or "")) + if target and target in fragment: + return index + x = number(element.get("x"), 0) + y = number(element.get("y"), 0) + width = number(element.get("width"), 0) + height = number(element.get("height"), 0) + center_x = x + width / 2 + center_y = y + height / 2 + return min( + range(len(text_nodes)), + key=lambda idx: ( + center_y - (number(text_nodes[idx].get("y"), 0) + number(text_nodes[idx].get("height"), 0) / 2) + ) + ** 2 + + ( + (center_x - (number(text_nodes[idx].get("x"), 0) + number(text_nodes[idx].get("width"), 0) / 2)) / 4 + ) + ** 2, + ) + + +def join_text_fragments(fragments: list[str]) -> str: + result = "" + for fragment in [item for item in fragments if item]: + if result and not result.endswith((" ", "/", "-", "(", "(")) and not fragment.startswith((" ", "/", "-", ")", ")", ".", ",", "。", ",")): + boundary = result[-1] + fragment[0] + if not has_cjk(boundary): + result += " " + result += fragment + return result + + +def validate_satori_preview_svg(satori_svg: str, *, strict: bool = True) -> dict[str, Any]: + try: + root = ElementTree.fromstring(satori_svg) + except ElementTree.ParseError as err: + raise ArtboardError(f"invalid Satori SVG: {err}") from err + issues = scan_unsupported(root) + if strict and issues: + raise ArtboardError(json.dumps({"issues": issues}, ensure_ascii=False)) + element_counts: dict[str, int] = {} + for element in root.iter(): + name = local_name(element.tag) + element_counts[name] = element_counts.get(name, 0) + 1 + return { + "status": "passed" if not issues else "warning", + "element_counts": element_counts, + "fail_fast": sorted(FAIL_FAST_ELEMENTS), + "issues": issues, + "strict": strict, + } + + +def write_contact_sheet(project: Path, png_paths: list[Path]) -> dict[str, Any]: + if not png_paths: + raise ArtboardError("cannot create contact sheet without page PNG files") + try: + from PIL import Image, ImageDraw + except Exception as err: # pragma: no cover - environment dependent + raise ArtboardError("Pillow is required to compose contact-sheet.png from resvg page PNGs") from err + thumbs = [] + for index, png in enumerate(png_paths, 1): + image = Image.open(png).convert("RGB") + image.thumbnail((320, 180), Image.LANCZOS) + tile = Image.new("RGB", (320, 180), (10, 14, 18)) + tile.paste(image, ((320 - image.width) // 2, (180 - image.height) // 2)) + draw = ImageDraw.Draw(tile) + draw.rectangle((8, 8, 46, 30), fill=(15, 23, 42)) + draw.text((16, 13), f"{index:02d}", fill=(248, 250, 252)) + thumbs.append(tile) + cols = min(3, len(thumbs)) + rows = (len(thumbs) + cols - 1) // cols + gap = 16 + sheet = Image.new("RGB", (cols * 320 + (cols + 1) * gap, rows * 180 + (rows + 1) * gap), (6, 10, 16)) + for index, tile in enumerate(thumbs): + x = gap + (index % cols) * (320 + gap) + y = gap + (index // cols) * (180 + gap) + sheet.paste(tile, (x, y)) + output = project / CONTACT_SHEET + output.parent.mkdir(parents=True, exist_ok=True) + sheet.save(output) + return {"path": CONTACT_SHEET.as_posix(), "sha256": file_sha256(output), "source_pngs": [relpath(path, project) for path in png_paths]} + + +def write_canvas_spec_validate(project: Path, pages: list[dict[str, Any]], issues: list[dict[str, Any]], registry_summary: dict[str, Any]) -> dict[str, Any]: + status = "failed" if issues else "passed" + result = { + "schema_version": "svglide-canvas-spec-validate/v1", + "stage": "canvas-spec-validate", + "status": status, + "action": "create_live" if status == "passed" else "repair_and_rerun", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": file_sha256(project / "02-plan/slide_plan.json"), + **registry_summary, + }, + "pages": pages, + "summary": {"error_count": len(issues), "warning_count": 0, "page_count": len(pages)}, + "issues": issues, + "output_path": CANVAS_SPEC_VALIDATE_CHECK.as_posix(), + } + write_json(project / CANVAS_SPEC_VALIDATE_CHECK, result) + write_json(project / CANVAS_SPEC_VALIDATE_RECEIPT, result) + return result + + +def render_project(project: Path) -> dict[str, Any]: + project = project.resolve() + plan = read_json(project / "02-plan/slide_plan.json") + slides = plan.get("slides") + if not isinstance(slides, list) or not slides: + raise ArtboardError("slide_plan.slides must contain at least one slide") + artboard_dir = project / "04-svg/artboard" + raw_dir = artboard_dir / "raw" + artboard_dir.mkdir(parents=True, exist_ok=True) + raw_dir.mkdir(parents=True, exist_ok=True) + validation_issues: list[dict[str, Any]] = [] + validation_pages: list[dict[str, Any]] = [] + prepared_specs: list[dict[str, Any]] = [] + registry_summaries: list[dict[str, Any]] = [] + for index, slide in enumerate(slides, 1): + if not isinstance(slide, dict): + validation_issues.append({"code": "slide_invalid", "message": f"slide {index} must be an object", "page": index}) + continue + spec = slide.get("canvas_spec") + if not isinstance(spec, dict): + validation_issues.append({"code": "canvas_spec_missing", "message": f"slide {index} is missing canvas_spec for generation_mode=artboard_satori", "page": index}) + continue + page_issues = validate_canvas_spec(spec, page=index) + registry_issues, registry_summary = validate_registry_bindings(project, spec, page=index) + page_issues.extend(registry_issues) + validation_issues.extend({**item, "page": index} for item in page_issues) + effective_spec = apply_registry_theme(spec, registry_summary) + prepared_specs.append({"page": index, "slide": slide, "spec": effective_spec, "registry": registry_summary}) + registry_summaries.append(registry_summary) + validation_pages.append( + { + "page": index, + "template_id": spec.get("template_id"), + "theme_id": spec.get("theme_id"), + "canvas_spec_sha256": json_sha256(spec), + "template_registry_sha256": registry_summary.get("template_registry_sha256"), + "theme_registry_sha256": registry_summary.get("theme_registry_sha256"), + "error_count": len(page_issues), + } + ) + registry_summary_for_receipt = { + "template_registry": registry_summaries[0].get("template_registry_path") if registry_summaries else None, + "template_registry_sha256": registry_summaries[0].get("template_registry_sha256") if registry_summaries else None, + "theme_registry": registry_summaries[0].get("theme_registry_path") if registry_summaries else None, + "theme_registry_sha256": registry_summaries[0].get("theme_registry_sha256") if registry_summaries else None, + "theme_files": registry_summaries[0].get("theme_files") if registry_summaries else [], + } + canvas_validate = write_canvas_spec_validate(project, validation_pages, validation_issues, registry_summary_for_receipt) + if validation_issues: + raise ArtboardError(json.dumps({"issues": validation_issues, "receipt": CANVAS_SPEC_VALIDATE_RECEIPT.as_posix()}, ensure_ascii=False)) + max_workers = min(4, len(prepared_specs)) + + def render_page_job(prepared: dict[str, Any]) -> dict[str, Any]: + index = prepared["page"] + spec = prepared["spec"] + registry_summary = prepared["registry"] + page_name = f"page-{index:03d}" + satori_path = raw_dir / f"{page_name}.satori.svg" + png_path = artboard_dir / f"{page_name}.png" + metadata_path = artboard_dir / f"{page_name}.render-metadata.json" + canvas_spec_artifact_path = artboard_dir / f"{page_name}.canvas-spec.json" + canvas_template_path = artboard_dir / f"{page_name}.canvas-template.svg" + semantic_map_path = artboard_dir / f"{page_name}.semantic-map.json" + node_layout_path = artboard_dir / f"{page_name}.node-layout-map.json" + svglide_path = project / "04-svg" / f"{page_name}.svg" + canvas_template_svg, nodes = render_satori_compatible_svg(spec) + canvas_template_path.write_text(canvas_template_svg, encoding="utf-8") + write_json(canvas_spec_artifact_path, spec) + actual_satori_package = use_node_satori_renderer() + node_adapter_path: Path | None = None + renderer_metadata: dict[str, Any] = {} + if actual_satori_package: + node_adapter_path = render_node_satori_svg(canvas_spec_artifact_path, satori_path, png_path, metadata_path) + satori_svg = satori_path.read_text(encoding="utf-8") + renderer_metadata = read_json(metadata_path) + satori_preview = validate_satori_preview_svg(satori_svg, strict=False) + else: + satori_svg = canvas_template_svg + satori_preview = validate_satori_preview_svg(satori_svg, strict=True) + metadata_path.write_text(json.dumps({"node_version": None, "satori_version": None, "resvg_version": None, "font_path": None}, indent=2) + "\n", encoding="utf-8") + svglide_svg, compiler = compile_canvas_template_svg_to_svglide(canvas_template_svg) + svglide_svg = align_text_boxes_to_node_layout(svglide_svg, nodes) + satori_path.write_text(satori_svg, encoding="utf-8") + svglide_path.write_text(svglide_svg, encoding="utf-8") + if not png_path.exists(): + raise ArtboardError(f"missing resvg PNG output for page {index}: {png_path}") + font_path = renderer_metadata.get("font_path") + font_hashes = [] + if isinstance(font_path, str) and Path(font_path).exists(): + font_hashes.append({"path": font_path, "sha256": file_sha256(Path(font_path))}) + semantic_map = { + "version": SEMANTIC_MAP_VERSION, + "page": index, + "template_id": spec.get("template_id"), + "theme_id": spec.get("theme_id"), + "semantic_source": "CanvasSpec", + "content_keys": sorted((spec.get("content") or {}).keys()) if isinstance(spec.get("content"), dict) else [], + "elements": semantic_elements_from_nodes(nodes), + } + node_layout_map = { + "version": NODE_LAYOUT_MAP_VERSION, + "page": index, + "source": "template-layout-map", + "drift": {"status": "not_measured_in_p0", "max_px": 0}, + "nodes": nodes, + } + write_json(semantic_map_path, semantic_map) + write_json(node_layout_path, node_layout_map) + receipt_path = artboard_dir / f"{page_name}.receipt.json" + receipt = { + "version": ARTBOARD_RECEIPT_VERSION, + "stage": "generate_svg", + "status": "passed", + "page": index, + "canvas_spec_path": f"02-plan/slide_plan.json#/slides/{index - 1}/canvas_spec", + "canvas_spec_sha256": json_sha256(spec), + "template_id": spec.get("template_id"), + "theme_id": spec.get("theme_id"), + "template_registry": registry_summary.get("template_registry_path"), + "template_registry_sha256": registry_summary.get("template_registry_sha256"), + "theme_registry": registry_summary.get("theme_registry_path"), + "theme_registry_sha256": registry_summary.get("theme_registry_sha256"), + "theme_files": registry_summary.get("theme_files"), + "node_version": renderer_metadata.get("node_version"), + "satori_version": renderer_metadata.get("satori_version"), + "resvg_version": renderer_metadata.get("resvg_version"), + "font_hashes": font_hashes, + "renderer": { + "name": "satori-resvg-p0", + "engine": "satori-node" if actual_satori_package else "local-static", + "actual_satori_package": actual_satori_package, + "adapter": renderer_receipt_path(node_adapter_path) if node_adapter_path else "skills/lark-slides/scripts/svglide_artboard_renderer.py", + }, + "satori_svg": relpath(satori_path, project), + "satori_svg_sha256": file_sha256(satori_path), + "png": relpath(png_path, project), + "png_sha256": file_sha256(png_path), + "render_metadata": relpath(metadata_path, project), + "render_metadata_sha256": file_sha256(metadata_path), + "canvas_template_svg": relpath(canvas_template_path, project), + "canvas_template_svg_sha256": file_sha256(canvas_template_path), + "compiler_input": relpath(canvas_template_path, project), + "compiler_input_sha256": file_sha256(canvas_template_path), + "semantic_map": relpath(semantic_map_path, project), + "semantic_map_sha256": file_sha256(semantic_map_path), + "node_layout_map": relpath(node_layout_path, project), + "node_layout_map_sha256": file_sha256(node_layout_path), + "svglide_svg": relpath(svglide_path, project), + "svglide_svg_sha256": file_sha256(svglide_path), + "compiler": compiler, + "satori_preview": satori_preview, + "created_at": now_iso(), + } + write_json(receipt_path, receipt) + receipt["path"] = relpath(receipt_path, project) + return { + "page": index, + "receipt": receipt, + "png_path": png_path, + "render_page": { + "page": index, + "template_id": spec.get("template_id"), + "theme_id": spec.get("theme_id"), + "canvas_spec_sha256": json_sha256(spec), + "satori_svg": relpath(satori_path, project), + "satori_svg_sha256": file_sha256(satori_path), + "png": relpath(png_path, project), + "png_sha256": file_sha256(png_path), + "render_metadata": relpath(metadata_path, project), + "render_metadata_sha256": file_sha256(metadata_path), + "canvas_template_svg": relpath(canvas_template_path, project), + "canvas_template_svg_sha256": file_sha256(canvas_template_path), + "node_layout_map": relpath(node_layout_path, project), + "node_layout_map_sha256": file_sha256(node_layout_path), + "node_version": renderer_metadata.get("node_version"), + "satori_version": renderer_metadata.get("satori_version"), + "resvg_version": renderer_metadata.get("resvg_version"), + "font_hashes": font_hashes, + }, + "bridge_page": { + "page": index, + "semantic_source": "CanvasSpec", + "canvas_spec_sha256": json_sha256(spec), + "semantic_map": relpath(semantic_map_path, project), + "semantic_map_sha256": file_sha256(semantic_map_path), + "node_layout_map": relpath(node_layout_path, project), + "node_layout_map_sha256": file_sha256(node_layout_path), + "canvas_template_svg": relpath(canvas_template_path, project), + "canvas_template_svg_sha256": file_sha256(canvas_template_path), + "compiler_input": relpath(canvas_template_path, project), + "compiler_input_sha256": file_sha256(canvas_template_path), + "compiler_input_type": compiler.get("compiler_input"), + "satori_svg_usage": compiler.get("satori_svg_usage"), + "satori_svg": relpath(satori_path, project), + "satori_svg_sha256": file_sha256(satori_path), + "svglide_svg": relpath(svglide_path, project), + "svglide_svg_sha256": file_sha256(svglide_path), + }, + } + + if max_workers > 1: + page_results: list[dict[str, Any]] = [] + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = {executor.submit(render_page_job, prepared): prepared["page"] for prepared in prepared_specs} + for future in as_completed(futures): + page = futures[future] + try: + page_results.append(future.result()) + except Exception as err: + raise ArtboardError(f"artboard page job failed for page {page}: {err}") from err + else: + page_results = [render_page_job(prepared) for prepared in prepared_specs] + page_results.sort(key=lambda item: int(item["page"])) + receipts = [item["receipt"] for item in page_results] + render_pages = [item["render_page"] for item in page_results] + bridge_pages = [item["bridge_page"] for item in page_results] + png_paths = [item["png_path"] for item in page_results] + contact_sheet = write_contact_sheet(project, png_paths) + artboard_render_receipt = { + "version": "svglide-artboard-render/v1", + "stage": "artboard-render", + "status": "passed", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": file_sha256(project / "02-plan/slide_plan.json"), + **registry_summary_for_receipt, + "canvas_spec_validate": CANVAS_SPEC_VALIDATE_RECEIPT.as_posix(), + "canvas_spec_validate_sha256": file_sha256(project / CANVAS_SPEC_VALIDATE_RECEIPT), + }, + "pages": render_pages, + "contact_sheet": contact_sheet, + "summary": {"error_count": 0, "warning_count": 0, "page_count": len(render_pages), "max_workers": max_workers}, + "created_at": now_iso(), + } + write_json(project / ARTBOARD_RENDER_RECEIPT, artboard_render_receipt) + satori_bridge_receipt = { + "version": "svglide-satori-bridge/v1", + "stage": "satori-bridge", + "status": "passed", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": file_sha256(project / "02-plan/slide_plan.json"), + "artboard_render": ARTBOARD_RENDER_RECEIPT.as_posix(), + "artboard_render_sha256": file_sha256(project / ARTBOARD_RENDER_RECEIPT), + }, + "pages": bridge_pages, + "summary": {"error_count": 0, "warning_count": 0, "page_count": len(bridge_pages), "max_workers": max_workers}, + "created_at": now_iso(), + } + write_json(project / SATORI_BRIDGE_RECEIPT, satori_bridge_receipt) + return { + "version": "svglide-artboard-render/v1", + "status": "passed", + "project": str(project), + "page_count": len(receipts), + "max_workers": max_workers, + "artboard_receipts": [receipt["path"] for receipt in receipts], + "additional_receipts": [ + CANVAS_SPEC_VALIDATE_RECEIPT.as_posix(), + ARTBOARD_RENDER_RECEIPT.as_posix(), + SATORI_BRIDGE_RECEIPT.as_posix(), + ], + "canvas_spec_validate": CANVAS_SPEC_VALIDATE_CHECK.as_posix(), + "artboard_render_receipt": ARTBOARD_RENDER_RECEIPT.as_posix(), + "satori_bridge_receipt": SATORI_BRIDGE_RECEIPT.as_posix(), + "contact_sheet": contact_sheet, + "generated_files": [{"path": receipt["svglide_svg"], "sha256": receipt["svglide_svg_sha256"]} for receipt in receipts], + } + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Render CanvasSpec artboards into SVGlide protocol SVG.") + parser.add_argument("project", type=Path) + parser.add_argument("--pretty", action="store_true") + args = parser.parse_args(argv) + try: + result = render_project(args.project) + except ArtboardError as error: + print(f"svglide_artboard_renderer: {error}", file=sys.stderr) + return 1 + print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/lark-slides/scripts/svglide_artboard_renderer_test.py b/skills/lark-slides/scripts/svglide_artboard_renderer_test.py new file mode 100644 index 00000000..de98d9b8 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_artboard_renderer_test.py @@ -0,0 +1,467 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_artboard_renderer as artboard + + +def write_json(path: Path, payload: dict[str, object]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload), encoding="utf-8") + + +def canvas_spec() -> dict[str, object]: + return { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "theme": { + "colors": { + "background": "#0F172A", + "panel": "#111827", + "primary": "#38BDF8", + "accent": "#A78BFA", + "text": "#F8FAFC", + "muted": "#CBD5E1", + } + }, + "content": { + "eyebrow": "P0A", + "title": "受控画板生成", + "subtitle": "CanvasSpec 作为语义真源。", + "chips": ["CanvasSpec", "Satori Preview", "SVGlide"], + }, + "semantic_elements": [ + { + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title", + "bbox": {"x": 84, "y": 142, "width": 628, "height": 142}, + } + ], + "quality_constraints": { + "max_title_lines": 2, + "min_font_size": 18, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + }, + } + + +class SVGlideArtboardRendererTest(unittest.TestCase): + def test_p0_theme_registry_components_and_fixtures_are_registered(self) -> None: + scripts_dir = Path(__file__).resolve().parent + renderer_dir = scripts_dir / "artboard_renderer" + theme_registry = json.loads((renderer_dir / "themes/registry.json").read_text(encoding="utf-8")) + theme_ids = [item["id"] for item in theme_registry["themes"] if item.get("status") == "active"] + repo_root = scripts_dir.parents[2] + required_theme_ids = { + "dark-clarity", + "forest-signal", + "warm-editorial", + "blueprint-technical", + "editorial-tritone", + "cobalt-grid", + "finance-dark", + "swiss-red", + "glass-neon", + "paper-research", + } + self.assertGreaterEqual(len(theme_ids), 10) + self.assertTrue(required_theme_ids.issubset(set(theme_ids))) + for record in theme_registry["themes"]: + theme_path = repo_root / record["path"] + payload = json.loads(theme_path.read_text(encoding="utf-8")) + self.assertEqual(payload["theme_id"], record["id"]) + self.assertTrue({"background", "panel", "primary", "accent", "text", "muted"}.issubset(payload["colors"])) + + template_registry = json.loads((scripts_dir.parent / "references/svglide-template-registry.json").read_text(encoding="utf-8")) + active_templates = {item["id"]: item for item in template_registry["templates"] if item.get("status") == "active"} + required_template_ids = { + "cover-hero", + "comparison-cards", + "summary-final", + "section-title", + "agenda-list", + "timeline-steps", + "process-flow", + "metric-dashboard", + "quote-focus", + "image-feature", + "research-poster", + "data-story", + "risk-alert", + "roadmap-lanes", + "architecture-blueprint", + } + self.assertGreaterEqual(len(active_templates), 15) + self.assertTrue(required_template_ids.issubset(set(active_templates))) + for template in active_templates.values(): + self.assertTrue(set(theme_ids).issubset(set(template["supported_theme_ids"]))) + + components = (renderer_dir / "components/primitives.mjs").read_text(encoding="utf-8") + for export_name in ["Title", "Subtitle", "Chip", "StatCard", "ImageFrame"]: + self.assertIn(f"export function {export_name}", components) + + component_registry = json.loads((scripts_dir.parent / "references/svglide-component-registry.json").read_text(encoding="utf-8")) + active_components = [item for item in component_registry["components"] if item.get("status") == "active"] + self.assertGreaterEqual(len(active_components), 20) + layout_registry = json.loads((scripts_dir.parent / "references/svglide-layout-archetypes.json").read_text(encoding="utf-8")) + active_layouts = [item for item in layout_registry["archetypes"] if item.get("status") == "active"] + self.assertGreaterEqual(len(active_layouts), 8) + source_intake = json.loads((scripts_dir.parent / "references/svglide-p1-source-intake.json").read_text(encoding="utf-8")) + self.assertEqual("forbidden", source_intake["policy"]["runtime_import"]) + self.assertGreaterEqual(source_intake["p1_abstractions"]["template_count"], 15) + required_source_fields = { + "source_path", + "source_type", + "extract_fields", + "conversion_target", + "acceptance_rule", + "forbidden_usage", + "source_hash_or_version", + "license_or_provenance", + } + for source in source_intake["sources"]: + self.assertTrue(required_source_fields.issubset(source)) + if source["id"] == "ppt-master-examples": + self.assertIn("MIT", source["license_or_provenance"]) + self.assertNotIn("no LICENSE", source["license_or_provenance"]) + self.assertTrue(source["conversion_records"]) + for record in source["conversion_records"]: + self.assertTrue(record["source_examples"]) + self.assertTrue(record["abstraction_record"]) + self.assertTrue(record["canvas_spec_fields"]) + self.assertTrue(record["registry_output"]["templates"]) + self.assertTrue(record["registry_output"]["themes"]) + self.assertTrue(record["registry_output"]["components"]) + self.assertTrue(record["registry_output"]["layouts"]) + self.assertTrue(record["registry_output"]["golden_fixtures"]) + self.assertTrue(record["acceptance_rule"]) + + p0b_plan = json.loads((scripts_dir / "fixtures/svglide_artboard/p0b-three-page/02-plan/slide_plan.json").read_text(encoding="utf-8")) + fixture_theme_ids = [slide["canvas_spec"]["theme_id"] for slide in p0b_plan["slides"]] + self.assertEqual(["dark-clarity", "forest-signal", "warm-editorial"], fixture_theme_ids) + golden_dir = scripts_dir / "fixtures/svglide_artboard/golden" + for template_id in active_templates: + golden = json.loads((golden_dir / f"{template_id}.canvas-spec.json").read_text(encoding="utf-8")) + self.assertEqual(golden["template_id"], template_id) + self.assertIn(golden["theme_id"], theme_ids) + issues = artboard.validate_canvas_spec(golden, page=1) + registry_issues, _ = artboard.validate_registry_bindings(Path(tempfile.gettempdir()), golden, page=1) + self.assertEqual([], issues + registry_issues) + + def test_p1_active_template_golden_fixtures_render(self) -> None: + scripts_dir = Path(__file__).resolve().parent + golden_dir = scripts_dir / "fixtures/svglide_artboard/golden" + template_registry = json.loads((scripts_dir.parent / "references/svglide-template-registry.json").read_text(encoding="utf-8")) + active_template_ids = sorted(item["id"] for item in template_registry["templates"] if item.get("status") == "active") + slides = [] + for page, template_id in enumerate(active_template_ids, 1): + golden = json.loads((golden_dir / f"{template_id}.canvas-spec.json").read_text(encoding="utf-8")) + slides.append({"page": page, "title": golden["content"]["title"], "canvas_spec": golden}) + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "02-plan/slide_plan.json", {"generation_mode": "artboard_satori", "slides": slides}) + + result = artboard.render_project(project) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["max_workers"], 4) + self.assertEqual(len(result["artboard_receipts"]), len(active_template_ids)) + self.assertTrue((project / "05-preview/contact-sheet.png").exists()) + for page in range(1, len(active_template_ids) + 1): + self.assertTrue((project / f"04-svg/artboard/page-{page:03d}.png").exists()) + self.assertTrue((project / f"04-svg/page-{page:03d}.svg").exists()) + + def test_satori_adapter_packaging_is_release_safe(self) -> None: + renderer_dir = Path(__file__).resolve().parent / "artboard_renderer" + package = json.loads((renderer_dir / "package.json").read_text(encoding="utf-8")) + self.assertEqual(package["dependencies"]["satori"], "0.26.0") + self.assertEqual(package["dependencies"]["@resvg/resvg-js"], "2.6.2") + self.assertNotIn("file:", json.dumps(package)) + self.assertTrue((renderer_dir / "dist/render.mjs").exists()) + lockfile = (renderer_dir / "pnpm-lock.yaml").read_text(encoding="utf-8") + self.assertNotIn("satori@file:", lockfile) + self.assertNotIn("@resvg/resvg-js@file:", lockfile) + self.assertNotIn("file:../../", lockfile) + + def test_render_project_writes_artboard_and_svglide_receipts(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json( + project / "02-plan/slide_plan.json", + { + "generation_mode": "artboard_satori", + "slides": [ + { + "page": 1, + "title": "受控画板生成", + "canvas_spec": canvas_spec(), + } + ], + }, + ) + + result = artboard.render_project(project) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["max_workers"], 1) + self.assertEqual(result["artboard_receipts"], ["04-svg/artboard/page-001.receipt.json"]) + self.assertEqual( + result["additional_receipts"], + [ + "receipts/canvas-spec-validate.json", + "receipts/artboard-render.json", + "receipts/satori-bridge.json", + ], + ) + self.assertEqual(result["contact_sheet"]["path"], "05-preview/contact-sheet.png") + self.assertTrue((project / "04-svg/artboard/page-001.png").exists()) + self.assertTrue((project / "04-svg/artboard/page-001.render-metadata.json").exists()) + self.assertTrue((project / "04-svg/artboard/page-001.canvas-template.svg").exists()) + self.assertTrue((project / "05-preview/contact-sheet.png").exists()) + svg = (project / "04-svg/page-001.svg").read_text(encoding="utf-8") + self.assertIn('xmlns:slide="https://slides.bytedance.com/ns"', svg) + self.assertIn('slide:role="slide"', svg) + self.assertIn('slide:shape-type="text"', svg) + self.assertIn('
None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + fixture_plan = json.loads( + (Path(__file__).resolve().parent / "fixtures/svglide_artboard/p0b-three-page/02-plan/slide_plan.json").read_text( + encoding="utf-8" + ) + ) + write_json(project / "02-plan/slide_plan.json", fixture_plan) + + result = artboard.render_project(project) + + self.assertEqual(result["max_workers"], 3) + self.assertEqual( + result["artboard_receipts"], + [ + "04-svg/artboard/page-001.receipt.json", + "04-svg/artboard/page-002.receipt.json", + "04-svg/artboard/page-003.receipt.json", + ], + ) + render_receipt = json.loads((project / "receipts/artboard-render.json").read_text(encoding="utf-8")) + bridge_receipt = json.loads((project / "receipts/satori-bridge.json").read_text(encoding="utf-8")) + self.assertEqual(render_receipt["summary"]["max_workers"], 3) + self.assertEqual(bridge_receipt["summary"]["max_workers"], 3) + self.assertEqual([page["page"] for page in render_receipt["pages"]], [1, 2, 3]) + self.assertEqual([page["page"] for page in bridge_receipt["pages"]], [1, 2, 3]) + + def test_render_project_rejects_unsupported_template(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + spec = canvas_spec() + spec["template_id"] = "freeform_html" + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1, "canvas_spec": spec}]}) + + with self.assertRaisesRegex(artboard.ArtboardError, "canvas_spec_template_unsupported"): + artboard.render_project(project) + + def test_render_project_rejects_unknown_theme(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + spec = canvas_spec() + spec["theme_id"] = "unregistered-theme" + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1, "canvas_spec": spec}]}) + + with self.assertRaisesRegex(artboard.ArtboardError, "canvas_spec_theme_unknown"): + artboard.render_project(project) + + def test_render_project_rejects_missing_required_content_and_card_overflow(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + spec = { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "template_id": "comparison-cards", + "theme_id": "forest-signal", + "theme": {"colors": {"background": "#0B1F1A"}}, + "content": { + "title": "新旧链路差异", + "left_title": "旧链路", + "left_points": ["一", "二", "三", "四"], + "right_points": ["一"], + }, + } + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1, "canvas_spec": spec}]}) + + with self.assertRaisesRegex(artboard.ArtboardError, "canvas_spec_template_required_content_missing"): + artboard.render_project(project) + + try: + artboard.render_project(project) + except artboard.ArtboardError as error: + self.assertIn("canvas_spec_template_too_many_items", str(error)) + + def test_render_project_rejects_overlong_title_text_budget(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + spec = canvas_spec() + spec["content"]["title"] = "这是一段明显超过封面模板标题预算的超长标题,用来证明输入质量门禁会在渲染前阻断" + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1, "canvas_spec": spec}]}) + + with self.assertRaisesRegex(artboard.ArtboardError, "canvas_spec_text_budget_exceeded"): + artboard.render_project(project) + + def test_render_project_rejects_semantic_bbox_outside_safe_area(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + spec = canvas_spec() + spec["semantic_elements"][0]["bbox"] = {"x": 12, "y": 12, "width": 628, "height": 142} + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1, "canvas_spec": spec}]}) + + with self.assertRaisesRegex(artboard.ArtboardError, "canvas_spec_bbox_out_of_safe_area"): + artboard.render_project(project) + + def test_satori_bridge_fails_fast_on_filter(self) -> None: + source = '' + + with self.assertRaisesRegex(artboard.ArtboardError, "satori_svg_effect_fail_fast"): + artboard.compile_satori_svg_to_svglide(source) + + def test_satori_bridge_ignores_unreferenced_mask_definitions(self) -> None: + source = ( + '' + '' + '' + '' + ) + + svg, compiler = artboard.compile_satori_svg_to_svglide(source) + + self.assertIn('slide:role="shape"', svg) + self.assertIn("rect", compiler["native_mapped"]) + + def test_satori_bridge_fails_fast_on_mask_usage(self) -> None: + source = ( + '' + '' + '' + '' + ) + + with self.assertRaisesRegex(artboard.ArtboardError, "satori_svg_effect_fail_fast"): + artboard.compile_satori_svg_to_svglide(source) + + def test_satori_bridge_recursively_maps_nested_groups(self) -> None: + source = ( + '' + '' + '' + 'Nested' + '' + '' + ) + + svg, compiler = artboard.compile_satori_svg_to_svglide(source) + + self.assertIn('slide:role="shape"', svg) + self.assertIn('', svg) + self.assertIn('transform="translate(10 20)"', svg) + self.assertIn('slide:shape-type="text"', svg) + self.assertEqual(compiler["semantic_source"], "SatoriSVG") + self.assertEqual(compiler["compiler_input"], "RawSatoriSVG") + self.assertEqual(compiler["satori_svg_usage"], "compiler_input") + + def test_align_text_boxes_merges_wrapped_satori_text_runs(self) -> None: + svglide_svg = ( + '' + '' + '
先稳定 renderer 并推进 live/
' + '
' + '' + '
readback。
' + '
' + '
' + ) + nodes = [{"id": "subtitle", "kind": "text", "x": 108, "y": 322, "width": 640, "height": 66}] + + aligned = artboard.align_text_boxes_to_node_layout(svglide_svg, nodes) + + self.assertEqual(aligned.count(" None: + svglide_svg = ( + '' + '' + '
发射复用、Starlink、Starship 期权共同支撑估值。
' + '
' + '' + '
发射复用
' + '
' + '' + '
Starship期权
' + '
' + '
' + ) + nodes = [ + {"id": "subtitle", "kind": "text", "x": 66, "y": 168, "width": 640, "height": 58, "text": "发射复用、Starlink、Starship 期权共同支撑估值。"}, + {"id": "node-1", "kind": "text", "x": 138, "y": 286, "width": 146, "height": 30, "text": "发射复用"}, + {"id": "node-3", "kind": "text", "x": 666, "y": 286, "width": 146, "height": 30, "text": "Starship期权"}, + ] + + aligned = artboard.align_text_boxes_to_node_layout(svglide_svg, nodes) + + self.assertEqual(aligned.count("发射复用<", aligned) + self.assertIn(">Starship期权<", aligned) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_asset_injector.py b/skills/lark-slides/scripts/svglide_asset_injector.py index 1e9c339e..455af9b7 100644 --- a/skills/lark-slides/scripts/svglide_asset_injector.py +++ b/skills/lark-slides/scripts/svglide_asset_injector.py @@ -180,6 +180,8 @@ def has_body_slot(svg_text: str, asset: dict[str, Any], page: int) -> bool: f"id='asset-slot-{asset_id}'", f'id="asset-slot-page-{page:03d}"', f"id='asset-slot-page-{page:03d}'", + 'data-node-id="image-label"', + "data-node-id='image-label'", "') asset_file = self.write_asset(project) @@ -232,11 +232,46 @@ class SVGlideAssetInjectorTest(unittest.TestCase): svg = (project / "04-svg/page-001.svg").read_text(encoding="utf-8") self.assertEqual(result["used_count"], 1) - self.assertIn('slide:shape-type="text"', svg) - self.assertIn("Visual evidence", svg) - self.assertIn("Source: https://example.com/source", svg) + self.assertIn(" None: + project = self.make_project() + self.write_svg(project, 1, 'Title') + asset_file = self.write_asset(project) + write_json( + project / "03-assets/asset-manifest.json", + { + "version": "svglide-assets/v1", + "status": "passed", + "acquired_assets": [ + { + "asset_id": "market-signal", + "page": 1, + "placement_role": "inline_figure", + "asset_kind": "user_file", + "status": "local_file", + "file": asset_file, + "source_url": "https://example.com/source", + "license": "preview_unverified", + } + ], + }, + ) + + result = svglide_asset_injector.inject_project_assets(project) + svg = (project / "04-svg/page-001.svg").read_text(encoding="utf-8") + + self.assertEqual(result["used_count"], 1) + self.assertEqual(result["by_page"][0]["status"], "injected") + self.assertEqual(result["by_page"][0]["renderer_id"], "ambient_asset_background") + self.assertEqual(result["by_page"][0]["slot_strategy"], "ambient_fallback") + self.assertIn('data-svglide-slot-strategy="ambient_fallback"', svg) + self.assertIn('href="@./03-assets/raw/hero.png"', svg) + self.assertIn("Title", svg) + if __name__ == "__main__": unittest.main() diff --git a/skills/lark-slides/scripts/svglide_assets.py b/skills/lark-slides/scripts/svglide_assets.py index 0d79dc10..7793294d 100644 --- a/skills/lark-slides/scripts/svglide_assets.py +++ b/skills/lark-slides/scripts/svglide_assets.py @@ -147,7 +147,7 @@ def collect_asset_contracts(plan: dict[str, Any], lock: dict[str, Any]) -> list[ item = dict(raw) item.setdefault("source", source_name) item.setdefault("key", key) - item.setdefault("id", item.get("name") or item.get("href") or item.get("path") or f"{key}-{index + 1}") + item.setdefault("id", item.get("name") or item.get("href") or item.get("path") or item.get("local_path") or f"{key}-{index + 1}") item.setdefault("required", True) contracts.append(item) return contracts @@ -266,7 +266,7 @@ def acquire_contract_asset( asset_id = safe_asset_id(contract.get("id"), index) role = placement_role(contract) query = image_query(contract) - href = contract.get("href") or contract.get("path") + href = contract.get("href") or contract.get("path") or contract.get("local_path") base = { "asset_id": asset_id, "page": contract.get("usage_page") or contract.get("page"), @@ -374,7 +374,7 @@ def local_asset_path(project: Path, ref: str) -> Path | None: def evaluate_contract(project: Path, contract: dict[str, Any], assets: dict[str, str]) -> dict[str, Any]: - href = contract.get("href") or contract.get("placeholder") or contract.get("path") + href = contract.get("href") or contract.get("placeholder") or contract.get("path") or contract.get("local_path") token = contract.get("token") or contract.get("file_token") required = bool(contract.get("required", True)) status = "declared" diff --git a/skills/lark-slides/scripts/svglide_assets_test.py b/skills/lark-slides/scripts/svglide_assets_test.py index 88b45fd6..ddc27fbd 100644 --- a/skills/lark-slides/scripts/svglide_assets_test.py +++ b/skills/lark-slides/scripts/svglide_assets_test.py @@ -49,6 +49,21 @@ class SVGlideAssetsTest(unittest.TestCase): self.assertEqual(result["status"], "passed") self.assertEqual(result["manifest"]["contracts"][0]["status"], "local_file") + def test_assets_stage_accepts_existing_local_path_asset(self) -> None: + project = self.make_project() + write_json( + project / "02-plan/svglide.lock.json", + {"asset_contracts": [{"id": "hero", "local_path": "@./03-assets/hero.png"}]}, + ) + (project / "03-assets").mkdir(parents=True, exist_ok=True) + (project / "03-assets/hero.png").write_bytes(b"png") + + result = svglide_assets.run_assets(project) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["manifest"]["contracts"][0]["status"], "local_file") + self.assertEqual(result["manifest"]["acquired_assets"][0]["status"], "local_file") + def test_assets_stage_blocks_required_http_asset(self) -> None: project = self.make_project() write_json(project / "02-plan/svglide.lock.json", {"asset_contracts": [{"id": "hero", "href": "https://example.com/hero.png"}]}) diff --git a/skills/lark-slides/scripts/svglide_gate8_special_cases.py b/skills/lark-slides/scripts/svglide_gate8_special_cases.py new file mode 100644 index 00000000..9a6cb5a3 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_gate8_special_cases.py @@ -0,0 +1,409 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import base64 +import hashlib +import json +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Callable +from xml.etree import ElementTree + +import svglide_artboard_renderer as artboard +import svglide_asset_injector +import svglide_chart_verify +import svglide_prepare +import svglide_readback +import svg_preflight + + +SCRIPT_DIR = Path(__file__).resolve().parent +FIXTURE_DIR = SCRIPT_DIR / "fixtures" / "svglide_artboard" / "gate8_special_cases" +OUTPUT_NAME = "gate8-special-cases.json" +PNG_1X1 = base64.b64decode( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=" +) + + +class Gate8Error(Exception): + pass + + +def now_iso() -> str: + return datetime.now(timezone.utc).isoformat() + + +def read_json(path: Path) -> dict[str, Any]: + payload = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(payload, dict): + raise Gate8Error(f"expected JSON object: {path}") + return payload + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def write_png(path: Path) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(PNG_1X1) + + +def file_sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def relpath(path: Path, base: Path) -> str: + return path.resolve().relative_to(base.resolve()).as_posix() + + +def xhtml_text(text: str, *, size: int = 24, color: str = "#F8FAFC") -> str: + escaped = text.replace("&", "&").replace("<", "<").replace(">", ">") + return ( + f'
' + f"{escaped}
" + ) + + +def svg_shell(body: str) -> str: + return ( + '\n' + f"{body}\n" + "\n" + ) + + +def write_minimal_plan(project: Path, slides: list[dict[str, Any]], *, title: str) -> None: + write_json( + project / "02-plan" / "slide_plan.json", + { + "route": "svglide-svg", + "language": "zh-CN", + "audience": "SVGlide engineers", + "title": title, + "slides": slides, + }, + ) + + +def fake_completed(payload: dict[str, Any]) -> Callable[..., Any]: + import subprocess + + def run(command: list[str], **_: Any) -> subprocess.CompletedProcess[str]: + return subprocess.CompletedProcess(command, 0, stdout=json.dumps(payload), stderr="") + + return run + + +def chart_payload() -> tuple[str, str, dict[str, Any]]: + spec = read_json(FIXTURE_DIR / "chart-spec.json") + raw = json.dumps(spec, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + payload = base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=") + return payload, "sha256:" + hashlib.sha256(raw).hexdigest(), spec + + +def chart_marker_svg() -> str: + payload, payload_hash, _ = chart_payload() + body = f""" + + {xhtml_text("Gate 8 chart marker", size=30)} + + {payload} + + + + +""" + return svg_shell(body) + + +def image_asset_svg() -> str: + return svg_shell( + f""" + + {xhtml_text("Gate 8 image asset binding", size=28)} + + +""" + ) + + +def raster_fallback_svg() -> str: + return svg_shell( + f""" + + {xhtml_text("Gate 8 raster fallback island", size=30)} + + + {xhtml_text("Only the decorative glow is rasterized.", size=22, color="#FFF7ED")} +""" + ) + + +def check_no_svg_errors(result: dict[str, Any]) -> bool: + return result.get("summary", {}).get("error_count") == 0 + + +def case_result(name: str, status: str, **extra: Any) -> dict[str, Any]: + return {"name": name, "status": status, **extra} + + +def run_unsupported_case(root: Path) -> dict[str, Any]: + project = root / "unsupported-feature" + spec = read_json(FIXTURE_DIR / "unsupported-filter.canvas-spec.json") + write_minimal_plan(project, [{"page": 1, "title": "Unsupported filter", "canvas_spec": spec}], title="Gate 8 Unsupported") + render_failed = False + render_error = "" + try: + artboard.render_project(project) + except artboard.ArtboardError as error: + render_failed = "canvas_spec_unsupported_features" in str(error) + render_error = str(error) + bridge_failed = False + bridge_error = "" + try: + artboard.compile_satori_svg_to_svglide( + '' + ) + except artboard.ArtboardError as error: + bridge_failed = "satori_svg_effect_fail_fast" in str(error) + bridge_error = str(error) + status = "passed" if render_failed and bridge_failed else "failed" + return case_result( + "unsupported_feature_fail_fast", + status, + project=relpath(project, root), + render_failed_before_live=render_failed, + render_error=render_error, + bridge_failed_before_live=bridge_failed, + bridge_error=bridge_error, + canvas_spec_fixture=relpath(FIXTURE_DIR / "unsupported-filter.canvas-spec.json", SCRIPT_DIR.parents[2]), + ) + + +def run_chart_case(root: Path) -> dict[str, Any]: + project = root / "chart-marker" + write_minimal_plan( + project, + [ + { + "page": 1, + "title": "Gate 8 chart marker", + "chart_contract": {"verify": "required", "labels": ["Q1", "Q2", "Q3"], "data": [12, 18, 25]}, + } + ], + title="Gate 8 Chart", + ) + svg_path = project / "04-svg" / "prepared" / "page-001.svg" + svg_path.parent.mkdir(parents=True, exist_ok=True) + svg_path.write_text(chart_marker_svg(), encoding="utf-8") + preflight = svg_preflight.lint_files([svg_path.as_posix()]) + chart_verify = svglide_chart_verify.run_chart_verify(project) + write_json(project / "07-create" / "live-create.json", {"xml_presentation_id": "gate8_chart", "slide_ids": ["chart-slide"]}) + readback = svglide_readback.run_readback( + project, + command_runner=fake_completed({"data": {"xml_presentation": {"content": f'{chart_marker_svg()}'}}}), + ) + status = ( + "passed" + if check_no_svg_errors(preflight) + and chart_verify["status"] == "passed" + and readback["checks"]["chart_markers"]["status"] == "passed" + else "failed" + ) + payload, payload_hash, spec = chart_payload() + return case_result( + "chart_marker_svglide_chart_spec_v1", + status, + project=relpath(project, root), + svg=relpath(svg_path, root), + chart_spec_sha256=hashlib.sha256(json.dumps(spec, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest(), + chart_payload_hash=payload_hash, + chart_payload_length=len(payload), + preflight_summary=preflight["summary"], + chart_verify_status=chart_verify["status"], + readback_chart_markers=readback["checks"]["chart_markers"], + ) + + +def run_image_case(root: Path) -> dict[str, Any]: + project = root / "image-asset" + write_minimal_plan(project, [{"page": 1, "title": "Gate 8 image asset"}], title="Gate 8 Image") + asset = project / "03-assets" / "raw" / "hero.png" + write_png(asset) + write_json( + project / "03-assets" / "asset-manifest.json", + { + "version": "svglide-assets/v1", + "status": "passed", + "acquired_assets": [ + { + "asset_id": "hero", + "page": 1, + "placement_role": "body_visual", + "asset_kind": "user_file", + "status": "local_file", + "file": "03-assets/raw/hero.png", + "source_url": "https://example.com/gate8-hero", + "license": "preview_unverified", + } + ], + }, + ) + write_json(project / "03-assets" / "assets.json", {"@./03-assets/raw/hero.png": "boxcn_gate8_hero"}) + source_svg = project / "04-svg" / "page-001.svg" + source_svg.parent.mkdir(parents=True, exist_ok=True) + source_svg.write_text(image_asset_svg(), encoding="utf-8") + injection = svglide_asset_injector.inject_project_assets(project) + prepare = svglide_prepare.prepare_project(project) + write_json(project / "07-create" / "live-create.json", {"xml_presentation_id": "gate8_image", "slide_ids": ["image-slide"]}) + readback = svglide_readback.run_readback( + project, + command_runner=fake_completed( + {"data": {"xml_presentation": {"content": ''}}} + ), + ) + status = ( + "passed" + if injection["used_count"] == 1 + and any(ref["refs"][0]["status"] == "mapped" for ref in prepare.get("asset_refs", [])) + and readback["checks"]["asset_tokens"]["status"] == "passed" + and readback["checks"]["image_assets"]["status"] == "passed" + else "failed" + ) + return case_result( + "image_asset_binding_readback", + status, + project=relpath(project, root), + source_svg=relpath(source_svg, root), + asset_sha256=file_sha256(asset), + injection_summary={key: injection[key] for key in ["used_count", "injected_count", "skipped_count"]}, + prepare_asset_refs=prepare.get("asset_refs", []), + readback_asset_tokens=readback["checks"]["asset_tokens"], + readback_image_assets=readback["checks"]["image_assets"], + ) + + +def raster_fallback_records(svg_text: str, project: Path) -> list[dict[str, Any]]: + root = ElementTree.fromstring(svg_text) + records: list[dict[str, Any]] = [] + for element in root.iter(): + if element.tag.rsplit("}", 1)[-1] != "image": + continue + marker = element.get("data-svglide-raster-fallback") + if marker != "isolated-decoration": + continue + href = element.get("href") or element.get("{http://www.w3.org/1999/xlink}href") or "" + bbox = { + "x": float(element.get("x") or 0), + "y": float(element.get("y") or 0), + "width": float(element.get("width") or 0), + "height": float(element.get("height") or 0), + } + local = project / href[3:] if href.startswith("@./") else None + records.append( + { + "marker": marker, + "reason": element.get("data-svglide-fallback-reason"), + "href": href, + "bbox": bbox, + "island": bbox["width"] <= 240 and bbox["height"] <= 240 and bbox["x"] >= 0 and bbox["y"] >= 0, + "file_exists": bool(local and local.exists()), + "file_sha256": file_sha256(local) if local and local.exists() else None, + } + ) + return records + + +def run_raster_fallback_case(root: Path) -> dict[str, Any]: + project = root / "raster-fallback" + write_minimal_plan(project, [{"page": 1, "title": "Gate 8 raster fallback"}], title="Gate 8 Raster Fallback") + asset = project / "03-assets" / "raw" / "glow.png" + write_png(asset) + write_json(project / "03-assets" / "assets.json", {"@./03-assets/raw/glow.png": "boxcn_gate8_glow"}) + svg_path = project / "04-svg" / "page-001.svg" + svg_path.parent.mkdir(parents=True, exist_ok=True) + svg_path.write_text(raster_fallback_svg(), encoding="utf-8") + prepare = svglide_prepare.prepare_project(project) + prepared = project / "04-svg" / "prepared" / "page-001.svg" + preflight = svg_preflight.lint_files([prepared.as_posix()]) + records = raster_fallback_records(prepared.read_text(encoding="utf-8"), project) + fallback_receipt = { + "version": "svglide-raster-fallback/v1", + "status": "passed" if records and all(item["island"] and item["file_exists"] for item in records) else "failed", + "source_svg": relpath(svg_path, project), + "prepared_svg": relpath(prepared, project), + "records": records, + } + write_json(project / "06-check" / "raster-fallback.json", fallback_receipt) + status = "passed" if fallback_receipt["status"] == "passed" and check_no_svg_errors(preflight) else "failed" + return case_result( + "local_raster_fallback_island", + status, + project=relpath(project, root), + prepare_asset_refs=prepare.get("asset_refs", []), + preflight_summary=preflight["summary"], + raster_fallback=fallback_receipt, + ) + + +def run_gate8(root: Path) -> dict[str, Any]: + root = root.resolve() + root.mkdir(parents=True, exist_ok=True) + cases = [ + run_unsupported_case(root), + run_chart_case(root), + run_image_case(root), + run_raster_fallback_case(root), + ] + failed = [item for item in cases if item["status"] != "passed"] + result = { + "version": "svglide-gate8-special-cases/v1", + "status": "failed" if failed else "passed", + "generated_at": now_iso(), + "root": str(root), + "cases": cases, + "summary": { + "case_count": len(cases), + "passed_count": len(cases) - len(failed), + "failed_count": len(failed), + }, + "output_path": OUTPUT_NAME, + } + write_json(root / OUTPUT_NAME, result) + return result + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Run Gate 8 special-case fixtures for SVGlide artboard/Satori.") + parser.add_argument("output_root", help="Directory where Gate 8 fixture projects and evidence should be written") + parser.add_argument("--pretty", action="store_true") + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + try: + result = run_gate8(Path(args.output_root)) + except (OSError, Gate8Error, artboard.ArtboardError, svglide_prepare.PrepareError) as error: + print(f"svglide_gate8_special_cases: error: {error}", file=sys.stderr) + return 2 + print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None)) + return 0 if result["status"] == "passed" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/lark-slides/scripts/svglide_gate8_special_cases_test.py b/skills/lark-slides/scripts/svglide_gate8_special_cases_test.py new file mode 100644 index 00000000..f7fc9317 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_gate8_special_cases_test.py @@ -0,0 +1,43 @@ +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_gate8_special_cases + + +class SVGlideGate8SpecialCasesTest(unittest.TestCase): + def test_gate8_special_cases_all_pass_and_write_evidence(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) / "gate8" + + result = svglide_gate8_special_cases.run_gate8(root) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["summary"]["case_count"], 4) + cases = {case["name"]: case for case in result["cases"]} + self.assertEqual(cases["unsupported_feature_fail_fast"]["status"], "passed") + self.assertTrue(cases["unsupported_feature_fail_fast"]["render_failed_before_live"]) + self.assertTrue(cases["unsupported_feature_fail_fast"]["bridge_failed_before_live"]) + self.assertEqual(cases["chart_marker_svglide_chart_spec_v1"]["chart_verify_status"], "passed") + self.assertEqual(cases["chart_marker_svglide_chart_spec_v1"]["readback_chart_markers"]["status"], "passed") + self.assertEqual(cases["image_asset_binding_readback"]["readback_asset_tokens"]["status"], "passed") + self.assertEqual(cases["image_asset_binding_readback"]["readback_image_assets"]["status"], "passed") + self.assertEqual(cases["local_raster_fallback_island"]["raster_fallback"]["status"], "passed") + + evidence = json.loads((root / "gate8-special-cases.json").read_text(encoding="utf-8")) + self.assertEqual(evidence["status"], "passed") + self.assertTrue((root / "chart-marker/06-check/chart-verify.json").exists()) + self.assertTrue((root / "image-asset/08-readback/readback-check.json").exists()) + self.assertTrue((root / "raster-fallback/06-check/raster-fallback.json").exists()) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_instruction_adherence.py b/skills/lark-slides/scripts/svglide_instruction_adherence.py new file mode 100644 index 00000000..5f07ff02 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_instruction_adherence.py @@ -0,0 +1,550 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import sys +from datetime import datetime +from pathlib import Path +from typing import Any + + +CHECK_VERSION = "svglide-instruction-adherence/v1" +INSTRUCTION_FILE = Path("00-input/instruction.json") +CANVAS_PLAN = Path("02-plan/slide_plan.json") +DECK_PLAN = Path("02-plan/deck-plan.json") +SLIDE_PLAN = Path("02-plan/slide-plan.json") +REPAIR_PLAN = Path("02-plan/repair-plan.json") +READBACK_CHECK = Path("08-readback/readback-check.json") +READBACK_RAW = Path("08-readback/xml-presentations-get.json") +QUALITY_GATE = Path("06-check/quality-gate.json") +DRY_RUN = Path("07-create/dry-run.json") +PPE_PROOF = Path("07-create/ppe-proof.json") +LIVE_CREATE = Path("07-create/live-create.json") +CHECK_PATH = Path("06-check/instruction-adherence.json") +RECEIPT_PATH = Path("receipts/instruction-adherence.json") + + +class InstructionAdherenceError(Exception): + pass + + +def now_iso() -> str: + return datetime.now().astimezone().replace(microsecond=0).isoformat() + + +def read_json(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as err: + raise InstructionAdherenceError(f"missing required file: {path}") from err + except json.JSONDecodeError as err: + raise InstructionAdherenceError(f"invalid JSON: {path}: {err}") from err + if not isinstance(payload, dict): + raise InstructionAdherenceError(f"expected JSON object: {path}") + return payload + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def file_sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def relpath(path: Path, project: Path) -> str: + return path.relative_to(project).as_posix() + + +def text_values(value: Any) -> list[str]: + result: list[str] = [] + if isinstance(value, str): + result.append(value) + elif isinstance(value, list): + for item in value: + result.extend(text_values(item)) + elif isinstance(value, dict): + for item in value.values(): + result.extend(text_values(item)) + return result + + +def normalize_text(value: str) -> str: + return re.sub(r"\s+", "", value).lower() + + +def contains_text(haystack_values: list[str], needle: str) -> bool: + if not needle: + return True + normalized = normalize_text(needle) + return any(normalized in normalize_text(value) for value in haystack_values) + + +def has_cjk(value: str) -> bool: + return bool(re.search(r"[\u4e00-\u9fff]", value)) + + +def slide_svg_paths(project: Path) -> list[Path]: + return sorted((project / "04-svg").glob("page-*.svg")) + + +def svg_text(path: Path) -> list[str]: + if not path.exists(): + return [] + text = path.read_text(encoding="utf-8", errors="replace") + raw = re.sub(r"<[^>]+>", " ", text) + return [raw] + + +def readback_payload(project: Path) -> tuple[dict[str, Any], dict[str, Any]]: + return read_json(project / READBACK_CHECK), read_json(project / READBACK_RAW) + + +def find_first_key(value: Any, keys: set[str]) -> Any: + if isinstance(value, dict): + for key, child in value.items(): + if key in keys: + return child + for child in value.values(): + found = find_first_key(child, keys) + if found is not None: + return found + elif isinstance(value, list): + for child in value: + found = find_first_key(child, keys) + if found is not None: + return found + return None + + +def extract_readback_content(raw_readback: dict[str, Any]) -> str: + value = find_first_key(raw_readback, {"content"}) + return value if isinstance(value, str) else "" + + +def split_readback_slides(content: str) -> list[tuple[str, str]]: + slides: list[tuple[str, str]] = [] + for match in re.finditer(r"]*\bid=\"([^\"]+)\"[^>]*>(.*?)", content, flags=re.DOTALL): + slide_id = match.group(1) + body = re.sub(r"<[^>]+>", " ", match.group(2)) + slides.append((slide_id, body)) + return slides + + +def readback_visible_text_by_page(raw_readback: dict[str, Any]) -> dict[int, list[str]]: + content = extract_readback_content(raw_readback) + slides = split_readback_slides(content) + return {index + 1: [text] for index, (_slide_id, text) in enumerate(slides)} + + +def all_readback_visible_text(raw_readback: dict[str, Any]) -> list[str]: + return [text for _slide_id, text in split_readback_slides(extract_readback_content(raw_readback))] + + +def readback_slide_ids(raw_readback: dict[str, Any]) -> list[str]: + return [slide_id for slide_id, _text in split_readback_slides(extract_readback_content(raw_readback))] + + +def build_plan_texts(project: Path) -> dict[str, list[str]]: + paths = [DECK_PLAN, SLIDE_PLAN, CANVAS_PLAN] + values: list[str] = [] + by_path: dict[str, list[str]] = {} + for rel in paths: + path = project / rel + if path.exists(): + payload = read_json(path) + texts = text_values(payload) + by_path[rel.as_posix()] = texts + values.extend(texts) + by_path["__all__"] = values + return by_path + + +def planned_slide_count(payload: dict[str, Any]) -> int | None: + raw = payload.get("target_slide_count") or payload.get("page_count") + if isinstance(raw, int): + return raw + slides = payload.get("slides") + if isinstance(slides, list): + return len(slides) + return None + + +def plan_slides(payload: dict[str, Any]) -> list[dict[str, Any]]: + slides = payload.get("slides") + if not isinstance(slides, list): + return [] + return [slide for slide in slides if isinstance(slide, dict)] + + +def slide_pages(slides: list[dict[str, Any]]) -> list[int]: + pages: list[int] = [] + for index, slide in enumerate(slides, start=1): + page = slide.get("page") + pages.append(page if isinstance(page, int) else index) + return pages + + +def template_matches(plan_slide: dict[str, Any], expected_template: str) -> bool: + actual = plan_slide.get("template_id") + if actual == expected_template: + return True + allowed = plan_slide.get("allowed_template_ids") + return isinstance(allowed, list) and expected_template in allowed + + +def theme_matches(plan_slide: dict[str, Any], expected_theme: str) -> bool: + actual = plan_slide.get("theme_id") + return actual is None or actual == expected_theme + + +def constraint_terms(constraint: dict[str, Any], surface: str) -> list[str]: + specific = constraint.get(f"required_{surface}_text") + if isinstance(specific, list): + return [item for item in specific if isinstance(item, str)] + if surface == "plan": + fallback = constraint.get("required_text") + if isinstance(fallback, list): + return [item for item in fallback if isinstance(item, str)] + return [] + + +def validate_repair_scope(project: Path, instruction: dict[str, Any]) -> tuple[list[dict[str, str]], dict[str, Any]]: + issues: list[dict[str, str]] = [] + repair_path = project / REPAIR_PLAN + policy = instruction.get("repair_policy") if isinstance(instruction.get("repair_policy"), dict) else {} + if not repair_path.exists(): + return issues, {"present": False} + payload = read_json(repair_path) + expected_target = policy.get("target_plan_path") + if isinstance(expected_target, str) and payload.get("target_plan_path") != expected_target: + issues.append({"code": "repair_target_plan_mismatch", "message": f"repair target_plan_path must be {expected_target}"}) + allowed_prefixes = policy.get("allowed_path_prefixes") if isinstance(policy.get("allowed_path_prefixes"), list) else ["/slides/"] + broad_paths = {"/", "/slides", "/slides/", "/slides/0", "/slides/1", "/slides/2", "/style_system", "/art_direction"} + patches = payload.get("patches") + if not isinstance(patches, list) or not patches: + issues.append({"code": "repair_patches_missing", "message": "repair plan must include non-empty patches"}) + return issues, {"present": True, "patch_count": 0} + scoped = [] + for index, patch in enumerate(patches): + if not isinstance(patch, dict): + issues.append({"code": "repair_patch_invalid", "message": f"repair patch {index} must be an object"}) + continue + path = patch.get("path") + if not isinstance(path, str) or not path.startswith("/"): + issues.append({"code": "repair_patch_path_invalid", "message": f"repair patch {index} must use absolute JSON Pointer path"}) + continue + if path in broad_paths or path.endswith("/canvas_spec") or path.endswith("/content"): + issues.append({"code": "repair_patch_too_broad", "message": f"repair patch {index} path is too broad: {path}"}) + if not any(path.startswith(prefix) for prefix in allowed_prefixes if isinstance(prefix, str)): + issues.append({"code": "repair_patch_outside_allowed_prefix", "message": f"repair patch {index} path is outside allowed prefixes: {path}"}) + value = patch.get("value") + if isinstance(value, (dict, list)): + issues.append({"code": "repair_patch_value_too_broad", "message": f"repair patch {index} must replace a leaf value, not object/list"}) + scoped.append({"op": patch.get("op"), "path": path, "leaf_value": not isinstance(value, (dict, list))}) + return issues, {"present": True, "patch_count": len(patches), "patches": scoped} + + +def validate_instruction_adherence(project: Path) -> dict[str, Any]: + project = project.resolve() + instruction_path = project / INSTRUCTION_FILE + canvas_plan_path = project / CANVAS_PLAN + instruction = read_json(instruction_path) + deck_plan = read_json(project / DECK_PLAN) + slide_plan = read_json(project / SLIDE_PLAN) + canvas_plan = read_json(canvas_plan_path) + readback_check, raw_readback = readback_payload(project) + issues: list[dict[str, str]] = [] + if instruction.get("version") != "svglide-instruction/v1": + issues.append({"code": "instruction_version_invalid", "message": "instruction must use svglide-instruction/v1"}) + target_slide_count = instruction.get("target_slide_count") + slides = plan_slides(canvas_plan) + deck_slides = plan_slides(deck_plan) + planner_slides = plan_slides(slide_plan) + if not slides: + issues.append({"code": "plan_slides_missing", "message": "canvas plan must include slides[]"}) + plan_count = len(slides) + svg_paths = slide_svg_paths(project) + readback_ids = readback_slide_ids(raw_readback) + readback_page_count = (((readback_check.get("checks") or {}).get("page_count") or {}).get("actual")) + if isinstance(target_slide_count, int): + for name, payload in (("deck-plan.json", deck_plan), ("slide-plan.json", slide_plan), ("slide_plan.json", canvas_plan)): + count = planned_slide_count(payload) + if count != target_slide_count: + issues.append({"code": "target_slide_count_mismatch", "message": f"{name} slide count must be {target_slide_count}, got {count}"}) + for name, plan_list in (("deck-plan.json", deck_slides), ("slide-plan.json", planner_slides), ("slide_plan.json", slides)): + if len(plan_list) != target_slide_count: + issues.append({"code": "planner_slide_count_mismatch", "message": f"{name} slides[] length must be {target_slide_count}, got {len(plan_list)}"}) + if canvas_plan.get("target_slide_count") is not None and canvas_plan.get("target_slide_count") != target_slide_count: + issues.append({"code": "target_slide_count_mismatch", "message": "canvas plan target_slide_count must match instruction"}) + if plan_count != target_slide_count: + issues.append({"code": "plan_slide_count_mismatch", "message": f"plan has {plan_count} slides, expected {target_slide_count}"}) + if len(svg_paths) != target_slide_count: + issues.append({"code": "output_slide_count_mismatch", "message": f"output has {len(svg_paths)} SVG pages, expected {target_slide_count}"}) + if readback_page_count != target_slide_count: + issues.append({"code": "readback_slide_count_mismatch", "message": f"readback has {readback_page_count} pages, expected {target_slide_count}"}) + if len(readback_ids) != target_slide_count: + issues.append({"code": "readback_slide_id_count_mismatch", "message": f"readback content has {len(readback_ids)} slide ids, expected {target_slide_count}"}) + expected_order = list(range(1, target_slide_count + 1)) if isinstance(target_slide_count, int) else slide_pages(slides) + for name, page_order in ( + ("instruction.json", slide_pages(plan_slides({"slides": instruction.get("slides") if isinstance(instruction.get("slides"), list) else []}))), + ("deck-plan.json", slide_pages(deck_slides)), + ("slide-plan.json", slide_pages(planner_slides)), + ("slide_plan.json", slide_pages(slides)), + ): + if page_order and page_order != expected_order: + issues.append({"code": "page_order_mismatch", "message": f"{name} page order must be {expected_order}, got {page_order}"}) + binding = readback_check.get("input_binding") if isinstance(readback_check.get("input_binding"), dict) else {} + binding_checks: list[dict[str, Any]] = [] + for binding_key, rel_path in ( + ("plan_sha256", CANVAS_PLAN), + ("quality_gate_sha256", QUALITY_GATE), + ("dry_run_sha256", DRY_RUN), + ("ppe_proof_sha256", PPE_PROOF), + ("live_create_sha256", LIVE_CREATE), + ): + path = project / rel_path + if not path.exists(): + continue + actual_sha = file_sha256(path) + expected_sha = binding.get(binding_key) + matched = expected_sha == actual_sha + binding_checks.append({"binding_key": binding_key, "path": rel_path.as_posix(), "matched": matched}) + if expected_sha is not None and not matched: + issues.append({"code": "readback_binding_hash_mismatch", "message": f"readback {binding_key} does not match current {rel_path.as_posix()}"}) + checks = readback_check.get("checks") if isinstance(readback_check.get("checks"), dict) else {} + for check_name in ("page_count", "slide_order", "core_visible_text"): + check = checks.get(check_name) if isinstance(checks.get(check_name), dict) else {} + if check.get("status") != "passed": + issues.append({"code": "readback_check_not_passed", "message": f"readback check {check_name} must be passed"}) + plan_texts = build_plan_texts(project) + all_plan_texts = plan_texts.get("__all__", []) + output_texts_by_page = {index + 1: svg_text(path) for index, path in enumerate(svg_paths)} + readback_texts_by_page = readback_visible_text_by_page(raw_readback) + all_output_texts = [text for texts in output_texts_by_page.values() for text in texts] + all_readback_texts = all_readback_visible_text(raw_readback) + page_checks: list[dict[str, Any]] = [] + expected_pages = instruction.get("slides") or instruction.get("page_order") + if not isinstance(expected_pages, list): + issues.append({"code": "instruction_page_order_missing", "message": "instruction must include slides[] or page_order[]"}) + expected_pages = [] + for expected in expected_pages: + if not isinstance(expected, dict): + issues.append({"code": "instruction_page_invalid", "message": "page_order entries must be objects"}) + continue + page = expected.get("page") + if not isinstance(page, int) or page < 1 or page > len(slides): + issues.append({"code": "instruction_page_out_of_range", "message": f"instruction page out of range: {page}"}) + continue + slide = slides[page - 1] if isinstance(slides[page - 1], dict) else {} + deck_slide = deck_slides[page - 1] if page <= len(deck_slides) else {} + planner_slide = planner_slides[page - 1] if page <= len(planner_slides) else {} + expected_title = expected.get("title") + expected_key_message = expected.get("key_message") + expected_template = expected.get("template_id") + expected_theme = expected.get("theme_id") + slide_template = (slide.get("canvas_spec") or {}).get("template_id") if isinstance(slide.get("canvas_spec"), dict) else slide.get("template_id") + slide_theme = (slide.get("canvas_spec") or {}).get("theme_id") if isinstance(slide.get("canvas_spec"), dict) else slide.get("theme_id") + if expected_title and slide.get("title") != expected_title: + issues.append({"code": "page_title_mismatch", "message": f"page {page} title mismatch"}) + if expected_key_message and slide.get("key_message") != expected_key_message: + issues.append({"code": "page_key_message_mismatch", "message": f"page {page} key_message mismatch"}) + for plan_name, plan_slide in (("deck-plan.json", deck_slide), ("slide-plan.json", planner_slide)): + if expected_title and plan_slide.get("title") != expected_title: + issues.append({"code": "planner_page_title_mismatch", "message": f"page {page} {plan_name} title mismatch"}) + if expected_key_message and plan_slide.get("key_message") != expected_key_message: + issues.append({"code": "planner_key_message_mismatch", "message": f"page {page} {plan_name} key_message mismatch"}) + if expected_template and slide_template != expected_template: + issues.append({"code": "page_template_mismatch", "message": f"page {page} template mismatch"}) + for plan_name, plan_slide in (("deck-plan.json", deck_slide), ("slide-plan.json", planner_slide)): + if expected_template and not template_matches(plan_slide, expected_template): + issues.append({"code": "planner_template_mismatch", "message": f"page {page} {plan_name} template mismatch"}) + if expected_theme and slide_theme != expected_theme: + issues.append({"code": "page_theme_mismatch", "message": f"page {page} theme mismatch"}) + for plan_name, plan_slide in (("deck-plan.json", deck_slide), ("slide-plan.json", planner_slide)): + if expected_theme and not theme_matches(plan_slide, expected_theme): + issues.append({"code": "planner_theme_mismatch", "message": f"page {page} {plan_name} theme mismatch"}) + required_text = expected.get("required_text") + missing_plan: list[str] = [] + missing_output: list[str] = [] + missing_readback: list[str] = [] + page_text_requirements: list[str] = [] + if isinstance(expected_title, str): + page_text_requirements.append(expected_title) + if isinstance(expected_key_message, str): + page_text_requirements.append(expected_key_message) + if isinstance(required_text, list): + page_text_requirements.extend([item for item in required_text if isinstance(item, str)]) + if page_text_requirements: + page_plan_texts = text_values(slide) + page_output_texts = output_texts_by_page.get(page, []) + page_readback_texts = readback_texts_by_page.get(page, []) + for item in page_text_requirements: + if not contains_text(page_plan_texts, item): + missing_plan.append(item) + if not contains_text(page_output_texts, item): + missing_output.append(item) + if not contains_text(page_readback_texts, item): + missing_readback.append(item) + if missing_plan: + issues.append({"code": "page_required_text_missing_in_plan", "message": f"page {page} missing required text in plan: {missing_plan}"}) + if missing_output: + issues.append({"code": "page_required_text_missing_in_output", "message": f"page {page} missing required text in SVG output: {missing_output}"}) + if missing_readback: + issues.append({"code": "page_required_text_missing_in_readback", "message": f"page {page} missing required text in readback: {missing_readback}"}) + page_checks.append({ + "page": page, + "title": slide.get("title"), + "template_id": slide_template, + "theme_id": slide_theme, + "required_text_count": len(page_text_requirements), + "missing_plan_text": missing_plan, + "missing_output_text": missing_output, + "missing_readback_text": missing_readback, + }) + language = instruction.get("language") + if language == "zh-CN": + for page, texts in output_texts_by_page.items(): + if not any(has_cjk(value) for value in texts): + issues.append({"code": "language_cjk_missing", "message": f"page {page} output has no CJK text"}) + for page, texts in readback_texts_by_page.items(): + if not any(has_cjk(value) for value in texts): + issues.append({"code": "readback_language_cjk_missing", "message": f"page {page} readback has no CJK text"}) + explicit_constraints = instruction.get("explicit_constraints") + constraint_checks: list[dict[str, Any]] = [] + if isinstance(explicit_constraints, list): + for constraint in explicit_constraints: + if not isinstance(constraint, dict): + issues.append({"code": "constraint_invalid", "message": "explicit constraint entries must be objects"}) + continue + constraint_id = constraint.get("id") + missing_by_surface: dict[str, list[str]] = {} + for surface, haystack in ( + ("plan", all_plan_texts), + ("output", all_output_texts), + ("readback", all_readback_texts), + ): + missing = [ + item + for item in constraint_terms(constraint, surface) + if not contains_text(haystack, item) + ] + if missing: + missing_by_surface[surface] = missing + issues.append({ + "code": "constraint_text_missing", + "message": f"constraint {constraint_id} missing {surface} evidence text: {missing}", + }) + constraint_checks.append({"id": constraint_id, "missing_text": missing_by_surface}) + else: + issues.append({"code": "explicit_constraints_missing", "message": "instruction lock must include explicit_constraints[]"}) + must_include = instruction.get("must_include") + must_include_missing: list[str] = [] + if isinstance(must_include, list): + for item in must_include: + if isinstance(item, str) and not contains_text(all_readback_texts, item): + must_include_missing.append(item) + if must_include_missing: + issues.append({"code": "must_include_missing_in_readback", "message": f"must_include missing in readback: {must_include_missing}"}) + else: + issues.append({"code": "must_include_missing", "message": "instruction must include must_include[]"}) + must_avoid = instruction.get("must_avoid") + must_avoid_present: list[str] = [] + if isinstance(must_avoid, list): + for item in must_avoid: + if isinstance(item, str) and (contains_text(all_readback_texts, item) or contains_text(all_output_texts, item)): + must_avoid_present.append(item) + if must_avoid_present: + issues.append({"code": "must_avoid_present_in_output", "message": f"must_avoid text present in output/readback: {must_avoid_present}"}) + else: + issues.append({"code": "must_avoid_missing", "message": "instruction must include must_avoid[]"}) + repair_issues, repair_scope = validate_repair_scope(project, instruction) + issues.extend(repair_issues) + repair_recommendations: list[dict[str, str]] = [] + for issue in issues: + code = issue.get("code", "") + if "slide_count" in code or "page_order" in code: + repair_recommendations.append({"strategy": "scoped_append_or_delete_page", "reason": code}) + elif "missing" in code or "mismatch" in code or "must_avoid" in code: + repair_recommendations.append({"strategy": "scoped_leaf_patch", "reason": code}) + if not repair_recommendations: + repair_recommendations.append({"strategy": "no_repair_needed", "reason": "instruction adherence passed"}) + output_hashes = [{"path": relpath(path, project), "sha256": file_sha256(path)} for path in svg_paths] + payload = { + "version": CHECK_VERSION, + "stage": "instruction_adherence", + "status": "passed" if not issues else "failed", + "checked_at": now_iso(), + "project": project.as_posix(), + "instruction": {"path": INSTRUCTION_FILE.as_posix(), "sha256": file_sha256(instruction_path)}, + "deck_plan": {"path": DECK_PLAN.as_posix(), "sha256": file_sha256(project / DECK_PLAN), "slide_count": planned_slide_count(deck_plan)}, + "slide_plan": {"path": SLIDE_PLAN.as_posix(), "sha256": file_sha256(project / SLIDE_PLAN), "slide_count": planned_slide_count(slide_plan)}, + "plan": {"path": CANVAS_PLAN.as_posix(), "sha256": file_sha256(canvas_plan_path), "slide_count": plan_count}, + "target_slide_count": target_slide_count, + "output_pages": output_hashes, + "readback": { + "check_path": READBACK_CHECK.as_posix(), + "check_sha256": file_sha256(project / READBACK_CHECK), + "raw_path": READBACK_RAW.as_posix(), + "raw_sha256": file_sha256(project / READBACK_RAW), + "slide_count": readback_page_count, + "slide_ids": readback_ids, + "binding_checks": binding_checks, + }, + "page_checks": page_checks, + "constraint_checks": constraint_checks, + "must_include_missing": must_include_missing, + "must_avoid_present": must_avoid_present, + "repair_scope": repair_scope, + "repair_recommendations": repair_recommendations, + "issues": issues, + } + return payload + + +def write_check_outputs(project: Path, payload: dict[str, Any]) -> None: + write_json(project / CHECK_PATH, payload) + write_json(project / RECEIPT_PATH, payload) + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Validate user instruction, plan, output, and repair adherence for an SVGlide project.") + parser.add_argument("project", type=Path) + parser.add_argument("--pretty", action="store_true") + return parser.parse_args(argv) + + +def main(argv: list[str]) -> int: + args = parse_args(argv) + try: + payload = validate_instruction_adherence(args.project) + write_check_outputs(args.project, payload) + print(json.dumps(payload, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=True)) + return 0 if payload["status"] == "passed" else 1 + except InstructionAdherenceError as err: + payload = { + "version": CHECK_VERSION, + "stage": "instruction_adherence", + "status": "failed", + "checked_at": now_iso(), + "project": args.project.as_posix(), + "issues": [{"code": "instruction_adherence_error", "message": str(err)}], + } + write_check_outputs(args.project, payload) + print(json.dumps(payload, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=True), file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/skills/lark-slides/scripts/svglide_instruction_adherence_test.py b/skills/lark-slides/scripts/svglide_instruction_adherence_test.py new file mode 100644 index 00000000..7d5196e1 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_instruction_adherence_test.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_instruction_adherence as adherence + + +def write_json(path: Path, payload: dict) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def make_happy_project(project: Path) -> None: + write_json( + project / adherence.INSTRUCTION_FILE, + { + "version": "svglide-instruction/v1", + "language": "zh-CN", + "target_slide_count": 1, + "must_include": ["冰岛火山研究"], + "must_avoid": ["自由 HTML"], + "slides": [ + { + "page": 1, + "title": "冰岛火山研究", + "key_message": "不编造具体数值", + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "required_text": ["冰岛火山研究"], + } + ], + "explicit_constraints": [ + { + "id": "no_metrics", + "required_plan_text": ["不编造具体数值"], + "required_output_text": ["不编造具体数值"], + "required_readback_text": ["不编造具体数值"], + } + ], + "repair_policy": {"target_plan_path": "02-plan/slide_plan.json", "allowed_path_prefixes": ["/slides/"]}, + }, + ) + for rel in (adherence.DECK_PLAN, adherence.SLIDE_PLAN): + write_json( + project / rel, + { + "target_slide_count": 1, + "slides": [ + { + "page": 1, + "title": "冰岛火山研究", + "key_message": "不编造具体数值", + "template_id": "cover-hero", + "theme_id": "dark-clarity", + } + ], + }, + ) + write_json( + project / adherence.CANVAS_PLAN, + { + "target_slide_count": 1, + "slides": [ + { + "page": 1, + "title": "冰岛火山研究", + "key_message": "不编造具体数值", + "canvas_spec": { + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "content": {"title": "冰岛火山研究", "note": "不编造具体数值"}, + }, + } + ], + }, + ) + (project / "04-svg").mkdir(parents=True) + (project / "04-svg/page-001.svg").write_text( + '冰岛火山研究 不编造具体数值', + encoding="utf-8", + ) + write_json( + project / adherence.READBACK_CHECK, + { + "status": "passed", + "checks": { + "page_count": {"status": "passed", "actual": 1}, + "slide_order": {"status": "passed", "actual": ["s1"], "expected": ["s1"]}, + "core_visible_text": {"status": "passed", "missing": []}, + }, + }, + ) + write_json( + project / adherence.READBACK_RAW, + { + "json": { + "data": { + "xml_presentation": { + "content": '冰岛火山研究 不编造具体数值' + } + } + } + }, + ) + + +class InstructionAdherenceTest(unittest.TestCase): + def test_contains_text_ignores_spacing(self) -> None: + self.assertTrue(adherence.contains_text(["冰岛 火山 研究"], "冰岛火山")) + self.assertFalse(adherence.contains_text(["火山研究"], "连续监测")) + + def test_repair_scope_rejects_broad_object_patch(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = Path(tmp) + write_json( + project / adherence.REPAIR_PLAN, + { + "target_plan_path": "02-plan/slide_plan.json", + "patches": [ + {"op": "replace", "path": "/slides/0/canvas_spec/content", "value": {"title": "bad"}} + ], + }, + ) + issues, scope = adherence.validate_repair_scope( + project, + { + "repair_policy": { + "target_plan_path": "02-plan/slide_plan.json", + "allowed_path_prefixes": ["/slides/"], + } + }, + ) + codes = {issue["code"] for issue in issues} + self.assertIn("repair_patch_too_broad", codes) + self.assertIn("repair_patch_value_too_broad", codes) + self.assertEqual(scope["patch_count"], 1) + + def test_validate_instruction_adherence_happy_path(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = Path(tmp) + make_happy_project(project) + payload = adherence.validate_instruction_adherence(project) + self.assertEqual(payload["status"], "passed", payload["issues"]) + adherence.write_check_outputs(project, payload) + self.assertTrue((project / adherence.CHECK_PATH).exists()) + self.assertTrue((project / adherence.RECEIPT_PATH).exists()) + + def test_slide_count_drift_fails_with_scoped_repair_recommendation(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = Path(tmp) + make_happy_project(project) + deck_plan = json.loads((project / adherence.DECK_PLAN).read_text(encoding="utf-8")) + deck_plan["slides"] = [] + write_json(project / adherence.DECK_PLAN, deck_plan) + + payload = adherence.validate_instruction_adherence(project) + codes = {issue["code"] for issue in payload["issues"]} + strategies = {item["strategy"] for item in payload["repair_recommendations"]} + self.assertEqual(payload["status"], "failed") + self.assertIn("planner_slide_count_mismatch", codes) + self.assertIn("scoped_append_or_delete_page", strategies) + + def test_missing_canvas_page_fails(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = Path(tmp) + make_happy_project(project) + canvas_plan = json.loads((project / adherence.CANVAS_PLAN).read_text(encoding="utf-8")) + canvas_plan["slides"] = [] + write_json(project / adherence.CANVAS_PLAN, canvas_plan) + + payload = adherence.validate_instruction_adherence(project) + codes = {issue["code"] for issue in payload["issues"]} + self.assertEqual(payload["status"], "failed") + self.assertIn("plan_slides_missing", codes) + self.assertIn("plan_slide_count_mismatch", codes) + + def test_missing_slide_plan_page_fails(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = Path(tmp) + make_happy_project(project) + slide_plan = json.loads((project / adherence.SLIDE_PLAN).read_text(encoding="utf-8")) + slide_plan["slides"] = [] + write_json(project / adherence.SLIDE_PLAN, slide_plan) + + payload = adherence.validate_instruction_adherence(project) + codes = {issue["code"] for issue in payload["issues"]} + strategies = {item["strategy"] for item in payload["repair_recommendations"]} + self.assertEqual(payload["status"], "failed") + self.assertIn("planner_slide_count_mismatch", codes) + self.assertIn("scoped_append_or_delete_page", strategies) + + def test_final_slide_key_message_drift_fails_even_with_fresh_binding(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = Path(tmp) + make_happy_project(project) + canvas_plan = json.loads((project / adherence.CANVAS_PLAN).read_text(encoding="utf-8")) + canvas_plan["slides"][0]["key_message"] = "漂移后的错误 key message" + write_json(project / adherence.CANVAS_PLAN, canvas_plan) + readback_check = json.loads((project / adherence.READBACK_CHECK).read_text(encoding="utf-8")) + readback_check["input_binding"] = {"plan_sha256": adherence.file_sha256(project / adherence.CANVAS_PLAN)} + write_json(project / adherence.READBACK_CHECK, readback_check) + + payload = adherence.validate_instruction_adherence(project) + codes = {issue["code"] for issue in payload["issues"]} + self.assertEqual(payload["status"], "failed") + self.assertIn("page_key_message_mismatch", codes) + + def test_forbidden_text_in_output_or_readback_fails(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = Path(tmp) + make_happy_project(project) + (project / "04-svg/page-001.svg").write_text( + '冰岛火山研究 自由 HTML', + encoding="utf-8", + ) + + payload = adherence.validate_instruction_adherence(project) + codes = {issue["code"] for issue in payload["issues"]} + self.assertEqual(payload["status"], "failed") + self.assertIn("must_avoid_present_in_output", codes) + + def test_readback_binding_hash_mismatch_fails(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = Path(tmp) + make_happy_project(project) + readback_check = json.loads((project / adherence.READBACK_CHECK).read_text(encoding="utf-8")) + readback_check["input_binding"] = {"plan_sha256": "stale"} + write_json(project / adherence.READBACK_CHECK, readback_check) + + payload = adherence.validate_instruction_adherence(project) + codes = {issue["code"] for issue in payload["issues"]} + self.assertEqual(payload["status"], "failed") + self.assertIn("readback_binding_hash_mismatch", codes) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_planner_contracts.py b/skills/lark-slides/scripts/svglide_planner_contracts.py new file mode 100644 index 00000000..5d5603a9 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_planner_contracts.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import sys +from pathlib import Path +from typing import Any + +import svglide_artboard_renderer as artboard +import svglide_schema + + +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = SCRIPT_DIR.parents[2] +CONTRACTS_PATH = Path("skills/lark-slides/references/svglide-planner-prompt-contracts.json") +OUTPUT_PATH = Path("06-check/planner-contract-check.json") +RECEIPT_PATH = Path("receipts/planner-contract-check.json") +REQUIRED_PROMPT_IDS = {"deck-planner", "slide-planner", "canvas-planner", "repair-planner"} +FORBIDDEN_MARKUP_PATTERNS = [ + re.compile(pattern, re.IGNORECASE) + for pattern in [ + r"<\s*html\b", + r"<\s*(div|span|p|section|article|main|body)\b", + r"<\s*style\b", + r"<\s*script\b", + r"<\s*svg\b", + r"```html", + r"```css", + r"```svg", + r" str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def read_json(path: Path) -> Any: + return json.loads(path.read_text(encoding="utf-8")) + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def issue(code: str, message: str, *, path: str | None = None, prompt_id: str | None = None) -> dict[str, Any]: + payload: dict[str, Any] = {"code": code, "message": message} + if path is not None: + payload["path"] = path + if prompt_id is not None: + payload["prompt_id"] = prompt_id + return payload + + +def repo_path(rel: str) -> Path: + return REPO_ROOT / rel + + +def project_path(project: Path, rel: str) -> Path: + return project / rel + + +def iter_strings(value: Any) -> list[str]: + result: list[str] = [] + if isinstance(value, str): + result.append(value) + elif isinstance(value, dict): + for item in value.values(): + result.extend(iter_strings(item)) + elif isinstance(value, list): + for item in value: + result.extend(iter_strings(item)) + return result + + +def markup_issues(payload: Any, *, path: str) -> list[dict[str, Any]]: + issues: list[dict[str, Any]] = [] + for text in iter_strings(payload): + for pattern in FORBIDDEN_MARKUP_PATTERNS: + if pattern.search(text): + issues.append(issue("planner_output_forbidden_markup", "planner output contains forbidden free markup", path=path)) + return issues + return issues + + +def validate_contract_file() -> tuple[dict[str, Any], list[dict[str, Any]], list[dict[str, Any]]]: + issues: list[dict[str, Any]] = [] + contracts_path = repo_path(CONTRACTS_PATH.as_posix()) + contracts = read_json(contracts_path) + records = contracts.get("contracts") if isinstance(contracts, dict) else None + if not isinstance(records, list): + return {}, [issue("prompt_contracts_invalid", "contracts must be an array", path=CONTRACTS_PATH.as_posix())], [] + prompt_ids = {item.get("id") for item in records if isinstance(item, dict)} + missing = REQUIRED_PROMPT_IDS - {item for item in prompt_ids if isinstance(item, str)} + for prompt_id in sorted(missing): + issues.append(issue("prompt_contract_missing", f"missing prompt contract: {prompt_id}", prompt_id=prompt_id)) + pages: list[dict[str, Any]] = [] + for item in records: + if not isinstance(item, dict): + issues.append(issue("prompt_contract_invalid", "contract entry must be an object")) + continue + prompt_id = str(item.get("id") or "") + required = ["prompt_path", "input_bundle", "output_schema", "output_path", "validation_command", "forbidden_outputs"] + for key in required: + if key not in item: + issues.append(issue("prompt_contract_field_missing", f"contract missing {key}", prompt_id=prompt_id)) + prompt_path = repo_path(str(item.get("prompt_path") or "")) + schema_path = repo_path(str(item.get("output_schema") or "")) + if not prompt_path.exists(): + issues.append(issue("prompt_file_missing", f"prompt file does not exist: {prompt_path}", prompt_id=prompt_id)) + continue + if not schema_path.exists(): + issues.append(issue("prompt_schema_missing", f"output schema does not exist: {schema_path}", prompt_id=prompt_id)) + prompt_text = prompt_path.read_text(encoding="utf-8") + expected_tokens = [str(item.get("output_schema") or ""), str(item.get("output_path") or ""), str(item.get("validation_command") or "")] + for token in expected_tokens: + if token and token not in prompt_text: + issues.append(issue("prompt_contract_declaration_missing", f"prompt file does not declare {token}", prompt_id=prompt_id)) + lowered = prompt_text.lower() + for token in ["json only", "html", "css", "svg"]: + if token not in lowered: + issues.append(issue("prompt_forbidden_output_not_declared", f"prompt file must explicitly mention {token}", prompt_id=prompt_id)) + forbidden = item.get("forbidden_outputs") + if not isinstance(forbidden, list) or not {"free_html", "free_css", "free_svg", "markdown_fence"}.issubset(set(forbidden)): + issues.append(issue("prompt_contract_forbidden_outputs_incomplete", "forbidden_outputs must include free_html/free_css/free_svg/markdown_fence", prompt_id=prompt_id)) + pages.append( + { + "id": prompt_id, + "prompt_path": str(item.get("prompt_path") or ""), + "prompt_sha256": file_sha256(prompt_path), + "output_schema": str(item.get("output_schema") or ""), + "output_path": str(item.get("output_path") or ""), + } + ) + return contracts, issues, pages + + +def validate_schema_payload(payload: Any, schema_rel: str, *, path: str) -> list[dict[str, Any]]: + schema = read_json(repo_path(schema_rel)) + return [issue(item["code"], item["message"], path=item["path"]) for item in svglide_schema.validate_json_schema(payload, schema)] + + +def validate_canvas_plan(project: Path, payload: dict[str, Any]) -> list[dict[str, Any]]: + issues: list[dict[str, Any]] = [] + issues.extend(validate_schema_payload(payload, "skills/lark-slides/references/svglide-plan.schema.json", path="02-plan/slide_plan.json")) + slides = payload.get("slides") + if not isinstance(slides, list): + return issues + for index, slide in enumerate(slides, 1): + if not isinstance(slide, dict): + issues.append(issue("planner_slide_invalid", f"slide {index} must be an object", path=f"$.slides[{index - 1}]")) + continue + spec = slide.get("canvas_spec") + if not isinstance(spec, dict): + issues.append(issue("planner_canvas_spec_missing", f"slide {index} is missing canvas_spec", path=f"$.slides[{index - 1}].canvas_spec")) + continue + for item in artboard.validate_canvas_spec(spec, page=index): + issues.append(issue(item["code"], item["message"], path=f"$.slides[{index - 1}].canvas_spec")) + registry_issues, _ = artboard.validate_registry_bindings(project, spec, page=index) + for item in registry_issues: + issues.append(issue(item["code"], item["message"], path=f"$.slides[{index - 1}].canvas_spec")) + return issues + + +def validate_slide_plan(project: Path, payload: dict[str, Any]) -> list[dict[str, Any]]: + issues: list[dict[str, Any]] = [] + _, _, templates = artboard.load_template_registry(project) + _, _, themes = artboard.load_theme_registry(project) + slides = payload.get("slides") + if not isinstance(slides, list): + return issues + for index, slide in enumerate(slides): + if not isinstance(slide, dict): + continue + template_id = slide.get("template_id") + theme_id = slide.get("theme_id") + template = templates.get(template_id) if isinstance(template_id, str) else None + theme = themes.get(theme_id) if isinstance(theme_id, str) else None + path = f"$.slides[{index}]" + if template is None: + issues.append(issue("slide_plan_template_unknown", f"template_id is not registered: {template_id!r}", path=f"{path}.template_id")) + elif template.get("status") != "active": + issues.append(issue("slide_plan_template_inactive", f"template_id is not active: {template_id!r}", path=f"{path}.template_id")) + if theme is None: + issues.append(issue("slide_plan_theme_unknown", f"theme_id is not registered: {theme_id!r}", path=f"{path}.theme_id")) + elif theme.get("status") != "active": + issues.append(issue("slide_plan_theme_inactive", f"theme_id is not active: {theme_id!r}", path=f"{path}.theme_id")) + allowed = template.get("supported_theme_ids") if isinstance(template, dict) else None + if isinstance(allowed, list) and isinstance(theme_id, str) and theme_id not in allowed: + issues.append(issue("slide_plan_theme_not_allowed", f"template_id {template_id!r} does not allow theme_id {theme_id!r}", path=f"{path}.theme_id")) + return issues + + +def validate_repair_plan(payload: dict[str, Any]) -> list[dict[str, Any]]: + issues: list[dict[str, Any]] = [] + for key in FORBIDDEN_REPAIR_TOP_LEVEL_KEYS: + if key in payload: + issues.append(issue("repair_plan_full_rewrite_forbidden", f"repair plan must not contain top-level {key}", path=f"$.{key}")) + patches = payload.get("patches") + if not isinstance(patches, list): + return issues + for index, patch in enumerate(patches): + if not isinstance(patch, dict): + issues.append(issue("repair_patch_invalid", "patch must be an object", path=f"$.patches[{index}]")) + continue + path = patch.get("path") + if not isinstance(path, str): + issues.append(issue("repair_patch_path_invalid", "patch.path must be a string", path=f"$.patches[{index}].path")) + continue + if path in UNSCOPED_PATCH_PATHS or re.fullmatch(r"/slides/\d+", path) or re.fullmatch(r"/slides/\d+/canvas_spec", path): + issues.append(issue("repair_patch_unscoped", f"patch path is too broad: {path}", path=f"$.patches[{index}].path")) + if re.fullmatch(r"/slides/\d+/canvas_spec/(content|theme|semantic_elements|quality_constraints)", path) or re.fullmatch(r"/slides/\d+/content_requirements", path): + issues.append(issue("repair_patch_broad_path", f"patch path must target a leaf field, not a whole object: {path}", path=f"$.patches[{index}].path")) + if not (path.startswith("/slides/") or path.startswith("/style_system/") or path.startswith("/art_direction/")): + issues.append(issue("repair_patch_path_not_allowed", f"patch path must target slides/style_system/art_direction: {path}", path=f"$.patches[{index}].path")) + if path in {"/style_system", "/art_direction"}: + issues.append(issue("repair_patch_broad_path", f"patch path must target a leaf field, not a whole object: {path}", path=f"$.patches[{index}].path")) + value = patch.get("value") + if patch.get("op") in {"add", "replace"} and isinstance(value, (dict, list)): + issues.append(issue("repair_patch_value_too_broad", "repair patch values must be scalar leaf values", path=f"$.patches[{index}].value")) + return issues + + +def validate_outputs(project: Path, contracts: dict[str, Any]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + issues: list[dict[str, Any]] = [] + pages: list[dict[str, Any]] = [] + for contract in contracts.get("contracts", []): + if not isinstance(contract, dict): + continue + prompt_id = str(contract.get("id") or "") + output_rel = str(contract.get("output_path") or "") + schema_rel = str(contract.get("output_schema") or "") + output_path = project_path(project, output_rel) + if not output_path.exists(): + issues.append(issue("planner_output_missing", f"missing planner output: {output_rel}", prompt_id=prompt_id)) + continue + try: + payload = read_json(output_path) + except json.JSONDecodeError as error: + issues.append(issue("planner_output_json_invalid", str(error), path=output_rel, prompt_id=prompt_id)) + continue + output_issues = validate_schema_payload(payload, schema_rel, path=output_rel) + output_issues.extend(markup_issues(payload, path=output_rel)) + if prompt_id == "slide-planner" and isinstance(payload, dict): + output_issues.extend(validate_slide_plan(project, payload)) + if prompt_id == "canvas-planner" and isinstance(payload, dict): + output_issues.extend(validate_canvas_plan(project, payload)) + if prompt_id == "repair-planner" and isinstance(payload, dict): + output_issues.extend(validate_repair_plan(payload)) + issues.extend({**item, "prompt_id": prompt_id} for item in output_issues) + pages.append( + { + "prompt_id": prompt_id, + "output_path": output_rel, + "output_sha256": file_sha256(output_path), + "schema": schema_rel, + "error_count": len(output_issues), + } + ) + return issues, pages + + +def run(project: Path) -> dict[str, Any]: + project = project.resolve() + contracts, contract_issues, prompt_pages = validate_contract_file() + output_issues, output_pages = validate_outputs(project, contracts) if contracts else ([], []) + issues = contract_issues + output_issues + status = "failed" if issues else "passed" + result = { + "schema_version": "svglide-planner-contract-check/v1", + "stage": "planner-contract-check", + "status": status, + "action": "continue_to_generate_svg" if status == "passed" else "repair_and_rerun", + "inputs": { + "prompt_contracts": CONTRACTS_PATH.as_posix(), + "prompt_contracts_sha256": file_sha256(repo_path(CONTRACTS_PATH.as_posix())), + "project": project.as_posix(), + }, + "prompt_contracts": prompt_pages, + "planner_outputs": output_pages, + "summary": { + "error_count": len(issues), + "warning_count": 0, + "prompt_contract_count": len(prompt_pages), + "planner_output_count": len(output_pages), + }, + "issues": issues, + "output_path": OUTPUT_PATH.as_posix(), + "receipt_path": RECEIPT_PATH.as_posix(), + } + write_json(project / OUTPUT_PATH, result) + write_json(project / RECEIPT_PATH, result) + return result + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Validate SVGlide planner prompt contracts and structured planner outputs.") + parser.add_argument("project", type=Path) + parser.add_argument("--pretty", action="store_true") + args = parser.parse_args(argv) + try: + result = run(args.project) + except (OSError, json.JSONDecodeError) as error: + print(f"svglide_planner_contracts: {error}", file=sys.stderr) + return 2 + print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None)) + return 0 if result["status"] == "passed" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/lark-slides/scripts/svglide_planner_contracts_test.py b/skills/lark-slides/scripts/svglide_planner_contracts_test.py new file mode 100644 index 00000000..da6c2502 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_planner_contracts_test.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT +from __future__ import annotations + +import json +import shutil +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_planner_contracts as planner + + +def read_json(path: Path) -> dict[str, object]: + return json.loads(path.read_text(encoding="utf-8")) + + +def write_json(path: Path, payload: dict[str, object]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +class SVGlidePlannerContractsTest(unittest.TestCase): + def copy_fixture(self, tmpdir: str) -> Path: + source = Path(__file__).resolve().parent / "fixtures/svglide_artboard/gate10_planner" + target = Path(tmpdir) / "gate10_planner" + shutil.copytree(source, target) + return target + + def test_gate10_planner_fixture_passes_contracts_and_canvas_admission(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = self.copy_fixture(tmpdir) + + result = planner.run(project) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["summary"]["prompt_contract_count"], 4) + self.assertEqual(result["summary"]["planner_output_count"], 4) + self.assertEqual([], result["issues"]) + self.assertTrue((project / "06-check/planner-contract-check.json").exists()) + self.assertTrue((project / "receipts/planner-contract-check.json").exists()) + outputs = {item["prompt_id"]: item for item in result["planner_outputs"]} + self.assertEqual(0, outputs["canvas-planner"]["error_count"]) + self.assertEqual("02-plan/slide_plan.json", outputs["canvas-planner"]["output_path"]) + + def test_rejects_free_html_css_svg_markup_in_planner_outputs(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = self.copy_fixture(tmpdir) + deck_plan_path = project / "02-plan/deck-plan.json" + deck_plan = read_json(deck_plan_path) + deck_plan["objective"] = "
bad free markup
" + write_json(deck_plan_path, deck_plan) + + result = planner.run(project) + + self.assertEqual(result["status"], "failed") + codes = {item["code"] for item in result["issues"]} + self.assertIn("planner_output_forbidden_markup", codes) + + def test_rejects_unscoped_repair_patch(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = self.copy_fixture(tmpdir) + repair_path = project / "02-plan/repair-plan.json" + repair_plan = read_json(repair_path) + repair_plan["patches"] = [{"op": "replace", "path": "/slides", "value": [], "reason": "too broad"}] + write_json(repair_path, repair_plan) + + result = planner.run(project) + + self.assertEqual(result["status"], "failed") + codes = {item["code"] for item in result["issues"]} + self.assertIn("repair_patch_unscoped", codes) + + def test_rejects_unregistered_slide_plan_template(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = self.copy_fixture(tmpdir) + slide_plan_path = project / "02-plan/slide-plan.json" + slide_plan = read_json(slide_plan_path) + slide_plan["slides"][0]["template_id"] = "missing-template" + write_json(slide_plan_path, slide_plan) + + result = planner.run(project) + + self.assertEqual(result["status"], "failed") + codes = {item["code"] for item in result["issues"]} + self.assertIn("slide_plan_template_unknown", codes) + + def test_rejects_broad_repair_content_object_patch(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = self.copy_fixture(tmpdir) + repair_path = project / "02-plan/repair-plan.json" + repair_plan = read_json(repair_path) + repair_plan["patches"] = [ + { + "op": "replace", + "path": "/slides/0/canvas_spec/content", + "value": {"title": "whole object rewrite"}, + "reason": "too broad", + } + ] + write_json(repair_path, repair_plan) + + result = planner.run(project) + + self.assertEqual(result["status"], "failed") + codes = {item["code"] for item in result["issues"]} + self.assertIn("repair_patch_broad_path", codes) + self.assertIn("repair_patch_value_too_broad", codes) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_ppe_proof.py b/skills/lark-slides/scripts/svglide_ppe_proof.py index b3dbc0ff..d91eff05 100644 --- a/skills/lark-slides/scripts/svglide_ppe_proof.py +++ b/skills/lark-slides/scripts/svglide_ppe_proof.py @@ -51,7 +51,20 @@ def issue(code: str, message: str) -> dict[str, str]: return {"code": code, "message": message} -def proof_issues(proof: dict[str, Any]) -> list[dict[str, str]]: +def repo_root() -> Path: + return Path(__file__).resolve().parents[3] + + +def resolve_declared_path(project: Path, raw_path: str) -> Path: + path = Path(raw_path) + candidates = [path] if path.is_absolute() else [project / path, repo_root() / path] + for candidate in candidates: + if candidate.exists(): + return candidate + return candidates[-1] + + +def proof_issues(proof: dict[str, Any], project: Path) -> list[dict[str, str]]: issues: list[dict[str, str]] = [] if proof.get("status") != "passed": issues.append(issue("ppe_status_not_passed", "ppe proof input status must be passed")) @@ -65,6 +78,38 @@ def proof_issues(proof: dict[str, Any]) -> list[dict[str, str]]: headers = proof.get("headers") if isinstance(headers, dict) and headers.get("x-tt-env") != "ppe_pure_svg": issues.append(issue("ppe_header_missing_x_tt_env", "ppe proof headers.x-tt-env must be ppe_pure_svg")) + proxy = proof.get("proxy") + if isinstance(proxy, dict) and proxy: + if proxy.get("mode") != "whistle": + issues.append(issue("ppe_proxy_mode_not_whistle", "ppe proof proxy.mode must be whistle")) + if proxy.get("capture") is not True: + issues.append(issue("ppe_proxy_capture_missing", "ppe proof proxy.capture must be true")) + for key in ["http_proxy", "https_proxy"]: + value = proxy.get(key) + if not isinstance(value, str) or "127.0.0.1:8899" not in value: + issues.append(issue(f"ppe_proxy_{key}_missing", f"ppe proof proxy.{key} must point to local Whistle")) + if proxy.get("rewrite_host") != "open.feishu-pre.cn": + issues.append(issue("ppe_proxy_rewrite_host_invalid", "ppe proof proxy.rewrite_host must be open.feishu-pre.cn")) + inject_headers = proxy.get("inject_headers") + if not isinstance(inject_headers, dict): + issues.append(issue("ppe_proxy_inject_headers_missing", "ppe proof proxy.inject_headers is required")) + else: + if inject_headers.get("Env") != "Pre_release": + issues.append(issue("ppe_proxy_env_header_missing", "ppe proof proxy.inject_headers.Env must be Pre_release")) + if inject_headers.get("x-tt-env") != "ppe_pure_svg": + issues.append(issue("ppe_proxy_x_tt_env_header_missing", "ppe proof proxy.inject_headers.x-tt-env must be ppe_pure_svg")) + rule_file = proxy.get("rule_file") + rule_sha256 = proxy.get("rule_sha256") + if not isinstance(rule_file, str) or not rule_file.strip(): + issues.append(issue("ppe_proxy_rule_file_missing", "ppe proof proxy.rule_file is required")) + elif not isinstance(rule_sha256, str) or not rule_sha256.strip(): + issues.append(issue("ppe_proxy_rule_sha256_missing", "ppe proof proxy.rule_sha256 is required")) + else: + resolved = resolve_declared_path(project, rule_file) + if not resolved.exists(): + issues.append(issue("ppe_proxy_rule_file_not_found", "ppe proof proxy.rule_file must exist")) + elif file_sha256(resolved) != rule_sha256: + issues.append(issue("ppe_proxy_rule_sha256_mismatch", "ppe proof proxy.rule_sha256 does not match rule_file")) return issues @@ -82,7 +127,7 @@ def run_ppe_proof(project: Path) -> dict[str, Any]: proof: dict[str, Any] = {} if proof_file.exists(): proof = read_json_object(proof_file) - issues.extend(proof_issues(proof)) + issues.extend(proof_issues(proof, project)) else: issues.append(issue("ppe_proof_input_missing", "07-create/ppe-proof.input.json is required before live create")) status = "failed" if issues else "passed" diff --git a/skills/lark-slides/scripts/svglide_ppe_proof_test.py b/skills/lark-slides/scripts/svglide_ppe_proof_test.py index 7b2eeadd..4738bf41 100644 --- a/skills/lark-slides/scripts/svglide_ppe_proof_test.py +++ b/skills/lark-slides/scripts/svglide_ppe_proof_test.py @@ -23,6 +23,31 @@ class SVGlidePPEProofTest(unittest.TestCase): write_json(project / "06-check/quality-gate.json", {"status": "passed"}) write_json(project / "07-create/dry-run.json", {"status": "passed"}) + def write_rule(self, project: Path) -> Path: + rule = project / "ppe-pure-svg.whistle.js" + rule.write_text("module.exports = function () {}\n", encoding="utf-8") + return rule + + def complete_proof_input(self, project: Path) -> dict[str, object]: + rule = self.write_rule(project) + return { + "status": "passed", + "environment": {"name": "Pre_release", "x-tt-env": "ppe_pure_svg"}, + "auth": {"identity": "user"}, + "proxy": { + "mode": "whistle", + "capture": True, + "http_proxy": "http://127.0.0.1:8899", + "https_proxy": "http://127.0.0.1:8899", + "rewrite_host": "open.feishu-pre.cn", + "rule_file": rule.name, + "rule_sha256": svglide_ppe_proof.file_sha256(rule), + "inject_headers": {"Env": "Pre_release", "x-tt-env": "ppe_pure_svg"}, + }, + "headers": {"x-tt-env": "ppe_pure_svg"}, + "route": {"name": "slides +create-svg"}, + } + def test_ppe_proof_requires_input(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: project = Path(tmpdir) @@ -37,23 +62,43 @@ class SVGlidePPEProofTest(unittest.TestCase): with tempfile.TemporaryDirectory() as tmpdir: project = Path(tmpdir) self.write_inputs(project) - write_json( - project / "07-create/ppe-proof.input.json", - { - "status": "passed", - "environment": {"name": "Pre_release", "x-tt-env": "ppe_pure_svg"}, - "auth": {"identity": "user"}, - "proxy": {"mode": "whistle"}, - "headers": {"x-tt-env": "ppe_pure_svg"}, - "route": {"name": "slides +create-svg"}, - }, - ) + write_json(project / "07-create/ppe-proof.input.json", self.complete_proof_input(project)) result = svglide_ppe_proof.run_ppe_proof(project) self.assertEqual(result["status"], "passed") self.assertTrue((project / "07-create/ppe-proof.json").exists()) + def test_ppe_proof_rejects_missing_proxy_capture(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + self.write_inputs(project) + proof = self.complete_proof_input(project) + proxy = proof["proxy"] + assert isinstance(proxy, dict) + proxy.pop("capture") + write_json(project / "07-create/ppe-proof.input.json", proof) + + result = svglide_ppe_proof.run_ppe_proof(project) + + self.assertEqual(result["status"], "failed") + self.assertIn("ppe_proxy_capture_missing", [item["code"] for item in result["issues"]]) + + def test_ppe_proof_rejects_rule_hash_mismatch(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + self.write_inputs(project) + proof = self.complete_proof_input(project) + proxy = proof["proxy"] + assert isinstance(proxy, dict) + proxy["rule_sha256"] = "not-the-real-hash" + write_json(project / "07-create/ppe-proof.input.json", proof) + + result = svglide_ppe_proof.run_ppe_proof(project) + + self.assertEqual(result["status"], "failed") + self.assertIn("ppe_proxy_rule_sha256_mismatch", [item["code"] for item in result["issues"]]) + if __name__ == "__main__": unittest.main() diff --git a/skills/lark-slides/scripts/svglide_pre_submit_review.py b/skills/lark-slides/scripts/svglide_pre_submit_review.py new file mode 100644 index 00000000..e6c6107d --- /dev/null +++ b/skills/lark-slides/scripts/svglide_pre_submit_review.py @@ -0,0 +1,631 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import hashlib +import json +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +CHECK_VERSION = "svglide-pre-submit-review/v1" +PLAN_PATH = Path("02-plan/slide_plan.json") +QUALITY_GATE_PATH = Path("06-check/quality-gate.json") +THEME_ADHERENCE_PATH = Path("06-check/theme-adherence.json") +VISUAL_DISTINCTNESS_PATH = Path("06-check/visual-distinctness.json") +PREPARED_SVG_DIR = Path("04-svg/prepared") +CONTACT_SHEET_PATH = Path("05-preview/contact-sheet.png") +PREVIEW_PATH = Path("05-preview/preview.html") +PREVIEW_MANIFEST_PATH = Path("05-preview/preview-manifest.json") +OUTPUT_PATH = Path("06-check/pre-submit-review.json") +RECEIPT_PATH = Path("receipts/pre-submit-review.json") + +REQUIRED_HUMAN_CHECKS = [ + "visual_acceptance", + "intent_acceptance", + "text_readability", + "asset_chart_reasonableness", + "worth_live_submit", +] + +ARTIFACT_PATHS = { + "contact_sheet": CONTACT_SHEET_PATH, + "preview": PREVIEW_PATH, + "preview_manifest": PREVIEW_MANIFEST_PATH, + "quality_gate": QUALITY_GATE_PATH, +} + +PROTECTED_RERUN_BOUNDARIES = [ + "Do not edit runner scripts for this receipt.", + "Do not weaken quality_gate/theme checks to make review pass.", + "Do not mutate prepared SVG or preview artifacts after human review; rerun from the named stage instead.", +] + + +class PreSubmitReviewFatalError(Exception): + pass + + +def now_iso() -> str: + return datetime.now(timezone.utc).replace(microsecond=0).isoformat() + + +def file_sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def relpath(path: Path, project: Path) -> str: + try: + return path.resolve().relative_to(project.resolve()).as_posix() + except ValueError: + return path.as_posix() + + +def issue(stage: str, code: str, message: str, *, category: str) -> dict[str, str]: + return { + "stage": stage, + "code": code, + "category": category, + "message": message, + } + + +def read_json_object(path: Path, issues: list[dict[str, str]], *, stage: str, required: bool = True) -> dict[str, Any]: + if not path.exists(): + if required: + issues.append(issue(stage, f"{stage}_missing", f"required JSON file is missing: {path}", category="missing_input")) + return {} + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError as error: + issues.append(issue(stage, f"{stage}_invalid_json", f"invalid JSON in {path}: {error}", category="invalid_input")) + return {} + except OSError as error: + issues.append(issue(stage, f"{stage}_read_failed", f"could not read {path}: {error}", category="invalid_input")) + return {} + if not isinstance(payload, dict): + issues.append(issue(stage, f"{stage}_not_object", f"expected JSON object: {path}", category="invalid_input")) + return {} + return payload + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def prepared_file_hashes(project: Path) -> list[dict[str, str]]: + svg_dir = project / PREPARED_SVG_DIR + if not svg_dir.exists(): + return [] + return [ + { + "path": path.relative_to(project).as_posix(), + "sha256": file_sha256(path), + } + for path in sorted(svg_dir.glob("*.svg")) + if path.is_file() + ] + + +def optional_sha256(path: Path) -> str | None: + return file_sha256(path) if path.exists() and path.is_file() else None + + +def normalize_hash_records(value: Any) -> list[dict[str, str]] | None: + if not isinstance(value, list): + return None + records: list[dict[str, str]] = [] + for item in value: + if not isinstance(item, dict): + return None + rel = item.get("path") or item.get("prepared") + sha = item.get("sha256") + if not isinstance(rel, str) or not isinstance(sha, str): + return None + records.append({"path": rel, "sha256": sha}) + return sorted(records, key=lambda item: item["path"]) + + +def normalize_prepared_reviewed_artifact(value: Any) -> list[dict[str, str]] | None: + if isinstance(value, dict): + if isinstance(value.get("files"), list): + return normalize_hash_records(value.get("files")) + if isinstance(value.get("artifacts"), list): + return normalize_hash_records(value.get("artifacts")) + if isinstance(value.get("path"), str) or isinstance(value.get("prepared"), str): + return normalize_hash_records([value]) + return normalize_hash_records(value) + + +def sorted_hash_records(records: list[dict[str, str]]) -> list[dict[str, str]]: + return sorted(records, key=lambda item: item["path"]) + + +def validate_hash_value( + issues: list[dict[str, str]], + *, + stage: str, + code: str, + field: str, + expected: str | None, + actual: Any, + path_label: str, +) -> None: + if not isinstance(actual, str) or not actual: + issues.append(issue(stage, f"{code}_missing", f"human review must include {field}", category="missing_input")) + return + if expected is None: + issues.append(issue(stage, f"{code}_target_missing", f"cannot verify {field}; target file is missing: {path_label}", category="missing_input")) + return + if actual != expected: + issues.append(issue(stage, f"{code}_stale", f"{field} does not match current {path_label}", category="stale_hash")) + + +def validate_human_checks(human: dict[str, Any], issues: list[dict[str, str]]) -> dict[str, Any]: + approval = human.get("human_approval") if isinstance(human.get("human_approval"), dict) else {} + if approval.get("approved") is not True: + issues.append( + issue( + "human_review", + "human_approval_not_approved", + "human_approval.approved must be true", + category="human_rejected", + ) + ) + + checks = human.get("checks") if isinstance(human.get("checks"), dict) else {} + check_statuses: dict[str, Any] = {} + if not checks: + issues.append(issue("human_review", "human_checks_missing", "human review must include checks", category="missing_input")) + for name in REQUIRED_HUMAN_CHECKS: + check = checks.get(name) if isinstance(checks, dict) else None + status = check.get("status") if isinstance(check, dict) else None + check_statuses[name] = status + if status != "passed": + issues.append( + issue( + "human_review", + f"{name}_not_passed", + f"checks.{name}.status must be passed", + category="human_rejected" if status == "failed" else "missing_input", + ) + ) + if isinstance(checks, dict): + for name, check in checks.items(): + if name in REQUIRED_HUMAN_CHECKS: + continue + if not isinstance(name, str) or not isinstance(check, dict) or "status" not in check: + continue + status = check.get("status") + check_statuses[name] = status + if status != "passed": + safe_name = "".join(ch if ch.isalnum() else "_" for ch in name).strip("_") or "extra_check" + issues.append( + issue( + "human_review", + f"{safe_name}_not_passed", + f"checks.{name}.status must be passed", + category="human_rejected" if status == "failed" else "missing_input", + ) + ) + return { + "approved": approval.get("approved"), + "checks": check_statuses, + } + + +def validate_current_check( + payload: dict[str, Any], + issues: list[dict[str, str]], + *, + stage: str, + path: Path, + project: Path, + current_plan_sha: str | None, + current_prepared: list[dict[str, str]], + require_prepared_when_present: bool = True, +) -> None: + if not payload: + return + if payload.get("status") != "passed": + issues.append(issue(stage, f"{stage}_not_passed", f"{path.as_posix()} status must be passed", category="check_not_passed")) + summary = payload.get("summary") + if isinstance(summary, dict): + error_count = summary.get("error_count") + if isinstance(error_count, int) and not isinstance(error_count, bool) and error_count > 0: + issues.append(issue(stage, f"{stage}_has_errors", f"{path.as_posix()} summary.error_count is {error_count}", category="check_not_passed")) + inputs = payload.get("inputs") if isinstance(payload.get("inputs"), dict) else {} + plan_sha = payload.get("plan_sha256") or inputs.get("plan_sha256") + if plan_sha is not None and plan_sha != current_plan_sha: + issues.append(issue(stage, f"{stage}_plan_stale", f"{path.as_posix()} plan_sha256 does not match current slide_plan.json", category="stale_hash")) + if require_prepared_when_present and "prepared_files" in payload: + recorded = normalize_hash_records(payload.get("prepared_files")) + if recorded is None or recorded != sorted_hash_records(current_prepared): + issues.append(issue(stage, f"{stage}_prepared_stale", f"{path.as_posix()} prepared_files do not match current prepared SVG files", category="stale_hash")) + output_path = project / path + if output_path.exists() and not output_path.is_file(): + issues.append(issue(stage, f"{stage}_not_file", f"{path.as_posix()} must be a file", category="invalid_input")) + + +def validate_quality_gate( + payload: dict[str, Any], + issues: list[dict[str, str]], + *, + current_prepared: list[dict[str, str]], +) -> None: + if not payload: + return + if payload.get("status") != "passed": + issues.append(issue("quality_gate", "quality_gate_not_passed", "quality gate status must be passed", category="check_not_passed")) + recorded = normalize_hash_records(payload.get("prepared_files")) + if recorded is None or recorded != sorted_hash_records(current_prepared): + issues.append( + issue( + "quality_gate", + "quality_gate_prepared_stale", + "quality gate prepared_files do not match current prepared SVG files", + category="stale_hash", + ) + ) + checks = payload.get("checks") + if isinstance(checks, list): + by_name = {item.get("name"): item for item in checks if isinstance(item, dict)} + visual = by_name.get("visual-distinctness") + if visual is None: + issues.append(issue("quality_gate", "quality_gate_visual_distinctness_missing", "quality gate must include visual-distinctness check", category="missing_input")) + elif visual.get("status") not in {"passed", "passed_with_waiver"}: + issues.append(issue("quality_gate", "quality_gate_visual_distinctness_not_passed", "quality gate visual-distinctness check must be passed", category="check_not_passed")) + + +def validate_preview_manifest( + manifest: dict[str, Any], + issues: list[dict[str, str]], + *, + project: Path, + current_prepared: list[dict[str, str]], +) -> None: + if not manifest: + return + expected_paths = [record["path"] for record in current_prepared] + if manifest.get("page_count") != len(current_prepared): + issues.append( + issue( + "preview", + "preview_manifest_page_count_stale", + "preview manifest page_count does not match current prepared SVG count", + category="stale_hash", + ) + ) + pages = manifest.get("pages") + if not isinstance(pages, list): + issues.append(issue("preview", "preview_manifest_pages_missing", "preview manifest must include pages", category="missing_input")) + return + source_paths = [page.get("source_path") for page in pages if isinstance(page, dict)] + if source_paths != expected_paths: + issues.append( + issue( + "preview", + "preview_manifest_sources_stale", + "preview manifest source_path list does not match current prepared SVG files", + category="stale_hash", + ) + ) + for page in pages: + if not isinstance(page, dict): + issues.append(issue("preview", "preview_manifest_page_invalid", "preview manifest pages must be objects", category="invalid_input")) + continue + source_path = page.get("source_path") + if not isinstance(source_path, str): + continue + actual_path = project / source_path + if not actual_path.exists(): + issues.append(issue("preview", "preview_manifest_source_missing", f"preview source is missing: {source_path}", category="missing_input")) + continue + source_bytes = page.get("source_bytes") + if isinstance(source_bytes, int) and not isinstance(source_bytes, bool) and source_bytes != actual_path.stat().st_size: + issues.append( + issue( + "preview", + "preview_manifest_source_bytes_stale", + f"preview source_bytes is stale for {source_path}", + category="stale_hash", + ) + ) + + +def validate_single_reviewed_artifact( + reviewed: dict[str, Any], + issues: list[dict[str, str]], + *, + project: Path, + name: str, + expected_rel: Path, +) -> dict[str, Any]: + record = reviewed.get(name) + expected_path = expected_rel.as_posix() + evidence: dict[str, Any] = {"name": name, "expected_path": expected_path, "matched": False} + if not isinstance(record, dict): + issues.append(issue("human_review", f"reviewed_artifact_{name}_missing", f"reviewed_artifacts.{name} is required", category="missing_input")) + return evidence + rel = record.get("path") + expected_sha = record.get("sha256") + evidence["path"] = rel + evidence["expected_sha256"] = expected_sha + if rel != expected_path: + issues.append(issue("human_review", f"reviewed_artifact_{name}_path_invalid", f"reviewed_artifacts.{name}.path must be {expected_path}", category="invalid_input")) + return evidence + if not isinstance(expected_sha, str) or not expected_sha: + issues.append(issue("human_review", f"reviewed_artifact_{name}_sha256_missing", f"reviewed_artifacts.{name}.sha256 is required", category="missing_input")) + return evidence + path = project / expected_rel + if not path.exists(): + issues.append(issue("artifacts", f"{name}_missing", f"artifact is missing: {expected_path}", category="missing_input")) + return evidence + actual_sha = file_sha256(path) + evidence["sha256"] = actual_sha + evidence["matched"] = expected_sha == actual_sha + if expected_sha != actual_sha: + issues.append(issue("human_review", f"reviewed_artifact_{name}_stale", f"reviewed_artifacts.{name}.sha256 does not match current {expected_path}", category="stale_hash")) + return evidence + + +def validate_reviewed_artifacts( + human: dict[str, Any], + issues: list[dict[str, str]], + *, + project: Path, + current_prepared: list[dict[str, str]], +) -> list[dict[str, Any]]: + reviewed = human.get("reviewed_artifacts") if isinstance(human.get("reviewed_artifacts"), dict) else {} + evidence: list[dict[str, Any]] = [] + if not reviewed: + issues.append(issue("human_review", "reviewed_artifacts_missing", "human review must include reviewed_artifacts", category="missing_input")) + return evidence + + prepared_record = normalize_prepared_reviewed_artifact(reviewed.get("prepared_svg")) + expected_prepared = sorted_hash_records(current_prepared) + evidence.append({"name": "prepared_svg", "expected_files": expected_prepared, "files": prepared_record, "matched": prepared_record == expected_prepared}) + if prepared_record is None: + issues.append(issue("human_review", "reviewed_artifact_prepared_svg_invalid", "reviewed_artifacts.prepared_svg must include path/sha256 records", category="invalid_input")) + elif prepared_record != expected_prepared: + issues.append(issue("human_review", "reviewed_artifact_prepared_svg_stale", "reviewed_artifacts.prepared_svg does not match current prepared SVG files", category="stale_hash")) + + for name, expected_rel in ARTIFACT_PATHS.items(): + evidence.append( + validate_single_reviewed_artifact( + reviewed, + issues, + project=project, + name=name, + expected_rel=expected_rel, + ) + ) + return evidence + + +def validate_human_bindings( + human: dict[str, Any], + issues: list[dict[str, str]], + *, + project: Path, + current_plan_sha: str | None, + current_quality_gate_sha: str | None, + current_prepared: list[dict[str, str]], +) -> None: + validate_hash_value( + issues, + stage="human_review", + code="human_plan_sha256", + field="plan_sha256", + expected=current_plan_sha, + actual=human.get("plan_sha256"), + path_label=PLAN_PATH.as_posix(), + ) + validate_hash_value( + issues, + stage="human_review", + code="human_quality_gate_sha256", + field="quality_gate_sha256", + expected=current_quality_gate_sha, + actual=human.get("quality_gate_sha256"), + path_label=QUALITY_GATE_PATH.as_posix(), + ) + recorded_prepared = normalize_hash_records(human.get("prepared_files")) + if recorded_prepared is None: + issues.append(issue("human_review", "human_prepared_files_missing", "human review must include prepared_files path/sha256 records", category="missing_input")) + elif recorded_prepared != sorted_hash_records(current_prepared): + issues.append(issue("human_review", "human_prepared_files_stale", "human review prepared_files do not match current prepared SVG files", category="stale_hash")) + + reviewed_artifacts = human.get("reviewed_artifacts") if isinstance(human.get("reviewed_artifacts"), dict) else {} + quality_gate_review = reviewed_artifacts.get("quality_gate") if isinstance(reviewed_artifacts, dict) else None + if isinstance(quality_gate_review, dict) and quality_gate_review.get("sha256") != human.get("quality_gate_sha256"): + issues.append(issue("human_review", "human_quality_gate_hash_disagrees", "quality_gate_sha256 must match reviewed_artifacts.quality_gate.sha256", category="stale_hash")) + + if current_prepared and not (project / PREPARED_SVG_DIR).exists(): + issues.append(issue("prepared_svg", "prepared_svg_dir_missing", f"missing {PREPARED_SVG_DIR.as_posix()}", category="missing_input")) + + +def validate_required_artifacts(project: Path, issues: list[dict[str, str]], *, current_prepared: list[dict[str, str]]) -> None: + if not (project / PREPARED_SVG_DIR).exists(): + issues.append(issue("prepared_svg", "prepared_svg_dir_missing", f"missing {PREPARED_SVG_DIR.as_posix()}", category="missing_input")) + elif not current_prepared: + issues.append(issue("prepared_svg", "prepared_svg_missing", f"no prepared SVG files found under {PREPARED_SVG_DIR.as_posix()}", category="missing_input")) + + for name, rel in ARTIFACT_PATHS.items(): + path = project / rel + if not path.exists(): + issues.append(issue("artifacts", f"{name}_missing", f"required artifact is missing: {rel.as_posix()}", category="missing_input")) + elif not path.is_file(): + issues.append(issue("artifacts", f"{name}_not_file", f"required artifact must be a file: {rel.as_posix()}", category="invalid_input")) + + +def minimal_rerun_from(issues: list[dict[str, str]]) -> str: + if not issues: + return "none" + codes = {item.get("code") for item in issues} + stages = {item.get("stage") for item in issues} + if any(code in codes for code in {"human_review_missing", "human_approval_not_approved"}) or "human_review" in stages and not any(item.get("category") == "stale_hash" for item in issues): + return "collect_pre_submit_human_review" + if any(code and "prepared" in code for code in codes): + return "prepare_svg_then_preview_quality_gate_and_human_review" + if any(code and "preview" in code for code in codes): + return "preview_then_human_review" + if any(code and "quality_gate" in code for code in codes) or "quality_gate" in stages: + return "quality_gate_then_human_review" + if "theme_adherence" in stages: + return "theme_adherence_then_quality_gate_and_human_review" + if "visual_distinctness" in stages: + return "visual_distinctness_then_quality_gate_and_human_review" + return "rerun_from_first_failed_stage" + + +def build_failure_triage(issues: list[dict[str, str]]) -> dict[str, Any]: + primary = issues[0] if issues else {} + return { + "primary_failure_stage": primary.get("stage", "unknown"), + "failure_category": primary.get("category", "unknown"), + "minimal_rerun_from": minimal_rerun_from(issues), + "do_not_touch": PROTECTED_RERUN_BOUNDARIES, + "blocking_issue_codes": [item.get("code") for item in issues[:10]], + } + + +def run_pre_submit_review(project: Path, human_review: Path) -> dict[str, Any]: + project = project.resolve() + if not project.exists() or not project.is_dir(): + raise PreSubmitReviewFatalError(f"project_root does not exist or is not a directory: {project}") + + issues: list[dict[str, str]] = [] + current_prepared = prepared_file_hashes(project) + current_plan_sha = optional_sha256(project / PLAN_PATH) + current_quality_gate_sha = optional_sha256(project / QUALITY_GATE_PATH) + current_theme_sha = optional_sha256(project / THEME_ADHERENCE_PATH) + current_visual_sha = optional_sha256(project / VISUAL_DISTINCTNESS_PATH) + current_artifact_hashes = { + name: optional_sha256(project / rel) + for name, rel in ARTIFACT_PATHS.items() + } + + plan = read_json_object(project / PLAN_PATH, issues, stage="plan") + quality_gate = read_json_object(project / QUALITY_GATE_PATH, issues, stage="quality_gate") + theme_adherence = read_json_object(project / THEME_ADHERENCE_PATH, issues, stage="theme_adherence") + visual_distinctness = read_json_object(project / VISUAL_DISTINCTNESS_PATH, issues, stage="visual_distinctness") + preview_manifest = read_json_object(project / PREVIEW_MANIFEST_PATH, issues, stage="preview") + human = read_json_object(human_review, issues, stage="human_review") + + validate_required_artifacts(project, issues, current_prepared=current_prepared) + validate_quality_gate(quality_gate, issues, current_prepared=current_prepared) + validate_current_check( + theme_adherence, + issues, + stage="theme_adherence", + path=THEME_ADHERENCE_PATH, + project=project, + current_plan_sha=current_plan_sha, + current_prepared=current_prepared, + ) + validate_current_check( + visual_distinctness, + issues, + stage="visual_distinctness", + path=VISUAL_DISTINCTNESS_PATH, + project=project, + current_plan_sha=current_plan_sha, + current_prepared=current_prepared, + require_prepared_when_present=False, + ) + validate_preview_manifest(preview_manifest, issues, project=project, current_prepared=current_prepared) + + human_summary: dict[str, Any] = {"approved": None, "checks": {}} + reviewed_evidence: list[dict[str, Any]] = [] + human_sha = optional_sha256(human_review) + if human: + human_summary = validate_human_checks(human, issues) + validate_human_bindings( + human, + issues, + project=project, + current_plan_sha=current_plan_sha, + current_quality_gate_sha=current_quality_gate_sha, + current_prepared=current_prepared, + ) + reviewed_evidence = validate_reviewed_artifacts(human, issues, project=project, current_prepared=current_prepared) + + result: dict[str, Any] = { + "version": CHECK_VERSION, + "stage": "pre_submit_review", + "status": "failed" if issues else "passed", + "checked_at": now_iso(), + "project": str(project), + "human_review": { + "path": relpath(human_review, project), + "sha256": human_sha, + }, + "inputs": { + "plan": PLAN_PATH.as_posix(), + "plan_sha256": current_plan_sha, + "quality_gate": QUALITY_GATE_PATH.as_posix(), + "quality_gate_sha256": current_quality_gate_sha, + "theme_adherence": THEME_ADHERENCE_PATH.as_posix(), + "theme_adherence_sha256": current_theme_sha, + "visual_distinctness": VISUAL_DISTINCTNESS_PATH.as_posix(), + "visual_distinctness_sha256": current_visual_sha, + "prepared_svg_dir": PREPARED_SVG_DIR.as_posix(), + "contact_sheet": CONTACT_SHEET_PATH.as_posix(), + "contact_sheet_sha256": current_artifact_hashes["contact_sheet"], + "preview": PREVIEW_PATH.as_posix(), + "preview_sha256": current_artifact_hashes["preview"], + "preview_manifest": PREVIEW_MANIFEST_PATH.as_posix(), + "preview_manifest_sha256": current_artifact_hashes["preview_manifest"], + }, + "plan_title": plan.get("title"), + "human_approval": human_summary, + "prepared_files": current_prepared, + "reviewed_artifacts": reviewed_evidence, + "summary": { + "issue_count": len(issues), + "prepared_svg_count": len(current_prepared), + }, + "issues": issues, + "outputs": { + "check": OUTPUT_PATH.as_posix(), + "receipt": RECEIPT_PATH.as_posix(), + }, + } + if issues: + result["failure_triage"] = build_failure_triage(issues) + + write_json(project / OUTPUT_PATH, result) + write_json(project / RECEIPT_PATH, result) + return result + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Validate structured human pre-submit review receipt freshness.") + parser.add_argument("project_root", help="SVGlide project root") + parser.add_argument("--human-review", required=True, type=Path, help="human review JSON path") + parser.add_argument("--pretty", action="store_true", help="pretty-print JSON output") + args = parser.parse_args(argv) + + try: + result = run_pre_submit_review(Path(args.project_root), args.human_review) + except (OSError, PreSubmitReviewFatalError) as error: + print(f"svglide_pre_submit_review: {error}", file=sys.stderr) + return 2 + + print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=args.pretty)) + return 0 if result["status"] == "passed" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/lark-slides/scripts/svglide_pre_submit_review_test.py b/skills/lark-slides/scripts/svglide_pre_submit_review_test.py new file mode 100644 index 00000000..ee169bd1 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_pre_submit_review_test.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_pre_submit_review as pre_submit_review + + +def write_json(path: Path, payload: dict[str, object]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def prepared_hashes(project: Path) -> list[dict[str, str]]: + return pre_submit_review.prepared_file_hashes(project) + + +def make_project(root: Path) -> Path: + project = root / "project" + (project / "02-plan").mkdir(parents=True) + (project / "04-svg/prepared").mkdir(parents=True) + (project / "05-preview").mkdir(parents=True) + (project / "06-check").mkdir(parents=True) + (project / "receipts").mkdir(parents=True) + + write_json( + project / "02-plan/slide_plan.json", + { + "title": "Theme System P0", + "slides": [ + { + "page": 1, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + } + ], + }, + ) + (project / "04-svg/prepared/page-001.svg").write_text( + 'Theme System', + encoding="utf-8", + ) + (project / "05-preview/preview.html").write_text("preview", encoding="utf-8") + (project / "05-preview/contact-sheet.png").write_bytes(b"contact-sheet") + write_json( + project / "05-preview/preview-manifest.json", + { + "project": str(project), + "source_dir": "04-svg/prepared", + "html_path": "05-preview/preview.html", + "manifest_path": "05-preview/preview-manifest.json", + "page_count": 1, + "pages": [ + { + "page": 1, + "source_path": "04-svg/prepared/page-001.svg", + "source_bytes": (project / "04-svg/prepared/page-001.svg").stat().st_size, + } + ], + }, + ) + + plan_sha = pre_submit_review.file_sha256(project / "02-plan/slide_plan.json") + current_prepared = prepared_hashes(project) + write_json( + project / "06-check/theme-adherence.json", + { + "schema_version": "svglide-theme-adherence/v1", + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": plan_sha, + }, + "prepared_files": current_prepared, + "summary": {"error_count": 0, "warning_count": 0}, + "issues": [], + }, + ) + write_json( + project / "06-check/visual-distinctness.json", + { + "schema_version": "svglide-visual-distinctness/v1", + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": plan_sha, + }, + "summary": {"error_count": 0, "warning_count": 0}, + "issues": [], + }, + ) + write_json( + project / "06-check/quality-gate.json", + { + "version": "svglide-quality-gate/v1", + "status": "passed", + "profile": "production", + "inputs": { + "theme_adherence": "06-check/theme-adherence.json", + "visual_distinctness": "06-check/visual-distinctness.json", + }, + "prepared_files": current_prepared, + "checks": [ + {"name": "visual-distinctness", "status": "passed", "issues": []}, + {"name": "theme-adherence", "status": "passed", "issues": []}, + ], + "summary": {"failed_check_count": 0, "source_error_count": 0}, + }, + ) + write_human_review(project) + return project + + +def human_review_payload(project: Path) -> dict[str, object]: + current_prepared = prepared_hashes(project) + return { + "schema_version": "svglide-pre-submit-human-review/v1", + "human_approval": { + "approved": True, + "reviewer": "unit-test", + }, + "checks": { + "visual_acceptance": {"status": "passed"}, + "intent_acceptance": {"status": "passed"}, + "text_readability": {"status": "passed"}, + "asset_chart_reasonableness": {"status": "passed"}, + "worth_live_submit": {"status": "passed"}, + }, + "plan_sha256": pre_submit_review.file_sha256(project / "02-plan/slide_plan.json"), + "quality_gate_sha256": pre_submit_review.file_sha256(project / "06-check/quality-gate.json"), + "prepared_files": current_prepared, + "reviewed_artifacts": { + "prepared_svg": current_prepared, + "contact_sheet": { + "path": "05-preview/contact-sheet.png", + "sha256": pre_submit_review.file_sha256(project / "05-preview/contact-sheet.png"), + }, + "preview": { + "path": "05-preview/preview.html", + "sha256": pre_submit_review.file_sha256(project / "05-preview/preview.html"), + }, + "preview_manifest": { + "path": "05-preview/preview-manifest.json", + "sha256": pre_submit_review.file_sha256(project / "05-preview/preview-manifest.json"), + }, + "quality_gate": { + "path": "06-check/quality-gate.json", + "sha256": pre_submit_review.file_sha256(project / "06-check/quality-gate.json"), + }, + }, + } + + +def write_human_review(project: Path, payload: dict[str, object] | None = None) -> Path: + path = project / "06-check/pre-submit-human-review.json" + write_json(path, payload or human_review_payload(project)) + return path + + +def issue_codes(result: dict[str, object]) -> set[str]: + issues = result.get("issues") + self_issues = issues if isinstance(issues, list) else [] + return {item.get("code") for item in self_issues if isinstance(item, dict)} + + +class PreSubmitReviewTest(unittest.TestCase): + def test_passed_writes_check_and_receipt(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = make_project(Path(tmp)) + result = pre_submit_review.run_pre_submit_review(project, project / "06-check/pre-submit-human-review.json") + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["issues"], []) + check = json.loads((project / "06-check/pre-submit-review.json").read_text(encoding="utf-8")) + receipt = json.loads((project / "receipts/pre-submit-review.json").read_text(encoding="utf-8")) + self.assertEqual(check["status"], "passed") + self.assertEqual(check, receipt) + + def test_missing_human_file_writes_failed_receipt(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = make_project(Path(tmp)) + missing = project / "06-check/missing-human-review.json" + result = pre_submit_review.run_pre_submit_review(project, missing) + + self.assertEqual(result["status"], "failed") + self.assertIn("human_review_missing", issue_codes(result)) + self.assertIn("failure_triage", result) + receipt = json.loads((project / "receipts/pre-submit-review.json").read_text(encoding="utf-8")) + self.assertEqual(receipt["status"], "failed") + self.assertIn("failure_triage", receipt) + + def test_approval_false_fails(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = make_project(Path(tmp)) + payload = human_review_payload(project) + approval = payload["human_approval"] + assert isinstance(approval, dict) + approval["approved"] = False + write_human_review(project, payload) + + result = pre_submit_review.run_pre_submit_review(project, project / "06-check/pre-submit-human-review.json") + + self.assertEqual(result["status"], "failed") + self.assertIn("human_approval_not_approved", issue_codes(result)) + self.assertEqual(result["failure_triage"]["failure_category"], "human_rejected") + + def test_worth_live_submit_failed_blocks_pass(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = make_project(Path(tmp)) + payload = human_review_payload(project) + checks = payload["checks"] + assert isinstance(checks, dict) + worth = checks["worth_live_submit"] + assert isinstance(worth, dict) + worth["status"] = "failed" + write_human_review(project, payload) + + result = pre_submit_review.run_pre_submit_review(project, project / "06-check/pre-submit-human-review.json") + + self.assertEqual(result["status"], "failed") + self.assertIn("worth_live_submit_not_passed", issue_codes(result)) + + def test_reviewed_artifacts_requires_preview_manifest_and_current_hash(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = make_project(Path(tmp)) + payload = human_review_payload(project) + reviewed = payload["reviewed_artifacts"] + assert isinstance(reviewed, dict) + reviewed.pop("preview_manifest") + write_human_review(project, payload) + + result = pre_submit_review.run_pre_submit_review(project, project / "06-check/pre-submit-human-review.json") + self.assertEqual(result["status"], "failed") + self.assertIn("reviewed_artifact_preview_manifest_missing", issue_codes(result)) + + payload = human_review_payload(project) + reviewed = payload["reviewed_artifacts"] + assert isinstance(reviewed, dict) + preview = reviewed["preview"] + assert isinstance(preview, dict) + preview["sha256"] = "0" * 64 + write_human_review(project, payload) + + result = pre_submit_review.run_pre_submit_review(project, project / "06-check/pre-submit-human-review.json") + self.assertEqual(result["status"], "failed") + self.assertIn("reviewed_artifact_preview_stale", issue_codes(result)) + + def test_quality_gate_hash_stale_fails(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = make_project(Path(tmp)) + quality_gate = json.loads((project / "06-check/quality-gate.json").read_text(encoding="utf-8")) + quality_gate["note"] = "mutated after human review" + write_json(project / "06-check/quality-gate.json", quality_gate) + + result = pre_submit_review.run_pre_submit_review(project, project / "06-check/pre-submit-human-review.json") + + self.assertEqual(result["status"], "failed") + self.assertIn("human_quality_gate_sha256_stale", issue_codes(result)) + self.assertIn("reviewed_artifact_quality_gate_stale", issue_codes(result)) + + def test_prepared_svg_stale_fails(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + project = make_project(Path(tmp)) + (project / "04-svg/prepared/page-001.svg").write_text( + 'Mutated', + encoding="utf-8", + ) + + result = pre_submit_review.run_pre_submit_review(project, project / "06-check/pre-submit-human-review.json") + + self.assertEqual(result["status"], "failed") + codes = issue_codes(result) + self.assertIn("human_prepared_files_stale", codes) + self.assertIn("reviewed_artifact_prepared_svg_stale", codes) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_project_runner.py b/skills/lark-slides/scripts/svglide_project_runner.py index 9e72d650..09cbd12b 100644 --- a/skills/lark-slides/scripts/svglide_project_runner.py +++ b/skills/lark-slides/scripts/svglide_project_runner.py @@ -26,6 +26,8 @@ PROJECT_VERSION = "svglide-project/v1" STATE_VERSION = "svglide-state/v1" STAGE_GRAPH = "svglide-workflow/v1" ROUTE = "svglide-svg" +DEFAULT_GENERATION_MODE = "direct_svg" +GENERATION_MODES = {DEFAULT_GENERATION_MODE, "artboard_satori"} DEFAULT_PLAN_ROOT = Path(".lark-slides/plan") SCRIPT_DIR = Path(__file__).resolve().parent LARK_CLI_COMMAND_ENV = "SVGLIDE_LARK_CLI_CMD" @@ -35,7 +37,9 @@ STAGES = [ "source", "plan", "strategy_review", + "theme_validate", "confirm_plan", + "package_check", "assets", "generate_svg", "prepare", @@ -47,9 +51,11 @@ STAGES = [ "semantic_review", "runtime_review", "visual_distinctness_review", + "theme_adherence", "quality_gate", "dry_run", "ppe_proof", + "pre_submit_review", "live_create", "readback", "export", @@ -59,18 +65,24 @@ STAGE_ALIASES = { "confirm-plan": "confirm_plan", "source-review": "source", "strategy-review": "strategy_review", + "theme-validate": "theme_validate", + "package-check": "package_check", + "artboard-package-check": "package_check", "aesthetic-review": "aesthetic_review", "chart-verify": "chart_verify", "semantic-review": "semantic_review", "runtime-review": "runtime_review", "visual-distinctness": "visual_distinctness_review", "visual-distinctness-review": "visual_distinctness_review", + "theme-adherence": "theme_adherence", "generate": "generate_svg", "generate-svg": "generate_svg", "preview-lint": "preview_lint", "quality-gate": "quality_gate", "dry-run": "dry_run", "ppe-proof": "ppe_proof", + "pre-submit-review": "pre_submit_review", + "pre-submit": "pre_submit_review", "live-create": "live_create", } @@ -96,7 +108,9 @@ IMPLEMENTED_STAGES = { "source", "plan", "strategy_review", + "theme_validate", "confirm_plan", + "package_check", "assets", "generate_svg", "prepare", @@ -108,9 +122,11 @@ IMPLEMENTED_STAGES = { "semantic_review", "runtime_review", "visual_distinctness_review", + "theme_adherence", "quality_gate", "dry_run", "ppe_proof", + "pre_submit_review", "live_create", "readback", } @@ -201,6 +217,12 @@ def resolve_run_target(until: str | None, profile: str | None) -> str: raise RunnerError("--until is required unless --profile is preview_only or production_live", exit_code=2) +def stage_required_for_profile(stage: str, profile: str) -> bool: + if stage == "pre_submit_review": + return profile == "production_live" + return True + + def validate_deck_id(deck_id: str) -> str: if not DECK_ID_RE.fullmatch(deck_id): raise RunnerError( @@ -783,6 +805,94 @@ def svg_generator_command(project_root: Path) -> list[str]: return [] +def plan_generation_mode(project_root: Path) -> str: + plan = read_json(plan_path(project_root)) + raw = plan.get("generation_mode") or DEFAULT_GENERATION_MODE + if raw not in GENERATION_MODES: + raise RunnerError(f"unsupported generation_mode '{raw}'") + return str(raw) + + +def validate_artboard_plan(project_root: Path) -> None: + plan = read_json(plan_path(project_root)) + slides = plan.get("slides") + if not isinstance(slides, list) or not slides: + raise RunnerError("generation_mode=artboard_satori requires slide_plan.slides") + for index, slide in enumerate(slides, 1): + if not isinstance(slide, dict): + raise RunnerError(f"generation_mode=artboard_satori slide {index} must be an object") + spec = slide.get("canvas_spec") + if not isinstance(spec, dict): + raise RunnerError(f"generation_mode=artboard_satori slide {index} requires canvas_spec") + canvas = spec.get("canvas") + content = spec.get("content") + if spec.get("version") != "svglide-canvas-spec/v1": + raise RunnerError(f"generation_mode=artboard_satori slide {index} canvas_spec.version is invalid") + if not isinstance(canvas, dict) or canvas.get("width") != 960 or canvas.get("height") != 540 or canvas.get("viewBox") != "0 0 960 540": + raise RunnerError(f"generation_mode=artboard_satori slide {index} canvas_spec.canvas must be 960x540") + if not isinstance(spec.get("template_id"), str) or not spec.get("template_id"): + raise RunnerError(f"generation_mode=artboard_satori slide {index} canvas_spec.template_id is required") + if not isinstance(spec.get("theme"), dict): + raise RunnerError(f"generation_mode=artboard_satori slide {index} canvas_spec.theme is required") + if not isinstance(content, dict) or not isinstance(content.get("title"), str) or not content.get("title").strip(): + raise RunnerError(f"generation_mode=artboard_satori slide {index} canvas_spec.content.title is required") + + +def artboard_generator_command(project_root: Path) -> list[str]: + return [ + "python3", + (SCRIPT_DIR / "svglide_artboard_renderer.py").as_posix(), + project_root.as_posix(), + "--pretty", + ] + + +def artboard_receipt_paths(project_root: Path) -> list[str]: + root = project_root / "04-svg" / "artboard" + if not root.exists(): + return [] + return [path.relative_to(project_root).as_posix() for path in sorted(root.glob("page-*.receipt.json")) if path.is_file()] + + +def refresh_artboard_receipts_after_asset_injection(project_root: Path, generated_files: list[dict[str, str]]) -> None: + final_hashes = { + item.get("path"): item.get("sha256") + for item in generated_files + if isinstance(item.get("path"), str) and isinstance(item.get("sha256"), str) + } + if not final_hashes: + return + + for receipt_rel in artboard_receipt_paths(project_root): + receipt_file = project_root / receipt_rel + payload = read_json(receipt_file) + svglide_svg = payload.get("svglide_svg") + final_hash = final_hashes.get(svglide_svg) + if isinstance(final_hash, str) and payload.get("svglide_svg_sha256") != final_hash: + payload["svglide_svg_sha256"] = final_hash + payload["post_asset_injection_refreshed_at"] = now_iso() + write_json(receipt_file, payload) + + satori_bridge_file = project_root / "receipts" / "satori-bridge.json" + if not satori_bridge_file.exists(): + return + satori_bridge = read_json(satori_bridge_file) + pages = satori_bridge.get("pages") + changed = False + if isinstance(pages, list): + for page in pages: + if not isinstance(page, dict): + continue + svglide_svg = page.get("svglide_svg") + final_hash = final_hashes.get(svglide_svg) + if isinstance(final_hash, str) and page.get("svglide_svg_sha256") != final_hash: + page["svglide_svg_sha256"] = final_hash + changed = True + if changed: + satori_bridge["post_asset_injection_refreshed_at"] = now_iso() + write_json(satori_bridge_file, satori_bridge) + + def require_source_current(project_root: Path) -> None: receipt = read_json(project_root / "source" / "source-receipt.json") if receipt.get("status") != "passed": @@ -822,6 +932,8 @@ def write_page_generation_receipts( generated_files: list[dict[str, str]], generator_mode: str, command: list[str], + generation_mode: str = DEFAULT_GENERATION_MODE, + artboard_receipts: list[str] | None = None, asset_injection_summary: dict[str, Any] | None = None, ) -> list[str]: receipt_paths: list[str] = [] @@ -879,6 +991,8 @@ def write_page_generation_receipts( "asset_manifest_path": "03-assets/asset-manifest.json" if asset_manifest_hash else None, "asset_manifest_sha256": asset_manifest_hash, "generator_mode": generator_mode, + "generation_mode": generation_mode, + "artboard_receipt": artboard_receipts[index - 1] if artboard_receipts and index <= len(artboard_receipts) else None, "generator_script_sha256": generator_script_hash, "asset_refs": asset_refs, "asset_injection": page_injections, @@ -897,10 +1011,19 @@ def write_page_generation_receipts( def validate_generator_receipt(project_root: Path, receipt: dict[str, Any]) -> list[dict[str, str]]: schema = svglide_schema.read_json(svglide_schema.schema_path("svglide-generator-receipt.schema.json")) issues = svglide_schema.validate_json_schema(receipt, schema) + generation_mode = receipt.get("generation_mode") or DEFAULT_GENERATION_MODE + if generation_mode not in GENERATION_MODES: + issues.append({"code": "generator_generation_mode_invalid", "path": "$.generation_mode", "message": "generation_mode must be direct_svg or artboard_satori"}) generated = receipt.get("generated_files") page_receipts = receipt.get("page_receipts") if isinstance(generated, list) and isinstance(page_receipts, list) and len(generated) != len(page_receipts): issues.append({"code": "generator_page_receipt_count_mismatch", "path": "$.page_receipts", "message": "page_receipts count must match generated_files"}) + artboard_receipts = receipt.get("artboard_receipts") + if generation_mode == "artboard_satori": + if not isinstance(artboard_receipts, list) or not artboard_receipts: + issues.append({"code": "generator_artboard_receipts_missing", "path": "$.artboard_receipts", "message": "artboard_satori generation must include artboard_receipts"}) + elif isinstance(generated, list) and len(artboard_receipts) != len(generated): + issues.append({"code": "generator_artboard_receipt_count_mismatch", "path": "$.artboard_receipts", "message": "artboard_receipts count must match generated_files"}) plan = read_json(plan_path(project_root)) slides = plan.get("slides") if isinstance(slides, list) and isinstance(generated, list) and len(slides) != len(generated): @@ -909,6 +1032,10 @@ def validate_generator_receipt(project_root: Path, receipt: dict[str, Any]) -> l for item in page_receipts: if not isinstance(item, str) or not (project_root / item).exists(): issues.append({"code": "generator_page_receipt_missing", "path": "$.page_receipts", "message": f"missing page receipt: {item}"}) + if isinstance(artboard_receipts, list): + for item in artboard_receipts: + if not isinstance(item, str) or not (project_root / item).exists(): + issues.append({"code": "generator_artboard_receipt_missing", "path": "$.artboard_receipts", "message": f"missing artboard receipt: {item}"}) return issues @@ -924,7 +1051,28 @@ def run_generate_svg_stage( require_source_current(project_root) require_assets_current(project_root) started_at = now_iso() + generation_mode = plan_generation_mode(project_root) command = svg_generator_command(project_root) + artboard_result: dict[str, Any] = {} + if generation_mode == "artboard_satori": + validate_artboard_plan(project_root) + if command: + complete_stage( + project_root, + state, + "generate_svg", + "failed", + started_at=started_at, + inputs=["02-plan/slide_plan.json", "03-assets/assets.json"], + outputs=[], + command=command, + error={ + "code": "generator_mode_conflict", + "message": "generation_mode=artboard_satori cannot be combined with project-local generate_svg.py", + }, + ) + raise RunnerError("generation_mode=artboard_satori cannot be combined with project-local generate_svg.py") + command = artboard_generator_command(project_root) if command: completed = command_runner(command, cwd=repo_root(), check=False, capture_output=True, text=True) if completed.returncode != 0: @@ -944,6 +1092,9 @@ def run_generate_svg_stage( }, ) raise RunnerError(f"stage 'generate_svg' failed with exit code {completed.returncode}") + if generation_mode == "artboard_satori": + parsed = parse_json_or_none(completed.stdout) + artboard_result = parsed if isinstance(parsed, dict) else {} try: source_file_hashes(project_root) @@ -966,8 +1117,29 @@ def run_generate_svg_stage( asset_injection_summary = svglide_asset_injector.inject_project_assets(project_root) generated_files = source_file_hashes(project_root) + if generation_mode == "artboard_satori": + refresh_artboard_receipts_after_asset_injection(project_root, generated_files) generator_mode = "script" if command else "external" - page_receipts = write_page_generation_receipts(project_root, generated_files, generator_mode, command, asset_injection_summary) + artboard_receipts = artboard_result.get("artboard_receipts") if isinstance(artboard_result.get("artboard_receipts"), list) else artboard_receipt_paths(project_root) + artboard_additional_receipts = artboard_result.get("additional_receipts") if isinstance(artboard_result.get("additional_receipts"), list) else [] + artboard_output_paths: list[str] = [] + if generation_mode == "artboard_satori": + for key in ["canvas_spec_validate", "artboard_render_receipt", "satori_bridge_receipt"]: + value = artboard_result.get(key) + if isinstance(value, str): + artboard_output_paths.append(value) + contact_sheet = artboard_result.get("contact_sheet") + if isinstance(contact_sheet, dict) and isinstance(contact_sheet.get("path"), str): + artboard_output_paths.append(contact_sheet["path"]) + page_receipts = write_page_generation_receipts( + project_root, + generated_files, + generator_mode, + command, + generation_mode, + artboard_receipts if generation_mode == "artboard_satori" else None, + asset_injection_summary, + ) receipt = complete_stage( project_root, state, @@ -975,12 +1147,23 @@ def run_generate_svg_stage( "passed", started_at=started_at, inputs=["02-plan/slide_plan.json", "03-assets/assets.json"], - outputs=[item["path"] for item in generated_files] + page_receipts, + outputs=[item["path"] for item in generated_files] + + page_receipts + + (artboard_receipts if generation_mode == "artboard_satori" else []) + + (artboard_additional_receipts if generation_mode == "artboard_satori" else []) + + artboard_output_paths, command=command, ) receipt["generator_mode"] = generator_mode + receipt["generation_mode"] = generation_mode receipt["generated_files"] = generated_files receipt["page_receipts"] = page_receipts + if generation_mode == "artboard_satori": + receipt["artboard_receipts"] = artboard_receipts + receipt["artboard_additional_receipts"] = artboard_additional_receipts + for key in ["canvas_spec_validate", "artboard_render_receipt", "satori_bridge_receipt", "contact_sheet"]: + if key in artboard_result: + receipt[key] = artboard_result[key] receipt["asset_injection_summary"] = asset_injection_summary page_receipt_payloads = [read_json(project_root / path) for path in page_receipts] receipt["fallback_skeleton_used"] = any(bool(payload.get("fallback_skeleton_used")) for payload in page_receipt_payloads) @@ -1001,6 +1184,10 @@ def run_generate_svg_stage( receipt["source_receipt_sha256"] = optional_project_file_hash(project_root, "source/source-receipt.json") receipt["generator_script_sha256"] = file_sha256(Path(command[1])) if len(command) > 1 and Path(command[1]).exists() else None receipt["visible_text_policy"] = "visible SVG text must be traceable to slide_plan.json or source/evidence.json" + if generation_mode == "artboard_satori": + receipt["template_fit_check"] = "06-check/template-fit.json" + if "06-check/template-fit.json" not in receipt["outputs"]: + receipt["outputs"].append("06-check/template-fit.json") schema_issues = validate_generator_receipt(project_root, receipt) if schema_issues: receipt["status"] = "failed" @@ -1010,6 +1197,25 @@ def run_generate_svg_stage( write_state(project_root, state) raise RunnerError("generate_svg receipt validation failed") write_json(receipt_path(project_root, "generate_svg"), receipt) + if generation_mode == "artboard_satori": + fit_command = [ + "python3", + (SCRIPT_DIR / "svglide_template_fit_check.py").as_posix(), + project_root.as_posix(), + "--pretty", + ] + fit_completed = subprocess.run(fit_command, cwd=repo_root(), check=False, capture_output=True, text=True) + if fit_completed.returncode != 0: + receipt["status"] = "failed" + receipt["error"] = { + "code": "template_fit_failed", + "returncode": fit_completed.returncode, + "stderr": fit_completed.stderr, + } + write_json(receipt_path(project_root, "generate_svg"), receipt) + record_stage(state, "generate_svg", "failed", receipt_path(project_root, "generate_svg")) + write_state(project_root, state) + raise RunnerError("artboard template fit check failed") return receipt @@ -1027,6 +1233,13 @@ def require_generated_svg_current(project_root: Path) -> None: for key, current in expected.items(): if receipt.get(key) != current: raise RunnerError(f"generate_svg receipt {key} does not match current project files; rerun generate_svg") + if receipt.get("generation_mode") == "artboard_satori": + artboard_receipts = receipt.get("artboard_receipts") + if not isinstance(artboard_receipts, list) or not artboard_receipts: + raise RunnerError("generate_svg receipt is missing artboard_receipts; rerun generate_svg") + for item in artboard_receipts: + if not isinstance(item, str) or not (project_root / item).exists(): + raise RunnerError(f"artboard receipt is missing: {item}; rerun generate_svg") command = receipt.get("command") if isinstance(command, list) and len(command) > 1 and isinstance(command[1], str): script = Path(command[1]) @@ -1034,7 +1247,7 @@ def require_generated_svg_current(project_root: Path) -> None: raise RunnerError("generator script changed after generate_svg; rerun generate_svg") -def require_existing_stage_current(project_root: Path, stage: str) -> None: +def require_existing_stage_current(project_root: Path, stage: str, *, profile: str = "production") -> None: if stage == "source": require_source_current(project_root) elif stage == "assets": @@ -1053,8 +1266,12 @@ def require_existing_stage_current(project_root: Path, stage: str) -> None: elif stage == "live_create": require_quality_gate_current(project_root) require_ppe_proof_current(project_root) + if profile == "production_live": + require_pre_submit_review_current(project_root) elif stage == "ppe_proof": require_ppe_proof_current(project_root) + elif stage == "pre_submit_review": + require_pre_submit_review_current(project_root) elif stage == "readback": require_quality_gate_current(project_root) @@ -1074,15 +1291,75 @@ def require_quality_gate_current(project_root: Path) -> dict[str, Any]: inputs = gate.get("inputs") if not isinstance(inputs, dict) or inputs.get("visual_distinctness") != "06-check/visual-distinctness.json": raise RunnerError("quality gate is missing visual_distinctness input; rerun quality_gate") + if inputs.get("theme_validate") != "06-check/theme-validate.json": + raise RunnerError("quality gate is missing theme_validate input; rerun theme_validate and quality_gate") + if inputs.get("theme_adherence") != "06-check/theme-adherence.json": + raise RunnerError("quality gate is missing theme_adherence input; rerun theme_adherence and quality_gate") checks = gate.get("checks") check_names = {item.get("name") for item in checks if isinstance(item, dict)} if isinstance(checks, list) else set() if "visual-distinctness" not in check_names: raise RunnerError("quality gate is missing visual-distinctness check; rerun quality_gate") + if "theme-validate" not in check_names: + raise RunnerError("quality gate is missing theme-validate check; rerun quality_gate") + if "theme-adherence" not in check_names: + raise RunnerError("quality gate is missing theme-adherence check; rerun quality_gate") if not (project_root / "06-check/visual-distinctness.json").exists(): raise RunnerError("visual distinctness receipt is missing; rerun visual_distinctness_review and quality_gate") + for input_name, rel in [ + ("theme_validate", "06-check/theme-validate.json"), + ("theme_adherence", "06-check/theme-adherence.json"), + ("visual_distinctness", "06-check/visual-distinctness.json"), + ]: + if not (project_root / rel).exists(): + raise RunnerError(f"{input_name} receipt is missing; rerun {input_name} and quality_gate") + input_hashes = gate.get("input_hashes") + if not isinstance(input_hashes, dict): + raise RunnerError("quality gate is missing input_hashes; rerun quality_gate") + for input_name, rel in [ + ("theme_validate", "06-check/theme-validate.json"), + ("theme_adherence", "06-check/theme-adherence.json"), + ("visual_distinctness", "06-check/visual-distinctness.json"), + ("generator_receipt", "receipts/generate_svg.json"), + ]: + if input_hashes.get(input_name) != optional_project_file_hash(project_root, rel): + raise RunnerError(f"quality gate {input_name} hash is stale; rerun quality_gate") + if inputs.get("generation_mode") == "artboard_satori": + if inputs.get("artboard_package_check") != "06-check/artboard-package-check.json": + raise RunnerError("quality gate is missing artboard_package_check input; rerun package_check and quality_gate") + if "artboard-package-check" not in check_names: + raise RunnerError("quality gate is missing artboard-package-check check; rerun quality_gate") + if input_hashes.get("artboard_package_check") != optional_project_file_hash(project_root, "06-check/artboard-package-check.json"): + raise RunnerError("quality gate artboard_package_check hash is stale; rerun quality_gate") return gate +def require_pre_submit_review_current(project_root: Path) -> dict[str, Any]: + review_path = project_root / "06-check" / "pre-submit-review.json" + if not review_path.exists(): + raise RunnerError("pre_submit_review must be passed before production live create") + review = read_json(review_path) + if review.get("status") != "passed": + raise RunnerError("pre_submit_review must be passed before production live create") + inputs = review.get("inputs") + if not isinstance(inputs, dict): + raise RunnerError("pre_submit_review inputs are missing; rerun pre_submit_review") + expected = { + "plan_sha256": optional_project_file_hash(project_root, "02-plan/slide_plan.json"), + "quality_gate_sha256": optional_project_file_hash(project_root, "06-check/quality-gate.json"), + "theme_adherence_sha256": optional_project_file_hash(project_root, "06-check/theme-adherence.json"), + "visual_distinctness_sha256": optional_project_file_hash(project_root, "06-check/visual-distinctness.json"), + } + for key, current in expected.items(): + if inputs.get(key) != current: + raise RunnerError(f"pre_submit_review {key} does not match current project files; rerun pre_submit_review") + if review.get("prepared_files") != prepared_file_hashes(project_root): + raise RunnerError("prepared SVG files changed after pre_submit_review; rerun pre_submit_review") + human = review.get("human_approval") + if not isinstance(human, dict) or human.get("approved") is not True: + raise RunnerError("pre_submit_review is missing human approval") + return review + + def require_ppe_proof_current(project_root: Path) -> dict[str, Any]: proof = read_json(project_root / "07-create" / "ppe-proof.json") if proof.get("status") != "passed": @@ -1101,6 +1378,66 @@ def require_ppe_proof_current(project_root: Path) -> dict[str, Any]: return proof +def write_direct_svg_package_check(project_root: Path, state: dict[str, Any]) -> dict[str, Any]: + started_at = now_iso() + payload = { + "version": "svglide-artboard-package-check/v1", + "stage": "package_check", + "status": "passed", + "action": "create_live", + "checked_at": now_iso(), + "generation_mode": DEFAULT_GENERATION_MODE, + "summary": {"error_count": 0, "warning_count": 0, "runtime_check_count": 0}, + "runtime_checks": [], + "issues": [], + "skip_reason": "generation_mode is direct_svg; artboard package runtime is not required", + } + check = project_root / "06-check" / "artboard-package-check.json" + receipt = project_root / "receipts" / "artboard-package-check.json" + write_json(check, payload) + write_json(receipt, payload) + return complete_stage( + project_root, + state, + "package_check", + "passed", + started_at=started_at, + inputs=["02-plan/slide_plan.json"], + outputs=[ + "06-check/artboard-package-check.json", + "receipts/artboard-package-check.json", + ], + command=[], + ) + + +def run_package_check_stage(project_root: Path, state: dict[str, Any]) -> dict[str, Any]: + require_stage_passed(state, "confirm_plan") + validate_plan_confirmation(project_root) + mode = plan_generation_mode(project_root) + if mode == DEFAULT_GENERATION_MODE: + return write_direct_svg_package_check(project_root, state) + return run_script_stage( + project_root, + state, + "package_check", + [ + "python3", + (SCRIPT_DIR / "svglide_artboard_package_check.py").as_posix(), + "--repo-root", + repo_root().as_posix(), + "--output-dir", + project_root.as_posix(), + "--pretty", + ], + inputs=["02-plan/slide_plan.json"], + outputs=[ + "06-check/artboard-package-check.json", + "receipts/artboard-package-check.json", + ], + ) + + def lark_cli_command_prefix() -> list[str]: raw = os.environ.get(LARK_CLI_COMMAND_ENV, "").strip() if not raw: @@ -1118,11 +1455,36 @@ def cli_arg_path(path: Path) -> str: return path.as_posix() +def ppe_live_request_headers(project_root: Path) -> list[str]: + proof = require_ppe_proof_current(project_root) + proof_payload = proof.get("proof") + if not isinstance(proof_payload, dict): + raise RunnerError("PPE proof payload is missing; rerun ppe_proof") + headers = proof_payload.get("headers") + if not isinstance(headers, dict): + raise RunnerError("PPE proof headers are missing; rerun ppe_proof") + items: list[str] = [] + for key, value in headers.items(): + if not isinstance(key, str) or not isinstance(value, str): + raise RunnerError("PPE proof headers must be string key/value pairs") + normalized_key = key.strip().lower() + normalized_value = value.strip() + if normalized_key != "x-tt-env" or normalized_value != "ppe_pure_svg": + raise RunnerError("PPE proof currently supports only x-tt-env=ppe_pure_svg") + items.append(f"{normalized_key}={normalized_value}") + if not items: + raise RunnerError("PPE proof headers are empty; rerun ppe_proof") + return sorted(items) + + def create_command(project_root: Path, *, dry_run: bool) -> list[str]: command = lark_cli_command_prefix() + ["slides", "+create-svg", "--as", "user", "--title", project_title(project_root)] assets = project_root / "03-assets" / "assets.json" if assets.exists(): command.extend(["--assets", cli_arg_path(assets)]) + if not dry_run: + for header in ppe_live_request_headers(project_root): + command.extend(["--request-header", header]) for path in prepared_svg_files(project_root): command.extend(["--file", cli_arg_path(path)]) if dry_run: @@ -1140,17 +1502,21 @@ def run_create_stage( stage: str, *, dry_run: bool, + profile: str = "production", command_runner=subprocess.run, ) -> dict[str, Any]: require_stage_passed(state, "confirm_plan") validate_plan_confirmation(project_root) - require_quality_gate_passed(project_root) + require_quality_gate_current(project_root) started_at = now_iso() hashes = prepared_file_hashes(project_root) if not dry_run: require_stage_passed(state, "ppe_proof") require_ppe_proof_current(project_root) + if profile == "production_live": + require_stage_passed(state, "pre_submit_review") + require_pre_submit_review_current(project_root) dry_run_record = read_json(project_root / "07-create" / "dry-run.json") if dry_run_record.get("prepared_files") != hashes: complete_stage( @@ -1242,10 +1608,24 @@ def run_implemented_stage(project_root: Path, stage: str, state: dict[str, Any], inputs=["02-plan/slide_plan.json"], outputs=["02-plan/strategy-review.json"], ) + if stage == "theme_validate": + require_stage_passed(state, "strategy_review") + return run_script_stage( + project_root, + state, + stage, + ["python3", (SCRIPT_DIR / "svglide_theme_validate.py").as_posix(), project_root.as_posix(), "--pretty"], + output_json=project_root / "06-check" / "theme-validate.json", + inputs=["02-plan/slide_plan.json"], + outputs=["06-check/theme-validate.json", "receipts/theme-validate.json"], + ) if stage == "confirm_plan": return run_confirm_plan_stage(project_root, state) + if stage == "package_check": + return run_package_check_stage(project_root, state) if stage == "assets": require_stage_passed(state, "confirm_plan") + require_stage_passed(state, "package_check") validate_plan_confirmation(project_root) require_source_current(project_root) return run_script_stage( @@ -1364,6 +1744,18 @@ def run_implemented_stage(project_root: Path, stage: str, state: dict[str, Any], inputs=["02-plan/slide_plan.json"], outputs=["06-check/visual-distinctness.json"], ) + if stage == "theme_adherence": + require_stage_passed(state, "visual_distinctness_review") + require_stage_passed(state, "theme_validate") + return run_script_stage( + project_root, + state, + stage, + ["python3", (SCRIPT_DIR / "svglide_theme_adherence.py").as_posix(), project_root.as_posix(), "--pretty"], + output_json=project_root / "06-check" / "theme-adherence.json", + inputs=["02-plan/slide_plan.json", "06-check/theme-validate.json", "04-svg/prepared"], + outputs=["06-check/theme-adherence.json", "receipts/theme-adherence.json"], + ) if stage == "quality_gate": require_stage_passed(state, "preflight") require_stage_passed(state, "preview_lint") @@ -1372,6 +1764,7 @@ def run_implemented_stage(project_root: Path, stage: str, state: dict[str, Any], require_stage_passed(state, "semantic_review") require_stage_passed(state, "runtime_review") require_stage_passed(state, "visual_distinctness_review") + require_stage_passed(state, "theme_adherence") return run_script_stage( project_root, state, @@ -1385,12 +1778,14 @@ def run_implemented_stage(project_root: Path, stage: str, state: dict[str, Any], "06-check/semantic-review.json", "06-check/runtime-review.json", "06-check/visual-distinctness.json", + "06-check/theme-validate.json", + "06-check/theme-adherence.json", "receipts/generate_svg.json", ], outputs=["06-check/quality-gate.json"], ) if stage == "dry_run": - return run_create_stage(project_root, state, stage, dry_run=True) + return run_create_stage(project_root, state, stage, dry_run=True, profile=profile) if stage == "ppe_proof": require_stage_passed(state, "dry_run") return run_script_stage( @@ -1402,8 +1797,32 @@ def run_implemented_stage(project_root: Path, stage: str, state: dict[str, Any], inputs=["06-check/quality-gate.json", "07-create/dry-run.json", "07-create/ppe-proof.input.json"], outputs=["07-create/ppe-proof.json"], ) + if stage == "pre_submit_review": + require_stage_passed(state, "ppe_proof") + return run_script_stage( + project_root, + state, + stage, + [ + "python3", + (SCRIPT_DIR / "svglide_pre_submit_review.py").as_posix(), + project_root.as_posix(), + "--human-review", + (project_root / "06-check" / "pre-submit-review.input.json").as_posix(), + "--pretty", + ], + output_json=project_root / "06-check" / "pre-submit-review.json", + inputs=[ + "06-check/pre-submit-review.input.json", + "06-check/quality-gate.json", + "06-check/theme-adherence.json", + "06-check/visual-distinctness.json", + "04-svg/prepared", + ], + outputs=["06-check/pre-submit-review.json", "receipts/pre-submit-review.json"], + ) if stage == "live_create": - return run_create_stage(project_root, state, stage, dry_run=False) + return run_create_stage(project_root, state, stage, dry_run=False, profile=profile) if stage == "readback": return run_script_stage( project_root, @@ -1424,7 +1843,7 @@ def run_stage(project_root: Path, stage: str, *, command: list[str] | None = Non record = state.get("stages", {}).get(normalized) if record: fail_if_existing_stage_failed(normalized, record) - require_existing_stage_current(project_root, normalized) + require_existing_stage_current(project_root, normalized, profile=profile) return {"stage": normalized, "status": "passed", "state": state} if normalized not in IMPLEMENTED_STAGES: @@ -1445,10 +1864,12 @@ def run_until(project_root: Path, until: str, *, profile: str = "production") -> target = normalize_stage(until) state = load_state(project_root) for stage in stages_until(target): + if not stage_required_for_profile(stage, profile): + continue record = state.get("stages", {}).get(stage) if record: fail_if_existing_stage_failed(stage, record) - require_existing_stage_current(project_root, stage) + require_existing_stage_current(project_root, stage, profile=profile) continue if stage not in IMPLEMENTED_STAGES: @@ -1486,6 +1907,18 @@ def build_parser() -> argparse.ArgumentParser: run.add_argument("--profile", choices=sorted(PROFILE_TARGETS)) add_network_args(run) + prompt_plan = subcommands.add_parser("prompt-plan", help="generate source and plan artifacts from a raw prompt") + prompt_plan.add_argument("project_root", type=Path) + prompt_plan.add_argument("--prompt", required=True) + prompt_plan.add_argument("--target-slide-count", type=int, default=8) + prompt_plan.add_argument("--language", default="zh-CN") + prompt_plan.add_argument("--audience", default="投资/战略分析读者") + prompt_plan.add_argument("--provider", default="codex", choices=["codex", "claude", "command"]) + prompt_plan.add_argument("--planner-command") + prompt_plan.add_argument("--no-search", action="store_true") + prompt_plan.add_argument("--timeout", type=int, default=300) + prompt_plan.add_argument("--force", action="store_true") + return parser @@ -1513,6 +1946,21 @@ def main(argv: list[str] | None = None) -> int: apply_cli_runner_options(args) if args.command == "init": result = init_project(args.deck_id, args.title, plan_root=args.plan_root, force=args.force) + elif args.command == "prompt-plan": + import svglide_prompt_planner + + result = svglide_prompt_planner.run_prompt_plan( + args.project_root, + prompt=args.prompt, + target_slide_count=args.target_slide_count, + language=args.language, + audience=args.audience, + provider=args.provider, + planner_command=args.planner_command, + search=not args.no_search, + timeout=args.timeout, + force=args.force, + ) elif args.command == "stage": result = run_stage(args.project_root, args.stage, profile=args.profile) elif args.command == "run": diff --git a/skills/lark-slides/scripts/svglide_project_runner_test.py b/skills/lark-slides/scripts/svglide_project_runner_test.py index aa47480d..2b51419f 100644 --- a/skills/lark-slides/scripts/svglide_project_runner_test.py +++ b/skills/lark-slides/scripts/svglide_project_runner_test.py @@ -136,6 +136,56 @@ class SVGlideProjectRunnerTest(unittest.TestCase): encoding="utf-8", ) + def write_artboard_plan(self, project_root: Path) -> None: + self.write_plan(project_root) + plan = json.loads((project_root / "02-plan/slide_plan.json").read_text(encoding="utf-8")) + plan["generation_mode"] = "artboard_satori" + first_slide = plan["slides"][0] + first_slide["renderer_id"] = "artboard_satori.cover-hero" + first_slide["layout_family"] = "cover" + first_slide["visual_recipe"] = "artboard-cover-hero" + first_slide["required_primitives"] = ["text", "rect", "circle"] + first_slide["svg_primitives"] = ["foreignObject", "rect", "circle"] + first_slide["canvas_spec"] = { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "theme": { + "colors": { + "background": "#0F172A", + "panel": "#111827", + "primary": "#38BDF8", + "accent": "#A78BFA", + "text": "#F8FAFC", + "muted": "#CBD5E1", + } + }, + "content": { + "eyebrow": "ARTBOARD P0A", + "title": "受控画板生成", + "subtitle": "CanvasSpec 驱动模板,输出可进入 SVGlide 后续链路的协议 SVG。", + "chips": ["CanvasSpec", "Satori Preview", "SVGlide SVG"], + }, + "semantic_elements": [ + { + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title", + "bbox": {"x": 84, "y": 142, "width": 628, "height": 142}, + } + ], + "quality_constraints": { + "max_title_lines": 2, + "min_font_size": 18, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + }, + } + plan["slides"] = [first_slide] + (project_root / "02-plan/slide_plan.json").write_text(json.dumps(plan), encoding="utf-8") + def write_plan_confirmation(self, project_root: Path) -> None: plan = project_root / "02-plan/slide_plan.json" payload = { @@ -184,14 +234,132 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.run_source(project_root) self.write_plan_confirmation(project_root) runner.run_confirm_plan_stage(project_root, runner.load_state(project_root)) + (project_root / "03-assets/asset-manifest.json").write_text( + json.dumps( + { + "version": "svglide-assets/v1", + "status": "passed", + "source_receipt_sha256": runner.file_sha256(project_root / "source/source-receipt.json"), + "summary": {"error_count": 0}, + } + ), + encoding="utf-8", + ) + (project_root / "04-svg/page-001.svg").write_text("", encoding="utf-8") (project_root / "04-svg/prepared/page-001.svg").write_text("", encoding="utf-8") - (project_root / "06-check/visual-distinctness.json").write_text(json.dumps({"status": "passed"}), encoding="utf-8") + source_files = runner.source_file_hashes(project_root) + (project_root / "04-svg/page-001.receipt.json").write_text( + json.dumps( + { + "version": "svglide-page-generation/v1", + "stage": "generate_svg", + "page": 1, + "source_svg": source_files[0]["path"], + "source_sha256": source_files[0]["sha256"], + "plan_sha256": runner.file_sha256(project_root / "02-plan/slide_plan.json"), + "evidence_sha256": runner.file_sha256(project_root / "source/evidence.json"), + } + ), + encoding="utf-8", + ) + (project_root / "receipts/generate_svg.json").write_text( + json.dumps( + { + "stage": "generate_svg", + "status": "passed", + "generator_mode": "external", + "generation_mode": "direct_svg", + "generated_files": source_files, + "page_receipts": ["04-svg/page-001.receipt.json"], + "plan_sha256": runner.file_sha256(project_root / "02-plan/slide_plan.json"), + "evidence_sha256": runner.file_sha256(project_root / "source/evidence.json"), + "asset_manifest_sha256": runner.file_sha256(project_root / "03-assets/asset-manifest.json"), + "source_receipt_sha256": runner.file_sha256(project_root / "source/source-receipt.json"), + "lock_sha256": None, + "generator_script_sha256": None, + "fallback_skeleton_used": False, + "page_identity_summary": [ + { + "page": 1, + "theme_archetype": "company_ecosystem", + "identity_fit_reason": "测试页符合视觉身份", + "reuse_risk_score": 0, + "fallback_skeleton_used": False, + } + ], + } + ), + encoding="utf-8", + ) + theme_validate = { + "schema_version": "svglide-theme-validate/v1", + "stage": "theme_validate", + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": runner.file_sha256(project_root / "02-plan/slide_plan.json"), + }, + "pages": [{"page": 1, "theme_id": "dark-clarity", "status": "passed", "issues": []}], + "summary": {"error_count": 0, "warning_count": 0, "page_count": 1, "theme_count": 1}, + "issues": [], + } + (project_root / "06-check/theme-validate.json").write_text(json.dumps(theme_validate), encoding="utf-8") + theme_adherence = { + "schema_version": "svglide-theme-adherence/v1", + "stage": "theme_adherence", + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": runner.file_sha256(project_root / "02-plan/slide_plan.json"), + "theme_validate": "06-check/theme-validate.json", + "theme_validate_sha256": runner.file_sha256(project_root / "06-check/theme-validate.json"), + }, + "prepared_files": runner.prepared_file_hashes(project_root), + "pages": [{"page": 1, "status": "passed", "issues": []}], + "summary": {"error_count": 0, "warning_count": 0, "page_count": 1}, + "issues": [], + } + (project_root / "06-check/theme-adherence.json").write_text(json.dumps(theme_adherence), encoding="utf-8") + (project_root / "06-check/visual-distinctness.json").write_text( + json.dumps( + { + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": runner.file_sha256(project_root / "02-plan/slide_plan.json"), + }, + "summary": {"error_count": 0}, + "issues": [], + } + ), + encoding="utf-8", + ) (project_root / "06-check/quality-gate.json").write_text( json.dumps( { "status": "passed", - "inputs": {"visual_distinctness": "06-check/visual-distinctness.json"}, - "checks": [{"name": "visual-distinctness", "status": "passed"}], + "inputs": { + "generation_mode": "direct_svg", + "generator_receipt": "receipts/generate_svg.json", + "visual_distinctness": "06-check/visual-distinctness.json", + "theme_validate": "06-check/theme-validate.json", + "theme_adherence": "06-check/theme-adherence.json", + }, + "input_hashes": { + "generator_receipt": runner.file_sha256(project_root / "receipts/generate_svg.json"), + "visual_distinctness": runner.file_sha256(project_root / "06-check/visual-distinctness.json"), + "theme_validate": runner.file_sha256(project_root / "06-check/theme-validate.json"), + "theme_adherence": runner.file_sha256(project_root / "06-check/theme-adherence.json"), + }, + "prepared_files": runner.prepared_file_hashes(project_root), + "checks": [ + {"name": "visual-distinctness", "status": "passed"}, + {"name": "theme-validate", "status": "passed"}, + {"name": "theme-adherence", "status": "passed"}, + ], } ), encoding="utf-8", @@ -202,13 +370,24 @@ class SVGlideProjectRunnerTest(unittest.TestCase): return subprocess.CompletedProcess(command, returncode, stdout=json.dumps(payload or {"ok": True}), stderr="") def write_ppe_input(self, project_root: Path) -> None: + rule_file = "skills/lark-slides/references/ppe-pure-svg.whistle.js" + rule_path = runner.repo_root() / rule_file (project_root / "07-create/ppe-proof.input.json").write_text( json.dumps( { "status": "passed", "environment": {"name": "Pre_release", "x-tt-env": "ppe_pure_svg"}, "auth": {"identity": "user"}, - "proxy": {"mode": "whistle", "rewrite_host": "ppe"}, + "proxy": { + "mode": "whistle", + "capture": True, + "http_proxy": "http://127.0.0.1:8899", + "https_proxy": "http://127.0.0.1:8899", + "rewrite_host": "open.feishu-pre.cn", + "rule_file": rule_file, + "rule_sha256": runner.file_sha256(rule_path), + "inject_headers": {"Env": "Pre_release", "x-tt-env": "ppe_pure_svg"}, + }, "headers": {"x-tt-env": "ppe_pure_svg"}, "route": {"name": "slides +create-svg", "lane": "pure-svg"}, } @@ -220,18 +399,24 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.assertEqual(runner.normalize_stage("confirm-plan"), "confirm_plan") self.assertEqual(runner.normalize_stage("source-review"), "source") self.assertEqual(runner.normalize_stage("strategy-review"), "strategy_review") + self.assertEqual(runner.normalize_stage("theme-validate"), "theme_validate") + self.assertEqual(runner.normalize_stage("package-check"), "package_check") + self.assertEqual(runner.normalize_stage("artboard-package-check"), "package_check") self.assertEqual(runner.normalize_stage("aesthetic-review"), "aesthetic_review") self.assertEqual(runner.normalize_stage("chart-verify"), "chart_verify") self.assertEqual(runner.normalize_stage("semantic-review"), "semantic_review") self.assertEqual(runner.normalize_stage("runtime-review"), "runtime_review") self.assertEqual(runner.normalize_stage("visual-distinctness"), "visual_distinctness_review") self.assertEqual(runner.normalize_stage("visual-distinctness-review"), "visual_distinctness_review") + self.assertEqual(runner.normalize_stage("theme-adherence"), "theme_adherence") self.assertEqual(runner.normalize_stage("generate"), "generate_svg") self.assertEqual(runner.normalize_stage("generate-svg"), "generate_svg") self.assertEqual(runner.normalize_stage("quality-gate"), "quality_gate") self.assertEqual(runner.normalize_stage("preview-lint"), "preview_lint") self.assertEqual(runner.normalize_stage("dry-run"), "dry_run") self.assertEqual(runner.normalize_stage("ppe-proof"), "ppe_proof") + self.assertEqual(runner.normalize_stage("pre-submit-review"), "pre_submit_review") + self.assertEqual(runner.normalize_stage("pre-submit"), "pre_submit_review") self.assertEqual(runner.normalize_stage("live-create"), "live_create") def test_stages_until_uses_normalized_stage_graph(self) -> None: @@ -239,6 +424,8 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.assertIn("source", dry_run) self.assertIn("confirm_plan", dry_run) self.assertIn("strategy_review", dry_run) + self.assertIn("theme_validate", dry_run) + self.assertIn("package_check", dry_run) self.assertIn("assets", dry_run) self.assertIn("generate_svg", dry_run) self.assertIn("aesthetic_review", dry_run) @@ -246,6 +433,7 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.assertIn("semantic_review", dry_run) self.assertIn("runtime_review", dry_run) self.assertIn("visual_distinctness_review", dry_run) + self.assertIn("theme_adherence", dry_run) self.assertIn("quality_gate", dry_run) self.assertIn("dry_run", dry_run) self.assertNotIn("ppe_proof", dry_run) @@ -255,6 +443,7 @@ class SVGlideProjectRunnerTest(unittest.TestCase): readback = runner.stages_until("readback") self.assertIn("ppe_proof", readback) + self.assertIn("pre_submit_review", readback) self.assertIn("live_create", readback) self.assertIn("readback", readback) @@ -291,12 +480,21 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.assertIn("semantic_review", called_stages) self.assertIn("runtime_review", called_stages) self.assertIn("visual_distinctness_review", called_stages) + self.assertIn("theme_validate", called_stages) + self.assertIn("package_check", called_stages) + self.assertIn("theme_adherence", called_stages) + self.assertLess(called_stages.index("strategy_review"), called_stages.index("theme_validate")) + self.assertLess(called_stages.index("theme_validate"), called_stages.index("confirm_plan")) + self.assertLess(called_stages.index("confirm_plan"), called_stages.index("package_check")) self.assertLess(called_stages.index("chart_verify"), called_stages.index("semantic_review")) self.assertLess(called_stages.index("semantic_review"), called_stages.index("quality_gate")) self.assertLess(called_stages.index("runtime_review"), called_stages.index("visual_distinctness_review")) + self.assertLess(called_stages.index("visual_distinctness_review"), called_stages.index("theme_adherence")) + self.assertLess(called_stages.index("theme_adherence"), called_stages.index("quality_gate")) self.assertLess(called_stages.index("visual_distinctness_review"), called_stages.index("quality_gate")) self.assertNotIn("dry_run", called_stages) self.assertNotIn("ppe_proof", called_stages) + self.assertNotIn("pre_submit_review", called_stages) self.assertNotIn("live_create", called_stages) self.assertNotIn("readback", called_stages) self.assertTrue(all(profile == "preview_only" for _, profile in calls)) @@ -324,8 +522,10 @@ class SVGlideProjectRunnerTest(unittest.TestCase): called_stages = [stage for stage, _ in calls] self.assertIn("ppe_proof", called_stages) + self.assertIn("pre_submit_review", called_stages) self.assertLess(called_stages.index("dry_run"), called_stages.index("ppe_proof")) - self.assertLess(called_stages.index("ppe_proof"), called_stages.index("live_create")) + self.assertLess(called_stages.index("ppe_proof"), called_stages.index("pre_submit_review")) + self.assertLess(called_stages.index("pre_submit_review"), called_stages.index("live_create")) self.assertTrue(all(profile == "production_live" for _, profile in calls)) def test_init_creates_project_directories_manifest_and_state(self) -> None: @@ -400,6 +600,27 @@ class SVGlideProjectRunnerTest(unittest.TestCase): state = json.loads((project_root / "01-project/state.json").read_text(encoding="utf-8")) self.assertEqual(state["stages"]["plan"]["status"], "passed") + def test_plan_stage_rejects_artboard_mode_without_canvas_spec(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + plan_root = Path(tmpdir) / ".lark-slides/plan" + result = runner.init_project("smoke", "Smoke", plan_root=plan_root) + project_root = Path(result["project_root"]) + self.write_plan(project_root) + plan_path = project_root / "02-plan/slide_plan.json" + plan = json.loads(plan_path.read_text(encoding="utf-8")) + plan["generation_mode"] = "artboard_satori" + for slide in plan["slides"]: + slide.pop("canvas_spec", None) + plan_path.write_text(json.dumps(plan, ensure_ascii=False), encoding="utf-8") + + with self.assertRaisesRegex(runner.RunnerError, "plan schema validation failed"): + runner.run_stage(project_root, "plan") + + receipt = json.loads((project_root / "receipts/plan.json").read_text(encoding="utf-8")) + self.assertEqual(receipt["status"], "failed") + paths = {item["path"] for item in receipt["error"]["issues"]} + self.assertIn("$.slides[0].canvas_spec", paths) + def test_plan_stage_adds_visual_identity_before_confirmation(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: plan_root = Path(tmpdir) / ".lark-slides/plan" @@ -502,6 +723,26 @@ class SVGlideProjectRunnerTest(unittest.TestCase): with self.assertRaisesRegex(runner.RunnerError, "assets"): runner.run_stage(project_root, "generate-svg") + def test_package_check_writes_noop_receipt_for_direct_svg(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + plan_root = Path(tmpdir) / ".lark-slides/plan" + result = runner.init_project("smoke", "Smoke", plan_root=plan_root) + project_root = Path(result["project_root"]) + self.write_plan(project_root) + self.write_plan_confirmation(project_root) + runner.run_stage(project_root, "confirm-plan") + + result = runner.run_stage(project_root, "package-check") + + self.assertEqual(result["status"], "passed") + check = json.loads((project_root / "06-check/artboard-package-check.json").read_text(encoding="utf-8")) + self.assertEqual(check["stage"], "package_check") + self.assertEqual(check["status"], "passed") + self.assertEqual(check["generation_mode"], "direct_svg") + self.assertEqual(check["summary"]["error_count"], 0) + state = runner.load_state(project_root) + self.assertEqual(state["stages"]["package_check"]["status"], "passed") + def test_generate_svg_adopts_existing_source_files(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: plan_root = Path(tmpdir) / ".lark-slides/plan" @@ -511,6 +752,7 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.write_plan_confirmation(project_root) self.run_source(project_root) runner.run_stage(project_root, "confirm-plan") + runner.run_stage(project_root, "package-check") runner.run_stage(project_root, "assets") for page in range(1, 4): (project_root / f"04-svg/page-{page:03d}.svg").write_text("", encoding="utf-8") @@ -551,6 +793,7 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.write_plan_confirmation(project_root) self.run_source(project_root) runner.run_stage(project_root, "confirm-plan") + runner.run_stage(project_root, "package-check") runner.run_stage(project_root, "assets") for page in range(1, 4): (project_root / f"04-svg/page-{page:03d}.svg").write_text( @@ -583,6 +826,7 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.write_plan_confirmation(project_root) self.run_source(project_root) runner.run_stage(project_root, "confirm-plan") + runner.run_stage(project_root, "package-check") runner.run_stage(project_root, "assets") generator = project_root / "logs/generate_svgs.py" generator.write_text( @@ -605,6 +849,69 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.assertEqual(receipt["generator_mode"], "script") self.assertIn("logs/generate_svgs.py", receipt["command"][1]) + def test_generate_svg_runs_artboard_satori_dispatcher(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + plan_root = Path(tmpdir) / ".lark-slides/plan" + result = runner.init_project("smoke", "Smoke", plan_root=plan_root) + project_root = Path(result["project_root"]) + self.write_artboard_plan(project_root) + self.write_plan_confirmation(project_root) + self.run_source(project_root) + runner.run_stage(project_root, "confirm-plan") + runner.run_stage(project_root, "package-check") + runner.run_stage(project_root, "assets") + + result = runner.run_stage(project_root, "generate-svg") + + self.assertEqual(result["status"], "passed") + source = project_root / "04-svg/page-001.svg" + self.assertTrue(source.exists()) + self.assertIn('slide:role="slide"', source.read_text(encoding="utf-8")) + receipt = json.loads((project_root / "receipts/generate_svg.json").read_text(encoding="utf-8")) + self.assertEqual(receipt["generation_mode"], "artboard_satori") + self.assertEqual(receipt["generator_mode"], "script") + self.assertEqual(receipt["artboard_receipts"], ["04-svg/artboard/page-001.receipt.json"]) + self.assertEqual( + receipt["artboard_additional_receipts"], + [ + "receipts/canvas-spec-validate.json", + "receipts/artboard-render.json", + "receipts/satori-bridge.json", + ], + ) + self.assertEqual(receipt["canvas_spec_validate"], "06-check/canvas-spec-validate.json") + self.assertEqual(receipt["artboard_render_receipt"], "receipts/artboard-render.json") + self.assertEqual(receipt["satori_bridge_receipt"], "receipts/satori-bridge.json") + self.assertEqual(receipt["contact_sheet"]["path"], "05-preview/contact-sheet.png") + self.assertEqual(receipt["template_fit_check"], "06-check/template-fit.json") + self.assertEqual(receipt["page_receipts"], ["04-svg/page-001.receipt.json"]) + self.assertTrue((project_root / "06-check/template-fit.json").exists()) + self.assertTrue((project_root / "receipts/template-fit-check.json").exists()) + self.assertTrue((project_root / "04-svg/artboard/page-001.png").exists()) + self.assertTrue((project_root / "05-preview/contact-sheet.png").exists()) + page_receipt = json.loads((project_root / "04-svg/page-001.receipt.json").read_text(encoding="utf-8")) + self.assertEqual(page_receipt["generation_mode"], "artboard_satori") + self.assertEqual(page_receipt["artboard_receipt"], "04-svg/artboard/page-001.receipt.json") + + def test_generate_svg_rejects_artboard_plan_without_canvas_spec(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + plan_root = Path(tmpdir) / ".lark-slides/plan" + result = runner.init_project("smoke", "Smoke", plan_root=plan_root) + project_root = Path(result["project_root"]) + self.write_artboard_plan(project_root) + plan_path = project_root / "02-plan/slide_plan.json" + plan = json.loads(plan_path.read_text(encoding="utf-8")) + plan["slides"][0].pop("canvas_spec") + plan_path.write_text(json.dumps(plan), encoding="utf-8") + self.write_plan_confirmation(project_root) + self.run_source(project_root) + runner.run_stage(project_root, "confirm-plan") + runner.run_stage(project_root, "package-check") + runner.run_stage(project_root, "assets") + + with self.assertRaisesRegex(runner.RunnerError, "requires canvas_spec"): + runner.run_stage(project_root, "generate-svg") + def test_prepare_requires_confirm_plan_stage(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: plan_root = Path(tmpdir) / ".lark-slides/plan" @@ -626,6 +933,7 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.write_plan_confirmation(project_root) self.run_source(project_root) runner.run_stage(project_root, "confirm-plan") + runner.run_stage(project_root, "package-check") runner.run_stage(project_root, "assets") (project_root / "04-svg/page-001.svg").write_text("", encoding="utf-8") @@ -654,6 +962,7 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.write_plan_confirmation(project_root) self.run_source(project_root) runner.run_stage(project_root, "confirm-plan") + runner.run_stage(project_root, "package-check") runner.run_stage(project_root, "assets") source = project_root / "04-svg/page-001.svg" for page in range(1, 4): @@ -675,8 +984,6 @@ class SVGlideProjectRunnerTest(unittest.TestCase): def test_dry_run_refuses_changed_prepared_hashes_after_quality_gate(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: project_root = self.make_project(tmpdir) - hashes = runner.prepared_file_hashes(project_root) - (project_root / "06-check/quality-gate.json").write_text(json.dumps({"status": "passed", "prepared_files": hashes}), encoding="utf-8") (project_root / "04-svg/prepared/page-001.svg").write_text("", encoding="utf-8") with self.assertRaisesRegex(runner.RunnerError, "changed after quality gate"): @@ -748,6 +1055,37 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.assertIn("+create-svg", command) self.assertIn("--dry-run", command) + def test_live_create_command_includes_ppe_request_header(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project_root = self.make_project(tmpdir) + captured: list[list[str]] = [] + + def fake(command: list[str], **_: object) -> subprocess.CompletedProcess[str]: + captured.append(command) + return self.completed(command, {"xml_presentation_id": "xml_1", "slide_ids": ["s1"]}) + + runner.run_create_stage( + project_root, + runner.load_state(project_root), + "dry_run", + dry_run=True, + command_runner=lambda command, **_: self.completed(command), + ) + self.write_ppe_input(project_root) + runner.run_stage(project_root, "ppe-proof") + runner.run_create_stage( + project_root, + runner.load_state(project_root), + "live_create", + dry_run=False, + command_runner=fake, + ) + + self.assertEqual(captured[0][captured[0].index("--request-header") + 1], "x-tt-env=ppe_pure_svg") + self.assertNotIn("--dry-run", captured[0]) + command_text = (project_root / "07-create/create-command.txt").read_text(encoding="utf-8") + self.assertIn("--request-header x-tt-env=ppe_pure_svg", command_text) + def test_cli_arg_path_uses_repo_relative_paths(self) -> None: repo_file = runner.repo_root() / "skills/lark-slides/scripts/svglide_project_runner.py" @@ -767,6 +1105,19 @@ class SVGlideProjectRunnerTest(unittest.TestCase): self.write_ppe_input(project_root) runner.run_stage(project_root, "ppe-proof") (project_root / "04-svg/prepared/page-001.svg").write_text("", encoding="utf-8") + theme_adherence_path = project_root / "06-check/theme-adherence.json" + theme_adherence = json.loads(theme_adherence_path.read_text(encoding="utf-8")) + theme_adherence["prepared_files"] = runner.prepared_file_hashes(project_root) + theme_adherence_path.write_text(json.dumps(theme_adherence), encoding="utf-8") + gate_path = project_root / "06-check/quality-gate.json" + gate = json.loads(gate_path.read_text(encoding="utf-8")) + gate["prepared_files"] = runner.prepared_file_hashes(project_root) + gate["input_hashes"]["theme_adherence"] = runner.file_sha256(theme_adherence_path) + gate_path.write_text(json.dumps(gate), encoding="utf-8") + proof_path = project_root / "07-create/ppe-proof.json" + proof = json.loads(proof_path.read_text(encoding="utf-8")) + proof["inputs"]["quality_gate_sha256"] = runner.file_sha256(gate_path) + proof_path.write_text(json.dumps(proof), encoding="utf-8") with self.assertRaisesRegex(runner.RunnerError, "changed after dry-run"): runner.run_create_stage( @@ -802,6 +1153,54 @@ class SVGlideProjectRunnerTest(unittest.TestCase): command_runner=lambda command, **_: self.completed(command, {"xml_presentation_id": "xml_1", "slide_ids": ["s1"]}), ) + def test_production_live_create_requires_pre_submit_review(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project_root = self.make_project(tmpdir) + + runner.run_create_stage( + project_root, + runner.load_state(project_root), + "dry_run", + dry_run=True, + command_runner=lambda command, **_: self.completed(command), + ) + self.write_ppe_input(project_root) + runner.run_stage(project_root, "ppe-proof") + + with self.assertRaisesRegex(runner.RunnerError, "pre_submit_review"): + runner.run_create_stage( + project_root, + runner.load_state(project_root), + "live_create", + dry_run=False, + profile="production_live", + command_runner=lambda command, **_: self.completed(command, {"xml_presentation_id": "xml_1", "slide_ids": ["s1"]}), + ) + + def test_existing_live_create_record_requires_pre_submit_for_production_live_profile(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project_root = self.make_project(tmpdir) + + runner.run_create_stage( + project_root, + runner.load_state(project_root), + "dry_run", + dry_run=True, + command_runner=lambda command, **_: self.completed(command), + ) + self.write_ppe_input(project_root) + runner.run_stage(project_root, "ppe-proof") + runner.run_create_stage( + project_root, + runner.load_state(project_root), + "live_create", + dry_run=False, + command_runner=lambda command, **_: self.completed(command, {"xml_presentation_id": "xml_1", "slide_ids": ["s1"]}), + ) + + with self.assertRaisesRegex(runner.RunnerError, "pre_submit_review"): + runner.run_stage(project_root, "live-create", profile="production_live") + if __name__ == "__main__": unittest.main() diff --git a/skills/lark-slides/scripts/svglide_prompt_planner.py b/skills/lark-slides/scripts/svglide_prompt_planner.py new file mode 100644 index 00000000..733d7a61 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_prompt_planner.py @@ -0,0 +1,679 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import shlex +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +import svglide_planner_contracts +import svglide_schema + + +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = SCRIPT_DIR.parents[2] +PLANNER_DIR = Path("02-plan/planner") +INSTRUCTION_PATH = Path("00-input/instruction.json") +PROMPT_RECEIPT_PATH = Path("receipts/prompt-planner.json") +SOURCE_PLAN_PATH = Path("source/source-plan.json") +SOURCE_NOTES_PATH = Path("source/source-notes.md") +EVIDENCE_PATH = Path("source/evidence.json") +PLAN_CONFIRMATION_PATH = Path("02-plan/plan-confirmation.json") +PLANNER_OUTPUTS = { + "deck-planner": Path("02-plan/deck-plan.json"), + "slide-planner": Path("02-plan/slide-plan.json"), + "canvas-planner": Path("02-plan/slide_plan.json"), +} +PROMPT_PATHS = { + "deck-planner": Path("skills/lark-slides/prompts/svglide/deck-planner.prompt.md"), + "slide-planner": Path("skills/lark-slides/prompts/svglide/slide-planner.prompt.md"), + "canvas-planner": Path("skills/lark-slides/prompts/svglide/canvas-planner.prompt.md"), +} +SCHEMA_PATHS = { + "deck-planner": Path("skills/lark-slides/references/svglide-deck-plan.schema.json"), + "slide-planner": Path("skills/lark-slides/references/svglide-slide-plan.schema.json"), + "canvas-planner": Path("skills/lark-slides/references/svglide-canvas-plan.schema.json"), +} + + +class PromptPlannerError(Exception): + pass + + +def now_iso() -> str: + return datetime.now(timezone.utc).isoformat() + + +def file_sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def relpath(path: Path, base: Path) -> str: + try: + return path.resolve().relative_to(base.resolve()).as_posix() + except ValueError: + return path.as_posix() + + +def repo_path(rel: Path | str) -> Path: + return REPO_ROOT / Path(rel) + + +def project_path(project: Path, rel: Path | str) -> Path: + return project / Path(rel) + + +def read_json(path: Path) -> Any: + return json.loads(path.read_text(encoding="utf-8")) + + +def write_json(path: Path, payload: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def write_text(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding="utf-8") + + +def compact_json(payload: Any) -> str: + return json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + + +def strip_markdown_fence(text: str) -> str: + raw = text.strip() + if not raw.startswith("```"): + return raw + raw = re.sub(r"^```(?:json)?\s*", "", raw, flags=re.IGNORECASE) + raw = re.sub(r"\s*```$", "", raw) + return raw.strip() + + +def extract_json_object(text: str) -> dict[str, Any]: + raw = strip_markdown_fence(text) + try: + payload = json.loads(raw) + except json.JSONDecodeError: + start = raw.find("{") + end = raw.rfind("}") + if start < 0 or end <= start: + raise PromptPlannerError("planner response did not contain a JSON object") + payload = json.loads(raw[start : end + 1]) + if not isinstance(payload, dict): + raise PromptPlannerError("planner response must be a JSON object") + return payload + + +def validate_payload(payload: dict[str, Any], schema_rel: Path, *, output_path: str) -> list[dict[str, str]]: + schema = read_json(repo_path(schema_rel)) + return [ + {"code": item["code"], "message": item["message"], "path": item["path"], "output_path": output_path} + for item in svglide_schema.validate_json_schema(payload, schema) + ] + + +def theme_registry_bundle() -> list[dict[str, Any]]: + registry_path = repo_path("skills/lark-slides/scripts/artboard_renderer/themes/registry.json") + registry = read_json(registry_path) + result: list[dict[str, Any]] = [] + for item in registry.get("themes", []) if isinstance(registry, dict) else []: + if not isinstance(item, dict) or item.get("status") != "active": + continue + record = {"id": item.get("id"), "status": item.get("status")} + theme_path = item.get("path") + if isinstance(theme_path, str): + path = repo_path(theme_path) + if path.exists(): + theme = read_json(path) + if isinstance(theme, dict): + record["colors"] = theme.get("colors") + record["typography"] = theme.get("typography") + result.append(record) + return result + + +def template_registry_bundle() -> list[dict[str, Any]]: + registry = read_json(repo_path("skills/lark-slides/references/svglide-template-registry.json")) + result: list[dict[str, Any]] = [] + for item in registry.get("templates", []) if isinstance(registry, dict) else []: + if not isinstance(item, dict) or item.get("status") != "active": + continue + result.append( + { + "id": item.get("id"), + "renderer_id": item.get("renderer_id"), + "layout_family": item.get("layout_family"), + "required_content": item.get("required_content"), + "optional_content": item.get("optional_content"), + "max_items": item.get("max_items"), + "text_budget": item.get("text_budget"), + "supported_theme_ids": item.get("supported_theme_ids"), + } + ) + return result + + +def load_context() -> dict[str, Any]: + return { + "templates": template_registry_bundle(), + "themes": theme_registry_bundle(), + "layout_archetypes": read_json(repo_path("skills/lark-slides/references/svglide-layout-archetypes.json")), + "component_registry": read_json(repo_path("skills/lark-slides/references/svglide-component-registry.json")), + "canvas_spec_schema": read_json(repo_path("skills/lark-slides/references/svglide-canvas-spec.schema.json")), + "planner_prompt_contracts": read_json(repo_path("skills/lark-slides/references/svglide-planner-prompt-contracts.json")), + } + + +def instruction_payload(*, prompt: str, language: str, target_slide_count: int, audience: str) -> dict[str, Any]: + return { + "schema_version": "svglide-instruction/v1", + "source": "cli_raw_prompt", + "raw_prompt": prompt, + "language": language, + "target_slide_count": target_slide_count, + "audience": audience, + "route": "svglide-svg", + "generation_mode": "artboard_satori", + "created_at": now_iso(), + "must_include": [ + "deck plan", + "slide plan", + "canvas spec", + "asset contracts", + "real visual asset acquisition before dry_run", + ], + "must_avoid": [ + "do not invent a confirmed SpaceX IPO date", + "do not present private-company valuation assumptions as facts", + "do not output free HTML, CSS, SVG, JSX, or TSX", + "do not call live_create", + ], + } + + +def build_source_prompt(instruction: dict[str, Any]) -> str: + return "\n".join( + [ + "You are the SVGlide Source Planner.", + "Use web/search knowledge if available, but return JSON only.", + "Output exactly this object shape:", + '{"schema_version":"svglide-source-plan/v1","source_notes_markdown":"...","evidence":{...}}', + "The evidence object must use schema_version svglide-evidence/v1, source_status ready, and at least 3 items.", + "Every evidence item must have id and text of at least 20 characters. Include source/url/date when known.", + "For SpaceX IPO analysis, avoid claiming a confirmed IPO date unless explicitly sourced; mark uncertainties as analysis context.", + "", + "Instruction:", + compact_json(instruction), + ] + ) + + +def build_deck_prompt(instruction: dict[str, Any], context: dict[str, Any]) -> str: + base = repo_path(PROMPT_PATHS["deck-planner"]).read_text(encoding="utf-8") + bundle = { + "instruction": instruction, + "available_template_registry": context["templates"], + "available_theme_registry": context["themes"], + "output_schema": read_json(repo_path(SCHEMA_PATHS["deck-planner"])), + } + return "\n\n".join( + [ + base, + "Additional run instruction: output JSON only and satisfy the schema exactly.", + "For this topic, build a complete investment-analysis deck narrative without inventing a confirmed IPO date.", + "Keep titles and key messages concise enough for slide canvases.", + "Input bundle:", + compact_json(bundle), + ] + ) + + +def build_slide_prompt(instruction: dict[str, Any], deck_plan: dict[str, Any], context: dict[str, Any], deck_sha: str) -> str: + base = repo_path(PROMPT_PATHS["slide-planner"]).read_text(encoding="utf-8") + bundle = { + "instruction": instruction, + "deck_plan": deck_plan, + "deck_plan_ref": {"path": "02-plan/deck-plan.json", "sha256": deck_sha}, + "available_template_registry": context["templates"], + "available_theme_registry": context["themes"], + "layout_archetypes": context["layout_archetypes"], + "component_registry": context["component_registry"], + "output_schema": read_json(repo_path(SCHEMA_PATHS["slide-planner"])), + } + return "\n\n".join( + [ + base, + "Additional run instruction: output JSON only and use only active template_id/theme_id values from the registry.", + "Use varied page shapes for SpaceX IPO analysis. Include at least one image-feature page and one data-story or risk-alert page.", + "Do not output asset contracts here; only choose template/theme/content requirements.", + "Input bundle:", + compact_json(bundle), + ] + ) + + +def build_canvas_prompt(instruction: dict[str, Any], deck_plan: dict[str, Any], slide_plan: dict[str, Any], context: dict[str, Any]) -> str: + base = repo_path(PROMPT_PATHS["canvas-planner"]).read_text(encoding="utf-8") + bundle = { + "instruction": instruction, + "deck_plan": deck_plan, + "slide_plan": slide_plan, + "available_template_registry": context["templates"], + "available_theme_registry": context["themes"], + "canvas_spec_schema": context["canvas_spec_schema"], + "output_schema": read_json(repo_path(SCHEMA_PATHS["canvas-planner"])), + "content_key_guidance": { + "cover-hero": ["eyebrow", "title", "subtitle", "chips"], + "section-title": ["eyebrow", "title", "subtitle"], + "agenda-list": ["title", "items"], + "comparison-cards": ["title", "left_title", "right_title", "left_points", "right_points", "conclusion"], + "timeline-steps": ["title", "events"], + "process-flow": ["title", "steps"], + "metric-dashboard": ["title", "metrics"], + "risk-alert": ["title", "risks", "severity", "summary"], + "image-feature": ["title", "subtitle", "points", "image_label", "caption"], + "data-story": ["title", "subtitle", "metrics", "callout"], + "summary-final": ["eyebrow", "title", "subtitle", "takeaways"], + }, + } + return "\n\n".join( + [ + base, + "Additional run instruction: output JSON only.", + "The top-level object must also be the final 02-plan/slide_plan.json.", + "It must include top-level asset_contracts as an array of at least 3 objects for real visual acquisition.", + "Each asset contract must include id, page or usage_page, placement_role, query, required true, safe_text_zones, and crop_hint.", + "Use placement_role cover for page 1, body_visual for image-feature pages, and closing for the final page.", + "For body_visual assets, choose image-feature pages so the generated SVG has an asset slot.", + "Use generation_mode artboard_satori and route svglide-svg.", + "Canvas specs must use 960x540 canvas, safe_area x=48 y=40 width=864 height=460, and at least one semantic element bbox inside safe_area.", + "Keep visible text short; no title over 34 Chinese chars or 44 Latin chars.", + "Input bundle:", + compact_json(bundle), + ] + ) + + +def provider_command( + *, + provider: str, + planner_command: str | None, + stage: str, + raw_output: Path, + schema_path: Path | None, + search: bool, +) -> list[str]: + if planner_command: + values = { + "stage": stage, + "raw_output": raw_output.as_posix(), + "schema": schema_path.as_posix() if schema_path else "", + } + return [part.format(**values) for part in shlex.split(planner_command)] + if provider == "claude": + return ["claude", "-p", "--output-format", "text"] + if provider == "codex": + command = ["codex", "exec", "--ephemeral", "--sandbox", "read-only"] + if search: + command.append("--search") + if schema_path is not None: + command.extend(["--output-schema", schema_path.as_posix()]) + command.extend(["--output-last-message", raw_output.as_posix(), "-"]) + return command + raise PromptPlannerError(f"unsupported planner provider: {provider}") + + +def planner_file(stage: str, suffix: str) -> Path: + safe = stage.replace("_", "-") + return PLANNER_DIR / f"{safe}.{suffix}" + + +def call_planner( + project: Path, + *, + stage: str, + prompt: str, + output_rel: Path | None, + schema_rel: Path | None, + provider: str, + planner_command: str | None, + search: bool, + timeout: int, +) -> tuple[dict[str, Any], dict[str, Any]]: + started_at = now_iso() + input_rel = planner_file(stage, "input.txt") + raw_rel = planner_file(stage, "raw.txt") + receipt_rel = planner_file(stage, "receipt.json") + write_text(project_path(project, input_rel), prompt) + raw_output = project_path(project, raw_rel) + if raw_output.exists(): + raw_output.unlink() + command = provider_command( + provider=provider, + planner_command=planner_command, + stage=stage, + raw_output=raw_output, + schema_path=repo_path(schema_rel) if schema_rel else None, + search=search, + ) + completed = subprocess.run( + command, + cwd=REPO_ROOT, + input=prompt, + text=True, + capture_output=True, + timeout=timeout, + check=False, + ) + if not raw_output.exists(): + write_text(raw_output, completed.stdout) + raw_text = raw_output.read_text(encoding="utf-8") + issues: list[dict[str, Any]] = [] + payload: dict[str, Any] | None = None + status = "passed" + if completed.returncode != 0: + status = "failed" + issues.append({"code": "planner_command_failed", "returncode": completed.returncode, "stderr": completed.stderr}) + else: + try: + payload = extract_json_object(raw_text) + except (json.JSONDecodeError, PromptPlannerError) as error: + status = "failed" + issues.append({"code": "planner_output_json_invalid", "message": str(error)}) + if payload is not None and schema_rel is not None and output_rel is not None: + schema_issues = validate_payload(payload, schema_rel, output_path=output_rel.as_posix()) + if schema_issues: + status = "failed" + issues.extend(schema_issues) + if payload is not None and output_rel is not None and not issues: + write_json(project_path(project, output_rel), payload) + receipt = { + "schema_version": "svglide-prompt-planner-stage-receipt/v1", + "stage": stage, + "status": status, + "provider": provider, + "search_enabled": search, + "started_at": started_at, + "ended_at": now_iso(), + "command": command, + "returncode": completed.returncode, + "input_path": input_rel.as_posix(), + "input_sha256": file_sha256(project_path(project, input_rel)), + "raw_output_path": raw_rel.as_posix(), + "raw_output_sha256": file_sha256(project_path(project, raw_rel)), + "output_path": output_rel.as_posix() if output_rel else None, + "output_sha256": file_sha256(project_path(project, output_rel)) if output_rel and project_path(project, output_rel).exists() else None, + "stdout_tail": completed.stdout[-1000:], + "stderr_tail": completed.stderr[-2000:], + "issues": issues, + } + write_json(project_path(project, receipt_rel), receipt) + if status != "passed" or payload is None: + raise PromptPlannerError(f"{stage} failed; see {project_path(project, receipt_rel)}") + return payload, receipt + + +def validate_source_plan(source_plan: dict[str, Any]) -> None: + evidence = source_plan.get("evidence") + if not isinstance(evidence, dict): + raise PromptPlannerError("source-planner output must include evidence object") + issues = validate_payload(evidence, Path("skills/lark-slides/references/svglide-evidence.schema.json"), output_path=EVIDENCE_PATH.as_posix()) + if evidence.get("source_status") != "ready": + issues.append({"code": "source_status_not_ready", "message": "source-planner evidence must be ready", "path": "$.source_status"}) + items = evidence.get("items") + if not isinstance(items, list) or len(items) < 3: + issues.append({"code": "source_item_count_too_low", "message": "source-planner evidence needs at least 3 items", "path": "$.items"}) + if issues: + raise PromptPlannerError(f"source-planner evidence failed validation: {issues}") + + +def write_source_outputs(project: Path, source_plan: dict[str, Any]) -> None: + validate_source_plan(source_plan) + write_json(project_path(project, SOURCE_PLAN_PATH), source_plan) + notes = source_plan.get("source_notes_markdown") + if not isinstance(notes, str) or not notes.strip(): + notes = "# Source Notes\n\n- Planner did not provide notes; see source/evidence.json.\n" + write_text(project_path(project, SOURCE_NOTES_PATH), notes.rstrip() + "\n") + write_json(project_path(project, EVIDENCE_PATH), source_plan["evidence"]) + + +def write_repair_plan(project: Path) -> Path: + repair = { + "schema_version": "svglide-repair-plan/v1", + "target_plan_path": "02-plan/slide_plan.json", + "change_reason": "No repair requested in prompt-plan path; keep scoped repair artifact available for contract validation.", + "patches": [ + { + "op": "test", + "path": "/slides/0/page", + "value": 1, + "reason": "Verify first slide page index without rewriting the deck.", + } + ], + } + path = project / "02-plan/repair-plan.json" + write_json(path, repair) + return path + + +def require_asset_contracts(plan: dict[str, Any]) -> None: + contracts = plan.get("asset_contracts") + if not isinstance(contracts, list) or len(contracts) < 3: + raise PromptPlannerError("canvas-planner output must include at least 3 top-level asset_contracts") + for index, contract in enumerate(contracts, 1): + if not isinstance(contract, dict): + raise PromptPlannerError(f"asset_contracts[{index - 1}] must be an object") + missing = [key for key in ["id", "query", "required"] if key not in contract] + if missing: + raise PromptPlannerError(f"asset_contracts[{index - 1}] missing {missing}") + if not (contract.get("page") or contract.get("usage_page")): + raise PromptPlannerError(f"asset_contracts[{index - 1}] must include page or usage_page") + + +def write_plan_confirmation(project: Path, *, source: str) -> dict[str, Any]: + plan = project / PLANNER_OUTPUTS["canvas-planner"] + payload = { + "version": "svglide-plan-confirmation/v1", + "status": "confirmed", + "confirmed_by": "user", + "confirmed_at": now_iso(), + "plan_path": PLANNER_OUTPUTS["canvas-planner"].as_posix(), + "plan_sha256": file_sha256(plan), + "confirmation_source": source, + } + write_json(project / PLAN_CONFIRMATION_PATH, payload) + return payload + + +def ensure_fresh_outputs(project: Path, *, force: bool) -> None: + existing = [ + INSTRUCTION_PATH, + SOURCE_PLAN_PATH, + EVIDENCE_PATH, + Path("02-plan/deck-plan.json"), + Path("02-plan/slide-plan.json"), + Path("02-plan/slide_plan.json"), + PLAN_CONFIRMATION_PATH, + ] + present = [path.as_posix() for path in existing if (project / path).exists()] + if present and not force: + raise PromptPlannerError(f"prompt-plan outputs already exist; pass --force to overwrite: {present}") + + +def run_prompt_plan( + project: Path, + *, + prompt: str, + target_slide_count: int = 8, + language: str = "zh-CN", + audience: str = "投资/战略分析读者", + provider: str = "codex", + planner_command: str | None = None, + search: bool = True, + timeout: int = 300, + force: bool = False, +) -> dict[str, Any]: + project = project.resolve() + started_at = now_iso() + ensure_fresh_outputs(project, force=force) + instruction = instruction_payload(prompt=prompt, language=language, target_slide_count=target_slide_count, audience=audience) + write_json(project / INSTRUCTION_PATH, instruction) + context = load_context() + receipts: list[dict[str, Any]] = [] + + source_plan, source_receipt = call_planner( + project, + stage="source-planner", + prompt=build_source_prompt(instruction), + output_rel=None, + schema_rel=None, + provider=provider, + planner_command=planner_command, + search=search, + timeout=timeout, + ) + write_source_outputs(project, source_plan) + receipts.append(source_receipt) + + deck_plan, deck_receipt = call_planner( + project, + stage="deck-planner", + prompt=build_deck_prompt(instruction, context), + output_rel=PLANNER_OUTPUTS["deck-planner"], + schema_rel=SCHEMA_PATHS["deck-planner"], + provider=provider, + planner_command=planner_command, + search=search, + timeout=timeout, + ) + receipts.append(deck_receipt) + deck_sha = file_sha256(project / PLANNER_OUTPUTS["deck-planner"]) + + slide_plan, slide_receipt = call_planner( + project, + stage="slide-planner", + prompt=build_slide_prompt(instruction, deck_plan, context, deck_sha), + output_rel=PLANNER_OUTPUTS["slide-planner"], + schema_rel=SCHEMA_PATHS["slide-planner"], + provider=provider, + planner_command=planner_command, + search=search, + timeout=timeout, + ) + receipts.append(slide_receipt) + + canvas_plan, canvas_receipt = call_planner( + project, + stage="canvas-planner", + prompt=build_canvas_prompt(instruction, deck_plan, slide_plan, context), + output_rel=PLANNER_OUTPUTS["canvas-planner"], + schema_rel=SCHEMA_PATHS["canvas-planner"], + provider=provider, + planner_command=planner_command, + search=search, + timeout=timeout, + ) + require_asset_contracts(canvas_plan) + receipts.append(canvas_receipt) + + repair_plan = write_repair_plan(project) + confirmation = write_plan_confirmation(project, source=INSTRUCTION_PATH.as_posix()) + contract_check = svglide_planner_contracts.run(project) + if contract_check.get("status") != "passed": + raise PromptPlannerError(f"planner contract check failed; see {project / '06-check/planner-contract-check.json'}") + + result = { + "schema_version": "svglide-prompt-planner-receipt/v1", + "stage": "prompt-plan", + "status": "passed", + "provider": provider, + "search_enabled": search, + "started_at": started_at, + "ended_at": now_iso(), + "inputs": { + "instruction": INSTRUCTION_PATH.as_posix(), + "instruction_sha256": file_sha256(project / INSTRUCTION_PATH), + "prompt": prompt, + "target_slide_count": target_slide_count, + "language": language, + "audience": audience, + }, + "outputs": { + "source_plan": SOURCE_PLAN_PATH.as_posix(), + "source_notes": SOURCE_NOTES_PATH.as_posix(), + "evidence": EVIDENCE_PATH.as_posix(), + "deck_plan": PLANNER_OUTPUTS["deck-planner"].as_posix(), + "slide_plan": PLANNER_OUTPUTS["slide-planner"].as_posix(), + "canvas_plan": PLANNER_OUTPUTS["canvas-planner"].as_posix(), + "repair_plan": relpath(repair_plan, project), + "plan_confirmation": PLAN_CONFIRMATION_PATH.as_posix(), + "planner_contract_check": "06-check/planner-contract-check.json", + }, + "planner_stage_receipts": [receipt["stage"] for receipt in receipts], + "planner_stage_receipt_paths": [planner_file(receipt["stage"], "receipt.json").as_posix() for receipt in receipts], + "plan_confirmation": confirmation, + "summary": { + "slide_count": len(canvas_plan.get("slides", [])) if isinstance(canvas_plan.get("slides"), list) else None, + "asset_contract_count": len(canvas_plan.get("asset_contracts", [])) if isinstance(canvas_plan.get("asset_contracts"), list) else 0, + "planner_contract_status": contract_check.get("status"), + }, + } + write_json(project / PROMPT_RECEIPT_PATH, result) + return result + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Generate SVGlide source/deck/slide/canvas plans from a raw prompt.") + parser.add_argument("project", type=Path) + parser.add_argument("--prompt", required=True) + parser.add_argument("--target-slide-count", type=int, default=8) + parser.add_argument("--language", default="zh-CN") + parser.add_argument("--audience", default="投资/战略分析读者") + parser.add_argument("--provider", default="codex", choices=["codex", "claude", "command"]) + parser.add_argument("--planner-command", help="custom command with {stage}, {raw_output}, and {schema} placeholders") + parser.add_argument("--no-search", action="store_true") + parser.add_argument("--timeout", type=int, default=300) + parser.add_argument("--force", action="store_true") + parser.add_argument("--pretty", action="store_true") + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + try: + result = run_prompt_plan( + args.project, + prompt=args.prompt, + target_slide_count=args.target_slide_count, + language=args.language, + audience=args.audience, + provider=args.provider, + planner_command=args.planner_command, + search=not args.no_search, + timeout=args.timeout, + force=args.force, + ) + except (OSError, subprocess.TimeoutExpired, PromptPlannerError, json.JSONDecodeError) as error: + print(f"svglide_prompt_planner: error: {error}", file=sys.stderr) + return 1 + print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/lark-slides/scripts/svglide_prompt_planner_test.py b/skills/lark-slides/scripts/svglide_prompt_planner_test.py new file mode 100644 index 00000000..ccb53e43 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_prompt_planner_test.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT +from __future__ import annotations + +import json +import shutil +import sys +import tempfile +import textwrap +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_project_runner as runner +import svglide_prompt_planner as prompt_planner + + +class SVGlidePromptPlannerTest(unittest.TestCase): + def fake_provider(self, tmpdir: str) -> Path: + provider = Path(tmpdir) / "fake_provider.py" + provider.write_text( + textwrap.dedent( + r''' + #!/usr/bin/env python3 + import argparse + import json + from pathlib import Path + + def source_plan(): + return { + "schema_version": "svglide-source-plan/v1", + "source_notes_markdown": "# Source Notes\n\n- SpaceX is a private launch and satellite company.\n- IPO timing is not confirmed.\n- Analysis should separate Starlink and launch-service drivers.\n", + "evidence": { + "schema_version": "svglide-evidence/v1", + "source_status": "ready", + "generated_from": "fake_provider", + "research_status": "fixture", + "items": [ + {"id": "item-001", "text": "SpaceX remains a private aerospace company, so IPO timing must be treated as unconfirmed."}, + {"id": "item-002", "text": "Starlink scale and launch cadence are central inputs for any SpaceX IPO narrative."}, + {"id": "item-003", "text": "A useful IPO analysis separates valuation drivers, risk factors, and investor questions."}, + ], + }, + } + + def deck_plan(): + return { + "schema_version": "svglide-deck-plan/v1", + "topic": "spacex IPO 分析", + "audience": "投资/战略分析读者", + "objective": "用一页说明 SpaceX IPO 分析的核心判断框架。", + "target_slide_count": 1, + "narrative_arc": ["提出问题", "建立框架", "收束判断"], + "theme_direction": { + "preferred_theme_ids": ["finance-dark"], + "visual_identity": "深色航天资本市场信号", + "tone": "审慎、分析型、可追溯", + }, + "constraints": { + "generation_mode": "artboard_satori", + "source_policy": "不编造 IPO 日期或估值事实。", + "forbidden_outputs": ["free_html", "free_css", "free_svg", "markdown_fence"], + }, + "slides": [ + { + "page": 1, + "title": "SpaceX IPO 分析框架", + "role": "cover", + "key_message": "IPO 价值判断取决于 Starlink、发射业务与风险折价。", + "content_goal": "建立分析框架。", + "visual_goal": "使用深色金融航天封面。", + "allowed_template_ids": ["cover-hero"], + } + ], + } + + def slide_plan(): + return { + "schema_version": "svglide-slide-plan/v1", + "deck_plan_ref": {"path": "02-plan/deck-plan.json"}, + "generation_mode": "artboard_satori", + "slides": [ + { + "page": 1, + "title": "SpaceX IPO 分析框架", + "key_message": "IPO 价值判断取决于 Starlink、发射业务与风险折价。", + "template_id": "cover-hero", + "theme_id": "finance-dark", + "content_requirements": { + "eyebrow": "SPACE CAPITAL MARKET", + "subtitle": "把未确认 IPO 传闻转成可审查的投资分析框架。", + "chips": ["Starlink", "Launch", "Risk"], + }, + "visual_role": "investment thesis cover", + "source_policy": "不编造 IPO 日期或估值事实。", + } + ], + } + + def canvas_plan(): + canvas_spec = { + "version": "svglide-canvas-spec/v1", + "canvas": {"width": 960, "height": 540, "viewBox": "0 0 960 540"}, + "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}, + "template_id": "cover-hero", + "theme_id": "finance-dark", + "theme": {"colors": {"background": "#07110E", "panel": "#10201A", "primary": "#22C55E", "accent": "#F59E0B", "text": "#ECFDF5", "muted": "#A7C4B7"}}, + "content": {"eyebrow": "SPACE CAPITAL MARKET", "title": "SpaceX IPO 分析框架", "subtitle": "把未确认 IPO 传闻转成可审查的投资分析框架。", "chips": ["Starlink", "Launch", "Risk"]}, + "semantic_elements": [ + {"element_id": "title", "kind": "text", "role": "title", "source_ref": "canvas_spec.content.title", "bbox": {"x": 84, "y": 142, "width": 628, "height": 142}} + ], + "quality_constraints": {"max_title_lines": 2, "min_font_size": 18, "safe_area": {"x": 48, "y": 40, "width": 864, "height": 460}}, + } + return { + "schema_version": "svglide-canvas-plan/v1", + "route": "svglide-svg", + "generation_mode": "artboard_satori", + "page_count": 1, + "target_slide_count": 1, + "plan_path": "02-plan/slide_plan.json", + "style_preset": "finance-dark", + "style_selection_reason": "SpaceX IPO 分析适合深色资本市场信号主题。", + "style_system": { + "palette": {"background": "#07110E", "text": "#ECFDF5", "accent": "#F59E0B"}, + "typography": "Satori-compatible static hierarchy", + "background_strategy": "dark market terminal", + "motif": "orbital capital signal", + }, + "loaded_rule_set": [ + "skills/lark-slides/references/svglide-canvas-spec.schema.json", + "skills/lark-slides/references/svglide-template-registry.json", + ], + "quality_gates": {"no_text_overflow": True, "no_debug_guides": True, "no_xml_like_pages": True}, + "art_direction": { + "cover_treatment": "深色发射资产封面叠加资本市场信号。", + "section_divider_treatment": "用轨道线条做节奏分隔。", + "closing_treatment": "以投资问题清单收束。", + "deck_motif": "发射窗口与资本信号线", + "svg_native_moments": ["封面 chips", "轨道线", "风险折价卡"], + }, + "asset_contracts": [ + {"id": "spacex-launch-cover", "page": 1, "placement_role": "cover", "query": "SpaceX Falcon 9 launch public domain", "required": True, "safe_text_zones": [{"x": 0.05, "y": 0.12, "w": 0.42, "h": 0.72}], "crop_hint": "rocket launch with dark negative space"}, + {"id": "starlink-orbit", "page": 1, "placement_role": "cover", "query": "Starlink satellites orbit public domain", "required": True, "safe_text_zones": [{"x": 0.05, "y": 0.12, "w": 0.42, "h": 0.72}], "crop_hint": "space network background"}, + {"id": "rocket-stage", "page": 1, "placement_role": "cover", "query": "rocket launch pad night public domain", "required": True, "safe_text_zones": [{"x": 0.05, "y": 0.12, "w": 0.42, "h": 0.72}], "crop_hint": "launch infrastructure"}, + ], + "slides": [ + { + "page": 1, + "title": "SpaceX IPO 分析框架", + "key_message": "IPO 价值判断取决于 Starlink、发射业务与风险折价。", + "renderer_id": "artboard_satori.cover-hero", + "layout_family": "cover", + "visual_recipe": "hero_typography", + "visual_intent": "建立投资分析框架。", + "visual_focal_point": "标题和 Starlink/Launch/Risk 标签。", + "visual_signature": "dark orbital market cover", + "svg_effects": ["typography", "asset_scrim"], + "required_primitives": ["typography", "rect", "circle"], + "svg_primitives": ["typography", "rect", "circle"], + "xml_like_risk": "普通 bullets 会弱化投资框架。", + "content_density_contract": "cover title plus 3 chips", + "risk_flags": [], + "source_policy": "不编造 IPO 日期或估值事实。", + "canvas_spec": canvas_spec, + } + ], + } + + parser = argparse.ArgumentParser() + parser.add_argument("--stage", required=True) + parser.add_argument("--raw-output", required=True) + args = parser.parse_args() + mapping = { + "source-planner": source_plan, + "deck-planner": deck_plan, + "slide-planner": slide_plan, + "canvas-planner": canvas_plan, + } + Path(args.raw_output).write_text(json.dumps(mapping[args.stage](), ensure_ascii=False), encoding="utf-8") + ''' + ).strip() + + "\n", + encoding="utf-8", + ) + return provider + + def test_prompt_plan_writes_instruction_receipts_and_planner_outputs(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + plan_root = Path(tmpdir) / ".lark-slides/plan" + result = runner.init_project("spacex-auto", "SpaceX Auto", plan_root=plan_root) + project = Path(result["project_root"]) + provider = self.fake_provider(tmpdir) + + receipt = prompt_planner.run_prompt_plan( + project, + prompt="spacex IPO 分析", + target_slide_count=1, + provider="command", + planner_command=f"{sys.executable} {provider} --stage {{stage}} --raw-output {{raw_output}}", + ) + + self.assertEqual("passed", receipt["status"]) + self.assertTrue((project / "00-input/instruction.json").exists()) + self.assertEqual("spacex IPO 分析", json.loads((project / "00-input/instruction.json").read_text(encoding="utf-8"))["raw_prompt"]) + self.assertTrue((project / "02-plan/deck-plan.json").exists()) + self.assertTrue((project / "02-plan/slide-plan.json").exists()) + self.assertTrue((project / "02-plan/slide_plan.json").exists()) + self.assertTrue((project / "02-plan/planner/deck-planner.input.txt").exists()) + self.assertTrue((project / "02-plan/planner/canvas-planner.raw.txt").exists()) + self.assertTrue((project / "02-plan/plan-confirmation.json").exists()) + self.assertTrue((project / "source/evidence.json").exists()) + self.assertTrue((project / "receipts/prompt-planner.json").exists()) + self.assertEqual("passed", json.loads((project / "06-check/planner-contract-check.json").read_text(encoding="utf-8"))["status"]) + canvas = json.loads((project / "02-plan/slide_plan.json").read_text(encoding="utf-8")) + self.assertGreaterEqual(len(canvas["asset_contracts"]), 3) + + def test_prompt_plan_refuses_to_overwrite_without_force(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + plan_root = Path(tmpdir) / ".lark-slides/plan" + result = runner.init_project("spacex-auto", "SpaceX Auto", plan_root=plan_root) + project = Path(result["project_root"]) + provider = self.fake_provider(tmpdir) + command = f"{sys.executable} {provider} --stage {{stage}} --raw-output {{raw_output}}" + prompt_planner.run_prompt_plan(project, prompt="spacex IPO 分析", target_slide_count=1, provider="command", planner_command=command) + + with self.assertRaisesRegex(prompt_planner.PromptPlannerError, "already exist"): + prompt_planner.run_prompt_plan(project, prompt="spacex IPO 分析", target_slide_count=1, provider="command", planner_command=command) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_quality_gate.py b/skills/lark-slides/scripts/svglide_quality_gate.py index 14b22714..596877b2 100644 --- a/skills/lark-slides/scripts/svglide_quality_gate.py +++ b/skills/lark-slides/scripts/svglide_quality_gate.py @@ -23,6 +23,12 @@ EVIDENCE_PATH = Path("source/evidence.json") SOURCE_RECEIPT_PATH = Path("source/source-receipt.json") ASSET_MANIFEST_PATH = Path("03-assets/asset-manifest.json") GENERATOR_RECEIPT_PATH = Path("receipts/generate_svg.json") +TEMPLATE_FIT_PATH = Path("06-check/template-fit.json") +CANVAS_SPEC_VALIDATE_RECEIPT = Path("receipts/canvas-spec-validate.json") +TEMPLATE_FIT_RECEIPT = Path("receipts/template-fit-check.json") +ARTBOARD_RENDER_RECEIPT = Path("receipts/artboard-render.json") +SATORI_BRIDGE_RECEIPT = Path("receipts/satori-bridge.json") +CONTACT_SHEET = Path("05-preview/contact-sheet.png") REQUIRED_CHECKS = [ ("preflight", CHECK_DIR / "preflight.json"), ("preview-lint", CHECK_DIR / "preview-lint.json"), @@ -31,6 +37,11 @@ REQUIRED_CHECKS = [ ("semantic-review", CHECK_DIR / "semantic-review.json"), ("visual-distinctness", CHECK_DIR / "visual-distinctness.json"), ] +THEME_REQUIRED_CHECKS = [ + ("theme-validate", CHECK_DIR / "theme-validate.json"), + ("theme-adherence", CHECK_DIR / "theme-adherence.json"), +] +ARTBOARD_PACKAGE_CHECK = ("artboard-package-check", CHECK_DIR / "artboard-package-check.json") CHART_VERIFY_CHECK = ("chart-verify", CHECK_DIR / "chart-verify.json") OPTIONAL_CHECKS = [] PASS_ACTION = "create_live" @@ -91,6 +102,10 @@ def optional_file_sha256(project: Path, rel: Path) -> str | None: return file_sha256(path) if path.exists() else None +def input_check_hashes(project: Path, checks: list[tuple[str, Path]]) -> dict[str, str | None]: + return {name.replace("-", "_"): optional_file_sha256(project, rel) for name, rel in checks} + + def error_count_from_payload(payload: Any) -> int | None: if not isinstance(payload, dict): return None @@ -128,6 +143,56 @@ def read_json_optional(project: Path, rel: Path) -> dict[str, Any]: return payload if isinstance(payload, dict) else {} +def generator_generation_mode(project: Path) -> str | None: + payload = read_json_optional(project, GENERATOR_RECEIPT_PATH) + raw = payload.get("generation_mode") if isinstance(payload, dict) else None + return raw if raw in {"direct_svg", "artboard_satori"} else None + + +def require_receipt(project: Path, rel: Path, issues: list[dict[str, str]], *, code_prefix: str) -> dict[str, Any]: + path = project / rel + if not path.exists(): + issues.append(issue(f"{code_prefix}_missing", f"required receipt is missing: {rel.as_posix()}")) + return {} + payload = read_json_optional(project, rel) + if not payload: + issues.append(issue(f"{code_prefix}_invalid_json", f"required receipt is not valid JSON: {rel.as_posix()}")) + return {} + if payload.get("status") != "passed": + issues.append(issue(f"{code_prefix}_not_passed", f"receipt status must be passed: {rel.as_posix()}")) + return payload + + +def check_recorded_artifact(project: Path, payload: dict[str, Any], path_key: str, hash_key: str, issues: list[dict[str, str]], *, code_prefix: str) -> None: + rel = payload.get(path_key) + recorded = payload.get(hash_key) + if not isinstance(rel, str) or not rel: + issues.append(issue(f"{code_prefix}_{path_key}_missing", f"receipt must include {path_key}")) + return + path = project / rel + if not path.exists(): + issues.append(issue(f"{code_prefix}_{path_key}_artifact_missing", f"artifact is missing: {rel}")) + return + if recorded != file_sha256(path): + issues.append(issue(f"{code_prefix}_{path_key}_stale", f"artifact hash is stale: {rel}")) + + +def check_contact_sheet(project: Path, contact_sheet: Any, issues: list[dict[str, str]]) -> None: + if not isinstance(contact_sheet, dict): + issues.append(issue("artboard_contact_sheet_missing", "generate_svg receipt must include contact_sheet")) + return + rel = contact_sheet.get("path") + recorded = contact_sheet.get("sha256") + if rel != CONTACT_SHEET.as_posix(): + issues.append(issue("artboard_contact_sheet_path_invalid", f"contact_sheet.path must be {CONTACT_SHEET.as_posix()}")) + return + if not (project / CONTACT_SHEET).exists(): + issues.append(issue("artboard_contact_sheet_file_missing", f"contact sheet is missing: {CONTACT_SHEET.as_posix()}")) + return + if recorded != file_sha256(project / CONTACT_SHEET): + issues.append(issue("artboard_contact_sheet_stale", "contact sheet hash does not match current file")) + + def load_online_readiness(project: Path, *, profile: str) -> dict[str, Any]: source_receipt = read_json_optional(project, SOURCE_RECEIPT_PATH) asset_manifest = read_json_optional(project, ASSET_MANIFEST_PATH) @@ -135,11 +200,25 @@ def load_online_readiness(project: Path, *, profile: str) -> dict[str, Any]: asset_summary = asset_manifest.get("summary") if isinstance(asset_manifest.get("summary"), dict) else {} research_status = research.get("status") if isinstance(research, dict) and isinstance(research.get("status"), str) else "legacy" asset_status = asset_manifest.get("status") if isinstance(asset_manifest.get("status"), str) else "legacy" + acquired_count = int(asset_summary.get("acquired_count") or 0) + local_file_count = int(asset_summary.get("local_file_count") or 0) + mapped_token_count = int(asset_summary.get("mapped_token_count") or 0) + fulfilled_count = acquired_count + local_file_count + mapped_token_count issues: list[dict[str, str]] = [] if profile in STRICT_PROFILES and research_status in {"blocked_by_network", "skipped_by_user"}: issues.append(issue("research_missing_for_current_topic", f"research status is {research_status}")) if asset_status == "failed": issues.append(issue("asset_manifest_failed", "asset manifest status is failed")) + if profile in STRICT_PROFILES: + contract_count = int(asset_summary.get("contract_count") or 0) + image_job_count = int(asset_summary.get("image_job_count") or 0) + if contract_count > 0 and image_job_count > 0 and fulfilled_count == 0: + issues.append( + issue( + "visual_asset_contracts_unfulfilled", + "asset contracts produced image jobs but no acquired, local, or token-backed asset", + ) + ) status = "failed" if issues else "skipped" if not source_receipt and not asset_manifest else "passed" return { "name": "online-readiness", @@ -152,7 +231,10 @@ def load_online_readiness(project: Path, *, profile: str) -> dict[str, Any]: "issues": issues, "research_status": research_status, "asset_status": asset_status, - "asset_real_coverage": asset_summary.get("acquired_count"), + "asset_real_coverage": fulfilled_count, + "asset_acquired_count": acquired_count, + "asset_local_file_count": local_file_count, + "asset_mapped_token_count": mapped_token_count, "asset_fallback_count": asset_summary.get("fallback_count"), "image_job_count": asset_summary.get("image_job_count"), } @@ -272,6 +354,194 @@ def load_generator_receipt(project: Path, *, profile: str) -> dict[str, Any]: page_receipt = project / item if not page_receipt.exists(): check["issues"].append(issue("generator_page_receipt_missing", f"page receipt is missing: {item}")) + generation_mode = payload.get("generation_mode") or "direct_svg" + if generation_mode not in {"direct_svg", "artboard_satori"}: + check["issues"].append(issue("generator_generation_mode_invalid", "generation_mode must be direct_svg or artboard_satori")) + if generation_mode == "artboard_satori": + if payload.get("canvas_spec_validate") != "06-check/canvas-spec-validate.json": + check["issues"].append(issue("generator_canvas_spec_validate_missing", "artboard_satori generator receipt must include canvas_spec_validate")) + if payload.get("artboard_render_receipt") != ARTBOARD_RENDER_RECEIPT.as_posix(): + check["issues"].append(issue("generator_artboard_render_receipt_missing", "artboard_satori generator receipt must include artboard_render_receipt")) + if payload.get("satori_bridge_receipt") != SATORI_BRIDGE_RECEIPT.as_posix(): + check["issues"].append(issue("generator_satori_bridge_receipt_missing", "artboard_satori generator receipt must include satori_bridge_receipt")) + additional_receipts = payload.get("artboard_additional_receipts") + expected_additional_receipts = [ + CANVAS_SPEC_VALIDATE_RECEIPT.as_posix(), + ARTBOARD_RENDER_RECEIPT.as_posix(), + SATORI_BRIDGE_RECEIPT.as_posix(), + ] + if additional_receipts != expected_additional_receipts: + check["issues"].append(issue("generator_artboard_additional_receipts_invalid", "artboard_satori generator receipt must include ordered aggregate receipts")) + check_contact_sheet(project, payload.get("contact_sheet"), check["issues"]) + canvas_validate = require_receipt(project, CANVAS_SPEC_VALIDATE_RECEIPT, check["issues"], code_prefix="canvas_spec_validate") + if canvas_validate: + inputs = canvas_validate.get("inputs") if isinstance(canvas_validate.get("inputs"), dict) else {} + if inputs.get("plan_sha256") != optional_file_sha256(project, PLAN_PATH): + check["issues"].append(issue("canvas_spec_validate_plan_stale", "canvas-spec-validate plan_sha256 does not match current slide_plan.json")) + if not inputs.get("template_registry_sha256") or not inputs.get("theme_registry_sha256"): + check["issues"].append(issue("canvas_spec_validate_registry_hash_missing", "canvas-spec-validate must include template/theme registry hashes")) + artboard_render = require_receipt(project, ARTBOARD_RENDER_RECEIPT, check["issues"], code_prefix="artboard_render") + if artboard_render: + inputs = artboard_render.get("inputs") if isinstance(artboard_render.get("inputs"), dict) else {} + if inputs.get("plan_sha256") != optional_file_sha256(project, PLAN_PATH): + check["issues"].append(issue("artboard_render_plan_stale", "artboard-render plan_sha256 does not match current slide_plan.json")) + if inputs.get("canvas_spec_validate_sha256") != optional_file_sha256(project, CANVAS_SPEC_VALIDATE_RECEIPT): + check["issues"].append(issue("artboard_render_canvas_validate_stale", "artboard-render canvas_spec_validate_sha256 is stale")) + if not inputs.get("template_registry_sha256") or not inputs.get("theme_registry_sha256"): + check["issues"].append(issue("artboard_render_registry_hash_missing", "artboard-render must include template/theme registry hashes")) + check_contact_sheet(project, artboard_render.get("contact_sheet"), check["issues"]) + pages = artboard_render.get("pages") if isinstance(artboard_render.get("pages"), list) else [] + if not pages: + check["issues"].append(issue("artboard_render_pages_missing", "artboard-render receipt must include pages")) + for page in pages: + if not isinstance(page, dict): + check["issues"].append(issue("artboard_render_page_invalid", "artboard-render pages must be objects")) + continue + if not page.get("template_id") or not page.get("theme_id"): + check["issues"].append(issue("artboard_render_template_theme_missing", "artboard-render pages must include template_id and theme_id")) + if not page.get("satori_version") or not page.get("resvg_version"): + check["issues"].append(issue("artboard_render_runtime_version_missing", "artboard-render pages must include satori_version and resvg_version")) + if not isinstance(page.get("font_hashes"), list) or not page.get("font_hashes"): + check["issues"].append(issue("artboard_render_font_hash_missing", "artboard-render pages must include font_hashes")) + for path_key, hash_key in [ + ("satori_svg", "satori_svg_sha256"), + ("png", "png_sha256"), + ("render_metadata", "render_metadata_sha256"), + ("canvas_template_svg", "canvas_template_svg_sha256"), + ("node_layout_map", "node_layout_map_sha256"), + ]: + check_recorded_artifact(project, page, path_key, hash_key, check["issues"], code_prefix="artboard_render") + satori_bridge = require_receipt(project, SATORI_BRIDGE_RECEIPT, check["issues"], code_prefix="satori_bridge") + if satori_bridge: + inputs = satori_bridge.get("inputs") if isinstance(satori_bridge.get("inputs"), dict) else {} + if inputs.get("plan_sha256") != optional_file_sha256(project, PLAN_PATH): + check["issues"].append(issue("satori_bridge_plan_stale", "satori-bridge plan_sha256 does not match current slide_plan.json")) + if inputs.get("artboard_render_sha256") != optional_file_sha256(project, ARTBOARD_RENDER_RECEIPT): + check["issues"].append(issue("satori_bridge_artboard_render_stale", "satori-bridge artboard_render_sha256 is stale")) + pages = satori_bridge.get("pages") if isinstance(satori_bridge.get("pages"), list) else [] + if not pages: + check["issues"].append(issue("satori_bridge_pages_missing", "satori-bridge receipt must include pages")) + for page in pages: + if not isinstance(page, dict): + check["issues"].append(issue("satori_bridge_page_invalid", "satori-bridge pages must be objects")) + continue + if page.get("semantic_source") != "CanvasSpec": + check["issues"].append(issue("satori_bridge_semantic_source_invalid", "satori-bridge semantic_source must be CanvasSpec")) + if page.get("compiler_input_type") != "CanvasSpecTemplateSVG": + check["issues"].append(issue("satori_bridge_compiler_input_type_invalid", "satori-bridge compiler_input_type must be CanvasSpecTemplateSVG")) + if page.get("satori_svg_usage") != "preview_only": + check["issues"].append(issue("satori_bridge_satori_usage_invalid", "satori-bridge satori_svg_usage must be preview_only")) + for path_key, hash_key in [ + ("semantic_map", "semantic_map_sha256"), + ("node_layout_map", "node_layout_map_sha256"), + ("canvas_template_svg", "canvas_template_svg_sha256"), + ("compiler_input", "compiler_input_sha256"), + ("satori_svg", "satori_svg_sha256"), + ("svglide_svg", "svglide_svg_sha256"), + ]: + check_recorded_artifact(project, page, path_key, hash_key, check["issues"], code_prefix="satori_bridge") + template_fit_receipt = require_receipt(project, TEMPLATE_FIT_RECEIPT, check["issues"], code_prefix="template_fit_receipt") + if template_fit_receipt: + inputs = template_fit_receipt.get("inputs") if isinstance(template_fit_receipt.get("inputs"), dict) else {} + if inputs.get("plan_sha256") != optional_file_sha256(project, PLAN_PATH): + check["issues"].append(issue("template_fit_receipt_plan_stale", "template-fit receipt plan_sha256 does not match current slide_plan.json")) + if inputs.get("generator_receipt_sha256") != optional_file_sha256(project, GENERATOR_RECEIPT_PATH): + check["issues"].append(issue("template_fit_receipt_generator_stale", "template-fit receipt generator_receipt_sha256 is stale")) + if not inputs.get("template_registry_sha256") or not inputs.get("theme_registry_sha256"): + check["issues"].append(issue("template_fit_receipt_registry_hash_missing", "template-fit receipt must include template/theme registry hashes")) + template_fit = read_json_optional(project, TEMPLATE_FIT_PATH) + if not template_fit: + check["issues"].append(issue("template_fit_missing", "artboard_satori generation requires 06-check/template-fit.json")) + else: + if template_fit.get("status") != "passed": + check["issues"].append(issue("template_fit_not_passed", "template fit status must be passed")) + inputs = template_fit.get("inputs") if isinstance(template_fit.get("inputs"), dict) else {} + if inputs.get("plan_sha256") != optional_file_sha256(project, PLAN_PATH): + check["issues"].append(issue("template_fit_plan_stale", "template fit plan_sha256 does not match current slide_plan.json")) + if inputs.get("generator_receipt_sha256") != optional_file_sha256(project, GENERATOR_RECEIPT_PATH): + check["issues"].append(issue("template_fit_generator_stale", "template fit generator_receipt_sha256 does not match current generate_svg receipt")) + artboard_receipts = payload.get("artboard_receipts") + if not isinstance(artboard_receipts, list) or not artboard_receipts: + check["issues"].append(issue("generator_artboard_receipts_missing", "artboard_satori generation must include artboard_receipts")) + elif isinstance(generated, list) and len(artboard_receipts) != len(generated): + check["issues"].append(issue("generator_artboard_receipt_count_mismatch", "artboard_receipts count must match generated_files")) + if isinstance(artboard_receipts, list): + artboard_schema = svglide_schema.read_json(svglide_schema.schema_path("svglide-artboard-receipt.schema.json")) + semantic_map_schema = svglide_schema.read_json(svglide_schema.schema_path("svglide-semantic-map.schema.json")) + node_layout_schema = svglide_schema.read_json(svglide_schema.schema_path("svglide-node-layout-map.schema.json")) + by_svg = {item.get("path"): item.get("sha256") for item in generated if isinstance(item, dict)} if isinstance(generated, list) else {} + for item in artboard_receipts: + if not isinstance(item, str): + check["issues"].append(issue("generator_artboard_receipt_invalid", "artboard_receipts must be string paths")) + continue + artboard_receipt_path = project / item + if not artboard_receipt_path.exists(): + check["issues"].append(issue("generator_artboard_receipt_missing", f"artboard receipt is missing: {item}")) + continue + try: + artboard_receipt = json.loads(artboard_receipt_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + check["issues"].append(issue("generator_artboard_receipt_invalid_json", f"could not read artboard receipt JSON: {error}")) + continue + schema_issues = svglide_schema.validate_json_schema(artboard_receipt, artboard_schema) + if schema_issues: + check["issues"].extend(issue("generator_artboard_receipt_schema_invalid", f"{item} {schema_issue['path']}: {schema_issue['message']}") for schema_issue in schema_issues) + continue + if not isinstance(artboard_receipt, dict) or artboard_receipt.get("status") != "passed": + check["issues"].append(issue("generator_artboard_receipt_not_passed", f"artboard receipt status must be passed: {item}")) + continue + svglide_svg = artboard_receipt.get("svglide_svg") + svglide_svg_sha256 = artboard_receipt.get("svglide_svg_sha256") + if not isinstance(svglide_svg, str) or by_svg.get(svglide_svg) != svglide_svg_sha256: + check["issues"].append(issue("generator_artboard_output_stale", f"artboard receipt output does not match generated_files: {item}")) + for path_key, hash_key in [ + ("satori_svg", "satori_svg_sha256"), + ("png", "png_sha256"), + ("render_metadata", "render_metadata_sha256"), + ("canvas_template_svg", "canvas_template_svg_sha256"), + ("compiler_input", "compiler_input_sha256"), + ("semantic_map", "semantic_map_sha256"), + ("node_layout_map", "node_layout_map_sha256"), + ("svglide_svg", "svglide_svg_sha256"), + ]: + rel = artboard_receipt.get(path_key) + recorded = artboard_receipt.get(hash_key) + if not isinstance(rel, str) or not (project / rel).exists(): + check["issues"].append(issue("generator_artboard_artifact_missing", f"artboard artifact is missing: {path_key} in {item}")) + continue + if recorded != file_sha256(project / rel): + check["issues"].append(issue("generator_artboard_artifact_stale", f"artboard artifact hash is stale: {path_key} in {item}")) + if not artboard_receipt.get("template_id") or not artboard_receipt.get("theme_id"): + check["issues"].append(issue("generator_artboard_template_theme_missing", f"artboard receipt must include template_id and theme_id: {item}")) + if not artboard_receipt.get("template_registry_sha256") or not artboard_receipt.get("theme_registry_sha256"): + check["issues"].append(issue("generator_artboard_registry_hash_missing", f"artboard receipt must include template/theme registry hashes: {item}")) + if not artboard_receipt.get("satori_version") or not artboard_receipt.get("resvg_version"): + check["issues"].append(issue("generator_artboard_runtime_version_missing", f"artboard receipt must include satori_version and resvg_version: {item}")) + if not isinstance(artboard_receipt.get("font_hashes"), list) or not artboard_receipt.get("font_hashes"): + check["issues"].append(issue("generator_artboard_font_hash_missing", f"artboard receipt must include font_hashes: {item}")) + compiler = artboard_receipt.get("compiler") if isinstance(artboard_receipt.get("compiler"), dict) else {} + if compiler.get("semantic_source") != "CanvasSpec": + check["issues"].append(issue("generator_artboard_compiler_semantic_source_invalid", f"artboard compiler semantic_source must be CanvasSpec: {item}")) + if compiler.get("compiler_input") != "CanvasSpecTemplateSVG": + check["issues"].append(issue("generator_artboard_compiler_input_invalid", f"artboard compiler_input must be CanvasSpecTemplateSVG: {item}")) + if compiler.get("satori_svg_usage") != "preview_only": + check["issues"].append(issue("generator_artboard_compiler_satori_usage_invalid", f"artboard compiler satori_svg_usage must be preview_only: {item}")) + if artboard_receipt.get("compiler_input") != artboard_receipt.get("canvas_template_svg"): + check["issues"].append(issue("generator_artboard_compiler_input_path_invalid", f"artboard compiler_input must point to canvas_template_svg: {item}")) + for path_key, artifact_schema, code in [ + ("semantic_map", semantic_map_schema, "generator_artboard_semantic_map_schema_invalid"), + ("node_layout_map", node_layout_schema, "generator_artboard_node_layout_schema_invalid"), + ]: + rel = artboard_receipt.get(path_key) + if not isinstance(rel, str) or not (project / rel).exists(): + continue + try: + artifact = json.loads((project / rel).read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + check["issues"].append(issue(code, f"could not read {path_key} JSON in {item}: {error}")) + continue + schema_issues = svglide_schema.validate_json_schema(artifact, artifact_schema) + check["issues"].extend(issue(code, f"{rel} {schema_issue['path']}: {schema_issue['message']}") for schema_issue in schema_issues) check["error_count"] = len(check["issues"]) check["status"] = "failed" if check["issues"] else "passed" return check @@ -357,7 +627,28 @@ def load_check(project: Path, name: str, rel: Path, *, required: bool, profile: check["issues"].extend(freshness) return check - if name in {"chart-verify", "runtime-review", "semantic-review"} and action not in {PASS_ACTION, "passed"}: + if name == "theme-validate" and isinstance(payload, dict): + freshness = plan_bound_check_freshness_issues(project, payload, "theme_validate", prepared=False) + if freshness: + check["issues"].extend(freshness) + return check + + if name == "theme-adherence" and isinstance(payload, dict): + freshness = plan_bound_check_freshness_issues(project, payload, "theme_adherence", prepared=True) + if freshness: + check["issues"].extend(freshness) + return check + inputs = payload.get("inputs") if isinstance(payload.get("inputs"), dict) else {} + if inputs.get("theme_validate_sha256") != optional_file_sha256(project, CHECK_DIR / "theme-validate.json"): + check["issues"].append(issue("theme_adherence_theme_validate_stale", "theme adherence theme_validate_sha256 does not match current theme-validate receipt")) + return check + + if name == "artboard-package-check" and isinstance(payload, dict): + if payload.get("stage") not in {"package_check", "artboard_package_check"}: + check["issues"].append(issue("artboard_package_check_stage_invalid", "artboard package check stage must be package_check")) + return check + + if name in {"chart-verify", "runtime-review", "semantic-review", "theme-validate", "theme-adherence", "artboard-package-check"} and action not in {PASS_ACTION, "passed"}: check["issues"].append(issue(f"{name.replace('-', '_')}_action_not_create_live", f"{name} action is {action!r}; expected {PASS_ACTION!r}")) return check @@ -380,6 +671,12 @@ def run_quality_gate(project: Path, *, profile: str = PRODUCTION_PROFILE) -> dic checks = [load_generator_receipt(project, profile=profile)] checks.append(load_online_readiness(project, profile=profile)) checks.extend(load_check(project, name, rel, required=True, profile=profile) for name, rel in REQUIRED_CHECKS) + checks.extend(load_check(project, name, rel, required=True, profile=profile) for name, rel in THEME_REQUIRED_CHECKS) + generation_mode = generator_generation_mode(project) + conditional_checks: list[tuple[str, Path]] = [] + if generation_mode == "artboard_satori": + conditional_checks.append(ARTBOARD_PACKAGE_CHECK) + checks.append(load_check(project, *ARTBOARD_PACKAGE_CHECK, required=True, profile=profile)) chart_required = plan_requires_chart_verify(project) if chart_required is None: checks.append( @@ -402,6 +699,8 @@ def run_quality_gate(project: Path, *, profile: str = PRODUCTION_PROFILE) -> dic source_error_count = sum(check["error_count"] or 0 for check in checks) status = "failed" if failed_checks else "passed_with_waiver" if waiver_checks else "passed" output_path = project / CHECK_DIR / QUALITY_GATE_NAME + input_checks = REQUIRED_CHECKS + THEME_REQUIRED_CHECKS + conditional_checks + ([CHART_VERIFY_CHECK] if chart_required else []) + OPTIONAL_CHECKS + required_input_names = {item[0] for item in REQUIRED_CHECKS + THEME_REQUIRED_CHECKS + conditional_checks} result = { "version": "svglide-quality-gate/v1", "project": str(project), @@ -409,9 +708,10 @@ def run_quality_gate(project: Path, *, profile: str = PRODUCTION_PROFILE) -> dic "status": status, "inputs": { name.replace("-", "_"): rel.as_posix() - for name, rel in REQUIRED_CHECKS + ([CHART_VERIFY_CHECK] if chart_required else []) + OPTIONAL_CHECKS - if (project / rel).exists() or name in {item[0] for item in REQUIRED_CHECKS} + for name, rel in input_checks + if (project / rel).exists() or name in required_input_names }, + "input_hashes": input_check_hashes(project, input_checks + [("generator-receipt", GENERATOR_RECEIPT_PATH)]), "prepared_files": prepared_file_hashes(project), "waivers": [ {"check": check["name"], "waivers": check["waivers"]} @@ -426,6 +726,9 @@ def run_quality_gate(project: Path, *, profile: str = PRODUCTION_PROFILE) -> dic "research_status": next((check.get("research_status") for check in checks if check.get("name") == "online-readiness"), None), "asset_status": next((check.get("asset_status") for check in checks if check.get("name") == "online-readiness"), None), "asset_real_coverage": next((check.get("asset_real_coverage") for check in checks if check.get("name") == "online-readiness"), None), + "asset_acquired_count": next((check.get("asset_acquired_count") for check in checks if check.get("name") == "online-readiness"), None), + "asset_local_file_count": next((check.get("asset_local_file_count") for check in checks if check.get("name") == "online-readiness"), None), + "asset_mapped_token_count": next((check.get("asset_mapped_token_count") for check in checks if check.get("name") == "online-readiness"), None), "asset_fallback_count": next((check.get("asset_fallback_count") for check in checks if check.get("name") == "online-readiness"), None), "image_job_count": next((check.get("image_job_count") for check in checks if check.get("name") == "online-readiness"), None), }, @@ -433,6 +736,7 @@ def run_quality_gate(project: Path, *, profile: str = PRODUCTION_PROFILE) -> dic "output_path": relpath(output_path, project), } result["inputs"]["generator_receipt"] = GENERATOR_RECEIPT_PATH.as_posix() + result["inputs"]["generation_mode"] = generation_mode or "unknown" schema = svglide_schema.read_json(svglide_schema.schema_path("svglide-quality-gate.schema.json")) schema_issues = svglide_schema.validate_json_schema(result, schema) if schema_issues: diff --git a/skills/lark-slides/scripts/svglide_quality_gate_test.py b/skills/lark-slides/scripts/svglide_quality_gate_test.py index ee566ae4..d326db4c 100644 --- a/skills/lark-slides/scripts/svglide_quality_gate_test.py +++ b/skills/lark-slides/scripts/svglide_quality_gate_test.py @@ -27,7 +27,7 @@ def write_passing_semantic_review(project: Path) -> None: (project / "04-svg").mkdir(parents=True, exist_ok=True) (project / "04-svg/prepared").mkdir(parents=True, exist_ok=True) if not (project / "02-plan/slide_plan.json").exists(): - write_json(project / "02-plan/slide_plan.json", {"language": "zh-CN", "slides": []}) + write_json(project / "02-plan/slide_plan.json", {"language": "zh-CN", "theme_id": "dark-clarity", "slides": [{"page": 1, "title": "测试"}]}) if not (project / "source/evidence.json").exists(): write_json(project / "source/evidence.json", {"schema_version": "svglide-evidence/v1", "source_status": "ready", "items": [{"id": "item-001", "text": "这是一条足够长的中文证据内容,用于质量门禁测试。"}]}) if not (project / "source/source-receipt.json").exists(): @@ -77,6 +77,7 @@ def write_passing_semantic_review(project: Path) -> None: "stage": "generate_svg", "status": "passed", "generator_mode": "external", + "generation_mode": "direct_svg", "generated_files": source_files, "page_receipts": ["04-svg/page-001.receipt.json"], "plan_sha256": svglide_quality_gate.file_sha256(project / "02-plan/slide_plan.json"), @@ -123,13 +124,51 @@ def write_passing_semantic_review(project: Path) -> None: "schema_version": "svglide-visual-distinctness/v1", "status": "passed", "action": "create_live", - "inputs": {"slide_plan": "02-plan/slide_plan.json"}, + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": svglide_quality_gate.file_sha256(project / "02-plan/slide_plan.json"), + }, "signature": {"theme_archetype": "company_ecosystem"}, "comparisons": [], "summary": {"error_count": 0, "warning_count": 0, "comparison_count": 0}, "issues": [], }, ) + write_json( + project / "06-check/theme-validate.json", + { + "schema_version": "svglide-theme-validate/v1", + "stage": "theme_validate", + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": svglide_quality_gate.file_sha256(project / "02-plan/slide_plan.json"), + }, + "pages": [{"page": 1, "theme_id": "dark-clarity", "status": "passed", "issues": []}], + "summary": {"error_count": 0, "warning_count": 0, "page_count": 1, "theme_count": 1}, + "issues": [], + }, + ) + write_json( + project / "06-check/theme-adherence.json", + { + "schema_version": "svglide-theme-adherence/v1", + "stage": "theme_adherence", + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": svglide_quality_gate.file_sha256(project / "02-plan/slide_plan.json"), + "theme_validate": "06-check/theme-validate.json", + "theme_validate_sha256": svglide_quality_gate.file_sha256(project / "06-check/theme-validate.json"), + }, + "prepared_files": svglide_quality_gate.prepared_file_hashes(project), + "pages": [{"page": 1, "status": "passed", "issues": []}], + "summary": {"error_count": 0, "warning_count": 0, "page_count": 1}, + "issues": [], + }, + ) write_json( project / "06-check/chart-verify.json", { @@ -168,6 +207,243 @@ def write_passing_semantic_review(project: Path) -> None: ) +def attach_passing_artboard_receipt(project: Path) -> None: + artboard_dir = project / "04-svg/artboard" + artboard_dir.mkdir(parents=True, exist_ok=True) + (project / "04-svg/artboard/raw").mkdir(parents=True, exist_ok=True) + (project / "05-preview").mkdir(parents=True, exist_ok=True) + satori_svg = project / "04-svg/artboard/raw/page-001.satori.svg" + satori_svg.write_text('', encoding="utf-8") + canvas_template_svg = project / "04-svg/artboard/page-001.canvas-template.svg" + canvas_template_svg.write_text( + 'Title', + encoding="utf-8", + ) + (project / "04-svg/artboard/page-001.png").write_bytes(b"png") + write_json( + project / "04-svg/artboard/page-001.render-metadata.json", + {"node_version": "v20.0.0", "satori_version": "0.26.0", "resvg_version": "2.6.2", "font_path": "/tmp/font.ttf"}, + ) + write_json( + project / "04-svg/artboard/page-001.semantic-map.json", + { + "version": "svglide-semantic-map/v1", + "page": 1, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "semantic_source": "CanvasSpec", + "content_keys": ["title"], + "elements": [ + { + "element_id": "title", + "kind": "text", + "role": "title", + "source_ref": "canvas_spec.content.title", + "text": "Title", + "bbox": {"x": 80, "y": 80, "width": 720, "height": 72}, + } + ], + }, + ) + write_json( + project / "04-svg/artboard/page-001.node-layout-map.json", + { + "version": "svglide-node-layout-map/v1", + "page": 1, + "source": "template-layout-map", + "drift": {"status": "not_measured_in_p0", "max_px": 0}, + "nodes": [{"id": "title", "kind": "text", "x": 80, "y": 80, "width": 720, "height": 72}], + }, + ) + (project / "05-preview/contact-sheet.png").write_bytes(b"contact") + source_hash = svglide_quality_gate.file_sha256(project / "04-svg/page-001.svg") + template_registry_sha256 = "template-registry-hash" + theme_registry_sha256 = "theme-registry-hash" + font_hashes = [{"path": "/tmp/font.ttf", "sha256": "font-hash"}] + write_json( + project / "04-svg/artboard/page-001.receipt.json", + { + "version": "svglide-artboard-receipt/v1", + "stage": "generate_svg", + "status": "passed", + "page": 1, + "canvas_spec_path": "02-plan/slide_plan.json#/slides/0/canvas_spec", + "canvas_spec_sha256": "test-canvas-spec", + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "template_registry": "skills/lark-slides/references/svglide-template-registry.json", + "template_registry_sha256": template_registry_sha256, + "theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json", + "theme_registry_sha256": theme_registry_sha256, + "theme_files": ["skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json"], + "node_version": "v20.0.0", + "satori_version": "0.26.0", + "resvg_version": "2.6.2", + "font_hashes": font_hashes, + "renderer": {"name": "satori-resvg-p0", "engine": "satori-node", "actual_satori_package": True}, + "satori_svg": "04-svg/artboard/raw/page-001.satori.svg", + "satori_svg_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/raw/page-001.satori.svg"), + "png": "04-svg/artboard/page-001.png", + "png_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.png"), + "render_metadata": "04-svg/artboard/page-001.render-metadata.json", + "render_metadata_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.render-metadata.json"), + "canvas_template_svg": "04-svg/artboard/page-001.canvas-template.svg", + "canvas_template_svg_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.canvas-template.svg"), + "compiler_input": "04-svg/artboard/page-001.canvas-template.svg", + "compiler_input_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.canvas-template.svg"), + "semantic_map": "04-svg/artboard/page-001.semantic-map.json", + "semantic_map_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.semantic-map.json"), + "node_layout_map": "04-svg/artboard/page-001.node-layout-map.json", + "node_layout_map_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.node-layout-map.json"), + "svglide_svg": "04-svg/page-001.svg", + "svglide_svg_sha256": source_hash, + "compiler": {"semantic_source": "CanvasSpec", "compiler_input": "CanvasSpecTemplateSVG", "satori_svg_usage": "preview_only"}, + }, + ) + receipt = json.loads((project / "receipts/generate_svg.json").read_text(encoding="utf-8")) + receipt["generation_mode"] = "artboard_satori" + receipt["artboard_receipts"] = ["04-svg/artboard/page-001.receipt.json"] + receipt["artboard_additional_receipts"] = [ + "receipts/canvas-spec-validate.json", + "receipts/artboard-render.json", + "receipts/satori-bridge.json", + ] + receipt["canvas_spec_validate"] = "06-check/canvas-spec-validate.json" + receipt["artboard_render_receipt"] = "receipts/artboard-render.json" + receipt["satori_bridge_receipt"] = "receipts/satori-bridge.json" + receipt["template_fit_check"] = "06-check/template-fit.json" + receipt["contact_sheet"] = { + "path": "05-preview/contact-sheet.png", + "sha256": svglide_quality_gate.file_sha256(project / "05-preview/contact-sheet.png"), + } + write_json(project / "receipts/generate_svg.json", receipt) + write_json( + project / "06-check/artboard-package-check.json", + { + "version": "svglide-artboard-package-check/v1", + "stage": "package_check", + "status": "passed", + "action": "create_live", + "summary": {"error_count": 0, "warning_count": 0, "runtime_check_count": 0}, + "runtime_checks": [], + "issues": [], + }, + ) + write_json( + project / "06-check/canvas-spec-validate.json", + { + "schema_version": "svglide-canvas-spec-validate/v1", + "stage": "canvas-spec-validate", + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": svglide_quality_gate.file_sha256(project / "02-plan/slide_plan.json"), + "template_registry_sha256": template_registry_sha256, + "theme_registry_sha256": theme_registry_sha256, + }, + "pages": [], + "summary": {"error_count": 0, "warning_count": 0, "page_count": 1}, + "issues": [], + }, + ) + write_json(project / "receipts/canvas-spec-validate.json", json.loads((project / "06-check/canvas-spec-validate.json").read_text(encoding="utf-8"))) + write_json( + project / "receipts/artboard-render.json", + { + "version": "svglide-artboard-render/v1", + "stage": "artboard-render", + "status": "passed", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": svglide_quality_gate.file_sha256(project / "02-plan/slide_plan.json"), + "template_registry_sha256": template_registry_sha256, + "theme_registry_sha256": theme_registry_sha256, + "canvas_spec_validate": "receipts/canvas-spec-validate.json", + "canvas_spec_validate_sha256": svglide_quality_gate.file_sha256(project / "receipts/canvas-spec-validate.json"), + }, + "pages": [ + { + "page": 1, + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "satori_version": "0.26.0", + "resvg_version": "2.6.2", + "font_hashes": font_hashes, + "satori_svg": "04-svg/artboard/raw/page-001.satori.svg", + "satori_svg_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/raw/page-001.satori.svg"), + "png": "04-svg/artboard/page-001.png", + "png_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.png"), + "render_metadata": "04-svg/artboard/page-001.render-metadata.json", + "render_metadata_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.render-metadata.json"), + "canvas_template_svg": "04-svg/artboard/page-001.canvas-template.svg", + "canvas_template_svg_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.canvas-template.svg"), + "node_layout_map": "04-svg/artboard/page-001.node-layout-map.json", + "node_layout_map_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.node-layout-map.json"), + } + ], + "contact_sheet": receipt["contact_sheet"], + "summary": {"error_count": 0, "warning_count": 0, "page_count": 1}, + }, + ) + write_json( + project / "receipts/satori-bridge.json", + { + "version": "svglide-satori-bridge/v1", + "stage": "satori-bridge", + "status": "passed", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": svglide_quality_gate.file_sha256(project / "02-plan/slide_plan.json"), + "artboard_render": "receipts/artboard-render.json", + "artboard_render_sha256": svglide_quality_gate.file_sha256(project / "receipts/artboard-render.json"), + }, + "pages": [ + { + "page": 1, + "semantic_source": "CanvasSpec", + "semantic_map": "04-svg/artboard/page-001.semantic-map.json", + "semantic_map_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.semantic-map.json"), + "node_layout_map": "04-svg/artboard/page-001.node-layout-map.json", + "node_layout_map_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.node-layout-map.json"), + "canvas_template_svg": "04-svg/artboard/page-001.canvas-template.svg", + "canvas_template_svg_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.canvas-template.svg"), + "compiler_input": "04-svg/artboard/page-001.canvas-template.svg", + "compiler_input_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/page-001.canvas-template.svg"), + "compiler_input_type": "CanvasSpecTemplateSVG", + "satori_svg_usage": "preview_only", + "satori_svg": "04-svg/artboard/raw/page-001.satori.svg", + "satori_svg_sha256": svglide_quality_gate.file_sha256(project / "04-svg/artboard/raw/page-001.satori.svg"), + "svglide_svg": "04-svg/page-001.svg", + "svglide_svg_sha256": source_hash, + } + ], + "summary": {"error_count": 0, "warning_count": 0, "page_count": 1}, + }, + ) + write_json( + project / "06-check/template-fit.json", + { + "schema_version": "svglide-template-fit/v1", + "status": "passed", + "action": "create_live", + "inputs": { + "slide_plan": "02-plan/slide_plan.json", + "plan_sha256": svglide_quality_gate.file_sha256(project / "02-plan/slide_plan.json"), + "generator_receipt": "receipts/generate_svg.json", + "generator_receipt_sha256": svglide_quality_gate.file_sha256(project / "receipts/generate_svg.json"), + "artboard_receipts": ["04-svg/artboard/page-001.receipt.json"], + "template_registry_sha256": template_registry_sha256, + "theme_registry_sha256": theme_registry_sha256, + }, + "pages": [], + "summary": {"error_count": 0, "warning_count": 0, "page_count": 1}, + "issues": [], + }, + ) + write_json(project / "receipts/template-fit-check.json", json.loads((project / "06-check/template-fit.json").read_text(encoding="utf-8"))) + + class SVGlideQualityGateTest(unittest.TestCase): def test_quality_gate_passes_when_required_checks_have_zero_errors(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: @@ -192,6 +468,109 @@ class SVGlideQualityGateTest(unittest.TestCase): self.assertEqual(result["summary"]["failed_check_count"], 0) self.assertTrue((project / "06-check/quality-gate.json").exists()) + def test_quality_gate_requires_theme_checks(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0, "warning_count": 1}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0, "warning_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0, "warning_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + (project / "06-check/theme-adherence.json").unlink() + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "failed") + missing = [check for check in result["checks"] if check["name"] == "theme-adherence"][0] + self.assertEqual(missing["status"], "missing") + self.assertIn("theme_adherence", result["inputs"]) + + def test_quality_gate_fails_when_theme_adherence_theme_validate_is_stale(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0, "warning_count": 1}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0, "warning_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0, "warning_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + theme_validate = json.loads((project / "06-check/theme-validate.json").read_text(encoding="utf-8")) + theme_validate["checked_at"] = "2026-06-21T00:00:00+08:00" + write_json(project / "06-check/theme-validate.json", theme_validate) + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "failed") + failed_codes = { + issue["code"] + for check in result["checks"] + for issue in check["issues"] + } + self.assertIn("theme_adherence_theme_validate_stale", failed_codes) + + def test_quality_gate_direct_svg_ignores_artboard_package_check(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0, "warning_count": 1}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0, "warning_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0, "warning_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + write_json( + project / "06-check/artboard-package-check.json", + { + "version": "svglide-artboard-package-check/v1", + "stage": "package_check", + "status": "failed", + "action": "repair_and_rerun", + "summary": {"error_count": 1, "warning_count": 0, "runtime_check_count": 0}, + "issues": [{"code": "should_be_ignored", "message": "direct_svg does not require artboard package"}], + }, + ) + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "passed") + self.assertNotIn("artboard_package_check", result["inputs"]) + self.assertNotIn("artboard-package-check", {check["name"] for check in result["checks"]}) + + def test_quality_gate_artboard_satori_requires_package_check(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0, "warning_count": 1}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0, "warning_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0, "warning_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + attach_passing_artboard_receipt(project) + (project / "06-check/artboard-package-check.json").unlink() + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "failed") + missing = [check for check in result["checks"] if check["name"] == "artboard-package-check"][0] + self.assertEqual(missing["status"], "missing") + self.assertEqual(result["inputs"]["generation_mode"], "artboard_satori") + self.assertIn("artboard_package_check", result["inputs"]) + + def test_online_readiness_counts_local_file_assets_as_real_coverage(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json( + project / "03-assets/asset-manifest.json", + { + "status": "passed", + "summary": { + "acquired_count": 1, + "local_file_count": 3, + "mapped_token_count": 2, + "fallback_count": 0, + }, + }, + ) + + result = svglide_quality_gate.load_online_readiness(project, profile="production") + + self.assertEqual(result["asset_real_coverage"], 6) + self.assertEqual(result["asset_acquired_count"], 1) + self.assertEqual(result["asset_local_file_count"], 3) + self.assertEqual(result["asset_mapped_token_count"], 2) + def test_quality_gate_fails_when_required_check_is_missing(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: project = Path(tmpdir) @@ -433,6 +812,116 @@ class SVGlideQualityGateTest(unittest.TestCase): } self.assertIn("generator_source_stale", failed_codes) + def test_quality_gate_requires_artboard_receipts_for_artboard_generation(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + receipt = json.loads((project / "receipts/generate_svg.json").read_text(encoding="utf-8")) + receipt["generation_mode"] = "artboard_satori" + write_json(project / "receipts/generate_svg.json", receipt) + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "failed") + failed_codes = { + issue["code"] + for check in result["checks"] + for issue in check["issues"] + } + self.assertIn("generator_artboard_receipts_missing", failed_codes) + + def test_quality_gate_fails_when_artboard_receipt_artifact_is_stale(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + attach_passing_artboard_receipt(project) + (project / "04-svg/artboard/raw/page-001.satori.svg").write_text("", encoding="utf-8") + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "failed") + failed_codes = { + issue["code"] + for check in result["checks"] + for issue in check["issues"] + } + self.assertIn("generator_artboard_artifact_stale", failed_codes) + + def test_quality_gate_rejects_raw_satori_as_artboard_compiler_input(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + attach_passing_artboard_receipt(project) + receipt_path = project / "04-svg/artboard/page-001.receipt.json" + receipt = json.loads(receipt_path.read_text(encoding="utf-8")) + receipt["compiler"]["compiler_input"] = "RawSatoriSVG" + receipt["compiler"]["satori_svg_usage"] = "compiler_input" + write_json(receipt_path, receipt) + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "failed") + failed_codes = { + issue["code"] + for check in result["checks"] + for issue in check["issues"] + } + self.assertIn("generator_artboard_compiler_input_invalid", failed_codes) + self.assertIn("generator_artboard_compiler_satori_usage_invalid", failed_codes) + + def test_quality_gate_fails_when_artboard_compiler_input_is_stale(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + attach_passing_artboard_receipt(project) + (project / "04-svg/artboard/page-001.canvas-template.svg").write_text("", encoding="utf-8") + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "failed") + failed_codes = { + issue["code"] + for check in result["checks"] + for issue in check["issues"] + } + self.assertIn("generator_artboard_artifact_stale", failed_codes) + self.assertIn("satori_bridge_compiler_input_stale", failed_codes) + + def test_quality_gate_validates_artboard_receipt_schema(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_passing_semantic_review(project) + attach_passing_artboard_receipt(project) + receipt_path = project / "04-svg/artboard/page-001.receipt.json" + receipt = json.loads(receipt_path.read_text(encoding="utf-8")) + receipt.pop("version") + write_json(receipt_path, receipt) + + result = svglide_quality_gate.run_quality_gate(project) + + self.assertEqual(result["status"], "failed") + failed_codes = { + issue["code"] + for check in result["checks"] + for issue in check["issues"] + } + self.assertIn("generator_artboard_receipt_schema_invalid", failed_codes) + def test_quality_gate_requires_chart_verify_when_plan_requires_it(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: project = Path(tmpdir) @@ -496,6 +985,68 @@ class SVGlideQualityGateTest(unittest.TestCase): } self.assertIn("research_missing_for_current_topic", failed_codes) + def test_quality_gate_blocks_strict_profile_when_image_jobs_have_no_assets(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json( + project / "03-assets/asset-manifest.json", + { + "version": "svglide-assets/v1", + "status": "passed", + "summary": { + "contract_count": 1, + "error_count": 0, + "mapped_token_count": 0, + "local_file_count": 0, + "acquired_count": 0, + "fallback_count": 0, + "image_job_count": 1, + }, + }, + ) + write_passing_semantic_review(project) + + result = svglide_quality_gate.run_quality_gate(project, profile="production") + + self.assertEqual(result["status"], "failed") + failed_codes = { + issue["code"] + for check in result["checks"] + for issue in check["issues"] + } + self.assertIn("visual_asset_contracts_unfulfilled", failed_codes) + + def test_quality_gate_allows_unfulfilled_image_jobs_in_preview_only_profile(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "06-check/preflight.json", {"summary": {"error_count": 0}}) + write_json(project / "06-check/preview-lint.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json(project / "06-check/aesthetic-review.json", {"summary": {"error_count": 0}, "action": "create_live"}) + write_json( + project / "03-assets/asset-manifest.json", + { + "version": "svglide-assets/v1", + "status": "passed", + "summary": { + "contract_count": 1, + "error_count": 0, + "mapped_token_count": 0, + "local_file_count": 0, + "acquired_count": 0, + "fallback_count": 0, + "image_job_count": 1, + }, + }, + ) + write_passing_semantic_review(project) + + result = svglide_quality_gate.run_quality_gate(project, profile="preview_only") + + self.assertEqual(result["status"], "passed") + def test_quality_gate_blocks_strict_profile_when_fallback_skeleton_used(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: project = Path(tmpdir) diff --git a/skills/lark-slides/scripts/svglide_readback.py b/skills/lark-slides/scripts/svglide_readback.py index 3203ffc9..0a379275 100644 --- a/skills/lark-slides/scripts/svglide_readback.py +++ b/skills/lark-slides/scripts/svglide_readback.py @@ -7,10 +7,14 @@ from __future__ import annotations import argparse import hashlib import json +import os +import re +import shlex import subprocess import sys from pathlib import Path from typing import Any, Callable +from urllib.parse import quote CREATE_DIR = Path("07-create") @@ -18,6 +22,7 @@ READBACK_DIR = Path("08-readback") LIVE_CREATE_NAME = "live-create.json" RAW_READBACK_NAME = "xml-presentations-get.json" READBACK_CHECK_NAME = "readback-check.json" +LARK_CLI_COMMAND_ENV = "SVGLIDE_LARK_CLI_CMD" class ReadbackError(Exception): @@ -89,6 +94,49 @@ def extract_slide_ids(live_create: Any) -> list[str]: return [] +def lark_cli_command_prefix() -> list[str]: + raw = os.environ.get(LARK_CLI_COMMAND_ENV, "").strip() + if not raw: + return ["lark-cli"] + parsed = shlex.split(raw) + return parsed if parsed else ["lark-cli"] + + +def extract_request_headers(live_create: Any) -> dict[str, str]: + raw = find_first_key(live_create, {"request_headers"}) + if not isinstance(raw, dict): + return {} + headers: dict[str, str] = {} + for key, value in raw.items(): + if not isinstance(key, str) or not isinstance(value, str): + raise ReadbackError("live-create request_headers must be a string key/value object") + normalized_key = key.strip().lower() + normalized_value = value.strip() + if normalized_key != "x-tt-env" or normalized_value != "ppe_pure_svg": + raise ReadbackError("readback currently supports only x-tt-env=ppe_pure_svg request header") + headers[normalized_key] = normalized_value + return headers + + +def build_readback_command(live_create: Any, xml_presentation_id: str) -> tuple[list[str], dict[str, str]]: + request_headers = extract_request_headers(live_create) + prefix = lark_cli_command_prefix() + if request_headers: + command = prefix + [ + "api", + "GET", + f"/open-apis/slides_ai/v1/xml_presentations/{quote(xml_presentation_id, safe='')}", + "--as", + "user", + ] + for key in sorted(request_headers): + command.extend(["--request-header", f"{key}={request_headers[key]}"]) + return command, request_headers + + params = json.dumps({"xml_presentation_id": xml_presentation_id}, separators=(",", ":")) + return prefix + ["slides", "xml_presentations", "get", "--as", "user", "--params", params], {} + + def build_input_binding(project: Path, live_create: Any) -> dict[str, Any]: revision = find_first_key(live_create, {"revision_id", "revision"}) slide_ids = extract_slide_ids(live_create) @@ -98,7 +146,7 @@ def build_input_binding(project: Path, live_create: Any) -> dict[str, Any]: "dry_run_sha256": optional_sha256(project / CREATE_DIR / "dry-run.json"), "ppe_proof_sha256": optional_sha256(project / CREATE_DIR / "ppe-proof.json"), "live_create_sha256": optional_sha256(project / CREATE_DIR / LIVE_CREATE_NAME), - "revision_id": revision if isinstance(revision, str) else None, + "revision_id": revision if isinstance(revision, (str, int)) else None, "expected_slide_count": expected_page_count(project), "created_slide_count": len(slide_ids), } @@ -138,12 +186,43 @@ def find_slide_list(value: Any) -> list[Any] | None: return None +def extract_presentation_content(readback: Any) -> str | None: + value = find_first_key(readback, {"content"}) + if isinstance(value, str) and " list[str]: + return re.findall(r"]*\bid=\"([^\"]+)\"", content) + + +def slide_ids_from_readback(readback: Any) -> list[str]: + content = extract_presentation_content(readback) + if content: + return slide_ids_from_content(content) + slides = find_slide_list(readback) + ids: list[str] = [] + if slides is not None: + for slide in slides: + if isinstance(slide, dict): + raw = slide.get("id") or slide.get("slide_id") + if isinstance(raw, str) and raw.strip(): + ids.append(raw) + return ids + + def actual_page_count(readback: Any) -> int | None: raw = find_first_key(readback, {"page_count", "slide_count"}) if isinstance(raw, int) and raw >= 0: return raw slides = find_slide_list(readback) - return len(slides) if slides is not None else None + if slides is not None: + return len(slides) + content = extract_presentation_content(readback) + if content: + return len(slide_ids_from_content(content)) + return None def expected_asset_tokens(project: Path) -> list[str]: @@ -200,20 +279,61 @@ def expected_business_claim_fragments(project: Path) -> list[str]: return [fragment.strip() for fragment in fragments if fragment.strip()] +def expected_core_visible_text_fragments(project: Path) -> list[str]: + plan_path = project / "02-plan" / "slide_plan.json" + if not plan_path.exists(): + return [] + plan = read_json(plan_path) + if not isinstance(plan, dict): + return [] + fragments: list[str] = [] + slides = plan.get("slides") + if not isinstance(slides, list): + return [] + for slide in slides: + if not isinstance(slide, dict): + continue + spec = slide.get("canvas_spec") + content = spec.get("content") if isinstance(spec, dict) else None + if isinstance(content, dict): + fragments.extend(item.strip() for item in iter_strings(content) if item.strip()) + else: + value = slide.get("title") + if isinstance(value, str) and value.strip(): + fragments.append(value.strip()) + seen: set[str] = set() + unique: list[str] = [] + for fragment in fragments: + if fragment not in seen: + seen.add(fragment) + unique.append(fragment) + return unique + + def expected_chart_marker_count(project: Path) -> int: count = 0 - svg_dirs = [project / "04-svg" / "prepared", project / "04-svg"] - seen: set[Path] = set() - for svg_dir in svg_dirs: - if not svg_dir.exists(): - continue - for path in sorted(svg_dir.glob("*.svg")): - if path in seen: - continue - seen.add(path) - text = path.read_text(encoding="utf-8", errors="ignore") - if 'slide:role="chart"' in text or "data-svglide-chart" in text: - count += 1 + for path in source_svgs_for_readback(project): + text = path.read_text(encoding="utf-8", errors="ignore") + if 'slide:role="chart"' in text or "data-svglide-chart" in text: + count += 1 + return count + + +def source_svgs_for_readback(project: Path) -> list[Path]: + prepared = project / "04-svg" / "prepared" + if prepared.exists(): + files = sorted(path for path in prepared.glob("*.svg") if path.is_file()) + if files: + return files + source = project / "04-svg" + return sorted(path for path in source.glob("*.svg") if path.is_file()) if source.exists() else [] + + +def expected_image_asset_count(project: Path) -> int: + count = 0 + for path in source_svgs_for_readback(project): + text = path.read_text(encoding="utf-8", errors="ignore") + count += len(re.findall(r" None: + project = self.make_project() + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1}]}) + write_json( + project / "07-create/live-create.json", + { + "json": { + "data": { + "xml_presentation_id": "xml_1", + "revision_id": 2, + "slide_ids": ["s1"], + "request_headers": {"x-tt-env": "ppe_pure_svg"}, + } + } + }, + ) + commands: list[list[str]] = [] + + def fake_runner(command: list[str], **_: object) -> subprocess.CompletedProcess[str]: + commands.append(command) + return self.completed({"data": {"xml_presentation": {"content": ''}}}) + + result = svglide_readback.run_readback(project, command_runner=fake_runner) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["input_binding"]["revision_id"], 2) + self.assertEqual(commands[0][:4], ["lark-cli", "api", "GET", "/open-apis/slides_ai/v1/xml_presentations/xml_1"]) + self.assertIn("--request-header", commands[0]) + self.assertIn("x-tt-env=ppe_pure_svg", commands[0]) + raw = json.loads((project / "08-readback/xml-presentations-get.json").read_text(encoding="utf-8")) + self.assertEqual(raw["request_headers"], {"x-tt-env": "ppe_pure_svg"}) + + def test_readback_rejects_unsupported_request_header(self) -> None: + project = self.make_project() + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1}]}) + write_json( + project / "07-create/live-create.json", + { + "xml_presentation_id": "xml_1", + "slide_ids": ["s1"], + "request_headers": {"authorization": "secret"}, + }, + ) + + result = svglide_readback.run_readback(project, command_runner=lambda *args, **kwargs: self.completed({})) + + self.assertEqual(result["status"], "failed") + self.assertEqual(result["checks"]["readback_command"]["status"], "failed") + self.assertIn("only x-tt-env", result["checks"]["readback_command"]["reason"]) + + def test_readback_counts_xml_content_and_checks_order_and_core_text(self) -> None: + project = self.make_project() + write_json( + project / "02-plan/slide_plan.json", + { + "slides": [ + {"page": 1, "title": "第一页标题", "canvas_spec": {"content": {"title": "第一页标题"}}}, + {"page": 2, "title": "第二页标题", "canvas_spec": {"content": {"title": "第二页标题", "subtitle": "第二页副标题"}}}, + ] + }, + ) + write_json(project / "07-create/live-create.json", {"xml_presentation_id": "xml_1", "slide_ids": ["s1", "s2"]}) + + result = svglide_readback.run_readback( + project, + command_runner=lambda *args, **kwargs: self.completed( + { + "data": { + "xml_presentation": { + "content": '第一页标题第二页标题 第二页副标题' + } + } + } + ), + ) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["checks"]["page_count"]["status"], "passed") + self.assertEqual(result["checks"]["slide_order"]["status"], "passed") + self.assertEqual(result["checks"]["core_visible_text"]["status"], "passed") + + def test_readback_fails_on_xml_slide_order_mismatch(self) -> None: + project = self.make_project() + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1}, {"page": 2}]}) + write_json(project / "07-create/live-create.json", {"xml_presentation_id": "xml_1", "slide_ids": ["s1", "s2"]}) + + result = svglide_readback.run_readback( + project, + command_runner=lambda *args, **kwargs: self.completed( + {"data": {"xml_presentation": {"content": ''}}} + ), + ) + + self.assertEqual(result["status"], "failed") + self.assertEqual(result["checks"]["slide_order"]["status"], "failed") + def test_readback_fails_without_presentation_id(self) -> None: project = self.make_project() write_json(project / "07-create/live-create.json", {"slide_ids": ["s1"]}) @@ -91,6 +187,44 @@ class SVGlideReadbackTest(unittest.TestCase): self.assertEqual(result["status"], "passed") self.assertEqual(result["checks"]["asset_tokens"]["status"], "passed") + def test_readback_checks_expected_image_assets(self) -> None: + project = self.make_project() + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1}]}) + write_json(project / "07-create/live-create.json", {"xml_presentation_id": "xml_1", "slide_ids": ["s1"]}) + (project / "04-svg/prepared").mkdir(parents=True, exist_ok=True) + (project / "04-svg/prepared/page-001.svg").write_text( + '', + encoding="utf-8", + ) + + result = svglide_readback.run_readback( + project, + command_runner=lambda *args, **kwargs: self.completed( + {"data": {"xml_presentation": {"content": ''}}} + ), + ) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["checks"]["image_assets"]["status"], "passed") + + def test_readback_fails_when_expected_image_asset_is_missing(self) -> None: + project = self.make_project() + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1}]}) + write_json(project / "07-create/live-create.json", {"xml_presentation_id": "xml_1", "slide_ids": ["s1"]}) + (project / "04-svg/prepared").mkdir(parents=True, exist_ok=True) + (project / "04-svg/prepared/page-001.svg").write_text( + '', + encoding="utf-8", + ) + + result = svglide_readback.run_readback( + project, + command_runner=lambda *args, **kwargs: self.completed({"data": {"slides": [{"id": "s1", "text": "no media"}]}}), + ) + + self.assertEqual(result["status"], "failed") + self.assertEqual(result["checks"]["image_assets"]["status"], "failed") + def test_readback_fails_when_business_claim_is_missing(self) -> None: project = self.make_project() write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1}], "business_claims": [{"fragment": "Revenue 130.5B"}]}) diff --git a/skills/lark-slides/scripts/svglide_schema.py b/skills/lark-slides/scripts/svglide_schema.py index e24aa46f..22a8d68c 100644 --- a/skills/lark-slides/scripts/svglide_schema.py +++ b/skills/lark-slides/scripts/svglide_schema.py @@ -42,6 +42,17 @@ def validate_json_schema(payload: Any, schema: dict[str, Any], *, path: str = "$ if isinstance(any_of, list) and any_of: if not any(isinstance(option, dict) and not validate_json_schema(payload, option, path=path) for option in any_of): issues.append({"code": "schema_any_of_mismatch", "path": path, "message": "value does not match any allowed schema"}) + all_of = schema.get("allOf") + if isinstance(all_of, list): + for option in all_of: + if isinstance(option, dict): + issues.extend(validate_json_schema(payload, option, path=path)) + if_schema = schema.get("if") + if isinstance(if_schema, dict): + matched = not validate_json_schema(payload, if_schema, path=path) + branch = schema.get("then") if matched else schema.get("else") + if isinstance(branch, dict): + issues.extend(validate_json_schema(payload, branch, path=path)) if isinstance(payload, dict): for required in schema.get("required", []): diff --git a/skills/lark-slides/scripts/svglide_template_fit_check.py b/skills/lark-slides/scripts/svglide_template_fit_check.py new file mode 100644 index 00000000..d4387d4e --- /dev/null +++ b/skills/lark-slides/scripts/svglide_template_fit_check.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import hashlib +import json +import sys +from pathlib import Path +from typing import Any + + +PLAN_PATH = Path("02-plan/slide_plan.json") +GENERATOR_RECEIPT = Path("receipts/generate_svg.json") +OUTPUT_PATH = Path("06-check/template-fit.json") +RECEIPT_PATH = Path("receipts/template-fit-check.json") + + +def file_sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def read_json(path: Path) -> dict[str, Any]: + payload = json.loads(path.read_text(encoding="utf-8")) + return payload if isinstance(payload, dict) else {} + + +def issue(code: str, message: str, *, page: int | None = None) -> dict[str, Any]: + payload: dict[str, Any] = {"code": code, "message": message} + if page is not None: + payload["page"] = page + return payload + + +def text_height_ok(node: dict[str, Any]) -> bool: + height = node.get("height") + kind = node.get("kind") + if kind != "text": + return True + return isinstance(height, (int, float)) and height >= 30 + + +def node_in_canvas(node: dict[str, Any]) -> bool: + x = node.get("x") + y = node.get("y") + width = node.get("width") + height = node.get("height") + if not all(isinstance(value, (int, float)) for value in [x, y, width, height]): + return False + return x >= 0 and y >= 0 and width > 0 and height > 0 and x + width <= 960 and y + height <= 540 + + +def run_template_fit(project: Path) -> dict[str, Any]: + project = project.resolve() + plan = read_json(project / PLAN_PATH) + generator = read_json(project / GENERATOR_RECEIPT) + artboard_receipts = generator.get("artboard_receipts") + issues: list[dict[str, Any]] = [] + pages: list[dict[str, Any]] = [] + registry_hashes: dict[str, Any] = {} + if not isinstance(artboard_receipts, list) or not artboard_receipts: + issues.append(issue("artboard_receipts_missing", "generator receipt must include artboard_receipts")) + artboard_receipts = [] + for item in artboard_receipts: + if not isinstance(item, str) or not (project / item).exists(): + issues.append(issue("artboard_receipt_missing", f"missing artboard receipt: {item}")) + continue + receipt = read_json(project / item) + if not registry_hashes: + registry_hashes = { + "template_registry": receipt.get("template_registry"), + "template_registry_sha256": receipt.get("template_registry_sha256"), + "theme_registry": receipt.get("theme_registry"), + "theme_registry_sha256": receipt.get("theme_registry_sha256"), + "theme_files": receipt.get("theme_files"), + } + page = receipt.get("page") if isinstance(receipt.get("page"), int) else None + node_layout = receipt.get("node_layout_map") + if not isinstance(node_layout, str) or not (project / node_layout).exists(): + issues.append(issue("node_layout_map_missing", f"missing node layout map in {item}", page=page)) + continue + layout = read_json(project / node_layout) + nodes = layout.get("nodes") if isinstance(layout.get("nodes"), list) else [] + page_issues: list[dict[str, Any]] = [] + for node in nodes: + if not isinstance(node, dict): + page_issues.append(issue("node_invalid", "node layout entry must be an object", page=page)) + continue + if not node_in_canvas(node): + page_issues.append(issue("node_out_of_canvas", f"node is outside canvas: {node.get('id')}", page=page)) + if not text_height_ok(node): + page_issues.append(issue("text_box_too_short", f"text node height is too short: {node.get('id')}", page=page)) + issues.extend(page_issues) + pages.append( + { + "page": page, + "artboard_receipt": item, + "node_layout_map": node_layout, + "node_layout_map_sha256": file_sha256(project / node_layout), + "node_count": len(nodes), + "error_count": len(page_issues), + } + ) + status = "failed" if issues else "passed" + result = { + "schema_version": "svglide-template-fit/v1", + "stage": "template-fit-check", + "status": status, + "action": "create_live" if status == "passed" else "repair_and_rerun", + "inputs": { + "slide_plan": PLAN_PATH.as_posix(), + "plan_sha256": file_sha256(project / PLAN_PATH), + "generator_receipt": GENERATOR_RECEIPT.as_posix(), + "generator_receipt_sha256": file_sha256(project / GENERATOR_RECEIPT), + "artboard_receipts": artboard_receipts, + **registry_hashes, + }, + "pages": pages, + "summary": { + "error_count": len(issues), + "warning_count": 0, + "page_count": len(pages), + }, + "issues": issues, + "output_path": OUTPUT_PATH.as_posix(), + "receipt_path": RECEIPT_PATH.as_posix(), + } + output = project / OUTPUT_PATH + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + receipt = project / RECEIPT_PATH + receipt.parent.mkdir(parents=True, exist_ok=True) + receipt.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + return result + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Check CanvasSpec template layout fit.") + parser.add_argument("project", type=Path) + parser.add_argument("--pretty", action="store_true") + args = parser.parse_args(argv) + try: + result = run_template_fit(args.project) + except (OSError, json.JSONDecodeError) as error: + print(f"svglide_template_fit_check: {error}", file=sys.stderr) + return 2 + print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None)) + return 0 if result["status"] == "passed" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/lark-slides/scripts/svglide_template_fit_check_test.py b/skills/lark-slides/scripts/svglide_template_fit_check_test.py new file mode 100644 index 00000000..e7471fb9 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_template_fit_check_test.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_template_fit_check as template_fit + + +def write_json(path: Path, payload: dict[str, object]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload), encoding="utf-8") + + +def write_project(project: Path, nodes: list[dict[str, object]]) -> None: + write_json(project / "02-plan/slide_plan.json", {"generation_mode": "artboard_satori", "slides": [{"page": 1}]}) + write_json( + project / "04-svg/artboard/page-001.node-layout-map.json", + {"version": "svglide-node-layout-map/v1", "page": 1, "nodes": nodes}, + ) + write_json( + project / "04-svg/artboard/page-001.receipt.json", + { + "version": "svglide-artboard-receipt/v1", + "status": "passed", + "page": 1, + "node_layout_map": "04-svg/artboard/page-001.node-layout-map.json", + }, + ) + write_json( + project / "receipts/generate_svg.json", + { + "stage": "generate_svg", + "status": "passed", + "generation_mode": "artboard_satori", + "artboard_receipts": ["04-svg/artboard/page-001.receipt.json"], + }, + ) + + +class SVGlideTemplateFitCheckTest(unittest.TestCase): + def test_template_fit_passes_for_nodes_inside_canvas(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_project( + project, + [ + {"id": "title", "kind": "text", "x": 80, "y": 80, "width": 720, "height": 72}, + {"id": "panel", "kind": "shape", "x": 64, "y": 220, "width": 320, "height": 180}, + ], + ) + + result = template_fit.run_template_fit(project) + + self.assertEqual(result["status"], "passed") + self.assertEqual(result["summary"]["page_count"], 1) + self.assertEqual(result["pages"][0]["node_count"], 2) + self.assertEqual(result["inputs"]["generator_receipt"], "receipts/generate_svg.json") + self.assertEqual( + result["pages"][0]["node_layout_map_sha256"], + template_fit.file_sha256(project / "04-svg/artboard/page-001.node-layout-map.json"), + ) + self.assertTrue((project / "06-check/template-fit.json").exists()) + self.assertTrue((project / "receipts/template-fit-check.json").exists()) + + def test_template_fit_fails_for_out_of_canvas_and_short_text(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_project( + project, + [ + {"id": "title", "kind": "text", "x": 40, "y": 40, "width": 320, "height": 24}, + {"id": "footer", "kind": "shape", "x": 920, "y": 500, "width": 80, "height": 60}, + ], + ) + + result = template_fit.run_template_fit(project) + + self.assertEqual(result["status"], "failed") + codes = {item["code"] for item in result["issues"]} + self.assertIn("text_box_too_short", codes) + self.assertIn("node_out_of_canvas", codes) + self.assertEqual(result["action"], "repair_and_rerun") + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_theme.py b/skills/lark-slides/scripts/svglide_theme.py new file mode 100644 index 00000000..00d990da --- /dev/null +++ b/skills/lark-slides/scripts/svglide_theme.py @@ -0,0 +1,496 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import hashlib +import json +import re +from pathlib import Path +from typing import Any +from xml.etree import ElementTree + + +THEME_SPEC_VERSION = "svglide-theme/v1" +SCRIPT_DIR = Path(__file__).resolve().parent +REPO_ROOT = SCRIPT_DIR.parents[2] +GLOBAL_THEME_REGISTRY = SCRIPT_DIR / "artboard_renderer" / "themes" / "registry.json" +PROJECT_THEME_REGISTRY = Path("02-plan/theme-registry.json") +PREPARED_SVG_DIR = Path("04-svg/prepared") +CORE_COLOR_ROLES = ( + "background", + "surface", + "text", + "muted", + "primary", + "accent", + "success", + "warning", + "danger", +) +CORE_TOKENS = tuple(f"color.{role}" for role in CORE_COLOR_ROLES) +DEFAULT_STATUS_COLORS = { + "success": "#22C55E", + "warning": "#F59E0B", + "danger": "#EF4444", +} +HEX_COLOR_RE = re.compile(r"^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$") +HEX_COLOR_SCAN_RE = re.compile(r"#(?:[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})(?![0-9A-Fa-f])") +SVG_COLOR_ATTRS = {"fill", "stroke", "color"} + + +class ThemeError(ValueError): + pass + + +def file_sha256(path: Path | str) -> str: + h = hashlib.sha256() + with Path(path).open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def stable_json_sha256(payload: Any) -> str: + data = json.dumps(_canonicalize(payload), ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(data).hexdigest() + + +def theme_sha256(theme: dict[str, Any]) -> str: + return stable_json_sha256(theme) + + +def normalize_hex_color(value: str) -> str: + if not isinstance(value, str): + raise ThemeError(f"hex color must be a string, got {type(value).__name__}") + raw = value.strip() + if not HEX_COLOR_RE.fullmatch(raw): + raise ThemeError(f"invalid hex color: {value!r}") + digits = raw[1:] + if len(digits) == 3: + digits = "".join(ch * 2 for ch in digits) + return f"#{digits.upper()}" + + +def relative_luminance(color: str) -> float: + normalized = normalize_hex_color(color) + channels = [int(normalized[index : index + 2], 16) / 255 for index in (1, 3, 5)] + linear = [channel / 12.92 if channel <= 0.03928 else ((channel + 0.055) / 1.055) ** 2.4 for channel in channels] + return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2] + + +def contrast_ratio(foreground: str, background: str) -> float: + first = relative_luminance(foreground) + second = relative_luminance(background) + lighter = max(first, second) + darker = min(first, second) + return (lighter + 0.05) / (darker + 0.05) + + +def load_registry(root: Path | str | None = None) -> dict[str, Any]: + path = theme_registry_path(root) + payload = _read_json_object(path) + if not isinstance(payload.get("themes"), list): + raise ThemeError(f"theme registry must contain a themes array: {path}") + return payload + + +def theme_registry_path(root: Path | str | None = None) -> Path: + return _theme_registry_path(root) + + +def theme_file_path(theme_id: str, root: Path | str | None = None) -> Path | None: + registry_path = theme_registry_path(root) + registry = _read_json_object(registry_path) + record = _theme_record(registry, theme_id) + if record is None: + raise ThemeError(f"theme {theme_id!r} is not present in registry {registry_path}") + raw_path = record.get("path") + if not isinstance(raw_path, str) or not raw_path: + return None + return _resolve_theme_file_path(registry_path, raw_path) + + +def load_theme(theme_id: str, root: Path | str | None = None) -> dict[str, Any]: + if not isinstance(theme_id, str) or not theme_id: + raise ThemeError("theme_id is required") + registry_path = theme_registry_path(root) + registry = _read_json_object(registry_path) + record = _theme_record(registry, theme_id) + if record is None: + raise ThemeError(f"theme {theme_id!r} is not present in registry {registry_path}") + if record.get("status") not in (None, "active"): + raise ThemeError(f"theme {theme_id!r} is not active") + theme = _resolve_theme_payload(registry_path, record) + adapted = _adapt_theme_spec(theme) + issues = validate_theme_spec(adapted) + if issues: + details = "; ".join(f"{item['path']}: {item['message']}" for item in issues[:3]) + raise ThemeError(f"invalid theme {theme_id!r}: {details}") + return adapted + + +def validate_theme_spec(theme: Any) -> list[dict[str, str]]: + issues: list[dict[str, str]] = [] + if not isinstance(theme, dict): + return [_issue("theme_type_invalid", "$", "ThemeSpec must be an object")] + + required = ["schema_version", "theme_id", "mode", "colors", "semantic_colors", "tokens", "contrast", "allowed_color_roles"] + for key in required: + if key not in theme: + issues.append(_issue("theme_required_missing", f"$.{key}", "required property is missing")) + + if theme.get("schema_version") != THEME_SPEC_VERSION: + issues.append(_issue("theme_version_invalid", "$.schema_version", f"schema_version must be {THEME_SPEC_VERSION}")) + if not isinstance(theme.get("theme_id"), str) or not theme.get("theme_id"): + issues.append(_issue("theme_id_invalid", "$.theme_id", "theme_id must be a non-empty string")) + if theme.get("mode") not in {"light", "dark"}: + issues.append(_issue("theme_mode_invalid", "$.mode", "mode must be light or dark")) + + colors = theme.get("colors") + if not isinstance(colors, dict): + issues.append(_issue("theme_colors_invalid", "$.colors", "colors must be an object")) + else: + for role in CORE_COLOR_ROLES: + if role not in colors: + issues.append(_issue("theme_color_missing", f"$.colors.{role}", "core color is missing")) + continue + if _normalize_optional_color(colors.get(role)) is None: + issues.append(_issue("theme_color_hex_invalid", f"$.colors.{role}", "core color must be #rgb or #rrggbb")) + for role, value in colors.items(): + if not isinstance(role, str): + issues.append(_issue("theme_color_role_invalid", "$.colors", "color role names must be strings")) + continue + if _normalize_optional_color(value) is None: + issues.append(_issue("theme_color_hex_invalid", f"$.colors.{role}", "color value must be #rgb or #rrggbb")) + + tokens = theme.get("tokens") + if not isinstance(tokens, dict): + issues.append(_issue("theme_tokens_invalid", "$.tokens", "tokens must be an object")) + else: + for token in CORE_TOKENS: + if token not in tokens: + issues.append(_issue("theme_token_missing", f"$.tokens.{token}", "core color token is missing")) + continue + if _resolve_color_value(tokens.get(token), theme) is None: + issues.append(_issue("theme_token_color_invalid", f"$.tokens.{token}", "token must resolve to #rgb or #rrggbb")) + for token, value in tokens.items(): + if not isinstance(token, str) or not token: + issues.append(_issue("theme_token_name_invalid", "$.tokens", "token names must be non-empty strings")) + continue + if _resolve_color_value(value, theme) is None: + issues.append(_issue("theme_token_color_invalid", f"$.tokens.{token}", "token must resolve to #rgb or #rrggbb")) + + semantic_colors = theme.get("semantic_colors") + if not isinstance(semantic_colors, dict) or not semantic_colors: + issues.append(_issue("theme_semantic_colors_invalid", "$.semantic_colors", "semantic_colors must be a non-empty object")) + else: + for role, value in semantic_colors.items(): + if not isinstance(role, str) or not role: + issues.append(_issue("theme_semantic_role_invalid", "$.semantic_colors", "semantic color roles must be non-empty strings")) + continue + if _resolve_color_value(value, theme) is None: + issues.append(_issue("theme_semantic_color_invalid", f"$.semantic_colors.{role}", "semantic color must resolve to #rgb or #rrggbb")) + + contrast = theme.get("contrast") + if not isinstance(contrast, dict): + issues.append(_issue("theme_contrast_invalid", "$.contrast", "contrast must be an object")) + else: + min_text = contrast.get("min_text_contrast") + if isinstance(min_text, bool) or not isinstance(min_text, (int, float)): + issues.append(_issue("theme_contrast_min_text_invalid", "$.contrast.min_text_contrast", "min_text_contrast must be a number")) + + allowed = theme.get("allowed_color_roles") + if not isinstance(allowed, list) or not allowed: + issues.append(_issue("theme_allowed_roles_invalid", "$.allowed_color_roles", "allowed_color_roles must be a non-empty array")) + else: + for index, role in enumerate(allowed): + if not isinstance(role, str) or not role: + issues.append(_issue("theme_allowed_role_invalid", f"$.allowed_color_roles[{index}]", "allowed color roles must be non-empty strings")) + missing_roles = [role for role in CORE_COLOR_ROLES if role not in allowed] + for role in missing_roles: + issues.append(_issue("theme_allowed_role_missing", f"$.allowed_color_roles.{role}", "core color role must be allowed")) + + data_series = theme.get("data_series") + if data_series is not None: + if not isinstance(data_series, list): + issues.append(_issue("theme_data_series_invalid", "$.data_series", "data_series must be an array")) + else: + for index, value in enumerate(data_series): + if _normalize_optional_color(value) is None: + issues.append(_issue("theme_data_series_color_invalid", f"$.data_series[{index}]", "data series color must be #rgb or #rrggbb")) + + return issues + + +def extract_svg_colors(svg_path: Path | str) -> list[str]: + try: + root = ElementTree.fromstring(Path(svg_path).read_text(encoding="utf-8")) + except (OSError, ElementTree.ParseError) as err: + raise ThemeError(f"unable to read SVG colors from {svg_path}: {err}") from err + + colors: list[str] = [] + seen: set[str] = set() + + def add_from_text(value: str) -> None: + for match in HEX_COLOR_SCAN_RE.finditer(value): + color = normalize_hex_color(match.group(0)) + if color not in seen: + seen.add(color) + colors.append(color) + + for element in root.iter(): + for raw_name, value in element.attrib.items(): + name = _local_name(raw_name).lower() + if name in SVG_COLOR_ATTRS or name == "style": + add_from_text(value) + return colors + + +def classify_color(color: str, theme: dict[str, Any]) -> dict[str, Any]: + normalized = normalize_hex_color(color) + spec = _adapt_theme_spec(theme) + + token_matches = _matching_resolved_values(spec.get("tokens"), spec, normalized) + if token_matches: + return {"kind": "theme_token", "color": normalized, "role": token_matches[0], "matches": token_matches} + + semantic_matches = _matching_resolved_values(spec.get("semantic_colors"), spec, normalized) + if semantic_matches: + return {"kind": "semantic", "color": normalized, "role": semantic_matches[0], "matches": semantic_matches} + + data_series = spec.get("data_series") + if isinstance(data_series, list): + matches = [f"data_series[{index}]" for index, value in enumerate(data_series) if _normalize_optional_color(value) == normalized] + if matches: + return {"kind": "data_series", "color": normalized, "role": matches[0], "matches": matches} + + return {"kind": "unknown", "color": normalized, "role": None, "matches": []} + + +def prepared_svg_hashes(project_root: Path | str) -> list[dict[str, str]]: + project = Path(project_root) + svg_dir = project / PREPARED_SVG_DIR + if not svg_dir.exists(): + return [] + return [ + { + "path": path.relative_to(project).as_posix(), + "sha256": file_sha256(path), + } + for path in sorted(svg_dir.glob("*.svg")) + if path.is_file() + ] + + +def _theme_registry_path(root: Path | str | None) -> Path: + if root is None: + return GLOBAL_THEME_REGISTRY + base = Path(root) + if base.is_file(): + return base + candidates = [ + base / "registry.json", + base / PROJECT_THEME_REGISTRY, + base / "skills/lark-slides/scripts/artboard_renderer/themes/registry.json", + base / "artboard_renderer/themes/registry.json", + GLOBAL_THEME_REGISTRY, + ] + for candidate in candidates: + if candidate.exists(): + return candidate + raise ThemeError(f"theme registry not found under {base}") + + +def _read_json_object(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as err: + raise ThemeError(f"missing JSON file: {path}") from err + except json.JSONDecodeError as err: + raise ThemeError(f"invalid JSON in {path}: {err}") from err + if not isinstance(payload, dict): + raise ThemeError(f"expected JSON object: {path}") + return payload + + +def _theme_record(registry: dict[str, Any], theme_id: str) -> dict[str, Any] | None: + themes = registry.get("themes") + if isinstance(themes, list): + for item in themes: + if isinstance(item, dict) and item.get("id") == theme_id: + return item + if isinstance(themes, dict): + item = themes.get(theme_id) + if isinstance(item, dict): + return item + return None + + +def _resolve_theme_payload(registry_path: Path, record: dict[str, Any]) -> dict[str, Any]: + raw_path = record.get("path") + if isinstance(raw_path, str) and raw_path: + return _read_json_object(_resolve_theme_file_path(registry_path, raw_path)) + if isinstance(record.get("colors"), dict): + return record + raise ThemeError(f"theme record {record.get('id')!r} has no theme payload") + + +def _resolve_theme_file_path(registry_path: Path, raw_path: str) -> Path: + path_value = Path(raw_path) + candidates = [path_value] if path_value.is_absolute() else [_registry_relative_base(registry_path) / path_value, registry_path.parent / path_value] + for candidate in candidates: + if candidate.exists(): + return candidate + raise ThemeError(f"theme file not found: {raw_path}") + + +def _registry_relative_base(registry_path: Path) -> Path: + parts = registry_path.parts + marker = ("skills", "lark-slides", "scripts", "artboard_renderer", "themes", "registry.json") + for index in range(0, len(parts) - len(marker) + 1): + if tuple(parts[index : index + len(marker)]) == marker: + return Path(*parts[:index]) + if registry_path.parent.name == "02-plan": + return registry_path.parent.parent + return registry_path.parent + + +def _adapt_theme_spec(theme: dict[str, Any]) -> dict[str, Any]: + if not isinstance(theme, dict): + raise ThemeError("theme must be an object") + raw_colors = theme.get("colors") if isinstance(theme.get("colors"), dict) else {} + colors = _normalized_colors(raw_colors) + adapted: dict[str, Any] = dict(theme) + adapted["schema_version"] = theme.get("schema_version") or THEME_SPEC_VERSION + adapted["theme_id"] = theme.get("theme_id") or theme.get("id") or "theme" + adapted["mode"] = theme.get("mode") if theme.get("mode") in {"light", "dark"} else _infer_mode(colors["background"]) + adapted["colors"] = colors + if "semantic_colors" not in adapted or not isinstance(adapted.get("semantic_colors"), dict): + adapted["semantic_colors"] = _default_semantic_colors(colors) + if "tokens" not in adapted or not isinstance(adapted.get("tokens"), dict): + adapted["tokens"] = _default_tokens(colors) + else: + tokens = dict(adapted["tokens"]) + for role in CORE_COLOR_ROLES: + tokens.setdefault(f"color.{role}", colors[role]) + adapted["tokens"] = tokens + if "contrast" not in adapted or not isinstance(adapted.get("contrast"), dict): + adapted["contrast"] = {"min_text_contrast": 4.5} + if "allowed_color_roles" not in adapted or not isinstance(adapted.get("allowed_color_roles"), list): + adapted["allowed_color_roles"] = list(CORE_COLOR_ROLES) + if "data_series" not in adapted: + adapted["data_series"] = [colors["primary"], colors["accent"], colors["success"], colors["warning"], colors["danger"]] + return _normalize_theme_colors(adapted) + + +def _normalized_colors(raw_colors: dict[str, Any]) -> dict[str, str]: + colors: dict[str, str] = {} + colors["background"] = normalize_hex_color(str(raw_colors.get("background") or "#0F172A")) + surface = raw_colors.get("surface") or raw_colors.get("panel") or "#111827" + colors["surface"] = normalize_hex_color(str(surface)) + colors["text"] = normalize_hex_color(str(raw_colors.get("text") or "#F8FAFC")) + colors["muted"] = normalize_hex_color(str(raw_colors.get("muted") or "#CBD5E1")) + colors["primary"] = normalize_hex_color(str(raw_colors.get("primary") or "#60A5FA")) + colors["accent"] = normalize_hex_color(str(raw_colors.get("accent") or "#A78BFA")) + for role, fallback in DEFAULT_STATUS_COLORS.items(): + colors[role] = normalize_hex_color(str(raw_colors.get(role) or fallback)) + for role, value in raw_colors.items(): + if isinstance(role, str) and role not in colors: + colors[role] = normalize_hex_color(str(value)) + colors.setdefault("panel", colors["surface"]) + return colors + + +def _default_tokens(colors: dict[str, str]) -> dict[str, str]: + return {f"color.{role}": colors[role] for role in CORE_COLOR_ROLES} + + +def _default_semantic_colors(colors: dict[str, str]) -> dict[str, str]: + return { + "canvas.background": colors["background"], + "surface.default": colors["surface"], + "text.default": colors["text"], + "text.muted": colors["muted"], + "brand.primary": colors["primary"], + "brand.accent": colors["accent"], + "status.success": colors["success"], + "status.warning": colors["warning"], + "status.danger": colors["danger"], + } + + +def _infer_mode(background: str) -> str: + return "dark" if relative_luminance(background) < 0.5 else "light" + + +def _normalize_theme_colors(theme: dict[str, Any]) -> dict[str, Any]: + normalized = json.loads(json.dumps(theme, ensure_ascii=False)) + colors = normalized.get("colors") + if isinstance(colors, dict): + normalized["colors"] = {role: normalize_hex_color(value) for role, value in colors.items()} + for key in ("tokens", "semantic_colors"): + values = normalized.get(key) + if isinstance(values, dict): + normalized[key] = { + role: normalize_hex_color(value) if isinstance(value, str) and HEX_COLOR_RE.fullmatch(value.strip()) else value + for role, value in values.items() + } + data_series = normalized.get("data_series") + if isinstance(data_series, list): + normalized["data_series"] = [normalize_hex_color(value) for value in data_series] + return normalized + + +def _normalize_optional_color(value: Any) -> str | None: + try: + return normalize_hex_color(value) + except ThemeError: + return None + + +def _resolve_color_value(value: Any, theme: dict[str, Any], *, depth: int = 0) -> str | None: + if depth > 4 or not isinstance(value, str): + return None + direct = _normalize_optional_color(value) + if direct is not None: + return direct + colors = theme.get("colors") if isinstance(theme.get("colors"), dict) else {} + tokens = theme.get("tokens") if isinstance(theme.get("tokens"), dict) else {} + color_key = value.removeprefix("colors.") if value.startswith("colors.") else value + if isinstance(colors, dict) and color_key in colors: + return _normalize_optional_color(colors[color_key]) + token_key = value.removeprefix("tokens.") if value.startswith("tokens.") else value + if isinstance(tokens, dict) and token_key in tokens: + return _resolve_color_value(tokens[token_key], theme, depth=depth + 1) + return None + + +def _matching_resolved_values(values: Any, theme: dict[str, Any], color: str) -> list[str]: + if not isinstance(values, dict): + return [] + matches: list[str] = [] + for role, value in values.items(): + if isinstance(role, str) and _resolve_color_value(value, theme) == color: + matches.append(role) + return matches + + +def _canonicalize(value: Any) -> Any: + if isinstance(value, dict): + return {str(key): _canonicalize(item) for key, item in value.items()} + if isinstance(value, list): + return [_canonicalize(item) for item in value] + if isinstance(value, str): + normalized = _normalize_optional_color(value) + return normalized if normalized is not None else value + return value + + +def _local_name(name: str) -> str: + return name.rsplit("}", 1)[-1] if "}" in name else name + + +def _issue(code: str, path: str, message: str) -> dict[str, str]: + return {"code": code, "path": path, "message": message} diff --git a/skills/lark-slides/scripts/svglide_theme_adherence.py b/skills/lark-slides/scripts/svglide_theme_adherence.py new file mode 100644 index 00000000..f46b6c14 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_theme_adherence.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import json +import re +import sys +from datetime import datetime +from pathlib import Path +from typing import Any +from xml.etree import ElementTree + +import svglide_theme + + +SCHEMA_VERSION = "svglide-theme-adherence/v1" +STAGE = "theme_adherence" +PLAN_PATH = Path("02-plan/slide_plan.json") +THEME_VALIDATE_PATH = Path("06-check/theme-validate.json") +CHECK_PATH = Path("06-check/theme-adherence.json") +RECEIPT_PATH = Path("receipts/theme-adherence.json") +PREPARED_DIR = Path("04-svg/prepared") + + +def now_iso() -> str: + return datetime.now().astimezone().replace(microsecond=0).isoformat() + + +def read_json(path: Path) -> dict[str, Any]: + payload = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(payload, dict): + raise ValueError(f"expected JSON object: {path}") + return payload + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def issue(code: str, message: str, *, page: int | None = None, path: str | None = None) -> dict[str, Any]: + payload: dict[str, Any] = {"code": code, "message": message} + if page is not None: + payload["page"] = page + if path is not None: + payload["path"] = path + return payload + + +def svg_files(project_root: Path) -> list[Path]: + svg_dir = project_root / PREPARED_DIR + if not svg_dir.exists(): + return [] + return [path for path in sorted(svg_dir.glob("*.svg")) if path.is_file()] + + +def style_value(style: str, key: str) -> str | None: + for item in style.split(";"): + if ":" not in item: + continue + name, value = item.split(":", 1) + if name.strip().lower() == key: + return value.strip() + return None + + +def first_hex(value: str | None) -> str | None: + if not isinstance(value, str): + return None + match = re.search(r"#(?:[0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})(?![0-9A-Fa-f])", value) + return svglide_theme.normalize_hex_color(match.group(0)) if match else None + + +def local_name(name: str) -> str: + return name.rsplit("}", 1)[-1] if "}" in name else name + + +def element_text_color(element: ElementTree.Element) -> str | None: + style = element.attrib.get("style") + for raw in ( + element.attrib.get("color"), + element.attrib.get("fill"), + style_value(style or "", "color"), + style_value(style or "", "fill"), + ): + color = first_hex(raw) + if color: + return color + return None + + +def svg_text_colors(svg_path: Path) -> list[dict[str, Any]]: + root = ElementTree.fromstring(svg_path.read_text(encoding="utf-8")) + items: list[dict[str, Any]] = [] + for element in root.iter(): + if local_name(element.tag).lower() not in {"text", "foreignobject"}: + continue + text = "".join(element.itertext()).strip() + items.append({"tag": local_name(element.tag), "text": text, "color": element_text_color(element)}) + return items + + +def page_theme_id(validate_receipt: dict[str, Any], page_index: int) -> str | None: + pages = validate_receipt.get("pages") + if not isinstance(pages, list): + return None + for item in pages: + if not isinstance(item, dict): + continue + if item.get("page") == page_index and isinstance(item.get("theme_id"), str): + return item["theme_id"] + if 1 <= page_index <= len(pages): + item = pages[page_index - 1] + if isinstance(item, dict) and isinstance(item.get("theme_id"), str): + return item["theme_id"] + return None + + +def validate_project(project_root: Path) -> dict[str, Any]: + project_root = project_root.resolve() + issues: list[dict[str, Any]] = [] + page_results: list[dict[str, Any]] = [] + unknown_colors: list[dict[str, Any]] = [] + contrast_unresolved: list[dict[str, Any]] = [] + contrast_failures: list[dict[str, Any]] = [] + + plan_file = project_root / PLAN_PATH + validate_file = project_root / THEME_VALIDATE_PATH + validate_receipt: dict[str, Any] = {} + try: + read_json(plan_file) + except (OSError, json.JSONDecodeError, ValueError) as err: + issues.append(issue("plan_invalid", f"could not read {PLAN_PATH.as_posix()}: {err}", path=PLAN_PATH.as_posix())) + try: + validate_receipt = read_json(validate_file) + except (OSError, json.JSONDecodeError, ValueError) as err: + issues.append(issue("theme_validate_invalid", f"could not read {THEME_VALIDATE_PATH.as_posix()}: {err}", path=THEME_VALIDATE_PATH.as_posix())) + if validate_receipt and validate_receipt.get("status") != "passed": + issues.append(issue("theme_validate_not_passed", "theme_validate receipt must be passed", path=THEME_VALIDATE_PATH.as_posix())) + inputs = validate_receipt.get("inputs") if isinstance(validate_receipt.get("inputs"), dict) else {} + if validate_receipt and inputs.get("plan_sha256") != (svglide_theme.file_sha256(plan_file) if plan_file.exists() else None): + issues.append(issue("theme_validate_plan_stale", "theme_validate plan_sha256 does not match current slide_plan.json", path=THEME_VALIDATE_PATH.as_posix())) + + prepared_files = svg_files(project_root) + if not prepared_files: + issues.append(issue("prepared_svg_missing", "theme_adherence requires 04-svg/prepared/*.svg", path=PREPARED_DIR.as_posix())) + + for index, svg_path in enumerate(prepared_files, start=1): + rel = svg_path.relative_to(project_root).as_posix() + page_issues: list[dict[str, Any]] = [] + theme_id = page_theme_id(validate_receipt, index) + theme: dict[str, Any] | None = None + if not theme_id: + page_issues.append(issue("theme_id_missing", "theme_validate receipt must include page theme_id", page=index, path=rel)) + else: + try: + theme = svglide_theme.load_theme(theme_id, project_root) + except (OSError, svglide_theme.ThemeError, json.JSONDecodeError) as err: + page_issues.append(issue("theme_load_failed", str(err), page=index, path=rel)) + if theme: + try: + colors = svglide_theme.extract_svg_colors(svg_path) + except svglide_theme.ThemeError as err: + page_issues.append(issue("svg_color_extract_failed", str(err), page=index, path=rel)) + colors = [] + for color in colors: + classified = svglide_theme.classify_color(color, theme) + if classified["kind"] == "unknown": + entry = {"page": index, "path": rel, "color": color} + unknown_colors.append(entry) + page_issues.append(issue("theme_unknown_color", f"color {color} is not allowed by theme {theme_id}", page=index, path=rel)) + try: + text_items = svg_text_colors(svg_path) + except (OSError, ElementTree.ParseError, svglide_theme.ThemeError) as err: + page_issues.append(issue("svg_text_parse_failed", str(err), page=index, path=rel)) + text_items = [] + background = theme.get("colors", {}).get("background") if isinstance(theme.get("colors"), dict) else None + min_ratio = theme.get("contrast", {}).get("min_text_contrast") if isinstance(theme.get("contrast"), dict) else 4.5 + for text_index, item in enumerate(text_items): + color = item.get("color") + if not color or not isinstance(background, str): + entry = {"page": index, "path": rel, "text_index": text_index, "reason": "text_or_background_color_unresolved"} + contrast_unresolved.append(entry) + page_issues.append(issue("contrast_unresolved", "text and background colors must be directly inferable in P0", page=index, path=rel)) + continue + ratio = svglide_theme.contrast_ratio(color, background) + if ratio < float(min_ratio): + entry = {"page": index, "path": rel, "text_index": text_index, "ratio": round(ratio, 4), "min": min_ratio} + contrast_failures.append(entry) + page_issues.append(issue("contrast_too_low", f"text contrast {ratio:.2f} is below {min_ratio}", page=index, path=rel)) + page_results.append( + { + "page": index, + "path": rel, + "theme_id": theme_id, + "sha256": svglide_theme.file_sha256(svg_path), + "status": "passed" if not page_issues else "failed", + "issues": page_issues, + } + ) + issues.extend(page_issues) + + status = "passed" if not issues else "failed" + result = { + "schema_version": SCHEMA_VERSION, + "stage": STAGE, + "status": status, + "action": "create_live" if status == "passed" else "repair_and_rerun", + "checked_at": now_iso(), + "inputs": { + "slide_plan": PLAN_PATH.as_posix(), + "plan_sha256": svglide_theme.file_sha256(plan_file) if plan_file.exists() else None, + "theme_validate": THEME_VALIDATE_PATH.as_posix(), + "theme_validate_sha256": svglide_theme.file_sha256(validate_file) if validate_file.exists() else None, + }, + "prepared_files": svglide_theme.prepared_svg_hashes(project_root), + "pages": page_results, + "unknown_colors": unknown_colors, + "contrast_unresolved": contrast_unresolved, + "contrast_failures": contrast_failures, + "summary": { + "error_count": len(issues), + "warning_count": 0, + "prepared_svg_count": len(prepared_files), + "unknown_color_count": len(unknown_colors), + "contrast_unresolved_count": len(contrast_unresolved), + }, + "issues": issues, + "output_path": CHECK_PATH.as_posix(), + } + return result + + +def write_outputs(project_root: Path, result: dict[str, Any]) -> None: + write_json(project_root / CHECK_PATH, result) + write_json(project_root / RECEIPT_PATH, result) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Validate final prepared SVG adherence to ThemeSpec.") + parser.add_argument("project_root", type=Path) + parser.add_argument("--pretty", action="store_true") + args = parser.parse_args(argv) + + if not args.project_root.exists() or not args.project_root.is_dir(): + print(f"svglide_theme_adherence: project_root does not exist: {args.project_root}", file=sys.stderr) + return 2 + try: + result = validate_project(args.project_root) + write_outputs(args.project_root, result) + except OSError as err: + print(f"svglide_theme_adherence: {err}", file=sys.stderr) + return 2 + print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=True)) + return 0 if result["status"] == "passed" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/lark-slides/scripts/svglide_theme_adherence_test.py b/skills/lark-slides/scripts/svglide_theme_adherence_test.py new file mode 100644 index 00000000..43f29faa --- /dev/null +++ b/skills/lark-slides/scripts/svglide_theme_adherence_test.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_theme +import svglide_theme_adherence +import svglide_theme_validate + + +def write_json(path: Path, payload: dict[str, object]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload), encoding="utf-8") + + +def make_project(tmpdir: str) -> Path: + project = Path(tmpdir) + write_json( + project / "02-plan/slide_plan.json", + { + "generation_mode": "artboard_satori", + "slides": [ + { + "page": 1, + "title": "主题测试", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "template_id": "cover-hero", + "theme_id": "dark-clarity", + "theme": {"colors": {"background": "#0F172A"}}, + }, + } + ], + }, + ) + validation = svglide_theme_validate.validate_project(project) + svglide_theme_validate.write_outputs(project, validation) + return project + + +def write_svg(project: Path, body: str) -> None: + path = project / "04-svg/prepared/page-001.svg" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(body, encoding="utf-8") + + +def write_project_theme_override(project: Path) -> None: + write_json( + project / "02-plan/theme-registry.json", + { + "schema_version": "svglide-theme-registry/v1", + "themes": [{"id": "dark-clarity", "status": "active", "path": "themes/dark-clarity.json"}], + }, + ) + write_json( + project / "themes/dark-clarity.json", + { + "schema_version": "svglide-theme/v1", + "theme_id": "dark-clarity", + "mode": "light", + "colors": { + "background": "#FFFFFF", + "surface": "#F8FAFC", + "text": "#111111", + "muted": "#64748B", + "primary": "#123456", + "accent": "#D946EF", + "success": "#16A34A", + "warning": "#D97706", + "danger": "#DC2626", + }, + }, + ) + + +class SVGlideThemeAdherenceTest(unittest.TestCase): + def test_adherence_passes_for_theme_colors_and_direct_contrast(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = make_project(tmpdir) + write_svg( + project, + '标题', + ) + + result = svglide_theme_adherence.validate_project(project) + svglide_theme_adherence.write_outputs(project, result) + + self.assertEqual(result["status"], "passed", result["issues"]) + self.assertEqual(result["prepared_files"], svglide_theme.prepared_svg_hashes(project)) + self.assertTrue((project / "06-check/theme-adherence.json").exists()) + self.assertTrue((project / "receipts/theme-adherence.json").exists()) + + def test_adherence_uses_project_theme_registry_override(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_project_theme_override(project) + write_json( + project / "02-plan/slide_plan.json", + {"slides": [{"page": 1, "theme_id": "dark-clarity"}]}, + ) + validation = svglide_theme_validate.validate_project(project) + svglide_theme_validate.write_outputs(project, validation) + write_svg( + project, + '标题', + ) + + result = svglide_theme_adherence.validate_project(project) + + self.assertEqual(validation["status"], "passed", validation["issues"]) + self.assertEqual(result["status"], "passed", result["issues"]) + self.assertEqual(result["pages"][0]["theme_id"], "dark-clarity") + + def test_adherence_fails_unknown_color(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = make_project(tmpdir) + write_svg(project, '标题') + + result = svglide_theme_adherence.validate_project(project) + + self.assertEqual(result["status"], "failed") + self.assertIn("theme_unknown_color", {item["code"] for item in result["issues"]}) + self.assertEqual(result["unknown_colors"][0]["color"], "#123456") + + def test_adherence_fails_contrast_unresolved(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = make_project(tmpdir) + write_svg(project, '标题') + + result = svglide_theme_adherence.validate_project(project) + + self.assertEqual(result["status"], "failed") + self.assertIn("contrast_unresolved", {item["code"] for item in result["issues"]}) + + def test_adherence_fails_low_contrast(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = make_project(tmpdir) + write_svg(project, '标题') + + result = svglide_theme_adherence.validate_project(project) + + self.assertEqual(result["status"], "failed") + self.assertIn("contrast_too_low", {item["code"] for item in result["issues"]}) + + def test_adherence_fails_stale_theme_validate_receipt(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = make_project(tmpdir) + write_svg(project, '标题') + plan = json.loads((project / "02-plan/slide_plan.json").read_text(encoding="utf-8")) + plan["slides"][0]["title"] = "变更后的标题" + write_json(project / "02-plan/slide_plan.json", plan) + + result = svglide_theme_adherence.validate_project(project) + + self.assertEqual(result["status"], "failed") + self.assertIn("theme_validate_plan_stale", {item["code"] for item in result["issues"]}) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_theme_test.py b/skills/lark-slides/scripts/svglide_theme_test.py new file mode 100644 index 00000000..0c15ad30 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_theme_test.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_theme + + +def write_text(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding="utf-8") + + +def strict_theme() -> dict[str, object]: + colors = { + "background": "#FFFFFF", + "surface": "#F8FAFC", + "text": "#111827", + "muted": "#64748B", + "primary": "#2563EB", + "accent": "#D946EF", + "success": "#16A34A", + "warning": "#D97706", + "danger": "#DC2626", + } + return { + "schema_version": "svglide-theme/v1", + "theme_id": "strict-demo", + "mode": "light", + "colors": colors, + "semantic_colors": { + "canvas.background": colors["background"], + "text.default": colors["text"], + "chart.highlight": "#0F766E", + }, + "tokens": {f"color.{role}": value for role, value in colors.items()}, + "contrast": {"min_text_contrast": 4.5}, + "allowed_color_roles": list(colors.keys()), + "data_series": ["#7C3AED", "#0891B2"], + } + + +def issue_codes(issues: list[dict[str, str]]) -> set[str]: + return {item["code"] for item in issues} + + +class SVGlideThemeTest(unittest.TestCase): + def test_theme_schema_requires_p0_fields(self) -> None: + schema_path = Path(__file__).resolve().parent.parent / "references" / "svglide-theme-spec.schema.json" + schema = json.loads(schema_path.read_text(encoding="utf-8")) + + self.assertEqual(schema["properties"]["schema_version"]["const"], "svglide-theme/v1") + self.assertTrue( + { + "theme_id", + "mode", + "colors", + "semantic_colors", + "tokens", + "contrast", + "allowed_color_roles", + }.issubset(set(schema["required"])) + ) + for role in ["background", "surface", "text", "muted", "primary", "accent", "success", "warning", "danger"]: + self.assertIn(role, schema["properties"]["colors"]["required"]) + + def test_validate_theme_spec_rejects_illegal_hex(self) -> None: + theme = strict_theme() + theme["colors"]["primary"] = "#12GGGG" # type: ignore[index] + + issues = svglide_theme.validate_theme_spec(theme) + + self.assertIn("theme_color_hex_invalid", issue_codes(issues)) + + def test_validate_theme_spec_rejects_missing_core_token(self) -> None: + theme = strict_theme() + del theme["tokens"]["color.primary"] # type: ignore[index] + + issues = svglide_theme.validate_theme_spec(theme) + + self.assertIn("theme_token_missing", issue_codes(issues)) + + def test_validate_theme_spec_rejects_missing_and_non_numeric_min_text_contrast(self) -> None: + missing = strict_theme() + del missing["contrast"]["min_text_contrast"] # type: ignore[index] + non_numeric = strict_theme() + non_numeric["contrast"]["min_text_contrast"] = "4.5" # type: ignore[index] + + self.assertIn("theme_contrast_min_text_invalid", issue_codes(svglide_theme.validate_theme_spec(missing))) + self.assertIn("theme_contrast_min_text_invalid", issue_codes(svglide_theme.validate_theme_spec(non_numeric))) + + def test_theme_hash_is_stable_for_key_order_and_hex_case(self) -> None: + first = strict_theme() + second = { + "allowed_color_roles": first["allowed_color_roles"], + "contrast": first["contrast"], + "tokens": dict(reversed(list(first["tokens"].items()))), # type: ignore[union-attr] + "semantic_colors": first["semantic_colors"], + "colors": {**first["colors"], "primary": "#2563eb"}, # type: ignore[arg-type] + "mode": "light", + "theme_id": "strict-demo", + "schema_version": "svglide-theme/v1", + "data_series": first["data_series"], + } + + self.assertEqual(svglide_theme.theme_sha256(first), svglide_theme.theme_sha256(second)) + + def test_contrast_ratio_black_white(self) -> None: + self.assertAlmostEqual(21.0, svglide_theme.contrast_ratio("#000000", "#FFFFFF"), places=3) + self.assertAlmostEqual(0.0, svglide_theme.relative_luminance("#000"), places=6) + self.assertAlmostEqual(1.0, svglide_theme.relative_luminance("#fff"), places=6) + + def test_extract_svg_colors_reads_fill_stroke_color_and_style(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + path = Path(tmpdir) / "page-001.svg" + write_text( + path, + """ + + Label + +""", + ) + + colors = svglide_theme.extract_svg_colors(path) + + self.assertEqual(["#AABBCC", "#000000", "#123456", "#00FF00", "#00AAFF", "#FFEEDD"], colors) + + def test_classify_color_does_not_allow_unknown_by_default(self) -> None: + theme = strict_theme() + + self.assertEqual("theme_token", svglide_theme.classify_color("#2563EB", theme)["kind"]) + self.assertEqual("semantic", svglide_theme.classify_color("#0F766E", theme)["kind"]) + self.assertEqual("data_series", svglide_theme.classify_color("#0891B2", theme)["kind"]) + self.assertEqual("unknown", svglide_theme.classify_color("#123456", theme)["kind"]) + + def test_load_theme_adapts_existing_artboard_renderer_theme(self) -> None: + theme = svglide_theme.load_theme("swiss-red") + + self.assertEqual("svglide-theme/v1", theme["schema_version"]) + self.assertEqual("swiss-red", theme["theme_id"]) + self.assertEqual("#FFFFFF", theme["colors"]["surface"]) + self.assertEqual([], svglide_theme.validate_theme_spec(theme)) + + def test_prepared_svg_hashes_are_stable_and_repo_relative(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_text(project / "04-svg/prepared/page-002.svg", "") + write_text(project / "04-svg/prepared/page-001.svg", "") + + hashes = svglide_theme.prepared_svg_hashes(project) + + self.assertEqual(["04-svg/prepared/page-001.svg", "04-svg/prepared/page-002.svg"], [item["path"] for item in hashes]) + self.assertRegex(hashes[0]["sha256"], r"^[0-9a-f]{64}$") + + +if __name__ == "__main__": + unittest.main() diff --git a/skills/lark-slides/scripts/svglide_theme_validate.py b/skills/lark-slides/scripts/svglide_theme_validate.py new file mode 100644 index 00000000..feeca4e0 --- /dev/null +++ b/skills/lark-slides/scripts/svglide_theme_validate.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import argparse +import json +import sys +from datetime import datetime +from pathlib import Path +from typing import Any + +import svglide_theme + + +SCHEMA_VERSION = "svglide-theme-validate/v1" +STAGE = "theme_validate" +PLAN_PATH = Path("02-plan/slide_plan.json") +CHECK_PATH = Path("06-check/theme-validate.json") +RECEIPT_PATH = Path("receipts/theme-validate.json") +TEMPLATE_REGISTRY_PATH = Path(__file__).resolve().parent.parent / "references" / "svglide-template-registry.json" + + +def now_iso() -> str: + return datetime.now().astimezone().replace(microsecond=0).isoformat() + + +def read_json(path: Path) -> dict[str, Any]: + payload = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(payload, dict): + raise ValueError(f"expected JSON object: {path}") + return payload + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def issue(code: str, message: str, *, page: int | None = None, path: str | None = None) -> dict[str, Any]: + payload: dict[str, Any] = {"code": code, "message": message} + if page is not None: + payload["page"] = page + if path is not None: + payload["path"] = path + return payload + + +def relpath(path: Path, base: Path) -> str: + try: + return path.resolve().relative_to(base.resolve()).as_posix() + except ValueError: + return path.as_posix() + + +def display_path(path: Path, project_root: Path) -> str: + try: + return path.resolve().relative_to(project_root.resolve()).as_posix() + except ValueError: + return relpath(path, svglide_theme.REPO_ROOT) + + +def template_records() -> dict[str, dict[str, Any]]: + payload = read_json(TEMPLATE_REGISTRY_PATH) + templates = payload.get("templates") if isinstance(payload.get("templates"), list) else [] + return {item["id"]: item for item in templates if isinstance(item, dict) and isinstance(item.get("id"), str)} + + +def theme_record_paths(registry: dict[str, Any]) -> dict[str, str]: + themes = registry.get("themes") if isinstance(registry.get("themes"), list) else [] + result: dict[str, str] = {} + for item in themes: + if isinstance(item, dict) and isinstance(item.get("id"), str) and isinstance(item.get("path"), str): + result[item["id"]] = item["path"] + return result + + +def slide_canvas_spec(slide: dict[str, Any]) -> dict[str, Any]: + spec = slide.get("canvas_spec") + return spec if isinstance(spec, dict) else {} + + +def slide_theme_id(slide: dict[str, Any], plan: dict[str, Any]) -> str | None: + spec = slide_canvas_spec(slide) + raw = spec.get("theme_id") + if isinstance(raw, str) and raw: + return raw + theme = spec.get("theme") + if isinstance(theme, str) and theme: + return theme + if isinstance(theme, dict) and isinstance(theme.get("theme_id"), str): + return theme["theme_id"] + for key in ("theme_id", "theme"): + raw_slide = slide.get(key) + if isinstance(raw_slide, str) and raw_slide: + return raw_slide + for key in ("theme_id", "theme"): + raw_plan = plan.get(key) + if isinstance(raw_plan, str) and raw_plan: + return raw_plan + return None + + +def slide_template_id(slide: dict[str, Any]) -> str | None: + spec = slide_canvas_spec(slide) + raw = spec.get("template_id") + if isinstance(raw, str) and raw: + return raw + raw = slide.get("template_id") + return raw if isinstance(raw, str) and raw else None + + +def validate_project(project_root: Path) -> dict[str, Any]: + project_root = project_root.resolve() + plan_file = project_root / PLAN_PATH + issues: list[dict[str, Any]] = [] + pages: list[dict[str, Any]] = [] + theme_files: list[dict[str, str]] = [] + theme_file_seen: set[str] = set() + plan: dict[str, Any] = {} + registry: dict[str, Any] = {} + registry_path = svglide_theme.GLOBAL_THEME_REGISTRY + registry_paths: dict[str, str] = {} + templates: dict[str, dict[str, Any]] = {} + + try: + plan = read_json(plan_file) + except (OSError, json.JSONDecodeError, ValueError) as err: + issues.append(issue("plan_invalid", f"could not read {PLAN_PATH.as_posix()}: {err}", path=PLAN_PATH.as_posix())) + + try: + registry_path = svglide_theme.theme_registry_path(project_root) + registry = svglide_theme.load_registry(project_root) + registry_paths = theme_record_paths(registry) + except (OSError, svglide_theme.ThemeError, json.JSONDecodeError) as err: + issues.append(issue("theme_registry_invalid", str(err), path=svglide_theme.GLOBAL_THEME_REGISTRY.as_posix())) + + try: + templates = template_records() + except (OSError, json.JSONDecodeError, ValueError) as err: + issues.append(issue("template_registry_invalid", str(err), path=TEMPLATE_REGISTRY_PATH.as_posix())) + + slides = plan.get("slides") if isinstance(plan.get("slides"), list) else [] + if not slides: + issues.append(issue("slides_missing", "slide_plan.slides must be a non-empty array", path=PLAN_PATH.as_posix())) + + for index, slide in enumerate(slides, start=1): + page_issues: list[dict[str, Any]] = [] + if not isinstance(slide, dict): + page_issues.append(issue("slide_invalid", "slide item must be an object", page=index)) + pages.append({"page": index, "status": "failed", "issues": page_issues}) + issues.extend(page_issues) + continue + theme_id = slide_theme_id(slide, plan) + template_id = slide_template_id(slide) + theme_hash: str | None = None + if not theme_id: + page_issues.append(issue("theme_id_missing", "slide or canvas_spec must declare theme_id", page=index)) + else: + try: + theme = svglide_theme.load_theme(theme_id, project_root) + theme_hash = svglide_theme.theme_sha256(theme) + raw_theme_path = registry_paths.get(theme_id) + if raw_theme_path and raw_theme_path not in theme_file_seen: + theme_file_seen.add(raw_theme_path) + theme_path = svglide_theme.theme_file_path(theme_id, project_root) + if theme_path is not None: + theme_files.append({"path": display_path(theme_path, project_root), "sha256": svglide_theme.file_sha256(theme_path)}) + except (OSError, svglide_theme.ThemeError, json.JSONDecodeError) as err: + page_issues.append(issue("theme_invalid", str(err), page=index)) + if template_id: + template = templates.get(template_id) + if not template: + page_issues.append(issue("template_unknown", f"template_id {template_id!r} is not present in template registry", page=index)) + elif theme_id: + allowed = template.get("supported_theme_ids") + if isinstance(allowed, list) and theme_id not in allowed: + page_issues.append(issue("template_theme_not_allowed", f"template_id {template_id!r} does not allow theme_id {theme_id!r}", page=index)) + pages.append( + { + "page": slide.get("page") if isinstance(slide.get("page"), int) else index, + "theme_id": theme_id, + "template_id": template_id, + "theme_sha256": theme_hash, + "status": "passed" if not page_issues else "failed", + "issues": page_issues, + } + ) + issues.extend(page_issues) + + status = "passed" if not issues else "failed" + result = { + "schema_version": SCHEMA_VERSION, + "stage": STAGE, + "status": status, + "action": "create_live" if status == "passed" else "repair_and_rerun", + "checked_at": now_iso(), + "inputs": { + "slide_plan": PLAN_PATH.as_posix(), + "plan_sha256": svglide_theme.file_sha256(plan_file) if plan_file.exists() else None, + "theme_registry": display_path(registry_path, project_root), + "theme_registry_sha256": svglide_theme.file_sha256(registry_path) + if registry_path.exists() + else None, + "template_registry": relpath(TEMPLATE_REGISTRY_PATH, svglide_theme.REPO_ROOT), + "template_registry_sha256": svglide_theme.file_sha256(TEMPLATE_REGISTRY_PATH) if TEMPLATE_REGISTRY_PATH.exists() else None, + }, + "theme_files": theme_files, + "pages": pages, + "summary": {"error_count": len(issues), "warning_count": 0, "page_count": len(pages), "theme_count": len(theme_files)}, + "issues": issues, + "output_path": CHECK_PATH.as_posix(), + } + return result + + +def write_outputs(project_root: Path, result: dict[str, Any]) -> None: + write_json(project_root / CHECK_PATH, result) + write_json(project_root / RECEIPT_PATH, result) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="Validate SVGlide ThemeSpec usage in a project plan.") + parser.add_argument("project_root", type=Path) + parser.add_argument("--pretty", action="store_true") + args = parser.parse_args(argv) + + if not args.project_root.exists() or not args.project_root.is_dir(): + print(f"svglide_theme_validate: project_root does not exist: {args.project_root}", file=sys.stderr) + return 2 + try: + result = validate_project(args.project_root) + write_outputs(args.project_root, result) + except OSError as err: + print(f"svglide_theme_validate: {err}", file=sys.stderr) + return 2 + print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None, sort_keys=True)) + return 0 if result["status"] == "passed" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/skills/lark-slides/scripts/svglide_theme_validate_test.py b/skills/lark-slides/scripts/svglide_theme_validate_test.py new file mode 100644 index 00000000..aabeae5d --- /dev/null +++ b/skills/lark-slides/scripts/svglide_theme_validate_test.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Lark Technologies Pte. Ltd. +# SPDX-License-Identifier: MIT +from __future__ import annotations + +import json +import contextlib +import io +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +import svglide_theme_validate + + +def write_json(path: Path, payload: dict[str, object]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload), encoding="utf-8") + + +def write_plan(project: Path, *, theme_id: str = "dark-clarity", template_id: str = "cover-hero") -> None: + write_json( + project / "02-plan/slide_plan.json", + { + "generation_mode": "artboard_satori", + "slides": [ + { + "page": 1, + "title": "主题测试", + "canvas_spec": { + "version": "svglide-canvas-spec/v1", + "template_id": template_id, + "theme_id": theme_id, + "theme": {"colors": {"background": "#0F172A"}}, + }, + } + ], + }, + ) + + +def write_project_theme(project: Path, *, theme_id: str = "project-theme", primary: str = "#123456") -> None: + write_json( + project / "02-plan/theme-registry.json", + { + "schema_version": "svglide-theme-registry/v1", + "themes": [{"id": theme_id, "status": "active", "path": "themes/project-theme.json"}], + }, + ) + write_json( + project / "themes/project-theme.json", + { + "schema_version": "svglide-theme/v1", + "theme_id": theme_id, + "mode": "light", + "colors": { + "background": "#FFFFFF", + "surface": "#F8FAFC", + "text": "#111111", + "muted": "#64748B", + "primary": primary, + "accent": "#D946EF", + "success": "#16A34A", + "warning": "#D97706", + "danger": "#DC2626", + }, + }, + ) + + +class SVGlideThemeValidateTest(unittest.TestCase): + def test_validate_project_passes_for_known_theme_and_template(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_plan(project) + + result = svglide_theme_validate.validate_project(project) + svglide_theme_validate.write_outputs(project, result) + + self.assertEqual(result["status"], "passed", result["issues"]) + self.assertEqual(result["summary"]["error_count"], 0) + self.assertEqual(result["pages"][0]["theme_id"], "dark-clarity") + self.assertTrue((project / "06-check/theme-validate.json").exists()) + self.assertTrue((project / "receipts/theme-validate.json").exists()) + + def test_validate_project_uses_project_theme_registry(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_project_theme(project) + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1, "theme_id": "project-theme"}]}) + + result = svglide_theme_validate.validate_project(project) + + self.assertEqual(result["status"], "passed", result["issues"]) + self.assertEqual(result["inputs"]["theme_registry"], "02-plan/theme-registry.json") + self.assertEqual(result["theme_files"][0]["path"], "themes/project-theme.json") + self.assertEqual(result["pages"][0]["theme_id"], "project-theme") + + def test_validate_project_fails_unknown_theme(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_plan(project, theme_id="missing-theme") + + result = svglide_theme_validate.validate_project(project) + + self.assertEqual(result["status"], "failed") + self.assertIn("theme_invalid", {item["code"] for item in result["issues"]}) + + def test_validate_project_fails_template_theme_mismatch(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_plan(project, theme_id="dark-clarity", template_id="missing-template") + + result = svglide_theme_validate.validate_project(project) + + self.assertEqual(result["status"], "failed") + self.assertIn("template_unknown", {item["code"] for item in result["issues"]}) + + def test_cli_writes_failed_receipt_for_missing_theme_id(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + project = Path(tmpdir) + write_json(project / "02-plan/slide_plan.json", {"slides": [{"page": 1, "canvas_spec": {"template_id": "cover-hero"}}]}) + + with contextlib.redirect_stdout(io.StringIO()): + code = svglide_theme_validate.main([project.as_posix()]) + + payload = json.loads((project / "06-check/theme-validate.json").read_text(encoding="utf-8")) + self.assertEqual(code, 1) + self.assertEqual(payload["status"], "failed") + self.assertIn("theme_id_missing", {item["code"] for item in payload["issues"]}) + + +if __name__ == "__main__": + unittest.main() diff --git a/skills_embed.go b/skills_embed.go index c5cdbbf3..f1699ef9 100644 --- a/skills_embed.go +++ b/skills_embed.go @@ -12,13 +12,17 @@ import ( "github.com/larksuite/cli/cmd" ) -// skillsEmbedFS embeds each skill's agent-readable content (SKILL.md + -// references/, plus lark-whiteboard's routes/ and scenes/) so the CLI serves -// content matching the binary version; machine-resource dirs (assets/, scripts/) -// are excluded, saving ~3.3 MB. It's a whitelist — a new subdirectory type is -// silently omitted until added here. +// skillsEmbedFS embeds each skill's versioned content so the CLI serves content +// matching the binary version. It is intentionally a whitelist: agent-readable +// docs are embedded for every skill, while lark-slides machine resources embed +// only script/prompt/renderer files needed by SVGlide. Broad directories such +// as node_modules/, fixtures/, assets/, and generated project outputs stay out +// of the Go binary. // -//go:embed skills/*/SKILL.md skills/*/references skills/*/routes skills/*/scenes +//go:embed skills/*/SKILL.md skills/*/references skills/*/routes skills/*/scenes skills/*/prompts +//go:embed skills/*/scripts/*.py +//go:embed skills/*/scripts/artboard_renderer/*.mjs skills/*/scripts/artboard_renderer/package.json skills/*/scripts/artboard_renderer/pnpm-lock.yaml +//go:embed skills/*/scripts/artboard_renderer/components skills/*/scripts/artboard_renderer/dist skills/*/scripts/artboard_renderer/templates skills/*/scripts/artboard_renderer/themes var skillsEmbedFS embed.FS // init wires the embedded tree in as the default skill content. It compiles into