AgentSkillsCN

parley-api

当您编写使用 parley 进行文本布局、字形塑形、光标/选区处理,或借助 vello_cpu/parley_draw 渲染文本时,可选用此方案。

SKILL.md
--- frontmatter
name: parley-api
description: Use when writing Rust code that uses parley for text layout, shaping glyphs, handling cursors/selection, or rendering text with vello_cpu/parley_draw

Parley API

Overview

Parley is a Rust library for rich text layout. It handles text shaping, line breaking, bidi resolution, styling, and cursor/selection management. For rendering, use parley_draw::GlyphRunBuilder with vello_cpu or another renderer implementing GlyphRenderer.

Brush type: Layout is generic over a Brush type. Any type implementing Clone + PartialEq + Default + Debug works. Define a custom struct wrapping your renderer's color type.

When to Use

  • Laying out styled text with fonts
  • Rendering glyphs via parley_draw::GlyphRunBuilder
  • Implementing text editors (cursor, selection)
  • Hit testing text coordinates
  • Line breaking and alignment

Quick Reference

TaskType/Method
Font databaseFontContext::new()
Layout scratch spaceLayoutContext::new()
Build with rangeslayout_cx.ranged_builder()
Build with treelayout_cx.tree_builder()
Break lineslayout.break_all_lines(max_width)
Align textlayout.align(width, Alignment::*, AlignmentOptions::default())
Iterate lineslayout.lines()
Get positioned glyphsglyph_run.positioned_glyphs()
Render glyphsGlyphRunBuilder::new(...).fill_glyphs(...)
Cursor from clickCursor::from_point(&layout, x, y)
Select wordSelection::word_from_point(&layout, x, y)

Core Pattern (with parley_draw + vello_cpu)

rust
use parley::{
    Alignment, AlignmentOptions, FontContext, FontWeight, GenericFamily,
    Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
};
use parley_draw::{Glyph, GlyphCaches, GlyphRunBuilder};
use vello_cpu::{Pixmap, RenderContext, kurbo, peniko::Color};

// Custom brush type (required - wraps your renderer's color)
#[derive(Clone, Copy, Debug, PartialEq, Default)]
struct ColorBrush { color: Color }

// Create once per app/thread
let mut font_cx = FontContext::new();
let mut layout_cx = LayoutContext::new();
let mut glyph_caches = GlyphCaches::new();

// Build layout
let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, 1.0, true);
builder.push_default(StyleProperty::Brush(ColorBrush { color: Color::BLACK }));
builder.push_default(GenericFamily::SystemUi);
builder.push_default(StyleProperty::FontSize(16.0));

let mut layout: Layout<ColorBrush> = builder.build(&text);
layout.break_all_lines(Some(400.0));
layout.align(Some(400.0), Alignment::Start, AlignmentOptions::default());

// Render using GlyphRunBuilder
for line in layout.lines() {
    for item in line.items() {
        let PositionedLayoutItem::GlyphRun(glyph_run) = item else { continue };

        renderer.set_paint(glyph_run.style().brush.color);
        let run = glyph_run.run();

        GlyphRunBuilder::new(run.font().clone(), *renderer.transform(), &mut renderer)
            .font_size(run.font_size())
            .hint(true)
            .normalized_coords(run.normalized_coords())
            .fill_glyphs(
                glyph_run.positioned_glyphs().map(|g| parley_draw::Glyph {
                    id: g.id, x: g.x, y: g.y,
                }),
                &mut glyph_caches,
            );
    }
}
glyph_caches.maintain(); // Call after rendering frame

// Output to PNG (vello_cpu)
let mut pixmap = Pixmap::new(width, height);
renderer.render_to_pixmap(&mut pixmap);
let png_data = pixmap.into_png().unwrap();

Two Builder Styles

RangedBuilder - Flat style spans

rust
let mut builder = layout_cx.ranged_builder(&mut font_cx, &text, scale, quantize);
builder.push_default(StyleProperty::Brush(MyBrush::default()));
builder.push_default(StyleProperty::FontSize(16.0));
builder.push(StyleProperty::FontWeight(FontWeight::new(700.0)), 0..10);
let layout: Layout<MyBrush> = builder.build(&text);

TreeBuilder - Nested style hierarchy

rust
let root_style = TextStyle {
    font_size: 16.0,
    brush: MyBrush::default(),
    ..Default::default()
};

let mut builder = layout_cx.tree_builder(&mut font_cx, scale, quantize, &root_style);
builder.push_style_modification_span(&[StyleProperty::FontWeight(FontWeight::new(700.0))]);
builder.push_text("Bold ");
builder.pop_style_span();
builder.push_text("normal");

let (layout, text) = builder.build();

Style Properties

rust
StyleProperty::Brush(my_brush)                      // Custom Brush type
StyleProperty::FontSize(24.0)
StyleProperty::FontWeight(FontWeight::new(700.0))   // 100-900
StyleProperty::FontStyle(FontStyle::Italic)
StyleProperty::Underline(true)
StyleProperty::Strikethrough(true)
StyleProperty::LineHeight(LineHeight::FontSizeRelative(1.5))
StyleProperty::LetterSpacing(2.0)
StyleProperty::WordSpacing(4.0)
StyleProperty::WordBreak(WordBreak::Normal)
StyleProperty::OverflowWrap(OverflowWrap::Normal)
GenericFamily::SystemUi  // Converts to StyleProperty automatically

Cursor & Selection

rust
use parley::{Cursor, Selection, Affinity};

// Cursor from text position
let cursor = Cursor::from_byte_index(&layout, byte_index, Affinity::Downstream);

// Cursor from mouse click
let cursor = Cursor::from_point(&layout, x, y);
let text_pos = cursor.index();

// Navigation
let next = cursor.next_logical_word(&layout);
let prev = cursor.previous_logical_word(&layout);

// Selection
let selection = Selection::word_from_point(&layout, x, y);
let range = selection.text_range(); // Range<usize>
let boxes = selection.geometry(&layout); // Vec<(BoundingBox, line_index)>

Inline Boxes

rust
use parley::InlineBox;

builder.push_inline_box(InlineBox {
    id: 1,
    index: 5,      // Insert at byte offset 5
    width: 32.0,
    height: 32.0,
});

Line Metrics

rust
for line in layout.lines() {
    let metrics = line.metrics();
    // metrics.ascent, metrics.descent, metrics.leading
    // metrics.baseline, metrics.advance
    // metrics.min_coord, metrics.max_coord
}

Common Mistakes

MistakeFix
Using skrifa directly for renderingUse parley_draw::GlyphRunBuilder
Using [u8; 4] as BrushDefine custom struct implementing Clone+PartialEq+Default+Debug
Forgetting glyph_caches.maintain()Call after each frame to evict unused entries
Creating FontContext per layoutCreate once, reuse
Forgetting break_all_lines()Call before rendering
Using glyphs() instead of positioned_glyphs()Use positioned_glyphs() for pre-computed positions

Key Types

  • FontContext - Font database (create once)
  • LayoutContext - Scratch space (create once)
  • Layout<B> - Computed layout, generic over Brush type
  • GlyphCaches - Cache for glyph outlines/hints (create once, call .maintain())
  • GlyphRunBuilder - Renders glyph runs via GlyphRenderer trait
  • Cursor / Selection - Text editing primitives