npm package: @kanbun-skam/skam-html-renderer
Description: SKAM to HTML renderer - SKAMドキュメントから静的HTMLを生成
Author: berlysia
Latest version: 0.1.0

# @kanbun-skam/skam-html-renderer

**SKAM ドキュメント**から**静的 HTML** を生成するレンダラー。

縦書き・横書き・縦中横対応、返り点・送り仮名・ルビ等の訓点レンダリングを提供。

## インストール

```bash
npm install @kanbun-skam/skam-html-renderer
# or
pnpm add @kanbun-skam/skam-html-renderer
```

## 使い方

### 基本的なレンダリング

```typescript
import { render } from '@kanbun-skam/skam-html-renderer';
import type { SKAMDocument } from '@kanbun-skam/skam';

const doc: SKAMDocument = {
  format: 'skam@0.1',
  tokens: [
    { id: 't1', text: '学' },
    { id: 't2', text: '而' },
  ],
  marks: [{ type: 'okurigana', anchor: { from: 't1', to: 't1' }, value: 'びて' }],
  readings: [],
};

const result = render(doc);
// result.html: レンダリングされた HTML
// result.css: 適用すべき CSS
```

### 書字方向の指定

```typescript
import { render } from '@kanbun-skam/skam-html-renderer';

// 縦書き（デフォルト）
const vertical = render(doc, { writingMode: 'vertical' });

// 横書き
const horizontal = render(doc, { writingMode: 'horizontal' });
```

### 表示プロファイル

```typescript
import { render, PROFILES } from '@kanbun-skam/skam-html-renderer';

// フルプロファイル（すべての要素を表示、デフォルト）
const full = render(doc, { profile: PROFILES.full });

// 学習用基本プロファイル（返り点のみ）
const basic = render(doc, { profile: PROFILES.learningBasic });

// 学習用ヒント付きプロファイル（返り点+送り仮名）
const hint = render(doc, { profile: PROFILES.learningHint });
```

### スタイルのカスタマイズ

```typescript
import { render, getDefaultStyles, generateCSS } from '@kanbun-skam/skam-html-renderer';

// CSS 生成オプション
const css = generateCSS({
  writingMode: 'vertical', // 'vertical' | 'horizontal' | 'both'
  inline: false, // インラインモード
  useLayer: true, // @layer でラップ（デフォルト: true）
  layerName: 'skam-kanbun', // レイヤー名（デフォルト: 'skam-kanbun'）
});

// レンダリングも同様のオプションに対応
const result = render(doc, { writingMode: 'vertical' });
```

### CSS Variables によるカスタマイズ

生成される CSS は CSS Variables を公開しており、ユーザー側で簡単にカスタマイズできます。

```css
/* ユーザー側でカスタマイズ */
.skam-document {
  --skam-color-fg: #333;
  --skam-color-kaeriten: #c00;
  --skam-color-ruby: #666;
  --skam-font-family: '游明朝', serif;
}
```

#### 公開 CSS Variables 一覧

| カテゴリ       | 変数名                    | デフォルト値   | 用途                       |
| -------------- | ------------------------- | -------------- | -------------------------- |
| **色**         | `--skam-color-fg`         | `currentColor` | 前景色（テキスト、傍線等） |
|                | `--skam-color-kaeriten`   | `currentColor` | 返り点の色                 |
|                | `--skam-color-ruby`       | `currentColor` | ルビ・送り仮名の色         |
|                | `--skam-color-emphasis`   | `currentColor` | 傍点の色                   |
| **フォント**   | `--skam-font-family`      | `inherit`      | 本文フォント               |
|                | `--skam-font-family-ruby` | `inherit`      | ルビ・送り仮名フォント     |
| **サイズ**     | `--skam-glyph-size`       | `1em`          | 基準グリフサイズ           |
|                | `--skam-ruby-font-size`   | `0.5em`        | ルビ・送り仮名サイズ       |
| **余白・間隔** | `--skam-line-height`      | `2`            | 行間                       |
|                | `--skam-letter-spacing`   | `0`            | 字間                       |

### コピー可能範囲の制御

デフォルトでは、テキスト選択・コピー時に**本文のみ**がコピーされ、返り点・送り仮名・ルビなどの注記はコピーされません。

#### API での指定

```typescript
import { render, renderHTML } from '@kanbun-skam/skam-html-renderer';

// デフォルト: 本文のみコピー可能
const result = render(doc);

// 読み仮名もコピー可能
const result = render(doc, { copyable: ['ruby'] });

// 複数指定
const result = render(doc, { copyable: ['ruby', 'okurigana'] });

// 全てコピー可能
const result = render(doc, { copyable: 'all' });
```

#### 生成される HTML

```html
<!-- デフォルト: 本文のみコピー可能 -->
<div class="skam-document">...</div>

<!-- 読み仮名もコピー可能 -->
<div class="skam-document" data-copyable="ruby">...</div>

<!-- 複数指定（スペース区切り） -->
<div class="skam-document" data-copyable="ruby okurigana">...</div>

<!-- 全てコピー可能 -->
<div class="skam-document" data-copyable="all">...</div>
```

#### 指定可能な値

| 値          | 対象             |
| ----------- | ---------------- |
| `ruby`      | 読み仮名（ルビ） |
| `okurigana` | 送り仮名         |
| `soegana`   | 添え仮名         |
| `kaeriten`  | 返り点           |
| `okototen`  | ヲコト点         |
| `all`       | 全ての注記要素   |

**Note:** `data-copyable` 属性は CSS で制御されるため、HTML を直接編集して属性を変更することで、ビルド後でも動的に切り替えられます。

### CSS @layer との統合

生成される CSS はデフォルトで `@layer skam-kanbun` でラップされます。
これにより、ユーザー側でカスケード順序を制御できます。

```css
/* ユーザー側でレイヤー順序を定義 */
@layer reset, base, skam-kanbun, app;

/* skam-kanbun レイヤーより後のレイヤーで上書き可能 */
@layer app {
  .skam-document {
    --skam-color-kaeriten: red;
  }
}
```

#### @layer を無効にする

```typescript
const css = generateCSS({ useLayer: false });
```

### リセット CSS との共存

本ライブラリは `:where()` セレクタで specificity を 0 に抑えているため、
一般的なリセット CSS と競合しにくい設計です。

#### sanitize.css との組み合わせ

```html
<link rel="stylesheet" href="sanitize.css" />
<style>
  @layer reset, skam-kanbun;
</style>
```

#### Tailwind CSS preflight との組み合わせ

```css
@layer base, skam-kanbun, components, utilities;
```

## レンダリング対応

| Mark Type   | 対応状況 |
| ----------- | -------- |
| `kaeri`     | ✅       |
| `okurigana` | ✅       |
| `yomigana`  | ✅       |
| `soegana`   | ✅       |
| `okimoji`   | ✅       |
| `joji`      | ✅       |
| `kutoten`   | ✅       |
| `emphasis`  | ✅       |
| `saidoku`   | ✅       |
| `tateten`   | ✅       |
| `highlight` | ✅       |
| `ref`       | ✅       |
| `okototen`  | 🚧       |

## 出力形式

```typescript
interface RenderResult {
  /** レンダリングされた HTML 文字列 */
  html: string;
  /** 適用すべき CSS 文字列 */
  css: string;
}
```

## 内部アーキテクチャ

### モジュール構成と依存方向

依存方向は `shared → display → API` の一方向に固定されています。

```
API層 (renderer.ts)
  ├── 公開 API (render, renderHTML, generateCSS)
  ├── option 正規化
  └── CSS 生成呼び出し (styles.ts)
       │
       ▼
display オーケストレータ層 (render-display-layer.ts)
  ├── group 解決 (tateten/highlight/range marks)
  ├── Pass 1/Pass 2 呼び出し
  └── block 単位合成
       │
       ▼
display 実装層
  ├── Pass 1: build-render-tree.ts (tokens+marks → BlockRenderTree)
  ├── Pass 2: render-tree.ts (BlockRenderTree → HTML string)
  └── token-renderer.ts (個別トークンの HTML 生成)
       │
       ▼
共通基盤層
  ├── render-config.ts (RenderProfile, RubyMethod, PROFILES)
  ├── html-utils.ts (escapeHtml, shouldApplyTateChuYoko 等)
  ├── mark-utils.ts (getBlockStartMarks 等)
  └── render-tree-types.ts (IR 型定義)
```

### CSS Grid 命名規約

`styles.ts` のグリッドコンテナは `grid-template-areas` で名前付き配置を使用しています。

| 領域名       | 用途                          |
| ------------ | ----------------------------- |
| `ruby`       | ルビ注記（読み仮名）          |
| `ruby-over`  | 再読文字の上側ルビ            |
| `ruby-under` | 再読文字の下側ルビ            |
| `base`       | 本文文字                      |
| `suffix`     | suffix-row（改行排除用）      |
| `emphasis`   | 傍点行                        |
| `okuri`      | 送り仮名（suffix-row 内）     |
| `kutoten`    | 句読点（suffix-row 内）       |
| `kaeri`      | 返り点（suffix-row 内）       |
| `saidoku`    | 再読送り仮名（suffix-row 内） |

### baseline 補正契約

`calibrate.ts` が Chromium の inline-grid baseline バグを実測検出し、
`--{prefix}-grid-baseline-fix: 1` を設定します。
各グリッドコンテナの `vertical-align` がこの変数を参照して補正値を算出します。

## 関連パッケージ

- [@kanbun-skam/skam](https://github.com/berlysia/kanbun-annotation/tree/master/packages/skam) - 型定義・バリデーター
- [@kanbun-skam/skam-xml-parser](https://github.com/berlysia/kanbun-annotation/tree/master/packages/skam-xml-parser) - SKAM-ML/XML パーサー

## Playground

インタラクティブなデモ: [https://berlysia.github.io/kanbun-annotation/](https://berlysia.github.io/kanbun-annotation/)

## ライセンス

MIT