Skip to content

fix(refig): REST API JSON without geometry=paths causes 0x0 raster surface panic (#585)#591

Merged
softmarshmallow merged 1 commit intomainfrom
chore/cli-build-command-failure-9541
Mar 21, 2026
Merged

fix(refig): REST API JSON without geometry=paths causes 0x0 raster surface panic (#585)#591
softmarshmallow merged 1 commit intomainfrom
chore/cli-build-command-failure-9541

Conversation

@softmarshmallow
Copy link
Copy Markdown
Member

@softmarshmallow softmarshmallow commented Mar 19, 2026

Summary

Fixes #585FigmaRenderer.render() panics with "Failed to create raster surface" when using a Figma REST API JSON fetched without the geometry=paths query parameter.

Root Cause

The Figma REST API GET /v1/files/:key response omits size and relativeTransform from every node when the geometry=paths query param is not included. Only absoluteBoundingBox is always present.

The io-figma positioning_trait helper fell back to 0 for both width and height when size was absent:

const szx = node.size?.x ?? 0;  // → 0 when size is missing
const szy = node.size?.y ?? 0;  // → 0 when size is missing

This produced 0×0 node dimensions in the Grida IR, which flowed into:

Backend::new_from_raster(0, 0)  // → raster_n32_premul((0, 0)) returns None → panic!

Fixes

packages/grida-canvas-io-figma/lib.ts

  • positioning_trait now accepts an optional parent argument and falls back to absoluteBoundingBox.width/height for dimensions when size is absent
  • Relative insets are computed from child.absoluteBoundingBox - parent.absoluteBoundingBox when relativeTransform is absent
  • All node type handlers pass parent to positioning_trait
  • TEXT and LINE node handlers use the same fallback pattern

crates/grida-canvas/src/export/export_as_{image,svg,pdf}.rs

  • Added defensive guards that return None before calling Backend::new_from_raster when pixel dimensions are <= 0, preventing panics from any future edge cases

Tests

  • packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.no-geometry.test.ts — 7 new unit tests verifying dimension/position fallback
  • packages/grida-canvas-sdk-render-figma/__tests__/refig.test.ts — 1 new integration test that renders the exact node structure from the reported file through WASM without panicking

Summary by CodeRabbit

  • Bug Fixes

    • Fixed export functions (image, PDF, SVG) to prevent invalid operations when dimensions are zero or negative
    • Added support for Figma REST API responses that omit certain geometry fields, using alternative data sources for positioning calculations
  • Tests

    • Added comprehensive test coverage for edge cases in exports and API document handling

…rface panic

Root cause of #585:

The Figma REST API GET /v1/files/:key response omits `size` and
`relativeTransform` from every node when `geometry=paths` is not
passed as a query param. The io-figma `positioning_trait` helper fell
back to 0 for both width and height, producing 0x0 node dimensions.
This caused `Backend::new_from_raster(0, 0)` to call
`skia_safe::surfaces::raster_n32_premul((0, 0))` which returns `None`
and the `.expect()` panicked with 'Failed to create raster surface'.

Fixes:
1. packages/grida-canvas-io-figma/lib.ts — positioning_trait now
   accepts an optional parent node and falls back to absoluteBoundingBox
   for dimensions when size is absent, and computes relative insets from
   child/parent absolute bounding boxes when relativeTransform is absent.
   All node type handlers (FRAME, RECTANGLE, ELLIPSE, GROUP, SECTION,
   BOOLEAN_OPERATION, LINE, VECTOR, X_VECTOR, X_STAR, X_REGULAR_POLYGON)
   pass parent to positioning_trait. TEXT node likewise uses absoluteBoundingBox
   as fallback for size and position.

2. crates/grida-canvas/src/export/{export_as_image,export_as_svg,export_as_pdf}.rs —
   Added defensive guards that return None before calling
   Backend::new_from_raster when pixel dimensions are <= 0, preventing
   the panic in any future edge cases.

Co-authored-by: Universe <universe@grida.co>
@cursor
Copy link
Copy Markdown

cursor Bot commented Mar 19, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Mar 19, 2026 5:32pm
grida Ready Ready Preview, Comment Mar 19, 2026 5:32pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
code Ignored Ignored Mar 19, 2026 5:32pm
legacy Ignored Ignored Mar 19, 2026 5:32pm
backgrounds Skipped Skipped Mar 19, 2026 5:32pm
blog Skipped Skipped Mar 19, 2026 5:32pm
viewer Skipped Skipped Mar 19, 2026 5:32pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 19, 2026

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f048f506-2ad9-4d67-8c73-facc22dea7be

📥 Commits

Reviewing files that changed from the base of the PR and between 7092d9e and 4bb99a7.

📒 Files selected for processing (6)
  • crates/grida-canvas/src/export/export_as_image.rs
  • crates/grida-canvas/src/export/export_as_pdf.rs
  • crates/grida-canvas/src/export/export_as_svg.rs
  • packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.no-geometry.test.ts
  • packages/grida-canvas-io-figma/lib.ts
  • packages/grida-canvas-sdk-render-figma/__tests__/refig.test.ts

Walkthrough

The PR addresses issue #585 by adding defensive guards in export functions to prevent raster creation with zero or negative dimensions, and enhancing the Figma IO factory to derive node dimensions from absoluteBoundingBox when REST API responses lack geometry=paths or size/relativeTransform fields. Comprehensive test coverage validates both the defensive checks and fallback positioning logic.

Changes

Cohort / File(s) Summary
Export defensive guards
crates/grida-canvas/src/export/export_as_image.rs, export_as_pdf.rs, export_as_svg.rs
Added early-return checks to prevent raster backend creation when computed pixel dimensions are zero or negative. Image export guards dimensions before casting to i32; PDF/SVG variants check cast result before proceeding. Image export also guards camera scaling division.
Figma IO positioning refactor
packages/grida-canvas-io-figma/lib.ts
Enhanced positioning_trait to accept optional parent context and derive layout_target_width/height from absoluteBoundingBox when size is absent, and compute insets using absolute coordinate deltas when relativeTransform is unavailable. Propagated parent context through all node type conversions (SECTION, COMPONENT, INSTANCE, FRAME, GROUP, RECTANGLE, ELLIPSE, BOOLEAN_OPERATION, VECTOR, TEXT, LINE, and IR nodes).
Test coverage
packages/grida-canvas-io-figma/__tests__/iofigma.rest-api.no-geometry.test.ts, packages/grida-canvas-sdk-render-figma/__tests__/refig.test.ts
Added test suite validating REST API document conversion without geometry=paths, ensuring dimensions derive from absoluteBoundingBox and insets compute correctly. Added integration test confirming FigmaRenderer successfully renders a node lacking size/relativeTransform fields without panic.

Sequence Diagram(s)

sequenceDiagram
    actor REST as REST API Response
    participant IO as Figma IO Factory
    participant EXP as Export Functions
    participant RAS as Raster Backend
    
    REST->>IO: Document with absoluteBoundingBox<br/>(no size/relativeTransform)
    IO->>IO: positioning_trait(node, parent)
    IO->>IO: Derive dimensions from<br/>absoluteBoundingBox
    IO->>IO: Compute insets from<br/>parent context
    IO->>EXP: Positioned Grida node
    EXP->>EXP: Validate pixel dimensions<br/>(pixel_w, pixel_h > 0)
    alt Dimensions valid
        EXP->>RAS: Create raster backend
        RAS->>RAS: Generate output (PNG/PDF/SVG)
    else Dimensions invalid
        EXP->>EXP: Return None
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

refig, bug-fix, canvas-wasm

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: preventing 0×0 raster surface panics when REST API JSON lacks geometry=paths, matching the core changes across export and figma-io modules.
Linked Issues check ✅ Passed The PR fully addresses issue #585 by adding absoluteBoundingBox fallbacks in positioning_trait, guarding zero/negative dimensions in export functions, and providing comprehensive tests covering the reported scenario.
Out of Scope Changes check ✅ Passed All changes directly address the root cause (missing size/relativeTransform in REST API responses): fallback dimension logic, export guards, and targeted test coverage for the no-geometry scenario.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/cli-build-command-failure-9541

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@softmarshmallow softmarshmallow marked this pull request as ready for review March 21, 2026 18:54
@softmarshmallow softmarshmallow merged commit e652a84 into main Mar 21, 2026
12 of 13 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bb99a71fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +630 to +631
const szx = node.size?.x ?? absBox?.width ?? 0;
const szy = node.size?.y ?? absBox?.height ?? 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Recover local dimensions before reapplying node rotation

When a REST node is fetched without geometry=paths but still carries a non-zero rotation, absoluteBoundingBox.width/height are the post-rotation axis-aligned bounds, not the node's local size. Writing those values straight into layout_target_width/height means the importer later feeds the AABB back into AffineTransform::from_box_center(..., width, height, rotation), effectively rotating the node twice. A 100×50 rectangle rotated 90° will import as a 50×100 local rect plus 90° rotation, so rotated FRAME/RECTANGLE/ELLIPSE/etc. render with the wrong size and placement in no-geometry REST documents.

Useful? React with 👍 / 👎.

Comment on lines +1626 to 1627
layout_target_width: node.size?.x ?? lineAbsBox?.width ?? 0,
layout_target_height: 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve line length in the no-geometry LINE fallback

For LINE nodes, absoluteBoundingBox.width is only the axis-aligned bbox width, not the segment length. The new fallback still hardcodes layout_target_height: 0, so a vertical or diagonal line fetched without geometry=paths collapses to roughly its stroke thickness once rotation is applied later. For example, a 100px vertical line with a 2px stroke will import as a 2px line instead of a 100px one.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refig / canvas-wasm] Node: panic "Failed to create raster surface" at scene.rs:154

2 participants