npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@penkit/adapter-miricanvas

v1.1.0

Published

Miricanvas host adapter for PenKit (payload contract + stub). Proves portability without engine coupling.

Readme

@penkit/adapter-miricanvas

PenKit의 Miricanvas adapter contract 패키지다. 중립 PenKit commit payload를 Miricanvas 형태의 create payload로 변환하고, host가 넘긴 callback 또는 lifecycle bridge로 runtime rendering, removal, preview를 위임할 수 있다.

npm install @penkit/adapter-miricanvas @penkit/core

이 패키지 하나만으로 live Miricanvas integration이 완성되는 것은 아니다. adapter boundary, 즉 payload conversion과 optional lifecycle bridge를 제공한다.

LLM Agent Quick Start

  1. @penkit/core는 host-neutral로 유지한다. Miricanvas command shape는 이 adapter 또는 host integration code에 둔다.
  2. 중립 PenCommitPayload를 Miricanvas create command로 바꿀 때 toPenItemCreatePayload(payload)를 사용한다.
  3. PenEngine 또는 @penkit/sdk가 실제 host renderer/command path를 호출해야 하면 createMiricanvasPenAdapter(...)로 command sync, renderer binding, brush pass dispatch를 하나의 adapter로 묶는다.
  4. 낮은 수준 callback만 필요하면 MiricanvasPenAdapterOptions를 직접 넘긴다.
  5. callback, lifecycle bridge, brushRenderer가 없으면 기본 render()remove()가 throw한다는 점을 기대한다.
  6. native renderer가 BrushRenderHints를 지원한다면 payload의 brushRenderPlan을 실행하거나 brushCapabilities로 지원 범위를 override한다.
  7. coordinate mapping은 @penkit/sdk/engine layer의 transformPoint에 둔다.

주요 모듈

toPenItemCreatePayload

중립 commit payload를 Miricanvas create-command shape로 변환한다.

import { toPenItemCreatePayload } from "@penkit/adapter-miricanvas";

const payload = toPenItemCreatePayload(commitPayload);
editor.commands.create(payload);

출력 shape:

type MiricanvasPenItemCreatePayload = {
  type: "PEN_ITEM";
  props: {
    strokes: PenStroke[];
    style: InkStyle;
    bounds: Bounds;
    brushRenderPlan: MiricanvasBrushRenderPlan;
  };
  transform: Transform;
};

brushRenderPlan은 built-in brush preset을 native renderer pass로 풀어낸다. 예를 들어 fountain은 base outline fill + wet-ink center shade, marker는 segment fill accumulation, pencil은 graphite hatch/dot pass로 내려간다.

toBrushRenderPlan

runtime item 또는 commit payload의 strokesstyle을 native brush pass 목록으로 변환한다.

import { toBrushRenderPlan } from "@penkit/adapter-miricanvas";

const plan = toBrushRenderPlan(item);
nativePenRenderer.render(plan.passes);

executeBrushRenderPlan

brushRenderPlan을 host native renderer callback으로 실행한다. host가 pass 전체를 직접 처리하려면 renderPass만 넘기고, PenKit 기본 dispatch를 쓰려면 fillPath, strokePath, texturePath를 구현한다.

import { executeBrushRenderPlan } from "@penkit/adapter-miricanvas";

executeBrushRenderPlan(nativePenRenderer, payload.props.brushRenderPlan, {
  payload,
  transform: payload.transform,
});

toPenItemCreatePayloadFromItem

runtime이 이미 transform이 있는 PenItemProps를 갖고 있을 때 사용한다.

import { toPenItemCreatePayloadFromItem } from "@penkit/adapter-miricanvas";

const payload = toPenItemCreatePayloadFromItem(item);

createMiricanvasPenAdapter

제품 연동에서는 command/state sync와 native renderer lifecycle이 같은 순서로 실행되어야 한다. createMiricanvasPenAdapter는 host bridge를 받아 syncPenItem -> renderPenItem -> renderBrushPlan/brushRenderer 순서로 묶은 MiricanvasPenAdapter를 만든다.

import { createMiricanvasPenAdapter } from "@penkit/adapter-miricanvas";

const adapter = createMiricanvasPenAdapter(
  {
    syncPenItem(payload, item) {
      editor.commands.upsertPenItem(item.id, payload);
    },
    renderPenItem(item, payload) {
      editor.renderer.bindPenItem(item.id, payload.transform);
    },
    removePenItem(itemId) {
      editor.commands.removeItem(itemId);
      editor.renderer.releasePenItem(itemId);
    },
    renderPreview(strokes, style, options) {
      editor.overlay.renderPenPreview(strokes, style, options);
    },
    clearPreview() {
      editor.overlay.clearPenPreview();
    },
  },
  {
    brushRenderer: {
      renderPass(pass, context) {
        editor.renderer.renderPenBrushPass(context.item?.id, pass);
      },
    },
  },
);

host가 brush plan dispatch를 직접 소유한다면 brushRenderer 대신 bridge의 renderBrushPlan(plan, context)를 구현한다. syncPenItem은 이름 그대로 create/update를 host command layer가 판정하는 idempotent sync hook이다.

MiricanvasPenAdapter

MiricanvasPenAdapterPenHostAdapter<MiricanvasPenItemCreatePayload>를 구현한다. 기본 createPenItem은 payload를 반환한다. 기본 renderremove는 명확한 stub error를 throw한다. local overlay demo나 native editor command path에 연결할 때 callback 또는 brushRenderer를 넘긴다.

import { MiricanvasPenAdapter } from "@penkit/adapter-miricanvas";

const adapter = new MiricanvasPenAdapter({
  render(item, payload) {
    editor.commands.addPenItem(payload);
    editor.renderer.renderPenItem(item, payload.props.brushRenderPlan);
  },
  remove(itemId) {
    editor.commands.removeItem(itemId);
  },
  renderPreview(strokes, style, options) {
    overlayRenderer.renderPreview(strokes, style, options);
  },
  clearPreview() {
    overlayRenderer.clearPreview();
  },
});

pass 단위 renderer만 있는 host라면 full render callback 대신 brushRenderer를 넘길 수 있다.

const adapter = new MiricanvasPenAdapter({
  brushRenderer: {
    beginPlan(plan, context) {
      editor.renderer.beginPenItem(context.payload, plan.capabilities);
    },
    fillPath(pass) {
      editor.renderer.fillPath(pass.pathD, pass);
    },
    strokePath(pass) {
      editor.renderer.strokePath(pass.pathD, pass);
    },
    texturePath(pass) {
      editor.renderer.texturePath(pass.pathD, pass);
    },
    endPlan() {
      editor.renderer.endPenItem();
    },
  },
});

Full SDK Overlay Example

import { createMiricanvasPenAdapter } from "@penkit/adapter-miricanvas";
import { createPenLayer } from "@penkit/sdk";

const adapter = createMiricanvasPenAdapter({
  syncPenItem(payload, item) {
    editor.commands.upsertPenItem(item.id, payload);
  },
  renderPenItem(item, payload) {
    editor.renderer.bindPenItem(item.id, payload.transform);
  },
  removePenItem(itemId) {
    editor.commands.removeItem(itemId);
    editor.renderer.releasePenItem(itemId);
  },
  renderPreview(strokes, style, options) {
    editor.overlay.renderPenPreview(strokes, style, options);
  },
  clearPreview() {
    editor.overlay.clearPenPreview();
  },
});

const pen = createPenLayer(editor.viewportElement, {
  adapter,
  penPriority: true,
  transformPoint(point) {
    const rect = editor.viewportElement.getBoundingClientRect();
    const localX = point.x - rect.left + editor.viewport.scrollLeft;
    const localY = point.y - rect.top + editor.viewport.scrollTop;
    return {
      ...point,
      x: localX / editor.viewport.zoom + editor.viewport.pan.x,
      y: localY / editor.viewport.zoom + editor.viewport.pan.y,
    };
  },
});

pen.engine.setTool("pen");

Host Responsibilities

host editor가 계속 소유하는 책임:

  • 실제 command bus 호출과 undo integration.
  • committed pen item renderer lifecycle.
  • brushRenderPlan pass를 native renderer의 fill/stroke/texture primitive로 매핑.
  • selection overlay, handle, native hit-test policy.
  • zoom, pan, scroll, page offset, CSS transform 같은 viewport transform.
  • Miricanvas 저장 schema가 PenDocumentSnapshot과 다를 때 persistence schema.

하지 말 것

  • lifecycle bridge가 실제 editor command/renderer API에 연결되기 전에는 complete native Miricanvas integration이라고 말하지 않는다.
  • @penkit/core가 Miricanvas command type을 import하게 만들지 않는다.
  • transformPoint를 무시하지 않는다. raw browser coordinate는 editor document coordinate로 변환되어야 한다.