Sign in

    moon_cosmic

    MoonBit port of cosmic-text (text shaping, layout, and editor primitives) built on moon_swash.

    text
    layout
    bidi
    shaping
    swash
    cosmic-text
    Download zip
    Author
    Version
    0.1.7
    License
    Apache-2.0
    Last updated
    7 months ago
    Downloads
    56K

    #Milky2018/moon_cosmic

    MoonBit port of Rust cosmic-text (text shaping, layout, and editor primitives), built on top of Milky2018/moon_swash.

    The project keeps behavior aligned with upstream cosmic-text through automated tests where feasible.

    #Features

    • Font system (in-memory): load TTF/OTF/TTC bytes; resolve fonts by family + weight; per-codepoint fallback.
    • Shaping: Shaping::Advanced uses moon_swash/shape to produce clustered glyphs and advances; tabs snap to stops.
    • Wrapping & layout: Wrap::{None,Glyph,Word,WordOrGlyph}, Align::{Left,Right,Center,Justified,End}, hinting.
    • BiDi: paragraph iteration + embedding levels and visual reordering of glyph runs.
    • Raster cache: CacheKey + SwashCache backed by moon_swash/scale (variable wght axis support; fake-italic transform).
    • Editor primitives: Buffer, Editor, cursor/selection, hit-testing, and motion actions.

    #Install

    Add dependency:

    moon add Milky2018/moon_cosmic

    #Quick Start (Layout)

    import Milky2018/moon_cosmic

    fn main {
    // Provide font bytes from your host app (file I/O is app-specific).
    let font_bytes : Bytes = /* ... */

    let font_system = FontSystem::new().load_font_data(font_bytes)
    let metrics = Metrics::new(14.0F, 20.0F)

    let attrs = Attrs::new().family(Family::Name("Inter"))
    let attrs_list = AttrsList::new(attrs)

    let buffer = Buffer::new(metrics)
    .set_wrap(Wrap::WordOrGlyph)
    .set_text("Hello world", attrs_list, Shaping::Advanced)
    .set_size(Some(200.0F), None)
    .layout_all_with_font_system(font_system)

    for run in buffer.layout_runs() {
    // `run.glyphs`: Array[LayoutGlyph]
    // `run.line_w`: line width in pixels
    ignore(run)
    }
    }

    #Quick Start (Draw)

    Buffer::draw renders via a callback (pixel-oriented for compatibility with the legacy renderer pattern).

    import Milky2018/moon_cosmic

    fn draw_demo(font_bytes : Bytes) -> Unit {
    let font_system = FontSystem::new().load_font_data(font_bytes)
    let cache = SwashCache::new()

    let buffer = Buffer::new(Metrics::new(14.0F, 20.0F))
    .set_text("Hi!", AttrsList::new(Attrs::new()), Shaping::Advanced)
    .set_size(Some(200.0F), None)

    let (_buffer, _cache) = buffer.draw(
    font_system,
    cache,
    Color::rgb(0, 0, 0),
    fn(_x, _y, _w, _h, _color) { () },
    )
    }

    #Integration Notes

    #Subpixel Cache Keys (Atlas Caching)

    Downstream atlas caches should key by PhysicalGlyph.cache_key (includes x_bin/y_bin), not only (glyph_id, x, y).

    #Font Collections (TTC / Multi-face)

    FontSystem::load_font_data may load multiple faces from one file/collection. Each face gets its own font_id. Use FontSystem::get_font(font_id) / FontSystem::get_font_entry(font_id) to rasterize the correct face for a given LayoutGlyph.

    #Dev Workflow

    Common commands:

    moon check # Lint/type-check moon test # Run tests moon fmt # Format moon info # Update .mbti moon info && moon fmt

    #Test Font Blob

    Some tests require real font metrics. This repo embeds Inter-Regular.ttf into src/test_fonts_test.mbt (base64).

    To regenerate it (if test font data changes):

    python3 scripts/gen_test_font_mbt.py moon fmt moon test

    #License

    Apache-2.0. Upstream cosmic-text is dual-licensed MIT OR Apache-2.0.

    CacheKeyFlags

    type CacheKeyFlags = UInt

    Flags that change rendering.

    Renderer

    pub(open) trait Renderer {
    rectangle(Self, x : Int, y : Int, w : UInt, h : UInt, color : Color) -> Unit
    glyph(Self, physical_glyph : PhysicalGlyph, color : Color) -> Unit
    }

    Custom renderer for buffers and editors.

    Action

    pub(all) enum Action {
    Motion(Motion)
    Escape
    Insert(Char)
    Enter
    Backspace
    Delete
    Indent
    Unindent
    Click(Int, Int)
    DoubleClick(Int, Int)
    TripleClick(Int, Int)
    Drag(Int, Int)
    Scroll(Float)
    }

    An action to perform on an editor.

    Affinity

    pub(all) enum Affinity {
    Before
    After
    }

    Whether to associate cursors placed at a boundary between runs with the run before or after it.
    impl Eq for Affinity

    Affinity::after

    fn Affinity::after(self : Affinity) -> Bool

    Affinity::before

    fn Affinity::before(self : Affinity) -> Bool

    Affinity::from_after

    fn Affinity::from_after(after : Bool) -> Affinity

    Affinity::from_before

    fn Affinity::from_before(before : Bool) -> Affinity

    Align

    pub(all) enum Align {
    Left
    Right
    Center
    Justified
    End
    }

    Align or justify

    Attrs

    pub struct Attrs {
    metadata : Int
    family : Family
    weight : Weight
    stretch :
    Stretch

    style :
    Style

    metrics_opt : CacheMetrics?
    }

    impl Eq for Attrs
    impl Hash for Attrs

    Attrs::family

    fn Attrs::family(self : Attrs, family : Family) -> Attrs

    Attrs::family_value

    fn Attrs::family_value(self : Attrs) -> Family

    Attrs::metadata

    fn Attrs::metadata(self : Attrs) -> Int

    Attrs::metrics

    fn Attrs::metrics(self : Attrs, metrics : Metrics) -> Attrs

    Set Metrics, overriding values in Buffer for this span.

    Attrs::metrics_opt

    fn Attrs::metrics_opt(self : Attrs) -> Metrics?

    Return optional Metrics override.

    Attrs::new

    fn Attrs::new() -> Attrs

    Attrs::stretch

    fn Attrs::stretch(self : Attrs, stretch :
    Stretch
    ) -> Attrs

    Attrs::stretch_value

    fn Attrs::stretch_value(self : Attrs) ->
    Stretch

    Attrs::style

    fn Attrs::style(self : Attrs, style :
    Style
    ) -> Attrs

    Attrs::style_value

    fn Attrs::style_value(self : Attrs) ->
    Style

    Attrs::weight

    fn Attrs::weight(self : Attrs, weight : Weight) -> Attrs

    Attrs::weight_value

    fn Attrs::weight_value(self : Attrs) -> Weight

    Attrs::with_metadata

    fn Attrs::with_metadata(metadata : Int) -> Attrs

    AttrsList

    pub struct AttrsList {
    defaults : Attrs
    spans : Array[AttrsSpan]
    }

    impl Eq for AttrsList
    impl Hash for AttrsList

    AttrsList::add_span

    fn AttrsList::add_span(self : AttrsList, start : Int, end : Int, attrs : Attrs) -> AttrsList

    AttrsList::defaults

    fn AttrsList::defaults(self : AttrsList) -> Attrs

    AttrsList::eq

    fn AttrsList::eq(self : AttrsList, other : AttrsList) -> Bool

    AttrsList::get_span

    fn AttrsList::get_span(self : AttrsList, pos : Int) -> Attrs

    AttrsList::new

    fn AttrsList::new(defaults : Attrs) -> AttrsList

    AttrsList::spans_iter

    fn AttrsList::spans_iter(self : AttrsList) -> Array[AttrsSpan]

    AttrsList::split_off

    fn AttrsList::split_off(self : AttrsList, index : Int) -> (AttrsList, AttrsList)

    Splits spans at index and returns (left, right).

    AttrsSpan

    pub struct AttrsSpan {
    start : Int
    end : Int
    attrs : Attrs
    }

    impl Eq for AttrsSpan
    impl Hash for AttrsSpan

    BidiParagraphs

    pub struct BidiParagraphs {
    text : String
    pos : Int
    ascii_fast : Bool
    }

    Paragraph iterator for bidi-aware text processing.

    Upstream cosmic-text uses unicode-bidi to find paragraph boundaries. This MoonBit port approximates the behavior:
    • Fast path for simple ASCII text: split on '\n' (does not special-case CRLF).
    • Otherwise: split on BidiClass::B (Paragraph_Separator) using swash properties.

    BidiParagraphs::iter

    fn BidiParagraphs::iter(self : BidiParagraphs) -> Iter[String]

    BidiParagraphs::new

    fn BidiParagraphs::new(text : String) -> BidiParagraphs

    BidiParagraphs::next

    fn BidiParagraphs::next(self : BidiParagraphs) -> (BidiParagraphs, String)?

    Returns (next, paragraph_text) if any.

    Buffer

    pub struct Buffer {
    lines : Array[BufferLine]
    metrics : Metrics
    width_opt : Float?
    height_opt : Float?
    scroll : Scroll
    redraw : Bool
    wrap : Wrap
    monospace_width : Float?
    tab_width : Int
    hinting : Hinting
    }

    Minimal Buffer port scaffold from cosmic-text/src/buffer.rs.

    This MVP focuses on line splitting, cache invalidation and redraw semantics.

    Buffer::cursor_motion

    fn Buffer::cursor_motion(self : Buffer, cursor : Cursor, cursor_x_opt : Int?, motion : Motion) -> (Cursor, Int?)?

    Cursor motion based on current layout runs (MVP).

    This uses visual lines from layout_runs() for vertical movement.

    Buffer::draw

    fn Buffer::draw(self : Buffer, font_system : FontSystem, swash_cache : SwashCache, text_color : Color, f : (Int, Int, UInt, UInt, Color) -> Unit) -> (Buffer, SwashCache)

    Draw the buffer with a font system and swash cache.

    This is a naming-aligned convenience wrapper (matches upstream's Buffer::draw intent).

    Buffer::draw_with_font_system

    fn Buffer::draw_with_font_system(self : Buffer, font_system : FontSystem, swash_cache : SwashCache, text_color : Color, f : (Int, Int, UInt, UInt, Color) -> Unit) -> (Buffer, SwashCache)

    Draw the buffer using a SwashCache.

    For compatibility with upstream's legacy renderer helper, the callback is called per pixel (w = 1, h = 1).

    Buffer::hinting

    fn Buffer::hinting(self : Buffer) -> Hinting

    Buffer::hit

    fn Buffer::hit(self : Buffer, x : Float, y : Float) -> Cursor?

    Hit-testing: map a physical x/y position to a cursor (MVP).

    Buffer::layout_all

    fn Buffer::layout_all(self : Buffer) -> Buffer

    Layout all lines using the built-in shaping (no font system).

    Buffer::layout_all_with_font_system

    fn Buffer::layout_all_with_font_system(self : Buffer, font_system : FontSystem) -> Buffer

    Layout all lines using a provided FontSystem (best-effort).

    Buffer::layout_runs

    fn Buffer::layout_runs(self : Buffer) -> Iter[LayoutRun]

    Buffer::lines

    fn Buffer::lines(self : Buffer) -> Array[BufferLine]

    Buffer::metrics

    fn Buffer::metrics(self : Buffer) -> Metrics

    Buffer::new

    fn Buffer::new(metrics : Metrics) -> Buffer

    Buffer::new_empty

    fn Buffer::new_empty(metrics : Metrics) -> Buffer

    Create an empty Buffer with the provided Metrics.

    Panics (fails) if metrics.line_height is zero (aligned with upstream).

    Buffer::redraw

    fn Buffer::redraw(self : Buffer) -> Bool

    Buffer::scroll

    fn Buffer::scroll(self : Buffer) -> Scroll

    Buffer::set_hinting

    fn Buffer::set_hinting(self : Buffer, hinting : Hinting) -> Buffer

    Buffer::set_metrics

    fn Buffer::set_metrics(self : Buffer, metrics : Metrics) -> Buffer

    Buffer::set_monospace_width

    fn Buffer::set_monospace_width(self : Buffer, monospace_width : Float?) -> Buffer

    Buffer::set_redraw

    fn Buffer::set_redraw(self : Buffer, redraw : Bool) -> Buffer

    Buffer::set_rich_text

    fn Buffer::set_rich_text(self : Buffer, spans : Array[(String, Attrs)], default_attrs : Attrs, shaping : Shaping, alignment : Align?) -> Buffer

    Set rich text of buffer using styled spans (pairs of text and attrs).

    This mirrors cosmic-text's Buffer::set_rich_text behavior:
    • Concatenates spans into one string.
    • Splits into paragraphs using BidiParagraphs.
    • Builds per-line AttrsList spans relative to each line.
    • Uses LineEnding::default() for all lines.

    Buffer::set_scroll

    fn Buffer::set_scroll(self : Buffer, scroll : Scroll) -> Buffer

    Buffer::set_size

    fn Buffer::set_size(self : Buffer, width_opt : Float?, height_opt : Float?) -> Buffer

    Buffer::set_tab_width

    fn Buffer::set_tab_width(self : Buffer, tab_width : Int) -> Buffer

    Buffer::set_text

    fn Buffer::set_text(self : Buffer, text : String, attrs_list : AttrsList, shaping : Shaping) -> Buffer

    Set full text of buffer (MVP).

    NOTE: attrs_list is copied per line (upstream spans are per line).

    Buffer::set_wrap

    fn Buffer::set_wrap(self : Buffer, wrap : Wrap) -> Buffer

    Buffer::shape_all_with_font_system

    fn Buffer::shape_all_with_font_system(self : Buffer, font_system : FontSystem) -> Buffer

    Shape all lines using a provided FontSystem (best-effort).

    This is a coarse-grained helper for clients that do not want to manage per-line shaping manually yet.

    Buffer::size

    fn Buffer::size(self : Buffer) -> (Float?, Float?)

    Buffer::wrap

    fn Buffer::wrap(self : Buffer) -> Wrap

    BufferLine

    pub struct BufferLine {
    text : String
    ending : LineEnding
    attrs_list : AttrsList
    align : Align?
    shape_opt : Cached[ShapeLine]
    layout_opt : Cached[Array[LayoutLine]]
    shaping : Shaping
    metadata : Int?
    }

    BufferLine::align

    fn BufferLine::align(self : BufferLine) -> Align?

    BufferLine::append

    fn BufferLine::append(self : BufferLine, other : BufferLine) -> BufferLine

    Append other to self (consumes both, returns updated line).

    BufferLine::attrs_list

    fn BufferLine::attrs_list(self : BufferLine) -> AttrsList

    BufferLine::ending

    fn BufferLine::ending(self : BufferLine) -> LineEnding

    BufferLine::layout

    fn BufferLine::layout(self : BufferLine, font_size : Float, width_opt : Float?, wrap : Wrap, match_mono_width : Float?, tab_width : Int, hinting : Hinting) -> BufferLine

    BufferLine::layout_opt

    fn BufferLine::layout_opt(self : BufferLine) -> Array[LayoutLine]?

    BufferLine::layout_with_font_system

    fn BufferLine::layout_with_font_system(self : BufferLine, font_system : FontSystem, font_size : Float, width_opt : Float?, wrap : Wrap, match_mono_width : Float?, tab_width : Int, hinting : Hinting) -> BufferLine

    Layout using a provided FontSystem (best-effort).

    BufferLine::new

    fn BufferLine::new(text : String, ending : LineEnding, attrs_list : AttrsList, shaping : Shaping) -> BufferLine

    BufferLine::reset

    fn BufferLine::reset(self : BufferLine) -> BufferLine

    BufferLine::reset_layout

    fn BufferLine::reset_layout(self : BufferLine) -> BufferLine

    BufferLine::reset_shaping

    fn BufferLine::reset_shaping(self : BufferLine) -> BufferLine

    BufferLine::set_align

    fn BufferLine::set_align(self : BufferLine, align : Align?) -> (BufferLine, Bool)

    BufferLine::set_ending

    fn BufferLine::set_ending(self : BufferLine, ending : LineEnding) -> BufferLine

    BufferLine::set_text

    fn BufferLine::set_text(self : BufferLine, text : String, ending : LineEnding, attrs_list : AttrsList) -> (BufferLine, Bool)

    BufferLine::shape

    fn BufferLine::shape(self : BufferLine, tab_width : Int) -> BufferLine

    BufferLine::shape_opt

    fn BufferLine::shape_opt(self : BufferLine) -> ShapeLine?

    BufferLine::shape_with_font_system

    fn BufferLine::shape_with_font_system(self : BufferLine, font_system : FontSystem, tab_width : Int) -> BufferLine

    Shape using a provided FontSystem (best-effort).

    This is an incremental step towards upstream cosmic-text: we keep the same cache behavior, but build glyph advances from the font.

    BufferLine::split_off

    fn BufferLine::split_off(self : BufferLine, index : Int) -> (BufferLine, BufferLine)

    Split off new line at index. Returns (left, right).

    BufferLine::text

    fn BufferLine::text(self : BufferLine) -> String

    CacheKey

    pub struct CacheKey {
    font_id : Int
    glyph_id : Int
    font_size_bits : UInt
    x_bin : SubpixelBin
    y_bin : SubpixelBin
    font_weight : Int
    flags : UInt
    }

    Key for building a glyph cache.

    NOTE: we keep this as a plain struct (instead of bitfields) to match the conceptual model of cosmic-text.
    impl Eq for CacheKey
    impl Hash for CacheKey
    impl Show for CacheKey

    CacheKey::has_disable_hinting

    fn CacheKey::has_disable_hinting(self : CacheKey) -> Bool

    CacheKey::has_fake_italic

    fn CacheKey::has_fake_italic(self : CacheKey) -> Bool

    CacheKey::has_pixel_font

    fn CacheKey::has_pixel_font(self : CacheKey) -> Bool

    CacheKey::new

    fn CacheKey::new(font_id : Int, glyph_id : Int, font_size : Float, pos : (Float, Float), font_weight : Int, flags : UInt) -> (CacheKey, Int, Int)

    CacheKey::x_bin

    fn CacheKey::x_bin(self : CacheKey) -> SubpixelBin

    CacheKey::y_bin

    fn CacheKey::y_bin(self : CacheKey) -> SubpixelBin

    CacheMetrics

    pub struct CacheMetrics {
    font_size_bits : UInt
    line_height_bits : UInt
    }

    Metrics, but implementing Eq and Hash using u32 representation of f32.

    This mirrors upstream cosmic-text CacheMetrics, enabling per-span metrics overrides (font size / line height) while keeping Attrs hashable.
    impl Eq for CacheMetrics

    CacheMetrics::font_size

    fn CacheMetrics::font_size(self : CacheMetrics) -> Float

    CacheMetrics::from_metrics

    fn CacheMetrics::from_metrics(metrics : Metrics) -> CacheMetrics

    CacheMetrics::line_height

    fn CacheMetrics::line_height(self : CacheMetrics) -> Float

    CacheMetrics::to_metrics

    fn CacheMetrics::to_metrics(self : CacheMetrics) -> Metrics

    Cached

    pub(all) enum Cached[T] {
    Empty
    Unused(T)
    Used(T)
    }

    Ported from cosmic-text/src/cached.rs (cosmic-text is dual-licensed MIT OR Apache-2.0).

    MoonBit version is implemented in a functional style (returns updated Cached), but keeps the same state machine semantics as the upstream &mut self API.

    Cached::get

    fn[T] Cached::get(self : Cached[T]) -> T?

    Cached::is_unused

    fn[T] Cached::is_unused(self : Cached[T]) -> Bool

    Cached::is_used

    fn[T] Cached::is_used(self : Cached[T]) -> Bool

    Cached::set_unused

    fn[T] Cached::set_unused(self : Cached[T]) -> Cached[T]

    Cached::set_used

    fn[T] Cached::set_used(_self : Cached[T], val : T) -> Cached[T]

    Cached::take_unused

    fn[T] Cached::take_unused(self : Cached[T]) -> (Cached[T], T?)

    Cached::take_used

    fn[T] Cached::take_used(self : Cached[T]) -> (Cached[T], T?)

    Change

    pub(all) struct Change {
    items : Array[ChangeItem]
    }

    A set of change items grouped into one logical change.

    Change::default

    fn Change::default() -> Change

    Change::reverse

    fn Change::reverse(self : Change) -> Change

    ChangeItem

    pub(all) struct ChangeItem {
    start : Cursor
    end : Cursor
    text : String
    insert : Bool
    }

    A unique change to an editor.

    ChangeItem::reverse

    fn ChangeItem::reverse(self : ChangeItem) -> ChangeItem

    Color

    pub struct Color {
    value : UInt
    }

    Text color (ARGB packed, aligned with upstream).
    impl Eq for Color

    Color::a

    fn Color::a(self : Color) -> Byte

    Color::as_rgba

    fn Color::as_rgba(self : Color) -> (Byte, Byte, Byte, Byte)

    Color::b

    fn Color::b(self : Color) -> Byte

    Color::g

    fn Color::g(self : Color) -> Byte

    Color::r

    fn Color::r(self : Color) -> Byte

    Color::rgb

    fn Color::rgb(r : Byte, g : Byte, b : Byte) -> Color

    Color::rgba

    fn Color::rgba(r : Byte, g : Byte, b : Byte, a : Byte) -> Color

    Cursor

    pub struct Cursor {
    line : Int
    index : Int
    affinity : Affinity
    }

    Current cursor location
    impl Eq for Cursor

    Cursor::new

    fn Cursor::new(line : Int, index : Int) -> Cursor

    Cursor::new_with_affinity

    fn Cursor::new_with_affinity(line : Int, index : Int, affinity : Affinity) -> Cursor

    Editor

    pub struct Editor {
    buffer : Buffer
    cursor : Cursor
    cursor_x_opt : Int?
    selection : Selection
    cursor_moved : Bool
    auto_indent : Bool
    change_opt : Change?
    }

    Editor::action

    fn Editor::action(self : Editor, action : Action) -> Editor

    Perform an action on the editor (MVP: cursor motion is text-index based, no hit-testing yet).

    Editor::apply_change

    fn Editor::apply_change(self : Editor, change : Change) -> (Editor, Bool)

    Editor::auto_indent

    fn Editor::auto_indent(self : Editor) -> Bool

    Editor::buffer

    fn Editor::buffer(self : Editor) -> Buffer

    Editor::copy_selection

    fn Editor::copy_selection(self : Editor) -> String?

    Editor::cursor

    fn Editor::cursor(self : Editor) -> Cursor

    Editor::cursor_position

    fn Editor::cursor_position(self : Editor) -> (Int, Int)?

    Get X and Y position of the top-left corner of the cursor (MVP).

    Editor::delete_range

    fn Editor::delete_range(self : Editor, start : Cursor, end : Cursor) -> Editor

    Delete text starting at start cursor and ending at end cursor.

    Editor::delete_selection

    fn Editor::delete_selection(self : Editor) -> (Editor, Bool)

    Editor::finish_change

    fn Editor::finish_change(self : Editor) -> (Editor, Change?)

    Editor::insert_at

    fn Editor::insert_at(self : Editor, cursor : Cursor, data : String, attrs_list_opt : AttrsList?) -> (Editor, Cursor)

    Insert data at the specified cursor, returning the updated editor and the new cursor.

    NOTE: attrs_list_opt is applied to inserted text; when None, we use the previous character's attrs as defaults.

    Editor::insert_string

    fn Editor::insert_string(self : Editor, data : String, attrs_list_opt : AttrsList?) -> Editor

    Insert string at current cursor, replacing selection if present.

    Editor::new

    fn Editor::new(buffer : Buffer) -> Editor

    Editor::render

    fn[R : Renderer] Editor::render(self : Editor, renderer : R, text_color : Color, cursor_color : Color, selection_color : Color, selected_text_color : Color) -> Unit

    Render editor contents with selection and cursor highlights.

    Editor::selection

    fn Editor::selection(self : Editor) -> Selection

    Editor::selection_bounds

    fn Editor::selection_bounds(self : Editor) -> (Cursor, Cursor)?

    Get the bounds of the current selection (line/word selection is expanded).

    Editor::set_auto_indent

    fn Editor::set_auto_indent(self : Editor, auto_indent : Bool) -> Editor

    Editor::set_cursor

    fn Editor::set_cursor(self : Editor, cursor : Cursor) -> Editor

    Editor::set_selection

    fn Editor::set_selection(self : Editor, selection : Selection) -> Editor

    Editor::set_tab_width

    fn Editor::set_tab_width(self : Editor, tab_width : Int) -> Editor

    Set tab width in spaces. A value of 0 is ignored (matches upstream).

    Editor::start_change

    fn Editor::start_change(self : Editor) -> Editor

    Editor::tab_width

    fn Editor::tab_width(self : Editor) -> Int

    Family

    pub(all) enum Family {
    Name(String)
    Serif
    SansSerif
    Cursive
    Fantasy
    Monospace
    }

    Font family selector (mirrors fontdb::Family in cosmic-text).
    impl Eq for Family
    impl Hash for Family
    impl Show for Family

    FontCachedCodepointSupportInfo

    type FontCachedCodepointSupportInfo

    FontEntry

    pub struct FontEntry {
    id : Int
    family_name : String
    postscript_name : String
    font :
    FontRef

    charmap_proxy :
    CharmapProxy

    attributes :
    Attributes

    is_monospace : Bool
    not_emoji : Bool
    }

    Minimal font system wrapper for the MoonBit cosmic-text port.

    Upstream cosmic-text uses fontdb for font discovery. In this MoonBit port we start with an in-memory font list backed by moon_swash.

    FontEntry::attributes

    FontEntry::charmap_proxy

    FontEntry::family_name

    fn FontEntry::family_name(self : FontEntry) -> String

    FontEntry::id

    fn FontEntry::id(self : FontEntry) -> Int

    FontEntry::is_monospace

    fn FontEntry::is_monospace(self : FontEntry) -> Bool

    FontEntry::not_emoji

    fn FontEntry::not_emoji(self : FontEntry) -> Bool

    FontEntry::postscript_name

    fn FontEntry::postscript_name(self : FontEntry) -> String

    FontMatchAttrs

    pub struct FontMatchAttrs {
    family : Family
    weight : Weight
    stretch :
    Stretch

    style :
    Style

    }

    FontSystem

    pub struct FontSystem {
    locale : String
    fonts : Array[FontEntry]
    font_matches_cache :
    HashMap
    [FontMatchAttrs, Array[Int]]
    codepoint_support_cache :
    HashMap
    [Int, FontCachedCodepointSupportInfo]
    shape_run_cache : ShapeRunCache
    // private fields
    }

    FontSystem::count_supported_codepoints

    fn FontSystem::count_supported_codepoints(self : FontSystem, font_id : Int, codepoints : Array[UInt]) -> Int

    FontSystem::font_matches

    fn FontSystem::font_matches(self : FontSystem, attrs : Attrs) -> Array[Int]

    Get ordered font match candidates for attrs (cached).

    FontSystem::font_matches_for_script

    fn FontSystem::font_matches_for_script(self : FontSystem, attrs : Attrs, script :
    Script
    ) -> Array[Int]

    FontSystem::font_matches_for_scripts

    fn FontSystem::font_matches_for_scripts(self : FontSystem, attrs : Attrs, scripts : Array[
    Script
    ]) -> Array[Int]

    FontSystem::fonts

    fn FontSystem::fonts(self : FontSystem) -> Array[FontEntry]

    FontSystem::get_font

    fn FontSystem::get_font(self : FontSystem, font_id : Int) ->
    FontRef
    ?

    FontSystem::get_font_entry

    fn FontSystem::get_font_entry(self : FontSystem, font_id : Int) -> FontEntry?

    Get the full FontEntry for a font_id (useful for downstream rasterization with TTC collections).

    FontSystem::load_font_data

    fn FontSystem::load_font_data(self : FontSystem, data : Bytes) -> FontSystem

    Load font data (TTF/OTF/TTC bytes) into the system.

    If the bytes do not parse as a font/collection, this is a no-op.

    FontSystem::locale

    fn FontSystem::locale(self : FontSystem) -> String

    FontSystem::new

    fn FontSystem::new() -> FontSystem

    FontSystem::new_with_locale

    fn FontSystem::new_with_locale(locale : String) -> FontSystem

    FontSystem::resolve

    fn FontSystem::resolve(self : FontSystem, attrs : Attrs) -> Int?

    Resolve a font ID for the given attrs (best-effort).

    FontSystem::resolve_for_codepoint

    fn FontSystem::resolve_for_codepoint(self : FontSystem, attrs : Attrs, codepoint : UInt) -> Int?

    Resolve a font ID for a specific codepoint (best-effort).

    This prefers the requested family (when present) and then minimizes weight difference, falling back to any loaded font that supports the codepoint.

    FontSystem::select_family

    fn FontSystem::select_family(self : FontSystem, family : Family) -> Int?

    Find a font ID that matches the family selector (best-effort).

    Hinting

    pub(all) enum Hinting {
    Disabled
    Enabled
    }

    Metrics hinting strategy

    Justify

    pub(all) enum Justify {
    Left
    Right
    Center
    Justified
    }

    LayoutCursor

    pub struct LayoutCursor {
    line : Int
    layout : Int
    glyph : Int
    }

    The position of a cursor within a Buffer.
    impl Eq for LayoutCursor

    LayoutCursor::new

    fn LayoutCursor::new(line : Int, layout : Int, glyph : Int) -> LayoutCursor

    LayoutGlyph

    pub struct LayoutGlyph {
    start : Int
    end : Int
    font_size : Float
    font_id : Int
    font_weight : Int
    glyph_id : Int
    x : Float
    y : Float
    w : Float
    level : Int
    line_height_opt : Float?
    x_offset : Float
    y_offset : Float
    color_opt : Color?
    metadata : Int
    cache_key_flags : UInt
    }

    A laid out glyph (MVP subset)

    LayoutGlyph::new

    fn LayoutGlyph::new(start : Int, end : Int, font_size : Float, font_id : Int, font_weight : Int, glyph_id : Int, x : Float, y : Float, w : Float, metadata : Int) -> LayoutGlyph

    Convenience constructor (fills optional fields with defaults).

    LayoutGlyph::physical

    fn LayoutGlyph::physical(self : LayoutGlyph, offset : (Float, Float), scale : Float) -> PhysicalGlyph

    LayoutLine

    pub struct LayoutLine {
    start : Int
    end : Int
    w : Float
    max_ascent : Float
    max_descent : Float
    line_height_opt : Float?
    glyphs : Array[LayoutGlyph]
    }

    A line of laid out glyphs (MVP subset)

    LayoutRun

    pub struct LayoutRun {
    line_i : Int
    line_y : Float
    line_top : Float
    line_height : Float
    text : String
    rtl : Bool
    glyphs : Array[LayoutGlyph]
    line_w : Float
    }

    A line of visible text for rendering (MVP subset).

    LegacyRenderer

    pub struct LegacyRenderer {
    font_system : FontSystem
    cache : SwashCache
    callback : (Int, Int, UInt, UInt, Color) -> Unit
    }

    Helper to migrate from the legacy callback-based renderer.

    This matches upstream LegacyRenderer: rectangles are forwarded to the callback, and glyphs are rasterized through SwashCache::with_pixels.

    LegacyRenderer::new

    fn LegacyRenderer::new(font_system : FontSystem, cache : SwashCache, callback : (Int, Int, UInt, UInt, Color) -> Unit) -> LegacyRenderer

    LineBreak

    pub(all) enum LineBreak {
    NoWrap
    AnyCharacter
    WordBoundary
    }

    Compatibility layer for Bevy-style Text2d concepts.

    Upstream cosmic-text uses Wrap + Align. Bevy exposes similar concepts as line-break and justification; we provide a small mapping layer here.

    LineEnding

    pub(all) enum LineEnding {
    Lf
    CrLf
    Cr
    LfCr
    None
    }

    Ported from cosmic-text/src/line_ending.rs (cosmic-text is dual-licensed MIT OR Apache-2.0).

    LineEnding::as_str

    fn LineEnding::as_str(self : LineEnding) -> String

    LineEnding::default

    fn LineEnding::default() -> LineEnding

    Default line ending (matches upstream: LF).

    LineIter

    pub struct LineIter {
    string : String
    start : Int
    end : Int
    }

    LineIter::new

    fn LineIter::new(string : String) -> LineIter

    LineIter::next

    fn LineIter::next(self : LineIter) -> (LineIter, (Int, Int, LineEnding)?)

    Returns (start, end, ending) where [start,end) is the line content range.

    Metrics

    pub struct Metrics {
    font_size : Float
    line_height : Float
    }

    Ported from cosmic-text/src/buffer.rs (cosmic-text is dual-licensed MIT OR Apache-2.0).

    Metrics::new

    fn Metrics::new(font_size : Float, line_height : Float) -> Metrics

    Metrics::relative

    fn Metrics::relative(font_size : Float, line_height_scale : Float) -> Metrics

    Create metrics with given font size and calculate line height using relative scale.

    Metrics::scale

    fn Metrics::scale(self : Metrics, scale : Float) -> Metrics

    Scale font size and line height.

    Motion

    pub(all) enum Motion {
    LayoutCursor(LayoutCursor)
    Previous
    Next
    Left
    Right
    Up
    Down
    Home
    SoftHome
    End
    ParagraphStart
    ParagraphEnd
    PageUp
    PageDown
    Vertical(Int)
    PreviousWord
    NextWord
    LeftWord
    RightWord
    BufferStart
    BufferEnd
    GotoLine(Int)
    }

    A motion to perform on a Cursor
    impl Eq for Motion

    PhysicalGlyph

    pub struct PhysicalGlyph {
    cache_key : CacheKey
    x : Int
    y : Int
    }

    A laid out glyph in physical/pixel coordinates, ready for rasterization.

    Scroll

    pub struct Scroll {
    line : Int
    vertical : Float
    horizontal : Float
    }

    Scroll position in Buffer

    Scroll::default

    fn Scroll::default() -> Scroll

    Scroll::new

    fn Scroll::new(line : Int, vertical : Float, horizontal : Float) -> Scroll

    Selection

    pub(all) enum Selection {
    None
    Normal(Cursor)
    Line(Cursor)
    Word(Cursor)
    }

    Selection mode.
    impl Eq for Selection

    ShapeGlyph

    pub struct ShapeGlyph {
    start : Int
    end : Int
    x_advance : Float
    y_advance : Float
    x_offset : Float
    y_offset : Float
    ascent : Float
    descent : Float
    font_id : Int
    font_weight : Int
    glyph_id : Int
    metadata : Int
    level : Int
    metrics_opt : Metrics?
    }

    ShapeGlyph::new

    fn ShapeGlyph::new(start : Int, end : Int, x_advance : Float, y_advance : Float, x_offset : Float, y_offset : Float, font_id : Int, glyph_id : Int, metadata : Int) -> ShapeGlyph

    ShapeGlyph::with_ascent_descent

    fn ShapeGlyph::with_ascent_descent(self : ShapeGlyph, ascent : Float, descent : Float) -> ShapeGlyph

    ShapeLine

    pub struct ShapeLine {
    rtl : Bool
    glyphs : Array[ShapeGlyph]
    }

    ShapeLine::build

    fn ShapeLine::build(_self : ShapeLine, text : String, attrs_list : AttrsList, shaping : Shaping, tab_width : Int) -> ShapeLine

    Build a shaped line from text and attributes list.

    MVP notes:
    • 1 code unit -> 1 glyph
    • glyph_id is the code unit value
    • x_advance is measured in "cells": 1.0 for normal chars, tab_width for tab.

    ShapeLine::build_with_font_system

    fn ShapeLine::build_with_font_system(_self : ShapeLine, font_system : FontSystem, text : String, attrs_list : AttrsList, shaping : Shaping, tab_width : Int) -> ShapeLine

    Build a shaped line using a FontSystem (best-effort).

    Current behavior is still "Basic shaping" (1 UTF-16 code unit -> 1 glyph), but uses moon_swash charmap + glyph metrics so advances are font-derived.

    ShapeLine::empty

    fn ShapeLine::empty() -> ShapeLine

    ShapeLine::new

    fn ShapeLine::new(rtl : Bool, glyphs : Array[ShapeGlyph]) -> ShapeLine

    ShapeRunCache

    pub struct ShapeRunCache {
    age : UInt64
    cache :
    HashMap
    [ShapeRunKey, (UInt64, Array[ShapeGlyph])]
    }

    A helper structure for caching shape runs.

    ShapeRunCache::clear

    fn ShapeRunCache::clear(self : ShapeRunCache) -> Unit

    Clear all cached runs.

    ShapeRunCache::get

    Get a cache item, updating age if found.

    ShapeRunCache::insert

    fn ShapeRunCache::insert(self : ShapeRunCache, key : ShapeRunKey, glyphs : Array[ShapeGlyph]) -> Unit

    Insert a cache item with current age.

    ShapeRunCache::new

    ShapeRunCache::peek

    Peek a cache item without updating its age.

    ShapeRunCache::trim

    fn ShapeRunCache::trim(self : ShapeRunCache, keep_ages : UInt64) -> Unit

    Remove anything in the cache with an age older than keep_ages.

    ShapeRunKey

    pub struct ShapeRunKey {
    text : String
    default_attrs : Attrs
    attrs_spans : Array[(Int, Int, Attrs)]
    }

    Key for caching shape runs.
    impl Eq for ShapeRunKey
    impl Hash for ShapeRunKey

    ShapeRunKey::new

    fn ShapeRunKey::new(text : String, attrs_list : AttrsList) -> ShapeRunKey

    Shaping

    pub(all) enum Shaping {
    Basic
    Advanced
    }

    Minimal shaping port scaffold from cosmic-text/src/shape.rs.

    This MVP produces 1 glyph per UTF-16 code unit and does not perform script runs, harfbuzz shaping, BiDi, or font fallback.

    SubpixelBin

    pub enum SubpixelBin {
    Zero
    One
    Two
    Three
    }

    Binning of subpixel position for cache optimization.
    impl Eq for SubpixelBin
    impl Hash for SubpixelBin
    impl Show for SubpixelBin

    SubpixelBin::as_float

    fn SubpixelBin::as_float(self : SubpixelBin) -> Float

    SubpixelBin::new

    fn SubpixelBin::new(pos : Float) -> (Int, SubpixelBin)

    Returns (integral pixel position, bin for fractional part).

    Matches upstream cosmic-text binning behavior, including negative positions.

    SwashCache

    SwashCache::get_image

    fn SwashCache::get_image(self : SwashCache, font_system : FontSystem, cache_key : CacheKey) ->
    Image
    ?

    SwashCache::get_image_uncached

    fn SwashCache::get_image_uncached(self : SwashCache, font_system : FontSystem, cache_key : CacheKey) ->
    Image
    ?

    SwashCache::get_outline_commands

    fn SwashCache::get_outline_commands(self : SwashCache, font_system : FontSystem, cache_key : CacheKey) -> (Array[
    Vector
    ], Array[
    Verb
    ])?

    Create outline commands (path data) from a cache key, caching results.

    This matches upstream SwashCache::get_outline_commands.

    SwashCache::get_outline_commands_uncached

    fn SwashCache::get_outline_commands_uncached(self : SwashCache, font_system : FontSystem, cache_key : CacheKey) -> (Array[
    Vector
    ], Array[
    Verb
    ])?

    Create outline commands (path data) from a cache key, without caching results.

    This matches upstream SwashCache::get_outline_commands_uncached.

    SwashCache::new

    fn SwashCache::new() -> SwashCache

    SwashCache::with_pixels

    fn SwashCache::with_pixels(self : SwashCache, font_system : FontSystem, cache_key : CacheKey, base : Color, f : (Int, Int, Color) -> Unit) -> Unit

    Enumerate pixels in a cached glyph image.

    Weight

    pub struct Weight {
    value : Int
    }

    Font weight (1..=1000). This is a simplified stand-in for fontdb::Weight.
    impl Eq for Weight
    impl Hash for Weight
    impl Show for Weight

    Weight::bold

    fn Weight::bold() -> Weight

    Weight::new

    fn Weight::new(value : Int) -> Weight

    Weight::normal

    fn Weight::normal() -> Weight

    Weight::value

    fn Weight::value(self : Weight) -> Int

    Wrap

    pub(all) enum Wrap {
    None
    Glyph
    Word
    WordOrGlyph
    }

    Wrapping mode

    CACHE_KEY_FLAG_DISABLE_HINTING

    let CACHE_KEY_FLAG_DISABLE_HINTING : UInt

    CACHE_KEY_FLAG_FAKE_ITALIC

    let CACHE_KEY_FLAG_FAKE_ITALIC : UInt

    CACHE_KEY_FLAG_PIXEL_FONT

    let CACHE_KEY_FLAG_PIXEL_FONT : UInt

    apply_align

    fn apply_align(text : String, lines : Array[LayoutLine], width : Float, align : Align) -> Array[LayoutLine]

    Apply Align to precomputed layout lines assuming LTR paragraph direction.

    apply_align_with_rtl

    fn apply_align_with_rtl(text : String, lines : Array[LayoutLine], width : Float, align : Align, rtl : Bool) -> Array[LayoutLine]

    Apply Align to precomputed layout lines (requires a finite width).

    cosmic_text_version

    fn cosmic_text_version() -> String

    Port scaffold for cosmic-text (MIT OR Apache-2.0).

    The port is implemented incrementally while keeping data-flow behavior aligned.

    floorf

    fn floorf(x : Float) -> Float

    grapheme_indices_uax29

    fn grapheme_indices_uax29(s : String) -> Array[(Int, Int)]

    Grapheme cluster ranges [start,end) in UTF-16 code units (UAX#29-ish).

    This is sufficient for making Backspace/Delete operate on user-perceived characters.

    justify_to_align

    fn justify_to_align(j : Justify) -> Align

    layout_ascii

    fn layout_ascii(text : String, glyph_w : Float, width_opt : Float?, wrap : Wrap) -> Array[LayoutLine]

    ASCII-only layout for wrap tests.

    Semantics:
    • Treat each code unit as one glyph.
    • glyph_w is a monospace advance in pixels.

    layout_from_shape

    fn layout_from_shape(text : String, shape : ShapeLine, font_size : Float, cell_w : Float, width_opt : Float?, wrap : Wrap) -> Array[LayoutLine]

    Layout from ShapeLine (MVP).

    cell_w is the pixel width per "advance cell" (ShapeGlyph.x_advance is in cells).

    line_break_to_wrap

    fn line_break_to_wrap(lb : LineBreak) -> Wrap

    roundf

    fn roundf(x : Float) -> Float

    truncf

    fn truncf(x : Float) -> Float

    word_indices_uax29

    fn word_indices_uax29(s : String) -> Array[(Int, Int)]

    Word boundary ranges based on swash Unicode analysis (UAX#29-ish).

    Returns segments separated by Boundary::Word/Line/Mandatory.

    word_indices_whitespace

    fn word_indices_whitespace(s : String) -> Array[(Int, Int)]

    Returns word ranges [start,end) (UTF-16 code unit indices), splitting on whitespace.

    wrap_word_segments

    fn wrap_word_segments(s : String) -> Array[(Int, Int, Bool)]

    Word segments used for wrapping (cosmic-text-style).

    Upstream uses unicode_linebreak::linebreaks to split into segments, then splits trailing whitespace into individual blank "words".

    This helper approximates that behavior using swash boundary analysis:
    • Use moon_swash.analyze to identify UAX#14 linebreak opportunities: split on Boundary::Line or Boundary::Mandatory (ignore Boundary::Word).
    • For each segment, split trailing whitespace characters into individual blank segments (mirrors upstream's char::is_whitespace() scan).

    Returns (start, end, blank) ranges in UTF-16 code units.