Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export default defineConfig({
text: "Charming Canvas",
link: "/canvas",
},
{
text: "Charming Path",
link: "/path",
},
],
},
],
Expand Down
22 changes: 22 additions & 0 deletions docs/api-index.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
# API Index

Charming offers a set of utilities for working with SVG and Canvas, which can be used either together or independently.

## [Charming DOM](/dom)

Create and manipulate DOM elements.

- [_cm_.**svg**](/dom#cm-svg) — SVG via tagged template literal.
- [_cm_.**html**](/dom#cm-html) — HTML via tagged template literal.
- [_cm_.**attr**](/dom#cm-attr) — get, set, or remove attributes (including styles and events) on a node.

## [Charming Canvas](/canvas)

Set up a 2D canvas and draw shapes.

- [_cm_.**context2d**](/canvas#cm-context2d) — create a 2D canvas rendering context (size, DPR, optional container).
- [_cm_.**cssFont**](/canvas#cm-css-font) — build a CSS `font` shorthand string (canvas, DOM, or D3).
- [_cm_.**strokeLine**](/canvas#cm-stroke-line) — stroke a line between two points.
- [_cm_.**fillCircle**](/canvas#cm-fill-circle) — fill a circle.
- [_cm_.**strokeCircle**](/canvas#cm-stroke-circle) — stroke a circle.
- [_cm_.**strokeEllipse**](/canvas#cm-stroke-ellipse) — stroke an axis-aligned ellipse.
- [_cm_.**fillEllipse**](/canvas#cm-fill-ellipse) — fill an axis-aligned ellipse.

## [Charming Path](/path)

Build SVG path strings for common shapes.

- [_cm_.**pathLine**](/path#cm-path-line) — open segment `M` … `L` …
- [_cm_.**pathCircle**](/path#cm-path-circle) — closed circle.
- [_cm_.**pathRect**](/path#cm-path-rect) — closed rectangle.
- [_cm_.**pathEllipse**](/path#cm-path-ellipse) — closed axis-aligned ellipse.
- [_cm_.**pathPolygon**](/path#cm-path-polygon) — closed path through `[x, y]` points.
38 changes: 38 additions & 0 deletions docs/canvas.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,44 @@ context.arc(50, 50, 25, 0, Math.PI * 2);
context.fill();
```

## _cm_.cssFont(_options_) {#cm-css-font}

Returns a **CSS `font` shorthand** string suitable for `CanvasRenderingContext2D#font`, DOM `element.style.font`, or libraries that accept a font string (for example D3’s `.style("font", …)`).

Options (all optional):

- **fontStyle** — default `"normal"`.
- **fontVariant** — default `"normal"`.
- **fontWeight** — default `"normal"`.
- **fontSize** — default `10`; may be a number (interpreted as pixels) or a string with units, e.g. `"1.2rem"`.
- **fontFamily** — default `"sans-serif"`.

Extra keys on the options object are ignored. The result has normalized spacing.

```js eval code=false
(() => {
const context = cm.context2d({width: 220, height: 56});
context.fillStyle = "orange";
context.fillRect(0, 0, 220, 56);
context.fillStyle = "#222";
context.font = cm.cssFont({fontSize: 22, fontFamily: "Georgia, serif"});
context.textBaseline = "middle";
context.fillText("Hello Canvas!", 12, 28);
return context.canvas;
})();
```

```js
const context = cm.context2d({width: 220, height: 56});
context.font = cm.cssFont({
fontStyle: "italic",
fontWeight: "700",
fontSize: 18,
fontFamily: "system-ui, sans-serif",
});
context.fillText("Hello Canvas!", 10, 30);
```

## _cm_.strokeLine(_context_, _x1_, _y1_, _x2_, _y2_) {#cm-stroke-line}

Strokes from (_x1_, _y1_) to (_x2_, _y2_) using the current stroke style and line width.
Expand Down
113 changes: 113 additions & 0 deletions docs/path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Charming Path

**Charming Path** builds SVG [`d`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d) path strings from numbers—handy for `<path>`, [`getPointAtLength`](https://developer.mozilla.org/en-US/docs/Web/API/SVGGeometryElement/getPointAtLength), or APIs such as `layoutTextInPath` that expect a path description. Shapes are axis-aligned in user space (no rotation in the path itself).

```js eval code=false
(() => {
const d = cm.pathCircle(100, 100, 80);
return cm.svg`<svg ${{width: 200, height: 200}}>
<path ${{fill: "none", stroke: "black", stroke_width: 2, d}} />
</svg>`;
})();
```

```js
const d = cm.pathCircle(100, 100, 80);

cm.svg`<svg ${{width: 200, height: 200}}>
<path ${{fill: "none", stroke: "black", stroke_width: 2, d}} />
</svg>`;
```

## _cm_.pathLine(_x1_, _y1_, _x2_, _y2_) {#cm-path-line}

Returns an **open** path: `M` to (_x1_, _y1_), `L` to (_x2_, _y2_).

```js eval code=false
(() => {
const d = cm.pathLine(10, 90, 90, 10);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "steelblue", stroke_width: 2, d}} />
</svg>`;
})();
```

```js
const d = cm.pathLine(10, 90, 90, 10);
```

## _cm_.pathCircle(_cx_, _cy_, _r_) {#cm-path-circle}

Returns a **closed** circle centered at (_cx_, _cy_) with radius _r_, using two arc segments.

```js eval code=false
(() => {
const d = cm.pathCircle(50, 50, 40);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "teal", stroke_width: 2, d}} />
</svg>`;
})();
```

```js
const d = cm.pathCircle(100, 100, 50);
```

## _cm_.pathRect(_x_, _y_, _width_, _height_) {#cm-path-rect}

Returns a **closed** rectangle with top-left (_x_, _y_) and size _width_ × _height_.

```js eval code=false
(() => {
const d = cm.pathRect(15, 25, 70, 50);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "purple", stroke_width: 2, d}} />
</svg>`;
})();
```

```js
const d = cm.pathRect(0, 0, 100, 50);
```

## _cm_.pathEllipse(_cx_, _cy_, _rx_, _ry_) {#cm-path-ellipse}

Returns a **closed** axis-aligned ellipse centered at (_cx_, _cy_) with radii _rx_ and _ry_, using two elliptical arcs.

```js eval code=false
(() => {
const d = cm.pathEllipse(50, 50, 42, 28);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "coral", stroke_width: 2, d}} />
</svg>`;
})();
```

```js
const d = cm.pathEllipse(100, 100, 60, 40);
```

## _cm_.pathPolygon(_points_) {#cm-path-polygon}

Returns a **closed** path through _points_, an array of `[x, y]` pairs. The first point is moved to with `M`; later points use `L`; the path ends with `Z`.

```js eval code=false
(() => {
const d = cm.pathPolygon([
[50, 10],
[90, 90],
[10, 90],
]);
return cm.svg`<svg ${{width: 100, height: 100, viewBox: "0 0 100 100"}}>
<path ${{fill: "none", stroke: "darkgreen", stroke_width: 2, d}} />
</svg>`;
})();
```

```js
const d = cm.pathPolygon([
[0, 0],
[100, 0],
[50, 80],
]);
```
Loading