scratch/ink-method-formatted.md

The ink() Method

Polypoint supplies three rendering methods: draw, pen, and ink. By default, these target canvas or SVG layers.

Draw methods handle the sketching phase, while pen actions render the final output. For simple shapes, pen alone suffices. For complex fills, you sketch with multiple draws then finalize with a single pen call.


What is ink()?

The ink() function combines draw and pen routines for built-in units (like springs). It handles all the plotting without the hassle.

// without ink
let p = new Point;
p.pen.fill(ctx, {color: 'red'})

// with ink
let p = new Point;
p.ink()

The ink method renders a unit using pre-determined settings. A spring's ink shows a zigzag, a balloon's ink runs multiple pen routines.

What Ink Can Do

  • Run draw and pen methods
  • Execute animations
  • Manipulate internal variables
  • Access context or workspace defaults

Typically, draw and pen methods don't edit unit arguments or rely on out-of-context defaults.


Why No render() Function?

Polypoint is built for customization. The render() method is usually the first thing developers override, so Polypoint keeps tooling under pen, targeting the draw layer.

The ink() method provides the bones of a unit until you create your own custom routine.


Options and Context

Most draw and pen routines require an explicit context for code clarity and runtime speed. But when sketching ideas, mandating ctx gets annoying.

The ink method makes context optional - options come first:

let p = new Point;
p.ink()
p.ink(options)
p.ink(options, stage.ctx)

Ink is also an overloadable executable method:

let p = new Point;
p.ink()
p.ink.tips()

Implementation Patterns

Function Constructor Pattern:

function Ink() {
  const state = { calls: 0 };

  function head(...args) {
    state.calls++;
    console.log('called with', args, 'total calls:', state.calls);
  }

  Object.setPrototypeOf(head, Ink.prototype);
  Object.defineProperty(head, 'constructor', { value: Ink });

  return head;
}

Ink.prototype.eggs = function() {
  console.log('eggs');
};

// usage
let foo = new Ink();
foo();
foo.eggs();
console.log(foo instanceof Ink); // true

Class with Bound Function:

class Ink {
  constructor() {
    this.state = { calls: 0 };

    const head = (...args) => {
      this.state.calls++;
      console.log('called with', args, 'total calls:', this.state.calls);
    };

    return head.bind(this);
  }

  eggs() {
    console.log('eggs  -  calls so far:', this.state.calls);
  }
}

Hybrid Pattern:

function Ink(...args) {
  if (!new.target) return new Ink(...args);
  return new PointInk(...args);
}

class PointInk {
  constructor() {
    const state = { calls: 0 };

    const head = (...args) => {
      state.calls++;
      console.log('called with', args, 'total calls:', state.calls);
    };

    head.state = state;

    Object.setPrototypeOf(head, Ink.prototype);
    Object.defineProperty(head, 'constructor', { value: Ink });

    return head;
  }

  eggs() {
    console.log('eggs  -  calls so far:', this.state.calls);
  }
}

Object.setPrototypeOf(Ink.prototype, PointInk.prototype);

const ink = new Ink();
ink();
ink.eggs();
console.log(ink instanceof Ink);      // true
console.log(ink instanceof PointInk); // true

Stage Weak Reference

function Ink(...args) {
  if (!new.target) return new Ink(...args);
  return new PointInk(...args);
}

class PointInk {
  constructor(stage, options = {}) {
    if (stage == null || typeof stage !== 'object') {
      throw new TypeError('PointInk: stage must be an object');
    }

    const state = { calls: 0, options };
    const stageRef = new WeakRef(stage);

    const head = (...args) => {
      state.calls++;
      const st = stageRef.deref();
      console.log('called with', args, 'calls:', state.calls, 'stage alive?', !!st);
      // use st safely: st?.draw?.(args);
    };

    head.state = state;
    head.getStage = () => stageRef.deref();

    Object.setPrototypeOf(head, Ink.prototype);
    Object.defineProperty(head, 'constructor', { value: Ink });

    return head;
  }

  eggs() {
    const st = this.getStage?.();
    console.log('eggs  -  stage alive?', !!st);
  }
}

Object.setPrototypeOf(Ink.prototype, PointInk.prototype);

// usage
const stage = { name: 'main' };
const ink = new Ink(stage, { color: 'cyan' });

ink();
ink.eggs();

// if stage has no strong refs, GC may collect it
console.log('stage now:', ink.getStage()); // possibly null after GC

Ink Advantages

Fire and Forget

Ink is built for convenience. A point needs coordinates, then pen actions. Shared themes reduce repetition.

It's overloadable - use quick ink() or chain methods like ink.nose(), ink.hands().

Animated

Ink may manipulate visual rendering to perform animations. Pen doesn't animate.

Smart

Ink collects sensible defaults where needed - colors from themes, clock speed from stage. Pen and draw stay thinner, with fewer options per tool.

Context-Aware

Ink sees the context, stage, and attached unit. Other tools stick to their own instance or parent.

Ink uses the stage context, letting developers omit ctx at ink time. It may use themes and point vars for unique colors.

Unique

One per point instance, holding weak references to external refs like the stage.


When to Avoid Ink

Sometimes lower-level methods work better:

Ink May Mutate

Ink is pretty but arrogant - it may manipulate visual styles and coordinates, hindering accurate coordinate analysis.

Ink Will be Slower

Ink performs more inspection than pen and draw. For thousands of points, no problem. For massive routines, use draw or raw context for better performance.

Ink is Opinionated

Objects like balloons get common options applied automatically. But accessing or extending pre-determined draw choices can be limiting.

For advanced work, ignore ink entirely. Penning complex polygons is easier with draw and pen functions (unless a specific ink.polygon function exists).

Info

keys: dict_keys(['exists', 'path', 'text', 'meta', 'rendered'])
path: scratch/ink-method-formatted.md
exists: True
meta: dict_keys(['filepath_exists', 'path', 'filepath', 'markdown'])