@pictor-sdk/ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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.0
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,
@@ -2829,38 +2831,70 @@ var import_react_native16 = require("react-native");
2829
2831
 
2830
2832
  // src/panels/layoutMath.ts
2831
2833
  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 }];
2834
+ return layoutCellBounds(preset).map((cell) => ({
2835
+ x: (cell.x / cell.width + 0.5) * cell.width,
2836
+ y: (cell.y / cell.height + 0.5) * cell.height,
2837
+ scale: cell.width
2838
+ }));
2839
+ }
2840
+ function layoutCellBounds(preset) {
2841
+ const dimensions = {
2842
+ "1": [1, 1],
2843
+ "2v": [1, 2],
2844
+ "2h": [2, 1],
2845
+ "3v": [1, 3],
2846
+ "4grid": [2, 2],
2847
+ "6grid": [2, 3]
2848
+ };
2849
+ const grid = dimensions[preset];
2850
+ if (!grid) throw new RangeError("Unknown layout preset");
2851
+ const [columns, rows] = grid;
2852
+ return Array.from({ length: columns * rows }, (_, index) => ({
2853
+ x: index % columns / columns,
2854
+ y: Math.floor(index / columns) / rows,
2855
+ width: 1 / columns,
2856
+ height: 1 / rows
2857
+ }));
2858
+ }
2859
+ function fitImageToLayoutCell(cell, documentSize, intrinsic, options = {}) {
2860
+ for (const size of [documentSize, intrinsic]) {
2861
+ if (![size.width, size.height].every((v) => Number.isFinite(v) && v > 0)) {
2862
+ throw new RangeError("Layout sizes must be finite and positive");
2862
2863
  }
2863
2864
  }
2865
+ 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) {
2866
+ throw new RangeError("Layout cell must lie inside the document");
2867
+ }
2868
+ const zoom = options.zoom ?? 1;
2869
+ const center = options.center ?? { x: 0.5, y: 0.5 };
2870
+ if (![zoom, center.x, center.y].every(Number.isFinite)) {
2871
+ throw new RangeError("Layout controls must be finite");
2872
+ }
2873
+ const base = centeredCropForAspect(
2874
+ intrinsic,
2875
+ cell.width * documentSize.width / (cell.height * documentSize.height)
2876
+ );
2877
+ const factor = Math.max(1, Math.min(32, zoom));
2878
+ const width = base.width / factor;
2879
+ const height = base.height / factor;
2880
+ return {
2881
+ pixelSnap: true,
2882
+ crop: {
2883
+ x: Math.max(0, Math.min(1 - width, center.x - width / 2)),
2884
+ y: Math.max(0, Math.min(1 - height, center.y - height / 2)),
2885
+ width,
2886
+ height
2887
+ },
2888
+ transform: {
2889
+ x: cell.x + cell.width / 2,
2890
+ y: cell.y + cell.height / 2,
2891
+ scale: cell.width,
2892
+ rotation: 0,
2893
+ anchor: { x: 0.5, y: 0.5 },
2894
+ flipX: false,
2895
+ flipY: false
2896
+ }
2897
+ };
2864
2898
  }
2865
2899
  function cellToTransformPatch(cell) {
2866
2900
  return { x: cell.x, y: cell.y, scale: cell.scale, rotation: 0 };
@@ -2873,17 +2907,23 @@ var PRESETS = [
2873
2907
  { id: "2v", label: "2 rows" },
2874
2908
  { id: "2h", label: "2 cols" },
2875
2909
  { id: "3v", label: "3 rows" },
2876
- { id: "4grid", label: "Grid" }
2910
+ { id: "4grid", label: "Grid" },
2911
+ { id: "6grid", label: "6 grid" }
2877
2912
  ];
2878
2913
  function LayoutPanel() {
2879
2914
  const { document, dispatch } = (0, import_core18.useEditor)();
2880
2915
  const theme = useTheme();
2881
2916
  const apply = (preset) => {
2882
- const cells = layoutCells(preset, document.size);
2917
+ const cells = layoutCellBounds(preset);
2883
2918
  const images = document.layers.filter((l) => l.type === "image");
2919
+ const fits = images.slice(0, cells.length).map((layer, i) => ({
2920
+ layer,
2921
+ fit: fitImageToLayoutCell(cells[i], document.size, layer.data.intrinsic)
2922
+ }));
2884
2923
  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]) });
2924
+ fits.forEach(({ layer, fit }) => {
2925
+ dispatch({ type: "layer/transform", id: layer.id, patch: fit.transform });
2926
+ dispatch({ type: "layer/patchData", id: layer.id, patch: { crop: fit.crop, pixelSnap: fit.pixelSnap } });
2887
2927
  });
2888
2928
  dispatch({ type: "history/endBatch" });
2889
2929
  };
@@ -4601,6 +4641,8 @@ function TextPanel() {
4601
4641
  createGestureController,
4602
4642
  createUiStateStore,
4603
4643
  defaultTheme,
4644
+ fitImageToLayoutCell,
4645
+ layoutCellBounds,
4604
4646
  layoutCells,
4605
4647
  mergeSlots,
4606
4648
  mergeTheme,