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:

  1. Break into parts - Draw head, body, limbs as separate paths for easier animation
  2. Use Bezier curves - C and Q commands in pathData for organic shapes
  3. Layer elements - Create in order (back to front) or use groups
  4. 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:

  1. segments - Array of [x, y] points (easier for simple shapes)
  2. 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: true with 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:

  1. textItemGroup items → selectable by default
  2. patternGroup items → NOT selectable unless selectable: true
  3. isDecorative: 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

  1. Use registryId - Items are tracked by item.data.registryId
  2. Save history - Call app.historyManager.saveState() after batch operations
  3. Clean up - Remove items with item.remove() or app.deleteSelected()
  4. Check existence - Use app.itemRegistry.has(id) before accessing items
  5. Position correctly - Use app.view.center for canvas center point
  6. Register Paper.js items - Use app.registerItem(item, type, props) for Paper.js items you want to be interactive
  7. Prefer app.create() - Use the built-in create method when possible; use Paper.js directly only for complex operations (boolean ops, path manipulation)
  8. Use width/height - Prefer pixel-based sizing over scale for predictable results
  9. 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