# cutan.characters.mouth_set

Generate the 9-shape default mouth set as parametric SVGs.

Produces drop-in art for `mouth_a` … `mouth_h` plus `mouth_x`, with
shape parameters cribbed from Daniel Wolf’s Rhubarb README (see research
§2.2). Every shape is rendered into the same per-mouth canvas
(`DEFAULT_MOUTH_VIEWBOX`, default 256×128) with a centered anchor, so
swapping attachments at runtime needs no per-shape offset.

The set is deliberately stylized — flat colors, bold strokes — so it reads
at the small sizes a typical cutout puppet uses (~30-40 px tall on a
1080p frame). It’s not meant to compete with hand-drawn art; it’s the
“always works” fallback.

```pycon
>>> svgs = generate_default_mouths()
>>> sorted(svgs.keys())
['mouth_a', 'mouth_b', 'mouth_c', 'mouth_d', 'mouth_e', 'mouth_f', 'mouth_g', 'mouth_h', 'mouth_x']
>>> len(svgs['mouth_a']) > 100
True
```

### Module Attributes

| [`DEFAULT_MOUTH_VIEWBOX`](#cutan.characters.mouth_set.DEFAULT_MOUTH_VIEWBOX)   | Mouth canvas viewBox (width, height) — small per-shape and centered so the anchor is always (0.5, 0.5).                    |
|--------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
| [`SMILE_CURVE_GAIN`](#cutan.characters.mouth_set.SMILE_CURVE_GAIN)        | How far a variant's `smile` lifts the mouth's corners, as a fraction of the shape's half width per unit of smile (an#253). |
| [`LIP_STROKE`](#cutan.characters.mouth_set.LIP_STROKE)              | wide enough that a form (a smile, a frown) still reads at full-figure framing (cutan#61; was 2).                           |
| [`DEFAULT_MOUTH_VARIANTS`](#cutan.characters.mouth_set.DEFAULT_MOUTH_VARIANTS)  | a `viseme@<form>` set per entry, its shapes drawn with this corner upturn added (an#98).                                   |

### Functions

| [`generate_default_mouths`](#cutan.characters.mouth_set.generate_default_mouths)(\*[, canvas, ...])       | Return `{"mouth_<letter>[_<form>]": <svg-string>, ...}` for every shape.                                                                                                            |
|---------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [`mouth_attachment_name`](#cutan.characters.mouth_set.mouth_attachment_name)(shape[, form])             | The attachment (and file stem) of one mouth drawing.                                                                                                                                |
| [`write_default_mouths`](#cutan.characters.mouth_set.write_default_mouths)(out_dir, \*[, canvas, ...]) | Write the default mouth SVGs into `out_dir` (created if missing), plus one `mouth_<shape>_<form>.svg` per shape for every `variants` entry (`{form: smile offset}`; `None` = none). |

### cutan.characters.mouth_set.DEFAULT_MOUTH_VARIANTS *: [dict](https://docs.python.org/3/builtins/stdtypes.html#dict)[[str](https://docs.python.org/3/builtins/stdtypes.html#str), [float](https://docs.python.org/3/builtins/functions.html#float)]* *= {'angry': -0.25, 'happy': 0.35, 'sad': -0.35}*

a
`viseme@<form>` set per entry, its shapes drawn with this corner upturn
added (an#98). Every preset that prefers a form the character lacks falls
back to `viseme` with a warning, so the default covers the two forms the
most-authored presets (`happy`/`amused`, `sad`, `angry`: cutan#61) ask for.

* **Type:**
  The mouth-form variants a synthesized character gets by default

### cutan.characters.mouth_set.DEFAULT_MOUTH_VIEWBOX *: [tuple](https://docs.python.org/3/builtins/stdtypes.html#tuple)[[int](https://docs.python.org/3/builtins/functions.html#int), [int](https://docs.python.org/3/builtins/functions.html#int)]* *= (256, 128)*

Mouth canvas viewBox (width, height) — small per-shape and centered so the
anchor is always (0.5, 0.5).

### cutan.characters.mouth_set.LIP_STROKE *: [float](https://docs.python.org/3/builtins/functions.html#float)* *= 3.0*

wide enough that a form (a smile,
a frown) still reads at full-figure framing (cutan#61; was 2).

* **Type:**
  The lip line’s width in the mouth canvas

### cutan.characters.mouth_set.SMILE_CURVE_GAIN *: [float](https://docs.python.org/3/builtins/functions.html#float)* *= 1.0*

How far a variant’s `smile` lifts the mouth’s corners, as a fraction of the
shape’s half width per unit of smile (an#253). The corners move against the
middle, so the line curves: a `viseme@happy` rest mouth is a closed smile,
`viseme@sad` a frown. At `DEFAULT_MOUTH_VARIANTS`’ 0.35 the corners of
the idle line rise about a sixth of its half width.

### cutan.characters.mouth_set.generate_default_mouths(, canvas=(256, 128), palette=None, shapes=('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'x'), smile=0.0, form=None)

Return `{"mouth_<letter>[_<form>]": <svg-string>, ...}` for every shape.

```pycon
>>> svgs = generate_default_mouths()
>>> 'mouth_x' in svgs and 'viewBox' in svgs['mouth_x']
True
>>> sorted(generate_default_mouths(shapes=["a"], smile=0.35, form="happy"))
['mouth_a_happy']
```

* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]

### cutan.characters.mouth_set.mouth_attachment_name(shape, form=None)

The attachment (and file stem) of one mouth drawing.

```pycon
>>> mouth_attachment_name("a"), mouth_attachment_name("a", "happy")
('mouth_a', 'mouth_a_happy')
```

* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)

### cutan.characters.mouth_set.write_default_mouths(out_dir, , canvas=(256, 128), palette=None, shapes=('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'x'), variants=None)

Write the default mouth SVGs into `out_dir` (created if missing),
plus one `mouth_<shape>_<form>.svg` per shape for every `variants`
entry (`{form: smile offset}`; `None` = none).

Returns the list of paths written: the neutral set in shape order, then
each variant’s.

* **Return type:**
  [`list`](https://docs.python.org/3/builtins/stdtypes.html#list)[[`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path)]
