Skip to content

Overview

  • cappan のコアライブラリです。フォントの読み込みからテキストレイアウト、ラスタライズ、ビットマップ出力までの全パイプラインを提供します。

詳細ドキュメント:

  • font — フォント読み込み・グリフアクセス・WOFF/WOFF2
  • layout — テキストレイアウト・シェーピング
  • raster — ラスタライズ・グリフキャッシュ・アトラステクスチャ
  • render — テキストレンダリング・ビットマップ・インクリメンタルレンダリング

クイックスタート

テキストをRGBAビットマップにレンダリング

const cappan = @import("cappan_core");
const Font = cappan.font.Font;
const renderer = cappan.render.renderer;

// フォント読み込み
var font = try Font.init(allocator, font_data, null);
defer font.deinit();

// レンダリング
const fonts = [_]Font{font};
var bitmap = try renderer.renderText(allocator, &fonts, "Hello, World!", .{
    .pixel_size = 48.0,
    .fg_color = .{ .r = 0, .g = 0, .b = 0, .a = 255 },
    .bg_color = .{ .r = 255, .g = 255, .b = 255, .a = 255 },
});
defer bitmap.deinit();

// bitmap.pixels: []u8 (RGBA, width * height * 4 bytes)
// bitmap.width, bitmap.height

ストリーミングレンダリング(行単位)

const RowRenderer = cappan.render.renderer.RowRenderer;

var row_renderer = try RowRenderer.init(allocator, &fonts, "Hello", .{});
defer row_renderer.deinit();

var y: u32 = 0;
while (y < row_renderer.height) : (y += 1) {
    const row_rgba = row_renderer.renderRow(y); // []const u8 (width * 4)
    // 行ごとにPNGエンコーダ等に渡す
}

テキストレイアウトのみ取得

const shaper = cappan.layout.shaper;

var layout = try shaper.layoutText(allocator, &fonts, "Hello, World!", .{
    .pixel_size = 48.0,
    .max_width = 200.0,      // ワードラップ
    .text_align = .center,
});
defer layout.deinit();

for (layout.positions) |pos| {
    // pos.glyph_id, pos.x_offset, pos.y_offset, pos.font_index
}

グリフアトラスの利用

const atlas_mod = cappan.raster.atlas;

var atlas = atlas_mod.GlyphAtlas.init(allocator, .{
    .page_width = 1024,
    .page_height = 1024,
});
defer atlas.deinit();

// グリフを自動ラスタライズしてアトラスにパック
const region = (try atlas.getOrInsert(font, 0, glyph_id, 48.0)) orelse continue;
// region.page, region.x, region.y, region.width, region.height

// ページのピクセルデータを取得(GPU アップロード等)
const pixels = atlas.getPagePixels(region.page).?;

インクリメンタルレンダリング

const incremental = cappan.render.incremental;

var reveal = try incremental.IncrementalRenderer.init(allocator, &fonts, "Hello", .{
    .pixel_size = 48.0,
    .strategy = .{ .sweep = .{} },  // sweep / fade / contour_trace / medial_axis
    .timing = .sequential,
});
defer reveal.deinit();

// progress: 0.0(非表示)→ 1.0(完全表示)
var frame = try reveal.renderFrame(0.5);
defer frame.deinit();

モジュール構成

cappan_core
├── font                   フォントパースとグリフデータ
│   ├── Font               高レベルフォントAPI
│   ├── parser             バイナリパーサー(OffsetTable, TableRecord)
│   ├── glyph              グリフアウトライン(Point, Contour, GlyphOutline)
│   ├── charstring         CFF Type2 チャーストリングインタープリタ
│   ├── woff               WOFF1 透過変換
│   ├── woff2              WOFF2 透過変換(Brotli + glyf/loca 変換)
│   └── table              個別テーブルパーサー
│       ├── head, maxp, hhea, cmap, loca, glyf, hmtx
│       ├── kern, gpos, otlayout
│       ├── cff, colr, cpal, name
├── raster                 ラスタライズパイプライン
│   ├── outline            アウトラインスケーリング、ベジェ曲線展開
│   ├── scanline           スキャンラインフィル(8xスーパーサンプリング)
│   ├── rasterizer         アウトライン→グレースケールビットマップ変換
│   ├── stroker            ストロークアウトライン生成
│   ├── glyph_cache        ラスタライズ結果キャッシュ(個別グリフ単位)
│   └── atlas              グリフアトラステクスチャ(Skylineパッキング)
├── layout                 テキストレイアウト
│   └── shaper             グリフ配置(カーニング・ワードラップ・アラインメント)
├── render                 テキストレンダリング
│   ├── renderer           テキスト→RGBAビットマップ(renderText / RowRenderer)
│   ├── bitmap             グレースケールビットマップ
│   ├── rgba_bitmap        RGBAビットマップ(Color, blendPixel)
│   ├── paint              マルチレイヤー描画(PaintStack)
│   ├── gamma              ガンマ補正(sRGB線形空間ブレンド)
│   ├── incremental        インクリメンタルレンダリング
│   ├── easing             イージング関数
│   └── reveal             リビールストラテジー
│       ├── sweep          水平スウィープ
│       ├── fade           フェードイン
│       ├── contour_trace  輪郭トレース
│       ├── medial_axis    中心軸
│       ├── distance_field ディスタンスフィールド
│       ├── extrema_wave   エクストリーマウェーブ
│       ├── skeleton_grow  スケルトングロウ
│       └── tangent_flow   タンジェントフロー
├── compress               圧縮
│   └── brotli             Brotli展開(pure Zig実装)
└── err                    共通エラー・診断情報

データフロー

テキスト (UTF-8)
┌─────────────┐
│ layout.shaper │  layoutText() / layoutStyledText()
│             │  カーニング、ワードラップ、アラインメント
└─────┬───────┘
      │  TextLayout { positions: []GlyphPosition }
┌─────────────┐
│ font.Font   │  getGlyphOutline(glyph_id)
│             │  TrueType / CFF アウトライン取得
└─────┬───────┘
      │  GlyphOutline { contours, bounding box }
┌──────────────┐
│ raster       │  rasterizeGlyph(outline, scale, padding)
│ .rasterizer  │  スケーリング → ベジェ展開 → スキャンラインフィル
│              │  ┌─ PaintStack 時 ─────────────────────────┐
│ .stroker     │  │ generateStrokeOutline() でストロークパス │
│              │  │ を生成し、scanline.rasterize() でフィル  │
│              │  └────────────────────────────────────────┘
└─────┬────────┘
      │  RasterResult / CachedRaster { pixels (grayscale), width, height, offsets }
┌──────────────┐
│ render       │  blendPixel() で各グリフをRGBAビットマップに合成
│ .renderer    │  ガンマ補正、LCDサブピクセル、フラクショナルポジショニング
│ .paint       │  PaintStack: 操作ごとにレイヤーを重ねがけ
└─────┬────────┘
  RgbaBitmap { pixels (RGBA), width, height }

モジュールのインポート

const cappan = @import("cappan_core");

// フォント
const Font = cappan.font.Font;

// レイアウト
const shaper = cappan.layout.shaper;

// ラスタライズ
const rasterizer = cappan.raster.rasterizer;
const stroker = cappan.raster.stroker;
const GlyphCache = cappan.raster.glyph_cache.GlyphCache;
const GlyphAtlas = cappan.raster.atlas.GlyphAtlas;

// レンダリング
const renderer = cappan.render.renderer;
const RgbaBitmap = cappan.render.rgba_bitmap.RgbaBitmap;
const Color = cappan.render.rgba_bitmap.Color;
const paint = cappan.render.paint;
const PaintOperation = cappan.render.paint.PaintOperation;
const Bitmap = cappan.render.bitmap.Bitmap;
const gamma = cappan.render.gamma;

// アニメーション
const incremental = cappan.render.incremental;

// 圧縮
const brotli = cappan.compress.brotli;