Copy and paste
What copy writes and paste reads, between the editor and other applications, how a desktop host pastes editable shapes, and the paste commands for hosts.
Users copy and paste with the keyboard shortcuts, the Edit menu, and the context menus. The editor reads and writes the system clipboard formats that browsers share with other applications, so text, pictures, shapes, and slides move between the editor, other editors on the page, other tabs, and desktop applications.
What a copy writes
| Selection | Formats on the clipboard |
|---|---|
| Text | Plain text, HTML with the formatting the slide shows, and the runs with their own formatting for this editor |
| Shapes | This editor's shape format, the shapes' text as plain text, and HTML: the formatted text when every shape is a text box with no fill, otherwise a PNG picture of the shapes |
| Slides in the slide list or the slide sorter (keyboard shortcuts only) | The presentation file with the indexes of the copied slides |
| A table cell range | Tab-separated text and this editor's cells format (see Tables) |
The picture of copied shapes states its resolution, so an application that sizes pictures by resolution pastes it at the size of the shapes. The editor draws the picture when Cmd or Ctrl goes down and when a context menu opens. If the picture is not ready when the copy runs, the editor writes it to the clipboard after the copy, in browsers that support navigator.clipboard.write.
What a paste reads
A paste uses the first format it finds, in this order:
- Cells, in a table: this editor's cells format, or tab-separated text.
- This editor's slides: the slides go in after the current slide, on this presentation's layouts. A paste into text you edit on the slide skips this format.
- This editor's text or shapes.
- HTML with text from another application. The text keeps bold, italic, underline, strikethrough, superscript and subscript, size, color, font, alignment, list levels, and bullets or numbers. Links, pictures, and tables in the HTML become plain text or are left out.
- A picture (PNG, JPEG, GIF, BMP, or WebP), at the size its resolution gives, up to 60% of the slide.
- Plain text.
When you edit text, pasted text replaces the selection or goes in at the caret. With no text selected, it goes in a new text box in the middle of the slide, and the new text box is selected. To paste without formatting, use the browser's paste-as-plain-text command, such as Cmd+Shift+V in Chrome.
Shapes copied in another application paste as a picture in a browser: browsers give web pages a picture of the shapes, not their shape format. A desktop host can paste them as editable shapes; see Paste shapes in a desktop app. A paste works when the focus is on the slide, in the slide list, or on a toolbar button, so a paste after a click on another slide in the slide list puts the shapes on that slide. With text selected and the focus on a toolbar button, plain text goes in at the caret.
Pasted content is untrusted input. HTML is read by a bounded tokenizer: nothing in it runs or loads, and only text and its formatting are kept. A pasted file goes through the same limits as an opened file.
Paste shapes in a desktop app
Presentation applications write copied shapes to the system clipboard as a DrawingML package and as a picture. A browser gives a web page only the picture. If your app runs the editor in a desktop wrapper that can read the system clipboard, such as Electron, pass a readClipboardDrawing function. When a paste holds a picture, the editor calls it and pastes the package's shapes as editable shapes, at the positions they had on the source slide.
DRAWING_CLIPBOARD_TYPES names the clipboard type of the package: com.microsoft.Art--GVML-ClipFormat on macOS and Art::GVML ClipFormat on Windows. In Electron, read the package in the main process, which knows the platform:
// main.ts (Electron main process)
import { clipboard, ipcMain } from 'electron';
// The values of DRAWING_CLIPBOARD_TYPES in @pptx-editor.dev/core.
const DRAWING_TYPE =
process.platform === 'darwin' ? 'com.microsoft.Art--GVML-ClipFormat' : 'Art::GVML ClipFormat';
ipcMain.handle('read-clipboard-drawing', () => {
const bytes = clipboard.readBuffer(DRAWING_TYPE);
return bytes.length ? new Uint8Array(bytes) : null;
});The preload script exposes the handler to the page:
// preload.ts
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('desktop', {
readClipboardDrawing: (): Promise<Uint8Array | null> =>
ipcRenderer.invoke('read-clipboard-drawing'),
});Declare the exposed API once for the page:
// desktop.d.ts
import type { ClipboardDrawingReader } from '@pptx-editor.dev/core';
declare global {
interface Window {
// Undefined in a browser build.
desktop?: { readClipboardDrawing: ClipboardDrawingReader };
}
}Pass the reader to the editor. In a browser build, window.desktop is undefined, and pictures paste as before. These samples read window, so render them on the client only:
import { PptxEditor } from '@pptx-editor.dev/react';
export function Editor({ bytes }: { bytes: Uint8Array }) {
return (
<PptxEditor document={bytes} readClipboardDrawing={window.desktop?.readClipboardDrawing} />
);
}<script setup lang="ts">
import { PptxEditor } from '@pptx-editor.dev/vue';
defineProps<{ bytes: Uint8Array }>();
const readDrawing = window.desktop?.readClipboardDrawing;
</script>
<template>
<PptxEditor :document="bytes" :read-clipboard-drawing="readDrawing" />
</template>The editor reads the prop once, when it is created. The reader returns the package bytes, null, or a promise of either. Shapes, pictures, connectors, groups, and tables paste as editable content. Theme colors and theme fonts follow the theme of the slide you paste on. Charts, diagrams, and embedded objects need parts that the package does not carry, so they are left out.
The picture pastes when the reader returns null, throws, takes longer than 2 seconds, or returns bytes that hold no shape the editor pastes. A paste into text you edit, and a paste with HTML text, do not call the reader.
To paste a package from your own code, convert it with drawingClipboardPayload and run paste:
import { drawingClipboardPayload, type Editor } from '@pptx-editor.dev/core';
function pasteDrawing(editor: Editor, bytes: Uint8Array) {
const payload = drawingClipboardPayload(bytes);
// Null: the bytes are not a DrawingML clipboard package with shapes the editor pastes.
if (!payload) return { ok: false, reason: 'errors.clipboard' } as const;
return editor.exec({ type: 'paste', payload });
}The package bytes are untrusted input. They are read within size limits, nothing in them runs or loads, and links to outside the package are dropped.
Paste from your own code
Run pasteSlides to put slides of another presentation after the current slide. This browser example reads a file the user picks:
import type { Editor } from '@pptx-editor.dev/core';
async function pasteFirstSlides(editor: Editor, file: File) {
const bytes = new Uint8Array(await file.arrayBuffer());
// Slide indexes of the other presentation, in the order they go in.
return editor.exec({ type: 'pasteSlides', file: bytes, slides: [0, 1] });
}Run pasteText to paste text as a paste from another application does. Build its paragraphs with textClipboardParagraphs, from HTML or plain text. With text selected, the text replaces the selection; otherwise it goes in a new text box:
import { textClipboardParagraphs, type Editor } from '@pptx-editor.dev/core';
function pasteSummary(editor: Editor) {
const paragraphs = textClipboardParagraphs({
html: '<p><b>Summary</b></p><ul><li>Revenue up 12%</li></ul>',
});
return editor.exec({ type: 'pasteText', paragraphs });
}Each command is one undo step, and a refusal returns { ok: false, reason: 'errors.clipboard' }.