myscript/iinkts/rendering-system

Claude Code and agent skill by myscript/iinkts

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Guide to the rendering architecture (SVG vs Canvas, drawing strokes/symbols, custom styling). Use when modifying rendering logic, debugging visual issues, or implementing custom renderers.

Install

npx vskill@latest install myscript/iinkts/rendering-system

SKILL.md

Rendering System Architecture

Overview

The rendering system draws digital ink and recognized content to screen. Two renderer implementations:

RendererTechnologyPerformanceUse Case
SVGRendererSVG DOMBetter qualityDefault for most editors
CanvasRendererCanvas 2DBetter performanceHigh stroke count scenarios

Base abstraction: BaseRenderer (src/renderer/BaseRenderer.ts)

Renderer Selection

Configured via editor options:

typescript
const options = {
configuration: {
rendering: {
type: 'svg', // or 'canvas'
},
},
}

Default: SVG for all editors.

SVG Renderer Architecture

Component Structure — registry dispatch, not per-type fields

Per-type SVG rendering is NOT hardcoded on SVGRenderer as private fields — it's dispatched through the same plugin registry used for symbol behavior everywhere else:

typescript
import { symbolRegistry } from "@/symbol-utils/SymbolRegistry"
class SVGRenderer extends BaseRenderer {
#layerCapture: SVGSVGElement // Stroke capture layer
#layerModel: SVGGElement // Recognized symbols layer
#buildElement(symbol: TSymbol): SVGGraphicsElement | undefined {
const util = symbolRegistry.getUtil(symbol.type)
return util?.getSVGElement?.(symbol)
}
}

Per-type rendering lives in each type's *Util class (src/symbol-utils/{type}/{Type}Util.ts): StrokeUtil, TextUtil, MathUtil, ShapeUtil, EdgeUtil, DecoratorUtil — each implements getSVGElement(symbol). See the symbol-system skill for the full *Ops/*Util layering. Adding a new renderable type means adding a getSVGElement() to its *Util, not touching SVGRenderer.

Other renderer pieces:

Implementation: src/renderer/svg/SVGRenderer.ts

Rendering Flow
  1. Capture Phase: User draws → strokes added to #layerCapture
  2. Recognition Phase: Backend responds → symbols rendered to #layerModel
  3. Clear Capture: Recognized strokes removed from capture layer
  4. Update Model: Symbols updated in model layer
SVG Element Structure
xml
<svg id="editor-svg" class="ms-editor">
<!-- Capture layer (temporary strokes) -->
<svg id="capture-layer">
<path id="stroke-1" d="M 0 0 L 10 10 ..." />
<path id="stroke-2" d="M 20 20 L 30 30 ..." />
</svg>
<!-- Model layer (recognized symbols) -->
<g id="model-layer">
<g id="group-1" class="symbol-group">
<text id="text-1" x="0" y="20">Hello</text>
<circle id="shape-1" cx="50" cy="50" r="20" />
</g>
<!-- Decorators (selection, resize handles) -->
<g id="decorator-layer">
<rect class="selection-box" />
<circle class="resize-handle" />
</g>
</g>
</svg>
Symbol-Specific Renderers
Stroke Rendering

`StrokeUtil.getSVGElement()` (src/symbol-utils/stroke/StrokeUtil.ts) — illustrative shape, see the real file for exact attrs:

typescript
getSVGElement(stroke: TStroke): SVGPathElement {
const path = document.createElementNS(SVG_NS, 'path')
// Convert points to SVG path data
const d = this.#buildPathData(stroke.points)
path.setAttribute('d', d)
// Apply style
path.setAttribute('stroke', stroke.style.color)
path.setAttribute('stroke-width', stroke.style.width)
path.setAttribute('fill', 'none')
return path
}
#buildPathData(points: TPoint[]): string {
if (points.length === 0) return ''
let d = `M ${points[0].x} ${points[0].y}`
for (let i = 1; i < points.length; i++) {
d += ` L ${points[i].x} ${points[i].y}`
}
return d
}

Pressure support: Variable stroke width via path interpolation.

Shape Rendering

`ShapeUtil.getSVGElement()` (src/symbol-utils/shape/ShapeUtil.ts) — illustrative shape:

typescript
getSVGElement(shape: TShape): SVGElement {
switch (shape.kind) {
case 'circle':
return this.#drawCircle(shape)
case 'ellipse':
return this.#drawEllipse(shape)
case 'polygon':
return this.#drawPolygon(shape)
// ... other shapes
}
}
#drawCircle(shape: TShapeCircle): SVGCircleElement {
const circle = document.createElementNS(SVG_NS, 'circle')
circle.setAttribute('cx', shape.center.x)
circle.setAttribute('cy', shape.center.y)
circle.setAttribute('r', shape.radius)
this.#applyStyle(circle, shape.style)
return circle
}

Supported shapes: Circle, Ellipse, Polygon.

Text Rendering

`TextUtil.getSVGElement()` (src/symbol-utils/text/TextUtil.ts) — illustrative shape:

typescript
getSVGElement(text: TText): SVGTextElement {
const textElement = document.createElementNS(SVG_NS, 'text')
// Position
textElement.setAttribute('x', text.baseline.x)
textElement.setAttribute('y', text.baseline.y)
// Content
textElement.textContent = text.label
// Style (font, size, color)
textElement.setAttribute('font-family', text.style.fontFamily)
textElement.setAttribute('font-size', text.style.fontSize)
textElement.setAttribute('fill', text.style.color)
return textElement
}

Math rendering: Uses MathML or LaTeX → SVG conversion (server-side).

Edge Rendering

Lines, arcs, polylines rendered as SVG paths with specific d attributes, via EdgeUtil.getSVGElement().

Decorator Rendering

`DecoratorUtil.getSVGElement()` (src/symbol-utils/decorator/DecoratorUtil.ts) renders UI overlays:

  • Selection box: Rectangle around selected symbols
  • Resize handles: Circles at corners/edges
  • Rotation handle: Circle above selection
  • Guides: Smart guides for alignment
typescript
drawSelectionBox(bounds: Box): SVGRectElement {
const rect = document.createElementNS(SVG_NS, 'rect')
rect.setAttribute('x', bounds.x)
rect.setAttribute('y', bounds.y)
rect.setAttribute('width', bounds.width)
rect.setAttribute('height', bounds.height)
rect.classList.add('selection-box')
return rect
}

Styling: Applied via CSS classes (.selection-box, .resize-handle).

Canvas Renderer Architecture

Component Structure
typescript
class CanvasRenderer extends BaseRenderer {
#canvas: HTMLCanvasElement
#context: CanvasRenderingContext2D
// Helper renderers
#strokeRenderer: CanvasRendererStroke
#shapeRenderer: CanvasRendererShape
#textRenderer: CanvasRendererText
}

Canvas Helper Classes (all in src/renderer/canvas/):

  1. CanvasRenderer - Main canvas renderer
  2. CanvasRendererStroke - Stroke rendering with pressure
  3. CanvasRendererShape - Shape rendering (circles, polygons, etc.)
  4. CanvasRendererText - Text rendering using Canvas text API

Implementation: src/renderer/canvas/CanvasRenderer.ts

Rendering Flow
  1. Clear Canvas: context.clearRect(0, 0, width, height)
  2. Draw Model: Iterate all symbols, draw each
  3. Draw Capture: Draw temporary strokes
  4. Draw Decorators: Selection, handles

Performance: Uses requestAnimationFrame for smooth updates.

Canvas Drawing
Stroke Drawing
typescript
drawStroke(stroke: Stroke) {
const ctx = this.#context
const points = stroke.points
ctx.beginPath()
ctx.moveTo(points[0].x, points[0].y)
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y)
}
ctx.strokeStyle = stroke.style.color
ctx.lineWidth = stroke.style.width
ctx.lineCap = 'round'
ctx.lineJoin = 'round'
ctx.stroke()
}

Pressure: Varies line width along stroke path.

Shape Drawing
typescript
drawCircle(circle: IICircle) {
const ctx = this.#context
ctx.beginPath()
ctx.arc(circle.center.x, circle.center.y, circle.radius, 0, 2 * Math.PI)
ctx.fillStyle = circle.style.fillColor
ctx.fill()
ctx.strokeStyle = circle.style.color
ctx.lineWidth = circle.style.width
ctx.stroke()
}
Text Drawing
typescript
drawText(text: IIText) {
const ctx = this.#context
ctx.font = `${text.style.fontSize}px ${text.style.fontFamily}`
ctx.fillStyle = text.style.color
ctx.textBaseline = 'alphabetic'
ctx.fillText(text.label, text.baseline.x, text.baseline.y)
}

Limitation: No native MathML support → rendered as plain text or images.

Style System

Pen Style

TPenStyle (src/style/Types.ts):

typescript
type TPenStyle = {
color: string // Hex color: '#000000'
width: number // Stroke width in pixels
'-myscript-pen-width'?: number // MyScript-specific width
'-myscript-pen-fill-style'?: 'none' | 'solid'
'-myscript-pen-fill-color'?: string
}

Usage:

typescript
const options = {
penStyle: {
color: '#2E7D32',
width: 2,
},
}
Theme System

TTheme for UI element styling:

typescript
type TTheme = {
ink: {
color: string
width: number
}
guides: {
enable: boolean
color: string
width: number
}
// ... more theme properties
}

Application: Injected as CSS variables.

Style Helpers

StyleHelper (src/style/StyleHelper.ts):

  • rgbToHex(r, g, b) - Color conversion
  • hexToRgb(hex) - Reverse conversion
  • computeStyleFromCss(element) - Extract computed styles

Transform System

Symbols can be transformed (translate, rotate, resize) via MatrixTransform.

Matrix Transform

MatrixTransform (src/transform/MatrixTransform.ts):

typescript
class MatrixTransform {
constructor(
public a: number, // Horizontal scaling
public b: number, // Vertical skewing
public c: number, // Horizontal skewing
public d: number, // Vertical scaling
public tx: number, // Horizontal translation
public ty: number // Vertical translation
) {}
static translate(x: number, y: number): MatrixTransform
static rotate(angle: number): MatrixTransform
static scale(sx: number, sy: number): MatrixTransform
applyToPoint(point: TPoint): TPoint
multiply(other: MatrixTransform): MatrixTransform
}

Usage:

typescript
// Rotate symbol 45 degrees
const rotation = MatrixTransform.rotate(Math.PI / 4)
const transformed = rotation.applyToPoint(symbol.center)
// Apply to SVG element
element.setAttribute('transform', rotation.toSVGString())
Symbol Bounds

Box class (src/model/Box.ts) for bounding boxes:

typescript
class Box {
constructor(
public x: number,
public y: number,
public width: number,
public height: number
) {}
contains(point: TPoint): boolean
overlaps(other: Box): boolean
union(other: Box): Box
}

Usage: Selection, hit testing, clipping.

Custom CSS Integration

iinkTS injects CSS at runtime via style element.

Default Styles

iink.css (src/iink.css):

css
.ms-editor {
width: 100%;
height: 100%;
touch-action: none;
user-select: none;
}
.ms-editor .capture-layer {
pointer-events: none;
}
.selection-box {
fill: none;
stroke: #1976D2;
stroke-width: 2;
stroke-dasharray: 5 5;
}

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/myscript/iinkts/rendering-system/badge)](https://verified-skill.com/skills/myscript/iinkts/rendering-system)

Something wrong with this skill or its page? Tell us.