본문으로 건너뛰기

Scale tool (K) — parameter-space scaling (A.k.a Apply Scale or K-Scale)

feature idstatusdescriptionPRs
parametric-scalingproposedParameter-space scaling operation for Grida.#471

Key principles

  • Visual accuracy over “clean” values: Scale (K) is an authoring-time operation whose primary goal is that the post-scale render is visually consistent with a uniform similarity transform. As a result, it is normal (and expected) for authored values to become fractional / “dirty” after repeated scaling.

  • No extra quantization / optimization in the core rewrite: The core scaling rules should apply the exact factor ss to existing numeric values without “cleaning up” the result. Any additional rounding/optimization risks accumulating error over repeated operations or round trips. (Gesture-input quantization may exist for UX stability, but is intentionally out of scope for this specification.)

  • Round-trip consistency (best-effort): Perfect round-trip guarantees are not always possible across multi-step edits, but for simple numeric cases the rewrite should behave consistently within the limits of JavaScript number precision. Example: 1to0.01xto100xto11 \\to 0.01x \\to 100x \\to 1 should return to (approximately) the original value.

  • Deterministic, minimal rewrite (do not change the nature of properties): Scaling MUST NOT reinterpret or “bake” non-numeric authored intent into numeric values. For example, a property that is auto, undefined, or otherwise non-numeric must remain so. Scale (K) bakes existing length values, not “bake-all”.

Context

This document specifies Scale (K) as a parameter-space scaling operation.

In Grida, nodes are authored as graphics primitives whose visual appearance is defined by a set of explicit parameters (box geometry, stroke widths, radii, text sizes, effects, etc.), rather than by a persistent transform matrix applied at render time.

As a result, a design-tool Scale operation cannot be defined as a simple geometric transform. Instead, it is an authoring-time operation that rewrites geometry-defining parameters so that the rendered output matches the result of applying a uniform similarity transform, while eliminating the transform itself.

In other words:

Parameter-space scale re-authors an object at a new scale.

This operation exists purely at authoring time. It is not a rendering, GPU, or runtime concept, and should not be confused with transform-based scaling used in game engines or scene graphs. Its sole purpose is to preserve visual identity while updating the underlying parameters so that future edits behave as if the object were originally authored at the new size.

Similar scaling behavior appears across multiple vector and design tools, although the concept is rarely named explicitly. This document intentionally avoids binding the definition to a single product, and instead specifies the operation as a general authoring-time model that can be implemented consistently across different systems.

Proposal status

  • Status: Draft proposal (WG)
  • Intended audience: editor + schema + rendering implementers
  • Scope: authoring-time document rewrite (not runtime rendering)

Problem statement

We need a scaling operation that:

  • produces the same visual result as geometric scaling, and
  • updates the authored parameters so subsequent edits behave as if the object was created at the new size.

Simple resize is insufficient because it changes box geometry but leaves geometry-contributing parameters unchanged (stroke widths, effect radii, etc.), producing a different visual identity.

Goals / non-goals

  • Goals

    • Visual identity preservation: scaling should preserve proportions of all geometry-contributing parameters.
    • Backend-independence: behavior is defined over the document model, not a renderer implementation.
    • Deterministic rewrite: applying scale produces a stable authored state (no latent transform needed).
  • Non-goals

    • Non-uniform scaling is not specified here (future extension).
    • Layout reflow is not performed; we do not recompute constraints or auto-layout.
    • Arbitrary CSS scaling is not attempted without a typed/safe subset model.
    • Content editing is not part of scale (text content, URLs, IDs, etc. are unchanged).

Core invariants (normative)

When applied with multiplier ss:

  • I1. Layout geometry update: numeric layout geometry fields (e.g. left/top/right/bottom/width/height) MUST be scaled by ss without reinterpreting author intent (non-numeric values like "auto" MUST be preserved).
  • I2. Parameter rewrite: all tracked, geometry-contributing parameters MUST be multiplied by ss (or scaled according to their field-level rules).
  • I3. Invariants preserved: unitless ratios, enums, IDs, and content MUST remain unchanged.
  • I4. No layout reflow: the operation MUST NOT attempt to resolve constraints or reflow layout. It only scales stored values.

Definitions

  • Resize (simple resize): updates only box geometry (typically width/height and left/top, and/or a transform), leaving stroke/effects/text sizes unchanged.
  • Scale (K) (parameter-space scale): applies a uniform scale multiplier to box geometry and all geometry-contributing parameters, baking the scale into authored values rather than storing a transform.

Scale factor

This specification defines uniform parameter-space scaling as the baseline behavior:

  • scale multiplier: ss where s0.01s \ge 0.01

(If we later support non-uniform scaling sx,sys_x, s_y, we must define how to map two factors into a single “thickness scale” for strokes/effects; see “Future extensions”.)

Layout geometry (coordinate-space; no anchor)

For parameter-space scaling we treat layout geometry fields as authored numeric values, and scale them just like other length values.

For a node with layout fields (left, top, right, bottom, width, height):

  • If a field is a number, multiply it by ss.
  • If a field is non-numeric (e.g. "auto"), preserve it as-is (do not bake or resolve it).

Notes:

  • For nodes using position: "relative", offsets (left/top/right/bottom) are still lengths but their meaning depends on layout context. K-scale scales the stored values when present, but does not attempt to reflow layout.
  • The editor UI may expose an “origin” control for interactive workflows, but anchor-based geometry rewriting is an implementation detail and is not required for the core parameter-space rewrite.

Examples

  • Rectangle

    • Before: width=100, height=100, stroke_width=3
    • Apply s=2s=2
    • After: width=200, height=200, stroke_width=6
  • Progressive blur

    • The progressive blur line coordinates are normalized (x1/y1/x2/y2 in -1..1), so they remain unchanged.
    • The blur radii (radius, radius2) scale by ss.
  • Noise

    • noise_size scales by ss so the grain’s apparent feature size scales with the object.
    • density remains unchanged (unitless).

What scales?

A property should be marked Scale = Y if it directly contributes to rendered geometry in absolute units (px-like lengths) or contains such geometry (e.g. vector path coordinates).

A property should be marked Scale = N if it is:

  • Identity / metadata (id, name)
  • Boolean/enum toggles (active, locked, blend_mode)
  • Unitless ratios/percentages (inner_radius in 0..1, line_height in %)
  • Colors/paints (paint geometry may be a future extension, but base color values do not scale)

Property scaling (tracked parameters)

This section tracks only parameters that are relevant to parameter-space scaling:

  • values that scale (lengths, coordinates), and
  • values that are explicitly invariant but matter for correct semantics (angles, ratios, enums that control how scaled geometry is interpreted).

Reference: packages/grida-canvas-schema/grida.ts

namerolescale (Y/N)reason / notes
left, top, right, bottomlayoutYAbsolute/offset lengths; scale relative to anchor.
width, heightlayoutY*Scale only numeric/px-like lengths. Do not scale %, viewport units, or \"auto\".
rotationtransformNAngle in degrees; scaling does not change angles.
corner_radiusshapeYLength.
rectangular_corner_radius_top_leftshape-rectYLength.
rectangular_corner_radius_top_rightshape-rectYLength.
rectangular_corner_radius_bottom_leftshape-rectYLength.
rectangular_corner_radius_bottom_rightshape-rectYLength.
corner_smoothingshapeNUnitless smoothing factor.
padding (and per-side fields)layoutYLength(s).
main_axis_gap, cross_axis_gaplayoutYLength gaps.
stroke_widthstrokeYLength (thickness).
stroke_dash_arraystrokeYDash/gap lengths.
rectangular_stroke_width_topstroke-rectYLength.
rectangular_stroke_width_rightstroke-rectYLength.
rectangular_stroke_width_bottomstroke-rectYLength.
rectangular_stroke_width_leftstroke-rectYLength.
stroke_width_profilestrokeY*See Stroke width profile (cg.VariableWidthProfile) for per-stop field scaling.
angle, angle_offsetshapeNDegrees (ellipse arc).
inner_radius (ellipse arc / star)shapeNRatio 0..1; keep topology.
font_sizetextYLength (px-like).
letter_spacing, word_spacingtextNStored as em-percentage; scaling font size already scales absolute spacing.
line_heighttextNStored as percentage; keep relative line-height.
fe_blureffectY*See Filter effects section; radii scale, normalized progressive coords do not.
fe_backdrop_blureffectY*See Filter effects section.
fe_shadowseffectY*See Filter effects section.
fe_liquid_glasseffectY*See Filter effects section.
fe_noiseseffectY*See Filter effects section; notably noise_size scales.
vector_networkvectorYControl point coordinates are geometric.
pathsvectorYPath geometry coordinates are geometric.
guides[].offsetsceneNSee Ambiguous / implementation-defined properties.
edges[] position points (x,y)sceneNSee Ambiguous / implementation-defined properties.

Properties not tracked (irrelevant to parameter-space scaling)

The following categories are intentionally not listed in the table above, because K-scale should not mutate them and they add noise to the specification:

  • Identity / editor state: id, name, type, userdata, active, locked, expanded, z_index, etc.
  • Document repositories and references: nodes, links, images, bitmaps, scenes_ref, entry_scene_id, ImageRef.*, etc.
  • Content and external references: text, html, src, href, poster, alt, etc.
  • Component/template schema + runtime props: properties, props, default, component_id, template_id, overrides, etc.
  • Paint/color values: fill, stroke, *_paints, colors.
  • Arbitrary CSS: style (unknown subset; not safely scalable without a typed model).

Ambiguous / implementation-defined properties

Some properties look geometric (they contain lengths/coordinates), but their meaning and desired behavior can vary by product rules and editor UX. For these properties, this proposal treats them as non-scaled by default.

  • Guides (guides[].offset)

    • Default: N (do not scale)
    • Rationale: guides are editor/workspace UI aids; scaling objects should not re-author workspace guides.
    • Allowed extension: an implementation MAY offer a separate “scale guides” command, but it is not part of parameter-space scaling.
  • Edges (edges[] and positional EdgePointPosition2D.x/y)

    • Default: N (do not scale)
    • Rationale: edges may represent editor relationships/measurements/constraints rather than authored geometry; scaling content should not implicitly rewrite these references.
    • Allowed extension: an implementation MAY define a rule set for scaling edges (e.g. only when edges are explicit geometry in the authored scene), but such rules must be documented alongside the implementation.

Filter effects (field-level scaling)

This section expands the fe_* node properties into per-effect, field-level tables.

Reference types:

  • packages/grida-canvas-schema/grida.ts (IEffectsfe_* fields)
  • packages/grida-canvas-cg/lib.ts (cg.FilterEffect and the cg.Fe* types)

Shadow (cg.FeShadow)

Used by fe_shadows?: cg.FeShadow[].

fieldrolescale (Y/N)reason / notes
typeeffectNDiscriminator ("shadow").
inseteffectNBoolean; inner vs outer shadow.
activeeffectNToggle.
dxeffectYPixel offset.
dyeffectYPixel offset.
blureffectYPixel blur radius.
spreadeffectYPixel spread radius.
coloreffectNColor/alpha only.

Layer Blur (cg.FeLayerBlur)

Used by fe_blur?: cg.FeLayerBlur.

fieldrolescale (Y/N)reason / notes
typeeffectNDiscriminator ("filter-blur").
activeeffectNToggle.
blureffectY*Scales only the radii inside the nested blur; see below.

Blur variant: Gaussian (cg.FeGaussianBlur / cg.IFeGaussianBlur)

fieldrolescale (Y/N)reason / notes
typeeffectNDiscriminator ("blur").
radiuseffectYPixel blur radius.

Blur variant: Progressive (cg.FeProgressiveBlur / cg.IFeProgressiveBlur)

In @grida/cg, the progressive blur line (x1/y1/x2/y2) is stored in normalized node-local space (-1..1), so it must not be scaled; it naturally follows the node’s scaled bounds.

fieldrolescale (Y/N)reason / notes
typeeffectNDiscriminator ("progressive-blur").
x1effectNNormalized coordinate (node-local).
y1effectNNormalized coordinate (node-local).
x2effectNNormalized coordinate (node-local).
y2effectNNormalized coordinate (node-local).
radiuseffectYPixel blur radius at start.
radius2effectYPixel blur radius at end.

Backdrop Blur (cg.FeBackdropBlur)

Used by fe_backdrop_blur?: cg.FeBackdropBlur.

fieldrolescale (Y/N)reason / notes
typeeffectNDiscriminator ("backdrop-filter-blur").
activeeffectNToggle.
blureffectY*Same nested blur rules as Layer Blur (scale radii only, not normalized progressive coords).

Liquid Glass (cg.FeLiquidGlass)

Used by fe_liquid_glass?: cg.FeLiquidGlass.

fieldrolescale (Y/N)reason / notes
typeeffectNDiscriminator ("glass").
activeeffectNToggle.
light_intensityeffectNUnitless 0..1.
light_angleeffectNDegrees.
refractioneffectNUnitless 0..1.
dispersioneffectNUnitless 0..1.
deptheffectYPixel “thickness” / SDF depth.
radiuseffectYPixel blur radius for frosted glass.

Noise (cg.FeNoise)

Used by fe_noises?: cg.FeNoise[].

fieldrolescale (Y/N)reason / notes
typeeffectNDiscriminator ("noise").
activeeffectNToggle.
modeeffectNEnum (mono/duo/multi).
blend_modeeffectNEnum.
noise_sizeeffectYGrain size parameter; treat as pixel-like “feature size” so the texture scales with the object.
densityeffectNUnitless 0..1.
num_octaveseffectNCount.
seedeffectNRandom seed.
coloreffectNRGBA; mono mode only.
color1effectNRGBA; duo mode pattern color.
color2effectNRGBA; duo mode background color.
opacityeffectNUnitless 0..1; multi mode only.

Image paint filters (cg.ImageFilters) (field-level scaling)

These are the per-image adjustment controls used inside cg.ImagePaint.filters (exposure/contrast/etc.). They are unitless adjustments and should not be modified by K-scale.

fieldrolescale (Y/N)reason / notes
exposureimage-filterNUnitless adjustment.
contrastimage-filterNUnitless adjustment.
saturationimage-filterNUnitless adjustment.
temperatureimage-filterNUnitless adjustment.
tintimage-filterNUnitless adjustment.
highlightsimage-filterNUnitless adjustment.
shadowsimage-filterNUnitless adjustment.

Stroke width profile (cg.VariableWidthProfile) (field-level scaling)

stroke_width_profile?: cg.VariableWidthProfile is a profile with an array of stops (stops: VariableWidthStop[]).

Reference type: packages/grida-canvas-cg/lib.ts (cg.VariableWidthProfile, cg.VariableWidthStop)

cg.VariableWidthProfile

fieldrolescale (Y/N)reason / notes
stopsstrokeY*Array container; scale depends on per-stop fields below.

Stop: cg.VariableWidthStop

fieldrolescale (Y/N)reason / notes
ustrokeNUnitless curve parameter in 0..1; keeps relative position along stroke.
rstrokeYRadius/width value in pixels; scales with K-scale.

Notes per node type (what K-scale touches)

This is a practical checklist for implementers.

  • Scene (scene): do not scale guides or edges as part of parameter-space scaling.
  • Group (group): scale positioning + its children are scaled by selection traversal (tool-level responsibility).
  • Shape nodes (rectangle, ellipse, polygon, star, line, vector, boolean):
    • scale left/top/right/bottom, width/height
    • scale stroke_width, stroke_dash_array, rectangular stroke widths
    • scale corner radii
    • scale effects (fe_*) where applicable
    • scale embedded geometry containers (vector_network, paths)
  • Text (text):
    • scale box geometry
    • scale font_size
    • scale text stroke stroke_width (if used)
    • do not scale unitless/percentage text spacing knobs
  • Media (image, video, iframe):
    • scale box geometry
    • scale corner radii
    • scale rectangular stroke widths (if used)
  • Container/component (container, component):
    • scale box geometry
    • scale padding + gaps
    • scale corner radii + effects
    • do not change flex alignment enums
  • Instance/template instance (instance, template_instance):
    • scale box geometry and positioning
    • do not mutate props/properties by default

Future extensions / open questions

  • Non-uniform scale: if we later support (sx,sy)(s_x, s_y), define a canonical “thickness scale” sts_t for stroke/effects. Candidates: arithmetic mean ((sx+sy)/2)((s_x+s_y)/2), geometric mean sxsy\sqrt{s_x s_y}, or max max(sx,sy)\max(s_x,s_y). For now this spec defines uniform scaling only, so we keep ss.
  • Paint transforms: gradients/patterns may have their own transform spaces; decide whether K-scale should scale those transforms.

Model survey: parameter-space scaling in existing tools

Parameter-space scaling is an authoring-time concept that appears across many design and graphics tools under different names and UX presentations. While implementations vary, the underlying idea—rewriting geometry-defining parameters to preserve visual appearance—is consistent.

This section surveys representative models to clarify common ground and differences.

Figma

Figma exposes parameter-space scaling as Scale (K). The tool applies a uniform scale factor to bounding geometry and geometry-contributing parameters such as stroke widths, corner radii, and effects, without introducing a persistent transform.

Although the implementation is user-facing, the concept itself is not formally named in Figma’s documentation and is presented as a mode of resizing rather than as a distinct authoring operation.

Figma: Scale layers while maintaining proportions

Adobe Illustrator

Adobe tools distinguish between transform-based scaling and parameter rewriting through options such as “Scale Strokes & Effects”.

When enabled, stroke widths, dash patterns, and effect radii are scaled alongside object geometry. This explicitly separates:

  • geometric transforms (matrix-based)
  • baked, appearance-affecting scale

This model closely matches parameter-space scaling as defined in this document.

Inkscape

Inkscape frames scaling as a transform operation with user-configurable preferences (e.g. “Scale stroke width”).

While presented differently in the UI, enabling these options causes stroke and effect parameters to be rewritten, functionally aligning with parameter-space scaling despite transform-oriented terminology.

CAD / DCC tools (e.g. Blender)

Many 3D and CAD tools separate object scale from applied / frozen scale.

  • Object scale: stored as a transform, evaluated at runtime.
  • Apply / freeze scale: bakes scale into geometry data.

While primarily focused on geometry rather than appearance parameters, this distinction mirrors the authoring-time vs runtime separation central to parameter-space scaling.

Blender: Apply Scale

Summary

Across tools and domains, the same conceptual boundary recurs:

  • Transform scale: runtime, reversible, matrix-based
  • Parameter-space scale: authoring-time, destructive, geometry-defining

This document adopts the latter as a first-class authoring operation, independent of any specific product’s UX or terminology.