AI Agents Guide
PinePaper is designed for programmatic access by AI agents through the window.PinePaper API.
New in v0.3: Use Agent Mode for streamlined agent workflows with smart export, platform presets, and batch operations via
window.PinePaperAgent.
What You Can Create with PinePaper
PinePaper provides a full vector canvas with SVG path support. You can draw anything - from simple shapes to complex characters, faces, and detailed illustrations.
Quick Start Use Cases
| Task | Approach |
|---|---|
| Animated text and typography | Built-in text + animations |
| Geometric shapes | Native primitives (circle, star, polygon) |
| Solar systems, orbits | Relations system (orbits, follows) |
| Logo animations | Import SVG or draw paths + effects |
| Kinetic infographics | Shapes + text + relations |
| Procedural backgrounds | Built-in generators |
| Characters and faces | SVG paths with precise control |
| Animated cats, witches, etc. | Multi-path composition + animation |
Complex Character Drawing
PinePaper supports full SVG path drawing. For complex characters (faces, animals, people):
Approach 1: Compose from multiple paths
// Draw a cat face using paths
const catFace = new paper.Group();
// Head outline
const head = app.create('path', {
pathData: 'M 200 300 C 200 200, 400 200, 400 300 C 400 400, 200 400, 200 300 Z',
fillColor: '#f97316',
strokeColor: '#c2410c',
strokeWidth: 2
});
// Left ear
const leftEar = app.create('path', {
pathData: 'M 220 220 L 180 150 L 250 200 Z',
fillColor: '#f97316',
strokeColor: '#c2410c'
});
// Right ear
const rightEar = app.create('path', {
pathData: 'M 380 220 L 420 150 L 350 200 Z',
fillColor: '#f97316',
strokeColor: '#c2410c'
});
// Eyes
const leftEye = app.create('ellipse', { x: 260, y: 280, width: 30, height: 40, color: '#fde047' });
const rightEye = app.create('ellipse', { x: 340, y: 280, width: 30, height: 40, color: '#fde047' });
// Pupils
const leftPupil = app.create('ellipse', { x: 260, y: 285, width: 10, height: 20, color: '#1f2937' });
const rightPupil = app.create('ellipse', { x: 340, y: 285, width: 10, height: 20, color: '#1f2937' });
// Nose
const nose = app.create('path', {
pathData: 'M 300 310 L 290 330 L 310 330 Z',
fillColor: '#ec4899'
});
// Whiskers
const whiskers = app.create('path', {
segments: [[220, 320], [180, 310], [220, 325], [180, 330], [220, 330], [180, 350]],
strokeColor: '#1f2937',
strokeWidth: 1
});
// Animate the cat
app.animate(leftPupil, { animationType: 'pulse', animationSpeed: 0.3 });
app.animate(rightPupil, { animationType: 'pulse', animationSpeed: 0.3 });
Approach 2: Use SVG pathData for complex curves
// Witch silhouette with detailed hat and cloak
const witchSilhouette = app.create('path', {
pathData: `
M 300 100
L 250 200 L 200 200
C 180 200, 180 220, 200 220
L 280 220
L 260 350
C 250 400, 350 400, 340 350
L 320 220
L 400 220
C 420 220, 420 200, 400 200
L 350 200
Z
`,
fillColor: '#1f2937',
strokeColor: '#000000',
strokeWidth: 2
});
// Add a green face
const witchFace = app.create('ellipse', {
x: 300, y: 250,
width: 60, height: 70,
color: '#22c55e'
});
// Animate with wobble for creepy effect
app.animate(witchSilhouette, { animationType: 'wobble', animationSpeed: 0.5 });
Key Tips for Character Drawing:
- Break into parts - Draw head, body, limbs as separate paths for easier animation
- Use Bezier curves -
CandQcommands in pathData for organic shapes - Layer elements - Create in order (back to front) or use groups
- Animate parts independently - Each path can have its own animation
API Access
The entire PinePaper API is exposed globally:
const app = window.PinePaper;
Creating Items
Text
const text = app.create('text', {
content: 'AI Generated',
x: 400,
y: 300,
fontSize: 48,
color: '#4f46e5'
});
Shapes
// Circle
const circle = app.create('circle', { x: 200, y: 200, radius: 50, color: '#ef4444' });
// Star
const star = app.create('star', { x: 300, y: 300, radius1: 60, radius2: 30, color: '#fbbf24' });
// Rectangle
const rect = app.create('rectangle', { x: 400, y: 400, width: 100, height: 60, color: '#22c55e' });
// Polygon (hexagon)
const hex = app.create('polygon', { x: 500, y: 200, sides: 6, radius: 40, color: '#8b5cf6' });
Custom Paths
Paths are the core drawing primitive - use them for everything from simple shapes to complex illustrations.
Two ways to define paths:
segments- Array of [x, y] points (easier for simple shapes)pathData- SVG path string (powerful for curves and complex shapes)
// Simple polygon using segments
const pentagon = app.create('path', {
segments: [[150, 50], [250, 100], [220, 200], [80, 200], [50, 100]],
fillColor: '#3b82f6',
closed: true,
smooth: false // sharp corners
});
// Smooth organic shape using segments
const blob = app.create('path', {
segments: [[100, 100], [200, 50], [300, 100], [350, 200], [300, 300], [200, 350], [100, 300], [50, 200]],
fillColor: '#22c55e',
closed: true,
smooth: true // smooth curves between points
});
// Complex curves using SVG pathData
const heart = app.create('path', {
pathData: 'M 300 350 C 300 300, 250 250, 200 250 C 150 250, 100 300, 100 350 C 100 450, 300 550, 300 550 C 300 550, 500 450, 500 350 C 500 300, 450 250, 400 250 C 350 250, 300 300, 300 350 Z',
fillColor: '#ef4444',
strokeColor: '#b91c1c',
strokeWidth: 2
});
// Decorative wave
const wave = app.create('path', {
pathData: 'M 0 100 Q 50 50, 100 100 T 200 100 T 300 100 T 400 100',
strokeColor: '#06b6d4',
strokeWidth: 3,
fillColor: 'none'
});
// Arrow with head
const arrow = app.create('path', {
pathData: 'M 100 200 L 280 200 L 260 180 M 280 200 L 260 220',
strokeColor: '#f97316',
strokeWidth: 4,
strokeCap: 'round',
strokeJoin: 'round'
});
// Line (shorthand for two-point path)
const line = app.create('line', {
from: [100, 100],
to: [300, 200],
strokeColor: '#ef4444',
strokeWidth: 2
});
// Arc through three points
const arc = app.create('arc', {
from: [100, 200],
through: [200, 100],
to: [300, 200],
strokeColor: '#8b5cf6',
strokeWidth: 3
});
SVG Path Commands Reference:
| Command | Description | Example |
|---|---|---|
M x y |
Move to | M 100 100 |
L x y |
Line to | L 200 200 |
H x |
Horizontal line | H 300 |
V y |
Vertical line | V 400 |
C x1 y1, x2 y2, x y |
Cubic bezier | C 150 50, 250 50, 300 100 |
Q x1 y1, x y |
Quadratic bezier | Q 200 50, 300 100 |
A rx ry rot large sweep x y |
Arc | A 50 50 0 0 1 200 200 |
Z |
Close path | Z |
Tips for Complex Drawings:
- Use
smooth: truewith segments for organic shapes - Use Bezier curves (
C,Q) in pathData for precise control - Combine multiple paths for complex characters (head, body, limbs as separate items)
- Each path can be animated independently
Applying Animations
// Simple animation
app.animate(item, {
animationType: 'pulse', // pulse, rotate, bounce, fade, wobble, slide, typewriter
animationSpeed: 1.0
});
// Keyframe animation
app.modify(item, {
animationType: 'keyframe',
keyframes: [
{ time: 0, properties: { position: [100, 100], opacity: 0 }, easing: 'easeIn' },
{ time: 1, properties: { position: [400, 300], opacity: 1 }, easing: 'easeOut' }
]
});
app.playKeyframeTimeline(2, true); // duration, loop
Relations
Create behavior rules between items:
// Get item IDs
const sunId = sun.data.registryId;
const earthId = earth.data.registryId;
// Earth orbits sun
app.addRelation(earthId, sunId, 'orbits', {
radius: 200,
speed: 0.1
});
// Label follows earth
app.addRelation(labelId, earthId, 'attached_to', {
offset: [0, -30]
});
Background Generators
// Sunburst background
await app.executeGenerator('drawSunburst', {
colors: ['#6366f1', '#8b5cf6', '#a855f7'],
rayCount: 16,
bgColor: '#1e1b4b'
});
// Animated sunset
await app.executeGenerator('drawSunsetScene', {
skyColors: ['#1e3a5f', '#fb923c', '#fbbf24'],
cloudCount: 5
});
// Grid pattern
await app.executeGenerator('drawGrid', {
gridType: 'dots',
spacing: 30,
lineColor: '#334155',
bgColor: '#0f172a'
});
Effects
// Sparkle effect
app.applyEffect(item, 'sparkle', {
color: '#fbbf24',
speed: 1.0,
size: 3
});
// Blast effect
app.applyEffect(item, 'blast', {
color: '#ef4444',
radius: 100,
count: 20
});
SVG Import
// Import SVG from string
const svg = app.importSVG(`
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="#3b82f6"/>
<text x="50" y="55" text-anchor="middle" fill="white" font-size="14">Icon</text>
</svg>
`);
// Position and transform
svg.position = app.view.center;
svg.scale(2);
svg.rotate(45);
// Apply animation to imported SVG
app.animate(svg, { animationType: 'rotate', animationSpeed: 0.3 });
// Access registry ID
const svgId = svg.data.registryId;
Image Tools
// Upload image from URL
const entry = await app.imageTools.uploadFromURL('https://example.com/logo.png');
// Place image on canvas
const raster = await app.imageTools.placeImage(entry.id, {
position: [400, 300],
maxWidth: 300,
maxHeight: 200
});
// The raster is automatically registered
console.log(raster.data.registryId); // 'item_5'
// Apply shape mask
app.imageTools.applyMask(raster, 'circle');
// Mask options: 'circle', 'rounded', 'hexagon', 'star'
// Apply effects to images
app.applyEffect(raster, 'sparkle', { color: '#fbbf24' });
// Interactive cropping
app.imageTools.startCrop(raster, { aspectRatio: '16:9' });
app.imageTools.confirmCrop();
// Get all images in library
const library = app.imageTools.getLibrary();
Filters
// Add filters
app.filterSystem.addFilter('grayscale', { intensity: 0.5 });
app.filterSystem.addFilter('vignette', { intensity: 0.3, radius: 0.5 });
// Apply preset
app.filterSystem.applyPreset('cinematic');
Export
// Export as animated SVG
const svg = app.exportAnimatedSVG();
// Record as video (WebM)
app.startRecording();
// ... let animations play ...
app.stopRecording(); // Downloads automatically
Scene Management
// Save current scene
app.sceneManager.saveCurrentAsScene('Scene 1');
// Create new scene
app.sceneManager.createScene('Scene 2');
// Load scene
app.sceneManager.loadScene(sceneId);
// Chain scenes for presentation
app.sceneManager.createChain([scene1Id, scene2Id, scene3Id], {
autoPlay: true,
duration: 3000
});
Item Registry
// Get all items
const items = app.itemRegistry.getAll();
// Get by type
const textItems = app.itemRegistry.getByType('text');
// Query with predicate
const animated = app.itemRegistry.query(entry =>
entry.item.data?.animationType
);
Direct Paper.js Access
For advanced operations beyond app.create(), use Paper.js directly:
// Paper.js is globally available
const path = new paper.Path();
path.add([100, 100]);
path.arcTo([200, 50], [300, 100]);
path.lineTo([300, 200]);
path.closePath();
path.fillColor = '#3b82f6';
path.strokeColor = '#1e40af';
path.strokeWidth = 2;
// Register with PinePaper (handles layer, data, cursor, registry)
const itemId = app.registerItem(path, 'customShape', { source: 'agent' });
// Now it's a full PinePaper item
app.select(path, true);
app.historyManager.saveState();
Boolean Operations
// Create shapes
const circle = new paper.Path.Circle({
center: [200, 200],
radius: 80
});
const rect = new paper.Path.Rectangle({
point: [150, 150],
size: [100, 100]
});
// Subtract to create cutout
const result = circle.subtract(rect);
result.fillColor = '#8b5cf6';
// Register the result with PinePaper
app.registerItem(result, 'compound');
// Clean up original shapes (they're not needed)
circle.remove();
rect.remove();
Complex Path Manipulation
// Create path and manipulate
const wave = new paper.Path();
const amplitude = 30;
const frequency = 0.05;
for (let x = 0; x <= 400; x += 5) {
const y = 200 + Math.sin(x * frequency) * amplitude;
wave.add([x + 100, y]);
}
wave.strokeColor = '#06b6d4';
wave.strokeWidth = 3;
wave.smooth();
// Register with custom properties
app.registerItem(wave, 'wave', { amplitude, frequency });
Working with Groups
// Create a badge as a group
const badge = new paper.Group();
// Outer ring
const outer = new paper.Path.Circle({
center: [200, 200],
radius: 60,
fillColor: '#fbbf24'
});
// Inner circle
const inner = new paper.Path.Circle({
center: [200, 200],
radius: 45,
fillColor: '#f59e0b'
});
// Text
const label = new paper.PointText({
point: [200, 210],
content: 'NEW',
fontSize: 20,
fontWeight: 'bold',
fillColor: '#ffffff',
justification: 'center'
});
// Add to group
badge.addChildren([outer, inner, label]);
// Register group (children come along)
app.registerItem(badge, 'badge', { text: 'NEW' });
See Also: Paper.js API Reference for complete Paper.js documentation.
Project Utilities
PinePaper provides convenient wrappers for common Paper.js project operations:
// Query items by criteria
const circles = app.queryItems({ data: { shapeType: 'circle' } });
const named = app.queryItems({ name: /^planet/ });
// Hit testing
const hit = app.hitTest([400, 300]);
if (hit) {
console.log('Clicked on:', hit.item.name);
}
// Get all items at point (overlapping)
const allHits = app.hitTestAll([mouseX, mouseY]);
// Check if canvas is empty
if (app.isEmpty()) {
// Show welcome content
}
// Layer information
const layers = app.getLayerInfo();
console.log('Layers:', layers.count);
Symbols for Efficiency
When creating many similar items, use symbols to save memory:
// Create symbol from an item
const star = app.create('star', { x: 0, y: 0, radius1: 20, radius2: 10, color: '#fbbf24' });
const starSymbol = app.createSymbol(star);
// Place multiple instances efficiently
for (let i = 0; i < 100; i++) {
const x = Math.random() * 800;
const y = Math.random() * 600;
const placed = app.placeSymbol(starSymbol, [x, y]);
placed.scale(0.5 + Math.random());
}
Project Backup
For complete state backup and restore:
// Export full project state
const backup = app.exportProjectJSON();
localStorage.setItem('backup', backup);
// Restore from backup
const saved = localStorage.getItem('backup');
if (saved) {
app.importProjectJSON(saved);
}
Note: For sharing templates, use
app.exportTemplate()instead - it’s optimized for portability and includes PinePaper metadata.
Item Modification
Sizing: width/height vs scale
Use pixel-based sizing (width, height) for predictable results:
// ✅ Recommended: Absolute pixel sizing
app.modify(item, { width: 200, height: 150 });
// ⚠️ Relative scaling - compounds on repeated use
app.modify(item, { scale: 1.5 });
| Approach | Behavior | Best For |
|---|---|---|
width/height |
Sets exact pixel dimensions | Precise layouts, design specs |
scale |
Multiplies current size | Proportional adjustments |
All Modify Properties
app.modify(item, {
// Position (absolute pixels)
x: 400,
y: 300,
// Size in pixels (absolute, recommended)
width: 200,
height: 150,
// Scale (relative)
scale: 1.0,
scaleX: 1.0,
scaleY: 1.0,
// Transform
rotation: 45,
opacity: 0.8,
// Appearance
color: '#ff0000',
strokeColor: '#000000',
strokeWidth: 2,
// Text-specific
fontSize: 48,
content: 'New text',
// Animation
animationType: 'pulse',
animationSpeed: 1.5
});
Item Data Flags
Control selection and interactivity with data flags:
// Make an item in patternGroup selectable
item.data = {
selectable: true, // Can be selected
isDraggable: true, // Can be dragged
isDecorative: false // Not decorative (can interact)
};
// Make an item non-interactive (orbit paths, guidelines)
orbit.data = { isDecorative: true };
Available Flags:
| Flag | Description |
|---|---|
selectable |
Allow selection (even in background layer) |
isDraggable |
Allow dragging when selected |
isDecorative |
Never selectable (overrides all) |
Selection Priority:
textItemGroupitems → selectable by defaultpatternGroupitems → NOT selectable unlessselectable: trueisDecorative: true→ never selectable (overrides all)
Example: Interactive Solar System
The solar system generator uses data flags:
- Sun has
data.selectable = true,data.isDraggable = true - Planets have
data.selectable = true,data.isDraggable = true,data.isPlanet = true - Orbits have
data.isDecorative = true(can’t be selected)
// Create interactive element in background
const star = new Path.Star({
center: [400, 300],
points: 5,
radius1: 60,
radius2: 30,
fillColor: '#fbbf24',
parent: app.patternGroup
});
// Make it interactive
star.data = {
selectable: true,
isDraggable: true,
customType: 'interactive-star'
};
Best Practices
- Use registryId - Items are tracked by
item.data.registryId - Save history - Call
app.historyManager.saveState()after batch operations - Clean up - Remove items with
item.remove()orapp.deleteSelected() - Check existence - Use
app.itemRegistry.has(id)before accessing items - Position correctly - Use
app.view.centerfor canvas center point - Register Paper.js items - Use
app.registerItem(item, type, props)for Paper.js items you want to be interactive - Prefer
app.create()- Use the built-in create method when possible; use Paper.js directly only for complex operations (boolean ops, path manipulation) - Use width/height - Prefer pixel-based sizing over scale for predictable results
- Use data flags - Control selectability with
selectable,isDraggable,isDecorative
Example: Complete Scene
// Create a motivational poster
async function createPoster() {
// Set background
await app.executeGenerator('drawSunburst', {
colors: ['#6366f1', '#4f46e5', '#3730a3'],
bgColor: '#1e1b4b'
});
// Create title
const title = app.create('text', {
content: 'DREAM BIG',
x: 540, y: 400,
fontSize: 96,
color: '#ffffff'
});
// Add animation
app.animate(title, { animationType: 'pulse', animationSpeed: 0.5 });
// Add sparkle effect
app.applyEffect(title, 'sparkle', { color: '#fbbf24', speed: 0.8 });
// Create subtitle
const subtitle = app.create('text', {
content: 'The journey of a thousand miles begins with a single step',
x: 540, y: 500,
fontSize: 24,
color: '#e2e8f0'
});
// Add vignette
app.filterSystem.addFilter('vignette', { intensity: 0.4, radius: 0.5 });
}
createPoster();
See Also
- Agent Mode - Streamlined workflow for AI agents
- Core Methods - Full API reference
- Relations Overview - Declarative animation via relationships
- MCP Integration Guide - Build MCP server tools for PinePaper
- MCP Tools Specification - Complete tool definitions and schemas
- Training Data Export - Export data for LLM fine-tuning