pptx-glimpse 3.0.0 → 3.1.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/dist/browser.js CHANGED
@@ -1,17 +1,31 @@
1
+ import {
2
+ collectUsedFonts
3
+ } from "./chunk-ZZKN4U5J.js";
1
4
  import {
2
5
  DEFAULT_OUTPUT_WIDTH,
6
+ addConnector,
7
+ addEmptySlideFromLayout,
8
+ addTextBox,
9
+ asEmu,
3
10
  asPartPath,
4
11
  asRelationshipId,
5
12
  asSourceNodeId,
6
- collectUsedFonts,
13
+ clearTextRunProperties,
7
14
  convertPptxToSvg,
15
+ deleteShape,
16
+ deleteSlide,
17
+ duplicateSlide,
8
18
  findShapeNodeBySourceHandle,
9
19
  readPptx,
20
+ renderPptxSourceModelToSvg,
21
+ replaceImageBytes,
10
22
  replaceParagraphPlainText,
11
23
  replaceTextRunPlainText,
24
+ setTextRunProperties,
25
+ unsafeBrandAssertion,
12
26
  updateShapeTransform,
13
27
  writePptx
14
- } from "./chunk-KMSN55AE.js";
28
+ } from "./chunk-NMAO44T7.js";
15
29
  import {
16
30
  DEFAULT_FONT_MAPPING,
17
31
  clearFontCache,
@@ -21,7 +35,7 @@ import {
21
35
  getMappedFont,
22
36
  getWarningEntries,
23
37
  getWarningSummary
24
- } from "./chunk-2AYNMYMC.js";
38
+ } from "./chunk-PGCREQQU.js";
25
39
  import {
26
40
  renderSvgToPng
27
41
  } from "./chunk-QPT6BMN5.js";
@@ -2795,13 +2809,23 @@ var EditorSession = class {
2795
2809
  const before = this.#document;
2796
2810
  if (commands.length === 0) return { ok: true, document: before };
2797
2811
  try {
2798
- const after = normalizeEditorEdits(
2799
- commands.reduce((document, command) => applyCommandToDocument(document, command), before)
2800
- );
2812
+ let after = before;
2813
+ const warnings = [];
2814
+ for (const command of commands) {
2815
+ after = applyCommandToDocument(after, command);
2816
+ warnings.push(...collectCommandWarnings(after, [command]));
2817
+ }
2818
+ after = normalizeEditorEdits(after);
2819
+ const dedupedWarnings = dedupeCommandWarnings(warnings);
2801
2820
  this.#document = after;
2821
+ this.reconcileSelectionAfterDocumentChange();
2802
2822
  this.#undoStack.push({ before, after });
2803
2823
  this.#redoStack.length = 0;
2804
- return { ok: true, document: after };
2824
+ return {
2825
+ ok: true,
2826
+ document: after,
2827
+ ...dedupedWarnings.length > 0 ? { warnings: dedupedWarnings } : {}
2828
+ };
2805
2829
  } catch (error) {
2806
2830
  return {
2807
2831
  ok: false,
@@ -2815,6 +2839,7 @@ var EditorSession = class {
2815
2839
  const entry = this.#undoStack.pop();
2816
2840
  if (entry === void 0) return { ok: false, reason: "empty-undo-stack" };
2817
2841
  this.#document = entry.before;
2842
+ this.reconcileSelectionAfterDocumentChange();
2818
2843
  this.#redoStack.push(entry);
2819
2844
  return { ok: true, document: entry.before };
2820
2845
  }
@@ -2822,9 +2847,16 @@ var EditorSession = class {
2822
2847
  const entry = this.#redoStack.pop();
2823
2848
  if (entry === void 0) return { ok: false, reason: "empty-redo-stack" };
2824
2849
  this.#document = entry.after;
2850
+ this.reconcileSelectionAfterDocumentChange();
2825
2851
  this.#undoStack.push(entry);
2826
2852
  return { ok: true, document: entry.after };
2827
2853
  }
2854
+ reconcileSelectionAfterDocumentChange() {
2855
+ if (this.#selection === void 0) return;
2856
+ if (findShapeNodeBySourceHandle(this.#document, this.#selection.shapeHandle) === void 0) {
2857
+ this.#selection = void 0;
2858
+ }
2859
+ }
2828
2860
  };
2829
2861
  function createEditorSession(document) {
2830
2862
  return new EditorSession(document);
@@ -2835,13 +2867,99 @@ function applyCommandToDocument(document, command) {
2835
2867
  return replaceTextRunPlainText(document, command.handle, command.text);
2836
2868
  case "replaceParagraphPlainText":
2837
2869
  return replaceParagraphPlainText(document, command.handle, command.text);
2870
+ case "setTextRunProperties":
2871
+ return setTextRunPropertiesCommand(document, command);
2872
+ case "clearTextRunProperties":
2873
+ return clearTextRunPropertiesCommand(document, command);
2838
2874
  case "moveShape":
2839
2875
  return moveShape(document, command);
2840
2876
  case "resizeShape":
2841
2877
  return resizeShape(document, command);
2842
2878
  case "setShapeTransform":
2843
2879
  return setShapeTransform(document, command);
2880
+ case "addTextBox":
2881
+ return addTextBoxCommand(document, command);
2882
+ case "addConnector":
2883
+ return addConnectorCommand(document, command);
2884
+ case "deleteShape":
2885
+ return deleteShape(document, command.handle);
2886
+ case "replaceImage":
2887
+ return replaceImageBytes(document, command.handle, command.bytes);
2888
+ case "addEmptySlideFromLayout":
2889
+ return addEmptySlideFromLayout(document, command);
2890
+ case "duplicateSlide":
2891
+ return duplicateSlide(document, command.handle);
2892
+ case "deleteSlide":
2893
+ return deleteSlide(document, command.handle);
2894
+ }
2895
+ }
2896
+ function collectCommandWarnings(document, commands) {
2897
+ if (!commands.some((command) => command.kind === "replaceImage")) return [];
2898
+ const warnings = [];
2899
+ const imageEdits = document.edits?.filter((edit) => edit.kind === "replaceImage") ?? [];
2900
+ for (const command of commands) {
2901
+ if (command.kind !== "replaceImage") continue;
2902
+ let edit;
2903
+ for (let index = imageEdits.length - 1; index >= 0; index -= 1) {
2904
+ const candidate = imageEdits[index];
2905
+ if (candidate.handle.partPath === command.handle.partPath && candidate.handle.nodeId === command.handle.nodeId && candidate.handle.relationshipId === command.handle.relationshipId && candidate.handle.orderingSlot === command.handle.orderingSlot) {
2906
+ edit = candidate;
2907
+ break;
2908
+ }
2909
+ }
2910
+ if (edit === void 0 || edit.sharedReferenceCount <= 1) continue;
2911
+ warnings.push({
2912
+ code: "shared-media-part",
2913
+ mediaPartPath: edit.mediaPartPath,
2914
+ referenceCount: edit.sharedReferenceCount,
2915
+ message: `replaceImage: media part '${edit.mediaPartPath}' is referenced by ${edit.sharedReferenceCount} pic shapes; all references now use the replacement image.`
2916
+ });
2917
+ }
2918
+ return warnings;
2919
+ }
2920
+ function dedupeCommandWarnings(warnings) {
2921
+ const deduped = /* @__PURE__ */ new Map();
2922
+ for (const warning of warnings) {
2923
+ const key = `${warning.code}\0${warning.mediaPartPath}`;
2924
+ if (!deduped.has(key)) deduped.set(key, warning);
2925
+ }
2926
+ return [...deduped.values()];
2927
+ }
2928
+ function addTextBoxCommand(document, command) {
2929
+ requireFiniteEmu(command.offsetX, "addTextBox", "offsetX");
2930
+ requireFiniteEmu(command.offsetY, "addTextBox", "offsetY");
2931
+ requirePositiveFiniteEmu(command.width, "addTextBox", "width");
2932
+ requirePositiveFiniteEmu(command.height, "addTextBox", "height");
2933
+ if (typeof command.text !== "string") {
2934
+ throw new Error("addTextBox: text must be a string");
2935
+ }
2936
+ if (command.name !== void 0 && command.name.trim() === "") {
2937
+ throw new Error("addTextBox: name must be a non-empty string when provided");
2844
2938
  }
2939
+ return addTextBox(document, command.slideHandle, command);
2940
+ }
2941
+ function addConnectorCommand(document, command) {
2942
+ requireFiniteEmu(command.offsetX, "addConnector", "offsetX");
2943
+ requireFiniteEmu(command.offsetY, "addConnector", "offsetY");
2944
+ requirePositiveFiniteEmu(command.width, "addConnector", "width");
2945
+ requirePositiveFiniteEmu(command.height, "addConnector", "height");
2946
+ return addConnector(document, command.slideHandle, command);
2947
+ }
2948
+ function setTextRunPropertiesCommand(document, command) {
2949
+ requireNonEmptyPropertySet(command.properties, "setTextRunProperties");
2950
+ validateTextRunPropertySet(command.properties, "setTextRunProperties");
2951
+ return setTextRunProperties(document, command.handle, command.properties);
2952
+ }
2953
+ function clearTextRunPropertiesCommand(document, command) {
2954
+ if (command.properties.length === 0) {
2955
+ throw new Error("clearTextRunProperties: properties must contain at least one property name");
2956
+ }
2957
+ for (const property of command.properties) {
2958
+ if (!EDITABLE_TEXT_RUN_PROPERTY_SET.has(property)) {
2959
+ throw new Error(`clearTextRunProperties: unsupported text run property '${property}'`);
2960
+ }
2961
+ }
2962
+ return clearTextRunProperties(document, command.handle, command.properties);
2845
2963
  }
2846
2964
  function moveShape(document, command) {
2847
2965
  requireFiniteEmu(command.offsetX, "moveShape", "offsetX");
@@ -2878,6 +2996,49 @@ function setShapeTransform(document, command) {
2878
2996
  height: command.height
2879
2997
  });
2880
2998
  }
2999
+ var EDITABLE_TEXT_RUN_PROPERTIES = [
3000
+ "bold",
3001
+ "italic",
3002
+ "underline",
3003
+ "fontSize",
3004
+ "color",
3005
+ "typeface"
3006
+ ];
3007
+ var EDITABLE_TEXT_RUN_PROPERTY_SET = new Set(EDITABLE_TEXT_RUN_PROPERTIES);
3008
+ function requireNonEmptyPropertySet(properties, commandName) {
3009
+ if (Object.values(properties).every((value) => value === void 0)) {
3010
+ throw new Error(`${commandName}: properties must contain at least one defined property`);
3011
+ }
3012
+ }
3013
+ function validateTextRunPropertySet(properties, commandName) {
3014
+ for (const property of Object.keys(properties)) {
3015
+ if (!EDITABLE_TEXT_RUN_PROPERTY_SET.has(property)) {
3016
+ throw new Error(`${commandName}: unsupported text run property '${property}'`);
3017
+ }
3018
+ }
3019
+ requireBooleanOrUndefined(properties.bold, commandName, "bold");
3020
+ requireBooleanOrUndefined(properties.italic, commandName, "italic");
3021
+ requireBooleanOrUndefined(properties.underline, commandName, "underline");
3022
+ if (properties.fontSize !== void 0 && (!Number.isFinite(properties.fontSize) || properties.fontSize <= 0)) {
3023
+ throw new Error(`${commandName}: fontSize must be a finite positive pt value`);
3024
+ }
3025
+ if (properties.typeface !== void 0 && properties.typeface.trim() === "") {
3026
+ throw new Error(`${commandName}: typeface must be a non-empty string`);
3027
+ }
3028
+ if (properties.color !== void 0) {
3029
+ if (properties.color.kind !== "srgb") {
3030
+ throw new Error(`${commandName}: only srgb text run color is supported`);
3031
+ }
3032
+ if (!/^[0-9A-Fa-f]{6}$/.test(properties.color.hex)) {
3033
+ throw new Error(`${commandName}: color.hex must be a 6-digit hex value`);
3034
+ }
3035
+ }
3036
+ }
3037
+ function requireBooleanOrUndefined(value, commandName, fieldName) {
3038
+ if (value !== void 0 && typeof value !== "boolean") {
3039
+ throw new Error(`${commandName}: ${fieldName} must be a boolean value`);
3040
+ }
3041
+ }
2881
3042
  function requireEditableShapeTransform(document, handle, commandName) {
2882
3043
  const shape = findShapeNodeBySourceHandle(document, handle);
2883
3044
  if (shape === void 0) {
@@ -2905,36 +3066,68 @@ function normalizeEditorEdits(document) {
2905
3066
  const edits = document.edits;
2906
3067
  if (edits === void 0) return document;
2907
3068
  const seenTextRuns = /* @__PURE__ */ new Set();
3069
+ const seenTextRunProperties = /* @__PURE__ */ new Map();
2908
3070
  const seenParagraphs = /* @__PURE__ */ new Set();
2909
3071
  const seenShapeTransforms = /* @__PURE__ */ new Set();
2910
3072
  const normalizedReversed = [];
3073
+ let changed = false;
2911
3074
  for (let index = edits.length - 1; index >= 0; index -= 1) {
2912
3075
  const edit = edits[index];
2913
3076
  if (edit.kind === "replaceTextRunPlainText") {
2914
3077
  const key = editHandleNodeKey(edit);
2915
3078
  const paragraphKey = textRunParagraphEditKey(edit);
2916
- if (paragraphKey !== void 0 && seenParagraphs.has(paragraphKey)) continue;
2917
- if (seenTextRuns.has(key)) continue;
3079
+ if (paragraphKey !== void 0 && seenParagraphs.has(paragraphKey)) {
3080
+ changed = true;
3081
+ continue;
3082
+ }
3083
+ if (seenTextRuns.has(key)) {
3084
+ changed = true;
3085
+ continue;
3086
+ }
2918
3087
  seenTextRuns.add(key);
2919
3088
  }
3089
+ if (edit.kind === "updateTextRunProperties") {
3090
+ const paragraphKey = textRunParagraphEditKey(edit);
3091
+ if (paragraphKey !== void 0 && seenParagraphs.has(paragraphKey)) {
3092
+ changed = true;
3093
+ continue;
3094
+ }
3095
+ const normalized = normalizeTextRunPropertiesEdit(edit, seenTextRunProperties);
3096
+ if (normalized === void 0) {
3097
+ changed = true;
3098
+ continue;
3099
+ }
3100
+ if (!editorEditsEqual(normalized, edit)) changed = true;
3101
+ normalizedReversed.push(normalized);
3102
+ continue;
3103
+ }
2920
3104
  if (edit.kind === "replaceParagraphPlainText") {
2921
3105
  const key = editHandleNodeKey(edit);
2922
- if (seenParagraphs.has(key)) continue;
3106
+ if (seenParagraphs.has(key)) {
3107
+ changed = true;
3108
+ continue;
3109
+ }
2923
3110
  seenParagraphs.add(key);
2924
3111
  }
2925
3112
  if (edit.kind === "updateShapeTransform") {
2926
3113
  const key = editHandleNodeKey(edit);
2927
- if (seenShapeTransforms.has(key)) continue;
3114
+ if (seenShapeTransforms.has(key)) {
3115
+ changed = true;
3116
+ continue;
3117
+ }
2928
3118
  seenShapeTransforms.add(key);
2929
3119
  }
2930
3120
  normalizedReversed.push(edit);
2931
3121
  }
2932
- if (normalizedReversed.length === edits.length) return document;
3122
+ if (!changed && normalizedReversed.length === edits.length) return document;
2933
3123
  return {
2934
3124
  ...document,
2935
3125
  edits: normalizedReversed.reverse()
2936
3126
  };
2937
3127
  }
3128
+ function editorEditsEqual(left, right) {
3129
+ return JSON.stringify(left) === JSON.stringify(right);
3130
+ }
2938
3131
  function editHandleNodeKey(edit) {
2939
3132
  return [
2940
3133
  edit.handle.partPath,
@@ -2957,10 +3150,70 @@ function textRunParagraphEditKey(edit) {
2957
3150
  paragraphOrderingSlot
2958
3151
  ].join("\0");
2959
3152
  }
3153
+ function normalizeTextRunPropertiesEdit(edit, seenTextRunProperties) {
3154
+ const key = editHandleNodeKey(edit);
3155
+ let seenProperties = seenTextRunProperties.get(key);
3156
+ if (seenProperties === void 0) {
3157
+ seenProperties = /* @__PURE__ */ new Set();
3158
+ seenTextRunProperties.set(key, seenProperties);
3159
+ }
3160
+ const set = {};
3161
+ if (edit.set?.bold !== void 0 && !seenProperties.has("bold")) {
3162
+ seenProperties.add("bold");
3163
+ set.bold = edit.set.bold;
3164
+ }
3165
+ if (edit.set?.italic !== void 0 && !seenProperties.has("italic")) {
3166
+ seenProperties.add("italic");
3167
+ set.italic = edit.set.italic;
3168
+ }
3169
+ if (edit.set?.underline !== void 0 && !seenProperties.has("underline")) {
3170
+ seenProperties.add("underline");
3171
+ set.underline = edit.set.underline;
3172
+ }
3173
+ if (edit.set?.fontSize !== void 0 && !seenProperties.has("fontSize")) {
3174
+ seenProperties.add("fontSize");
3175
+ set.fontSize = edit.set.fontSize;
3176
+ }
3177
+ if (edit.set?.color !== void 0 && !seenProperties.has("color")) {
3178
+ seenProperties.add("color");
3179
+ set.color = edit.set.color;
3180
+ }
3181
+ if (edit.set?.typeface !== void 0 && !seenProperties.has("typeface")) {
3182
+ seenProperties.add("typeface");
3183
+ set.typeface = edit.set.typeface;
3184
+ }
3185
+ const clear = (edit.clear ?? []).filter((property) => !seenProperties.has(property));
3186
+ for (const property of clear) seenProperties.add(property);
3187
+ if (clear.length === 0 && Object.keys(set).length === 0) return void 0;
3188
+ const normalized = {
3189
+ kind: "updateTextRunProperties",
3190
+ handle: edit.handle,
3191
+ ...Object.keys(set).length > 0 ? { set } : {},
3192
+ ...clear.length > 0 ? { clear } : {}
3193
+ };
3194
+ return normalized;
3195
+ }
2960
3196
 
2961
3197
  // src/browser-editor.ts
2962
3198
  var EMU_PER_INCH = 914400;
2963
3199
  var DEFAULT_DPI = 96;
3200
+ var EMU_PER_PIXEL = EMU_PER_INCH / DEFAULT_DPI;
3201
+ var DEFAULT_TEXT_BOX_BOUNDS_PX = {
3202
+ x: 96,
3203
+ y: 96,
3204
+ width: 288,
3205
+ height: 72
3206
+ };
3207
+ var DEFAULT_TEXT_BOX_TEXT = "New text box";
3208
+ var IMAGE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
3209
+ var IMAGE_ACCEPT_BY_CONTENT_TYPE = {
3210
+ "image/png": "image/png,.png",
3211
+ "image/jpeg": "image/jpeg,.jpg,.jpeg",
3212
+ "image/gif": "image/gif,.gif",
3213
+ "image/bmp": "image/bmp,.bmp",
3214
+ "image/tiff": "image/tiff,.tif,.tiff",
3215
+ "image/webp": "image/webp,.webp"
3216
+ };
2964
3217
  var BrowserPptxEditorSession = class _BrowserPptxEditorSession {
2965
3218
  #session;
2966
3219
  #slides = [];
@@ -2991,25 +3244,31 @@ var BrowserPptxEditorSession = class _BrowserPptxEditorSession {
2991
3244
  get selection() {
2992
3245
  return this.#session.selection;
2993
3246
  }
2994
- response() {
3247
+ response(warnings) {
2995
3248
  return {
2996
3249
  slides: this.#slides,
2997
3250
  history: this.history,
2998
- ...this.selection !== void 0 ? { selection: this.selection } : {}
3251
+ ...this.selection !== void 0 ? { selection: this.selection } : {},
3252
+ ...warnings !== void 0 && warnings.length > 0 ? { warnings } : {}
2999
3253
  };
3000
3254
  }
3001
3255
  shapes(slideNumber) {
3002
3256
  const slide = this.#session.document.slides[slideNumber - 1];
3003
3257
  if (slide === void 0) return [];
3004
- return slide.shapes.flatMap((shape, index) => shapeInfo(shape, index));
3258
+ return slide.shapes.flatMap(
3259
+ (shape, index) => shapeInfo(this.#session.document, shape, index, true, slide.shapes)
3260
+ );
3005
3261
  }
3006
3262
  async renderCurrentSlides() {
3007
- const report = await convertPptxToSvg(writePptx(this.#session.document), {
3263
+ const report = await renderPptxSourceModelToSvg(this.#session.document, {
3008
3264
  textOutput: "text",
3009
3265
  skipSystemFonts: true,
3010
3266
  ...this.#renderOptions
3011
3267
  });
3012
- this.#slides = report.slides;
3268
+ this.#slides = report.slides.map((slide) => ({
3269
+ ...slide,
3270
+ ...this.#session.document.slides[slide.slideNumber - 1]?.handle !== void 0 ? { handle: this.#session.document.slides[slide.slideNumber - 1]?.handle } : {}
3271
+ }));
3013
3272
  return this.#slides;
3014
3273
  }
3015
3274
  async apply(command) {
@@ -3018,7 +3277,45 @@ var BrowserPptxEditorSession = class _BrowserPptxEditorSession {
3018
3277
  throw new Error(result.message);
3019
3278
  }
3020
3279
  await this.renderCurrentSlides();
3021
- return this.response();
3280
+ return this.response(result.warnings);
3281
+ }
3282
+ async addTextBox(slideNumber = 1, options = {}) {
3283
+ const slide = this.#session.document.slides[slideNumber - 1];
3284
+ if (slide?.handle === void 0) {
3285
+ throw new Error("addTextBox: slide handle was not found in PptxSourceModel source");
3286
+ }
3287
+ const existingShapeKeys = new Set(slide.shapes.map(shapeSourceKey));
3288
+ const result = this.#session.apply({
3289
+ kind: "addTextBox",
3290
+ slideHandle: slide.handle,
3291
+ offsetX: pxToEmu(options.x ?? DEFAULT_TEXT_BOX_BOUNDS_PX.x),
3292
+ offsetY: pxToEmu(options.y ?? DEFAULT_TEXT_BOX_BOUNDS_PX.y),
3293
+ width: pxToEmu(options.width ?? DEFAULT_TEXT_BOX_BOUNDS_PX.width),
3294
+ height: pxToEmu(options.height ?? DEFAULT_TEXT_BOX_BOUNDS_PX.height),
3295
+ text: options.text ?? DEFAULT_TEXT_BOX_TEXT,
3296
+ ...options.name !== void 0 ? { name: options.name } : {}
3297
+ });
3298
+ if (!result.ok) {
3299
+ throw new Error(result.message);
3300
+ }
3301
+ this.#selectNewShape(slideNumber, existingShapeKeys);
3302
+ await this.renderCurrentSlides();
3303
+ return this.response(result.warnings);
3304
+ }
3305
+ async deleteShape(handle) {
3306
+ const result = this.#session.apply({ kind: "deleteShape", handle });
3307
+ if (!result.ok) {
3308
+ throw new Error(result.message);
3309
+ }
3310
+ await this.renderCurrentSlides();
3311
+ return this.response(result.warnings);
3312
+ }
3313
+ async deleteSelectedShape() {
3314
+ const selection = this.#session.selection;
3315
+ if (selection === void 0) {
3316
+ throw new Error("deleteShape: no selected shape");
3317
+ }
3318
+ return this.deleteShape(selection.shapeHandle);
3022
3319
  }
3023
3320
  async applyTextBodyDocJson(handle, docJson) {
3024
3321
  const textBody = this.#requireEditableShapeTextBody(handle);
@@ -3029,7 +3326,7 @@ var BrowserPptxEditorSession = class _BrowserPptxEditorSession {
3029
3326
  throw new Error(result.message);
3030
3327
  }
3031
3328
  await this.renderCurrentSlides();
3032
- return this.response();
3329
+ return this.response(result.warnings);
3033
3330
  }
3034
3331
  selectShape(handle) {
3035
3332
  const result = this.#session.selectShape(handle);
@@ -3069,11 +3366,17 @@ var BrowserPptxEditorSession = class _BrowserPptxEditorSession {
3069
3366
  }
3070
3367
  return shape.textBody;
3071
3368
  }
3369
+ #selectNewShape(slideNumber, existingShapeKeys) {
3370
+ const slide = this.#session.document.slides[slideNumber - 1];
3371
+ const addedShape = slide?.shapes.find((shape) => !existingShapeKeys.has(shapeSourceKey(shape)));
3372
+ if (addedShape?.handle === void 0) return;
3373
+ this.#session.selectShape(addedShape.handle);
3374
+ }
3072
3375
  };
3073
3376
  function createBrowserPptxEditorSession(input, renderOptions) {
3074
3377
  return BrowserPptxEditorSession.create(input, renderOptions);
3075
3378
  }
3076
- function shapeInfo(shape, index, editableTransform = true) {
3379
+ function shapeInfo(source, shape, index, editableTransform = true, slideShapes = []) {
3077
3380
  const canEditTransform = shape.kind !== "raw" && shape.transform !== void 0 && editableTransform && isEditableTransformShape(shape);
3078
3381
  const base = {
3079
3382
  id: String(shape.nodeId ?? shape.handle?.nodeId ?? `${shape.kind}:${String(index)}`),
@@ -3084,17 +3387,21 @@ function shapeInfo(shape, index, editableTransform = true) {
3084
3387
  bounds: transformBoundsPx(shape.transform),
3085
3388
  editableTransform: true
3086
3389
  } : {},
3390
+ ...editableTransform && isDeletableShape(shape, slideShapes) ? { editableDelete: true } : {},
3087
3391
  ..."textBody" in shape && shape.textBody !== void 0 ? {
3088
3392
  textRuns: collectTextRuns(
3089
3393
  shape.textBody.paragraphs.flatMap((paragraph) => paragraph.runs)
3090
3394
  )
3091
3395
  } : {},
3092
- ...shape.kind === "shape" && canEditTransform && shape.textBody !== void 0 ? editableTextBody(shape.textBody) : {}
3396
+ ...shape.kind === "shape" && canEditTransform && shape.textBody !== void 0 ? editableTextBody(shape.textBody) : {},
3397
+ ...shape.kind === "image" ? editableImageReplacement(source, shape) : {}
3093
3398
  };
3094
3399
  if (shape.kind !== "group") return [base];
3095
3400
  return [
3096
3401
  base,
3097
- ...shape.children.flatMap((child, childIndex) => shapeInfo(child, childIndex, false))
3402
+ ...shape.children.flatMap(
3403
+ (child, childIndex) => shapeInfo(source, child, childIndex, false, slideShapes)
3404
+ )
3098
3405
  ];
3099
3406
  }
3100
3407
  function shapeName(shape) {
@@ -3106,6 +3413,20 @@ function isEditableTransformShape(shape) {
3106
3413
  }
3107
3414
  return !shape.rawSidecars?.some((sidecar) => sidecar.node.name === "mc:AlternateContent");
3108
3415
  }
3416
+ function isDeletableShape(shape, slideShapes) {
3417
+ if (shape.kind !== "shape" || shape.handle?.nodeId === void 0) {
3418
+ return false;
3419
+ }
3420
+ if (isShapeReferencedByConnector(shape, slideShapes)) {
3421
+ return false;
3422
+ }
3423
+ return !shape.rawSidecars?.some((sidecar) => sidecar.node.name === "mc:AlternateContent");
3424
+ }
3425
+ function isShapeReferencedByConnector(shape, slideShapes) {
3426
+ return slideShapes.some(
3427
+ (candidate) => candidate.kind === "connector" && (candidate.connection?.start?.shapeId === shape.nodeId || candidate.connection?.end?.shapeId === shape.nodeId)
3428
+ );
3429
+ }
3109
3430
  function collectTextRuns(runs) {
3110
3431
  return runs.flatMap((run) => {
3111
3432
  if (run.handle === void 0) return [];
@@ -3119,6 +3440,134 @@ function editableTextBody(textBody) {
3119
3440
  return {};
3120
3441
  }
3121
3442
  }
3443
+ function editableImageReplacement(source, image) {
3444
+ const media = imageMediaPart(source, image);
3445
+ if (media === void 0) return {};
3446
+ const accept = IMAGE_ACCEPT_BY_CONTENT_TYPE[media.contentType];
3447
+ if (accept === void 0) return {};
3448
+ return {
3449
+ editableImageReplacement: {
3450
+ contentType: media.contentType,
3451
+ accept,
3452
+ mediaPartPath: media.partPath,
3453
+ sharedReferenceCount: countImageReferencesToMedia(source, media.partPath)
3454
+ }
3455
+ };
3456
+ }
3457
+ function imageMediaPart(source, image) {
3458
+ if (image.blipRelationshipId === void 0 || image.handle?.partPath === void 0) {
3459
+ return void 0;
3460
+ }
3461
+ const mediaPartPath = imageMediaPartPath(source, image.handle.partPath, image.blipRelationshipId);
3462
+ if (mediaPartPath === void 0) return void 0;
3463
+ return source.packageGraph.media.find((part) => part.partPath === mediaPartPath);
3464
+ }
3465
+ function imageMediaPartPath(source, sourcePartPath, relationshipId) {
3466
+ const relationships = source.packageGraph.relationships.find(
3467
+ (candidate) => candidate.sourcePartPath === sourcePartPath
3468
+ );
3469
+ const relationship = relationships?.relationships.find(
3470
+ (candidate) => candidate.id === relationshipId && candidate.type === IMAGE_REL_TYPE
3471
+ );
3472
+ if (relationship === void 0) return void 0;
3473
+ return resolveInternalRelationshipTarget(sourcePartPath, relationship);
3474
+ }
3475
+ function countImageReferencesToMedia(source, mediaPartPath) {
3476
+ const parsedImageRelationshipKeys = /* @__PURE__ */ new Set();
3477
+ let count = 0;
3478
+ for (const slide of source.slides) {
3479
+ count += countImageReferencesInTree(
3480
+ source,
3481
+ slide.partPath,
3482
+ slide.shapes,
3483
+ mediaPartPath,
3484
+ parsedImageRelationshipKeys
3485
+ );
3486
+ }
3487
+ for (const layout of source.slideLayouts) {
3488
+ count += countImageReferencesInTree(
3489
+ source,
3490
+ layout.partPath,
3491
+ layout.shapes,
3492
+ mediaPartPath,
3493
+ parsedImageRelationshipKeys
3494
+ );
3495
+ }
3496
+ for (const master of source.slideMasters) {
3497
+ count += countImageReferencesInTree(
3498
+ source,
3499
+ master.partPath,
3500
+ master.shapes,
3501
+ mediaPartPath,
3502
+ parsedImageRelationshipKeys
3503
+ );
3504
+ }
3505
+ for (const relationships of source.packageGraph.relationships) {
3506
+ for (const relationship of relationships.relationships) {
3507
+ if (relationship.type !== IMAGE_REL_TYPE) continue;
3508
+ if (parsedImageRelationshipKeys.has(
3509
+ imageRelationshipKey(relationships.sourcePartPath, relationship.id)
3510
+ )) {
3511
+ continue;
3512
+ }
3513
+ if (resolveInternalRelationshipTarget(relationships.sourcePartPath, relationship) === mediaPartPath) {
3514
+ count += 1;
3515
+ }
3516
+ }
3517
+ }
3518
+ return count;
3519
+ }
3520
+ function countImageReferencesInTree(source, sourcePartPath, shapes, mediaPartPath, parsedImageRelationshipKeys) {
3521
+ let count = 0;
3522
+ for (const shape of shapes) {
3523
+ if (shape.kind === "group") {
3524
+ count += countImageReferencesInTree(
3525
+ source,
3526
+ sourcePartPath,
3527
+ shape.children,
3528
+ mediaPartPath,
3529
+ parsedImageRelationshipKeys
3530
+ );
3531
+ continue;
3532
+ }
3533
+ if (shape.kind !== "image" || shape.blipRelationshipId === void 0) continue;
3534
+ if (imageMediaPartPath(source, sourcePartPath, shape.blipRelationshipId) === mediaPartPath) {
3535
+ count += 1;
3536
+ parsedImageRelationshipKeys.add(
3537
+ imageRelationshipKey(sourcePartPath, shape.blipRelationshipId)
3538
+ );
3539
+ }
3540
+ }
3541
+ return count;
3542
+ }
3543
+ function imageRelationshipKey(partPath, relationshipId) {
3544
+ return `${partPath}\0${relationshipId}`;
3545
+ }
3546
+ function resolveInternalRelationshipTarget(sourcePartPath, relationship) {
3547
+ if (relationship.targetMode === "External") return void 0;
3548
+ return unsafeBrandAssertion(
3549
+ normalizePackagePath(
3550
+ relationship.target.startsWith("/") ? relationship.target.slice(1) : joinPackageRelativeTarget(sourcePartPath, relationship.target)
3551
+ )
3552
+ );
3553
+ }
3554
+ function joinPackageRelativeTarget(sourcePartPath, target) {
3555
+ const slash = sourcePartPath.lastIndexOf("/");
3556
+ const baseDir = slash === -1 ? "" : sourcePartPath.slice(0, slash);
3557
+ return baseDir === "" ? target : `${baseDir}/${target}`;
3558
+ }
3559
+ function normalizePackagePath(path) {
3560
+ const segments = [];
3561
+ for (const segment of path.split("/")) {
3562
+ if (segment === "" || segment === ".") continue;
3563
+ if (segment === "..") {
3564
+ segments.pop();
3565
+ continue;
3566
+ }
3567
+ segments.push(segment);
3568
+ }
3569
+ return segments.join("/");
3570
+ }
3122
3571
  function transformBoundsPx(transform) {
3123
3572
  return {
3124
3573
  x: emuToPixels(transform.offsetX),
@@ -3130,6 +3579,19 @@ function transformBoundsPx(transform) {
3130
3579
  function emuToPixels(value) {
3131
3580
  return value / EMU_PER_INCH * DEFAULT_DPI;
3132
3581
  }
3582
+ function pxToEmu(value) {
3583
+ return asEmu(Math.round(value * EMU_PER_PIXEL));
3584
+ }
3585
+ function shapeSourceKey(shape) {
3586
+ const handle = shape.handle;
3587
+ return [
3588
+ shape.kind,
3589
+ handle?.partPath ?? "",
3590
+ handle?.nodeId ?? "",
3591
+ handle?.relationshipId ?? "",
3592
+ handle?.orderingSlot ?? ""
3593
+ ].join("\0");
3594
+ }
3133
3595
 
3134
3596
  // src/browser.ts
3135
3597
  async function convertPptxToPng(input, options) {
@@ -3176,5 +3638,6 @@ export {
3176
3638
  getMappedFont,
3177
3639
  getWarningEntries,
3178
3640
  getWarningSummary,
3179
- initResvgWasm2 as initResvgWasm
3641
+ initResvgWasm2 as initResvgWasm,
3642
+ renderPptxSourceModelToSvg
3180
3643
  };