@pictor-sdk/ui 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ photo and video editor SDK for React Native. Public APIs are pre-1.0 and experim
8
8
  ## Install
9
9
 
10
10
  ```sh
11
- pnpm add @pictor-sdk/ui@0.1.0
11
+ pnpm add @pictor-sdk/ui@0.2.1
12
12
  ```
13
13
 
14
14
  Install the package's declared peer dependencies in your app. Keep React, React Native,
@@ -21,6 +21,17 @@ of device verification on every supported version/platform.
21
21
 
22
22
  ## Documentation and contributing
23
23
 
24
+ ### Story Layout in 0.2.0
25
+
26
+ Upgrade `@pictor-sdk/core`, `@pictor-sdk/skia` and `@pictor-sdk/ui` together.
27
+ `layoutCellBounds('6grid')` supplies a two-column, three-row grid;
28
+ `fitImageToLayoutCell(cell, document.size, intrinsic, { zoom, center })` returns
29
+ the fixed-cell transform, source crop and `pixelSnap` flag. Apply all three in
30
+ one history batch. Zoom and pan change the crop, not the layer's cell bounds.
31
+ See the [host integration guide](https://github.com/zahidhasanaunto/pictor/blob/main/docs/STORY-LAYOUT.md).
32
+
33
+ ### Project links
34
+
24
35
  - [Repository and example app](https://github.com/zahidhasanaunto/pictor)
25
36
  - [Public API inventory](https://github.com/zahidhasanaunto/pictor/blob/main/docs/API-SURFACE.md)
26
37
  - [Release and contributor guide](https://github.com/zahidhasanaunto/pictor/blob/main/docs/RELEASING.md)
package/dist/index.d.mts CHANGED
@@ -758,7 +758,7 @@ declare function LayersPanel(): React__default.JSX.Element;
758
758
  declare function LayoutPanel(): React__default.JSX.Element;
759
759
 
760
760
  /** The bundled multi-photo arrangements, by cell count and split direction. */
761
- type LayoutPreset = '1' | '2v' | '2h' | '3v' | '4grid';
761
+ type LayoutPreset = '1' | '2v' | '2h' | '3v' | '4grid' | '6grid';
762
762
  /** One slot in a layout: where its photo sits, in normalized document units. */
763
763
  interface LayoutCell {
764
764
  /** centre + scale in normalized document units */
@@ -773,6 +773,38 @@ interface LayoutCell {
773
773
  * canvas size works.
774
774
  */
775
775
  declare function layoutCells(preset: LayoutPreset, _doc: Size): LayoutCell[];
776
+ /**
777
+ * Axis-aligned cell rectangles, row-major, normalized to document width/height.
778
+ * x/y are TOP-LEFT coordinates, unlike the centres in legacy LayoutCell.
779
+ * Returns fresh rectangles; no document aspect ratio is assumed.
780
+ */
781
+ declare function layoutCellBounds(preset: LayoutPreset): Rect[];
782
+ /** Source-window controls; pan is a source-space crop centre, not a layer translation. */
783
+ interface LayoutCropOptions {
784
+ /** Magnification relative to crop-to-fill. Clamped to [1, 32]; default 1. */
785
+ zoom?: number;
786
+ /** Desired source-normalized crop centre; clamped so the crop stays in the source. */
787
+ center?: {
788
+ x: number;
789
+ y: number;
790
+ };
791
+ }
792
+ /** Crop and complete transform to apply together in one history batch. */
793
+ interface LayoutImageFit {
794
+ crop: Rect;
795
+ transform: Transform;
796
+ /** Apply to ImageLayerData together with crop for seam-free cell edges. */
797
+ pixelSnap: true;
798
+ }
799
+ /**
800
+ * Fill a fixed cell without bleed. Zoom/pan alter only the source crop, never
801
+ * the cell bounds. intrinsic must describe the decoded, orientation-corrected
802
+ * source. Recompute after source replacement or document resize.
803
+ * Throws RangeError for non-finite controls, invalid sizes or out-of-document cells.
804
+ * Apply transform with layer/transform and crop + pixelSnap with layer/patchData in one batch.
805
+ * General layer gestures must not subsequently move/rotate/scale a fixed cell.
806
+ */
807
+ declare function fitImageToLayoutCell(cell: Rect, documentSize: Size, intrinsic: Size, options?: LayoutCropOptions): LayoutImageFit;
776
808
  /** The transform patch that moves a layer into a cell, leaving its other fields alone. */
777
809
  declare function cellToTransformPatch(cell: LayoutCell): Partial<Transform>;
778
810
 
@@ -830,4 +862,4 @@ declare function UiStateProvider({ children }: {
830
862
  /** Read and write one key of the ephemeral UI state. Throws outside an `<Editor>`. */
831
863
  declare function useUiState<T>(key: string, fallback: T): [T, (value: T) => void];
832
864
 
833
- export { type AddPhotoController, type AddPhotoControllerOptions, AddPhotoPanel, AdjustPanel, type BrushConfig, CanvasStage, type CanvasStageProps, CropGestureSurface, type CropGestureSurfaceProps, CropGrid, CropPanel, DEFAULT_BRUSH, type DrawController, DrawPanel, DrawingSurface, Editor, EditorGestures, type EditorGesturesProps, type EditorProps, type EditorSlots, type EditorTheme, FilterStrip, type GestureController, type GestureControllerOptions, type GestureMetrics, type GestureSnapEvent, type GestureSurfaceKind, type HostCallbacks, HostCallbacksProvider, InteractionGuides, type InteractionGuidesProps, InteractionProvider, InvalidImageResultError, LayerGestureSurface, type LayerGestureSurfaceProps, LayersPanel, type LayoutCell, LayoutPanel, type LayoutPreset, MediaPanel, type PartialTheme, STRAIGHTEN_LIMIT, Slider, type SliderProps, type StickerAsset, StickerPanel, StoryMediaGestureSurface, type StoryMediaGestureSurfaceProps, StraightenRuler, type StraightenRulerProps, TextComposer, TextComposerHost, type TextComposerProps, TextPanel, type TextSessionController, ThemeContext, ToolPanelHost, ToolRail, TopBar, type TopBarProps, UiStateProvider, type UiStateStore, type UseTextSessionOptions, canvasSizeForAspect, cellToTransformPatch, centeredCropForAspect, createAddPhotoController, createDrawController, createGestureController, createUiStateStore, defaultTheme, layoutCells, mergeSlots, mergeTheme, radiansToStraighten, resolveGestureSurface, resolvePanel, straightenToRadians, useBaseImageLayer, useBaseMediaLayer, useHostCallbacks, useInteractionState, useTargetImageLayer, useTextSession, useTheme, useUiState };
865
+ export { type AddPhotoController, type AddPhotoControllerOptions, AddPhotoPanel, AdjustPanel, type BrushConfig, CanvasStage, type CanvasStageProps, CropGestureSurface, type CropGestureSurfaceProps, CropGrid, CropPanel, DEFAULT_BRUSH, type DrawController, DrawPanel, DrawingSurface, Editor, EditorGestures, type EditorGesturesProps, type EditorProps, type EditorSlots, type EditorTheme, FilterStrip, type GestureController, type GestureControllerOptions, type GestureMetrics, type GestureSnapEvent, type GestureSurfaceKind, type HostCallbacks, HostCallbacksProvider, InteractionGuides, type InteractionGuidesProps, InteractionProvider, InvalidImageResultError, LayerGestureSurface, type LayerGestureSurfaceProps, LayersPanel, type LayoutCell, type LayoutCropOptions, type LayoutImageFit, LayoutPanel, type LayoutPreset, MediaPanel, type PartialTheme, STRAIGHTEN_LIMIT, Slider, type SliderProps, type StickerAsset, StickerPanel, StoryMediaGestureSurface, type StoryMediaGestureSurfaceProps, StraightenRuler, type StraightenRulerProps, TextComposer, TextComposerHost, type TextComposerProps, TextPanel, type TextSessionController, ThemeContext, ToolPanelHost, ToolRail, TopBar, type TopBarProps, UiStateProvider, type UiStateStore, type UseTextSessionOptions, canvasSizeForAspect, cellToTransformPatch, centeredCropForAspect, createAddPhotoController, createDrawController, createGestureController, createUiStateStore, defaultTheme, fitImageToLayoutCell, layoutCellBounds, layoutCells, mergeSlots, mergeTheme, radiansToStraighten, resolveGestureSurface, resolvePanel, straightenToRadians, useBaseImageLayer, useBaseMediaLayer, useHostCallbacks, useInteractionState, useTargetImageLayer, useTextSession, useTheme, useUiState };
package/dist/index.d.ts CHANGED
@@ -758,7 +758,7 @@ declare function LayersPanel(): React__default.JSX.Element;
758
758
  declare function LayoutPanel(): React__default.JSX.Element;
759
759
 
760
760
  /** The bundled multi-photo arrangements, by cell count and split direction. */
761
- type LayoutPreset = '1' | '2v' | '2h' | '3v' | '4grid';
761
+ type LayoutPreset = '1' | '2v' | '2h' | '3v' | '4grid' | '6grid';
762
762
  /** One slot in a layout: where its photo sits, in normalized document units. */
763
763
  interface LayoutCell {
764
764
  /** centre + scale in normalized document units */
@@ -773,6 +773,38 @@ interface LayoutCell {
773
773
  * canvas size works.
774
774
  */
775
775
  declare function layoutCells(preset: LayoutPreset, _doc: Size): LayoutCell[];
776
+ /**
777
+ * Axis-aligned cell rectangles, row-major, normalized to document width/height.
778
+ * x/y are TOP-LEFT coordinates, unlike the centres in legacy LayoutCell.
779
+ * Returns fresh rectangles; no document aspect ratio is assumed.
780
+ */
781
+ declare function layoutCellBounds(preset: LayoutPreset): Rect[];
782
+ /** Source-window controls; pan is a source-space crop centre, not a layer translation. */
783
+ interface LayoutCropOptions {
784
+ /** Magnification relative to crop-to-fill. Clamped to [1, 32]; default 1. */
785
+ zoom?: number;
786
+ /** Desired source-normalized crop centre; clamped so the crop stays in the source. */
787
+ center?: {
788
+ x: number;
789
+ y: number;
790
+ };
791
+ }
792
+ /** Crop and complete transform to apply together in one history batch. */
793
+ interface LayoutImageFit {
794
+ crop: Rect;
795
+ transform: Transform;
796
+ /** Apply to ImageLayerData together with crop for seam-free cell edges. */
797
+ pixelSnap: true;
798
+ }
799
+ /**
800
+ * Fill a fixed cell without bleed. Zoom/pan alter only the source crop, never
801
+ * the cell bounds. intrinsic must describe the decoded, orientation-corrected
802
+ * source. Recompute after source replacement or document resize.
803
+ * Throws RangeError for non-finite controls, invalid sizes or out-of-document cells.
804
+ * Apply transform with layer/transform and crop + pixelSnap with layer/patchData in one batch.
805
+ * General layer gestures must not subsequently move/rotate/scale a fixed cell.
806
+ */
807
+ declare function fitImageToLayoutCell(cell: Rect, documentSize: Size, intrinsic: Size, options?: LayoutCropOptions): LayoutImageFit;
776
808
  /** The transform patch that moves a layer into a cell, leaving its other fields alone. */
777
809
  declare function cellToTransformPatch(cell: LayoutCell): Partial<Transform>;
778
810
 
@@ -830,4 +862,4 @@ declare function UiStateProvider({ children }: {
830
862
  /** Read and write one key of the ephemeral UI state. Throws outside an `<Editor>`. */
831
863
  declare function useUiState<T>(key: string, fallback: T): [T, (value: T) => void];
832
864
 
833
- export { type AddPhotoController, type AddPhotoControllerOptions, AddPhotoPanel, AdjustPanel, type BrushConfig, CanvasStage, type CanvasStageProps, CropGestureSurface, type CropGestureSurfaceProps, CropGrid, CropPanel, DEFAULT_BRUSH, type DrawController, DrawPanel, DrawingSurface, Editor, EditorGestures, type EditorGesturesProps, type EditorProps, type EditorSlots, type EditorTheme, FilterStrip, type GestureController, type GestureControllerOptions, type GestureMetrics, type GestureSnapEvent, type GestureSurfaceKind, type HostCallbacks, HostCallbacksProvider, InteractionGuides, type InteractionGuidesProps, InteractionProvider, InvalidImageResultError, LayerGestureSurface, type LayerGestureSurfaceProps, LayersPanel, type LayoutCell, LayoutPanel, type LayoutPreset, MediaPanel, type PartialTheme, STRAIGHTEN_LIMIT, Slider, type SliderProps, type StickerAsset, StickerPanel, StoryMediaGestureSurface, type StoryMediaGestureSurfaceProps, StraightenRuler, type StraightenRulerProps, TextComposer, TextComposerHost, type TextComposerProps, TextPanel, type TextSessionController, ThemeContext, ToolPanelHost, ToolRail, TopBar, type TopBarProps, UiStateProvider, type UiStateStore, type UseTextSessionOptions, canvasSizeForAspect, cellToTransformPatch, centeredCropForAspect, createAddPhotoController, createDrawController, createGestureController, createUiStateStore, defaultTheme, layoutCells, mergeSlots, mergeTheme, radiansToStraighten, resolveGestureSurface, resolvePanel, straightenToRadians, useBaseImageLayer, useBaseMediaLayer, useHostCallbacks, useInteractionState, useTargetImageLayer, useTextSession, useTheme, useUiState };
865
+ export { type AddPhotoController, type AddPhotoControllerOptions, AddPhotoPanel, AdjustPanel, type BrushConfig, CanvasStage, type CanvasStageProps, CropGestureSurface, type CropGestureSurfaceProps, CropGrid, CropPanel, DEFAULT_BRUSH, type DrawController, DrawPanel, DrawingSurface, Editor, EditorGestures, type EditorGesturesProps, type EditorProps, type EditorSlots, type EditorTheme, FilterStrip, type GestureController, type GestureControllerOptions, type GestureMetrics, type GestureSnapEvent, type GestureSurfaceKind, type HostCallbacks, HostCallbacksProvider, InteractionGuides, type InteractionGuidesProps, InteractionProvider, InvalidImageResultError, LayerGestureSurface, type LayerGestureSurfaceProps, LayersPanel, type LayoutCell, type LayoutCropOptions, type LayoutImageFit, LayoutPanel, type LayoutPreset, MediaPanel, type PartialTheme, STRAIGHTEN_LIMIT, Slider, type SliderProps, type StickerAsset, StickerPanel, StoryMediaGestureSurface, type StoryMediaGestureSurfaceProps, StraightenRuler, type StraightenRulerProps, TextComposer, TextComposerHost, type TextComposerProps, TextPanel, type TextSessionController, ThemeContext, ToolPanelHost, ToolRail, TopBar, type TopBarProps, UiStateProvider, type UiStateStore, type UseTextSessionOptions, canvasSizeForAspect, cellToTransformPatch, centeredCropForAspect, createAddPhotoController, createDrawController, createGestureController, createUiStateStore, defaultTheme, fitImageToLayoutCell, layoutCellBounds, layoutCells, mergeSlots, mergeTheme, radiansToStraighten, resolveGestureSurface, resolvePanel, straightenToRadians, useBaseImageLayer, useBaseMediaLayer, useHostCallbacks, useInteractionState, useTargetImageLayer, useTextSession, useTheme, useUiState };
package/dist/index.js CHANGED
@@ -73,6 +73,8 @@ __export(index_exports, {
73
73
  createGestureController: () => createGestureController,
74
74
  createUiStateStore: () => createUiStateStore,
75
75
  defaultTheme: () => defaultTheme,
76
+ fitImageToLayoutCell: () => fitImageToLayoutCell,
77
+ layoutCellBounds: () => layoutCellBounds,
76
78
  layoutCells: () => layoutCells,
77
79
  mergeSlots: () => mergeSlots,
78
80
  mergeTheme: () => mergeTheme,
@@ -808,10 +810,12 @@ function LayerGestureSurface({ onSnap }) {
808
810
  );
809
811
  const aspect = document.size.height / document.size.width;
810
812
  const [measureVersion, setMeasureVersion] = (0, import_react9.useState)(0);
811
- (0, import_react9.useEffect)(
812
- () => renderer.textMeasures.subscribe(() => setMeasureVersion((v) => v + 1)),
813
- [renderer]
814
- );
813
+ (0, import_react9.useEffect)(() => {
814
+ const refresh = () => setMeasureVersion((v) => v + 1);
815
+ const unsubscribe = renderer.textMeasures.subscribe(refresh);
816
+ refresh();
817
+ return unsubscribe;
818
+ }, [renderer]);
815
819
  const hitCandidates = (0, import_react9.useMemo)(
816
820
  () => document.layers.map((layer, index) => ({ layer, index })).filter(
817
821
  ({ layer, index }) => !layer.hidden && !layer.locked && (0, import_core8.isDirectManipulationLayer)(layer, index, profile.interaction?.layers)
@@ -2829,38 +2833,70 @@ var import_react_native16 = require("react-native");
2829
2833
 
2830
2834
  // src/panels/layoutMath.ts
2831
2835
  function layoutCells(preset, _doc) {
2832
- switch (preset) {
2833
- case "1":
2834
- return [{ x: 0.5, y: 0.5, scale: 1 }];
2835
- case "2v":
2836
- return [
2837
- { x: 0.5, y: 0.25, scale: 1 },
2838
- { x: 0.5, y: 0.75, scale: 1 }
2839
- ];
2840
- case "2h":
2841
- return [
2842
- { x: 0.25, y: 0.5, scale: 0.5 },
2843
- { x: 0.75, y: 0.5, scale: 0.5 }
2844
- ];
2845
- case "3v":
2846
- return [
2847
- { x: 0.5, y: 1 / 6, scale: 1 },
2848
- { x: 0.5, y: 0.5, scale: 1 },
2849
- { x: 0.5, y: 5 / 6, scale: 1 }
2850
- ];
2851
- case "4grid":
2852
- return [
2853
- { x: 0.25, y: 0.25, scale: 0.5 },
2854
- { x: 0.75, y: 0.25, scale: 0.5 },
2855
- { x: 0.25, y: 0.75, scale: 0.5 },
2856
- { x: 0.75, y: 0.75, scale: 0.5 }
2857
- ];
2858
- default: {
2859
- const _exhaustive = preset;
2860
- void _exhaustive;
2861
- return [{ x: 0.5, y: 0.5, scale: 1 }];
2836
+ return layoutCellBounds(preset).map((cell) => ({
2837
+ x: (cell.x / cell.width + 0.5) * cell.width,
2838
+ y: (cell.y / cell.height + 0.5) * cell.height,
2839
+ scale: cell.width
2840
+ }));
2841
+ }
2842
+ function layoutCellBounds(preset) {
2843
+ const dimensions = {
2844
+ "1": [1, 1],
2845
+ "2v": [1, 2],
2846
+ "2h": [2, 1],
2847
+ "3v": [1, 3],
2848
+ "4grid": [2, 2],
2849
+ "6grid": [2, 3]
2850
+ };
2851
+ const grid = dimensions[preset];
2852
+ if (!grid) throw new RangeError("Unknown layout preset");
2853
+ const [columns, rows] = grid;
2854
+ return Array.from({ length: columns * rows }, (_, index) => ({
2855
+ x: index % columns / columns,
2856
+ y: Math.floor(index / columns) / rows,
2857
+ width: 1 / columns,
2858
+ height: 1 / rows
2859
+ }));
2860
+ }
2861
+ function fitImageToLayoutCell(cell, documentSize, intrinsic, options = {}) {
2862
+ for (const size of [documentSize, intrinsic]) {
2863
+ if (![size.width, size.height].every((v) => Number.isFinite(v) && v > 0)) {
2864
+ throw new RangeError("Layout sizes must be finite and positive");
2862
2865
  }
2863
2866
  }
2867
+ if (![cell.x, cell.y, cell.width, cell.height].every(Number.isFinite) || cell.x < 0 || cell.y < 0 || cell.width <= 0 || cell.height <= 0 || cell.x + cell.width > 1 + 1e-12 || cell.y + cell.height > 1 + 1e-12) {
2868
+ throw new RangeError("Layout cell must lie inside the document");
2869
+ }
2870
+ const zoom = options.zoom ?? 1;
2871
+ const center = options.center ?? { x: 0.5, y: 0.5 };
2872
+ if (![zoom, center.x, center.y].every(Number.isFinite)) {
2873
+ throw new RangeError("Layout controls must be finite");
2874
+ }
2875
+ const base = centeredCropForAspect(
2876
+ intrinsic,
2877
+ cell.width * documentSize.width / (cell.height * documentSize.height)
2878
+ );
2879
+ const factor = Math.max(1, Math.min(32, zoom));
2880
+ const width = base.width / factor;
2881
+ const height = base.height / factor;
2882
+ return {
2883
+ pixelSnap: true,
2884
+ crop: {
2885
+ x: Math.max(0, Math.min(1 - width, center.x - width / 2)),
2886
+ y: Math.max(0, Math.min(1 - height, center.y - height / 2)),
2887
+ width,
2888
+ height
2889
+ },
2890
+ transform: {
2891
+ x: cell.x + cell.width / 2,
2892
+ y: cell.y + cell.height / 2,
2893
+ scale: cell.width,
2894
+ rotation: 0,
2895
+ anchor: { x: 0.5, y: 0.5 },
2896
+ flipX: false,
2897
+ flipY: false
2898
+ }
2899
+ };
2864
2900
  }
2865
2901
  function cellToTransformPatch(cell) {
2866
2902
  return { x: cell.x, y: cell.y, scale: cell.scale, rotation: 0 };
@@ -2873,17 +2909,23 @@ var PRESETS = [
2873
2909
  { id: "2v", label: "2 rows" },
2874
2910
  { id: "2h", label: "2 cols" },
2875
2911
  { id: "3v", label: "3 rows" },
2876
- { id: "4grid", label: "Grid" }
2912
+ { id: "4grid", label: "Grid" },
2913
+ { id: "6grid", label: "6 grid" }
2877
2914
  ];
2878
2915
  function LayoutPanel() {
2879
2916
  const { document, dispatch } = (0, import_core18.useEditor)();
2880
2917
  const theme = useTheme();
2881
2918
  const apply = (preset) => {
2882
- const cells = layoutCells(preset, document.size);
2919
+ const cells = layoutCellBounds(preset);
2883
2920
  const images = document.layers.filter((l) => l.type === "image");
2921
+ const fits = images.slice(0, cells.length).map((layer, i) => ({
2922
+ layer,
2923
+ fit: fitImageToLayoutCell(cells[i], document.size, layer.data.intrinsic)
2924
+ }));
2884
2925
  dispatch({ type: "history/beginBatch", label: "layout" });
2885
- images.slice(0, cells.length).forEach((layer, i) => {
2886
- dispatch({ type: "layer/transform", id: layer.id, patch: cellToTransformPatch(cells[i]) });
2926
+ fits.forEach(({ layer, fit }) => {
2927
+ dispatch({ type: "layer/transform", id: layer.id, patch: fit.transform });
2928
+ dispatch({ type: "layer/patchData", id: layer.id, patch: { crop: fit.crop, pixelSnap: fit.pixelSnap } });
2887
2929
  });
2888
2930
  dispatch({ type: "history/endBatch" });
2889
2931
  };
@@ -4601,6 +4643,8 @@ function TextPanel() {
4601
4643
  createGestureController,
4602
4644
  createUiStateStore,
4603
4645
  defaultTheme,
4646
+ fitImageToLayoutCell,
4647
+ layoutCellBounds,
4604
4648
  layoutCells,
4605
4649
  mergeSlots,
4606
4650
  mergeTheme,