7 n__wrap_8h
gullradriel edited this page 2026-10-29 20:54:40 +01:00

include/nilorea/n_wrap.h

Greedy word-wrap over pre-measured, styled items. More...

Structures

Name
struct n_wrap_item
One pre-measured layout item: a glyph, an emoji cell, an inline image, or a whole word, the wrap only ever looks at width and flags.

Types

Name
typedef void(*)(size_t line, double x0, double x1, void *user) n_wrap_rect_emit
Called once per line that a span covers, by n_wrap_span_rects().
typedef void(*)(size_t first, size_t last, size_t line, double width, void *user) n_wrap_emit
Called once per laid-out line, in order.

Functions

Name
size_t n_wrap_span_rects(const n_wrap_item * items, size_t count, double max_width, size_t lo, size_t hi, n_wrap_rect_emit emit, void * user)
The rectangles that cover the items [lo, hi): one per line the range touches, in order, for painting a selection highlight.
size_t n_wrap_layout(const n_wrap_item * items, size_t count, double max_width, n_wrap_emit emit, void * user)
Break items into lines no wider than max_width.
size_t n_wrap_index_at(const n_wrap_item * items, size_t count, double max_width, double line_height, double x, double y)
The item boundary nearest a point: the inverse of n_wrap_layout(), for turning a click or a drag into a caret or a selection end.

Defines

Name
N_WRAP_SPACE
the item is a legal break point (typically a space); it is DROPPED when a line breaks there, appearing on neither line
N_WRAP_NEWLINE
the item is a hard line break; it ends the line and occupies no cell

Detailed Description

Greedy word-wrap over pre-measured, styled items.

Author: Castagnier Mickael

Version: 1.0

The line-breaking half of laying out styled text, with the measuring half left to the caller. A caller turns its text into an array of items that each carry only a width and two flags, and this decides where the lines break. Because an item is just a width, the same wrap serves a glyph, an inline image, an emoji cell or a whole word, in any mix of fonts and styles, the module never sees a font, a glyph or a renderer.

Why it is one function rather than two. A measure pass and a draw pass that walk separately drift, and a height cache built on the measure pass then disagrees with what is drawn. Here the measure pass IS the draw pass with emit set to NULL: the same code decides the breaks either way, so the height a caller caches and the lines it later paints cannot diverge.

Algorithm: greedy, breaking at the last legal break point (an N_WRAP_SPACE item) that fits, or mid-run when there is none. Rules worth knowing before you rely on them:

  • The space a line breaks at is dropped: it ends neither line, so a wrapped paragraph never starts a line with a space.
  • When the item that overflows is ITSELF a break point, the line breaks there rather than backtracking to the previous one, a trailing space never pushes a word that already fitted onto the next line.
  • An item **wider than max_width** is placed alone on its own line rather than looping forever. Nothing is ever discarded to make text fit.
  • An N_WRAP_NEWLINE item is a hard break that occupies no cell.
  • The line count is always at least 1, so empty text still occupies one line's height.
  • A trailing hard break counts a final empty line but does not emit it, count and emission agree with each other, which is what matters, but a caller drawing a caret on that last empty line must place it itself.

Types Documentation

typedef n_wrap_rect_emit

typedef void(* n_wrap_rect_emit) (size_t line, double x0, double x1, void *user);

Called once per line that a span covers, by n_wrap_span_rects().

Parameters:

  • line zero-based line number (its top is line * line_height)
  • x0 left edge of the covered part, from the line's start
  • x1 right edge (x1 > x0)
  • user the caller's cookie

typedef n_wrap_emit

typedef void(* n_wrap_emit) (size_t first, size_t last, size_t line, double width, void *user);

Called once per laid-out line, in order.

Parameters:

  • first index of the line's first item
  • last index ONE PAST its last item (so the range is [first, last) and an empty line has first == last)
  • line zero-based line number
  • width summed width of the items in the range, for centring / right alignment / a selection rectangle
  • user the caller's cookie, passed through untouched

Functions Documentation

function n_wrap_span_rects

size_t n_wrap_span_rects(
    const n_wrap_item * items,
    size_t count,
    double max_width,
    size_t lo,
    size_t hi,
    n_wrap_rect_emit emit,
    void * user
)

The rectangles that cover the items [lo, hi): one per line the range touches, in order, for painting a selection highlight.

Parameters:

  • items the items given to n_wrap_layout()
  • count number of items
  • max_width the width they were wrapped to
  • lo first boundary of the span
  • hi other boundary (clamped to count)
  • emit called once per covered line; may be NULL to only count
  • user cookie handed back to emit

Return: the number of rectangles

Uses the same layout as n_wrap_layout(), so the rectangles sit exactly under the drawn items. A space dropped at a line break belongs to no line and so lies under no rectangle; a line whose covered part has zero width (only a hard break) emits nothing. lo and hi may come in either order.

function n_wrap_layout

size_t n_wrap_layout(
    const n_wrap_item * items,
    size_t count,
    double max_width,
    n_wrap_emit emit,
    void * user
)

Break items into lines no wider than max_width.

Parameters:

  • items pre-measured items; may be NULL when count is 0
  • count number of items
  • max_width the width to wrap to; a value <= 0 puts every non-empty line on its own item, since nothing but the first item of a line can ever fit
  • emit called once per line, or NULL to only count lines (the measure pass, same breaks, no output)
  • user cookie handed back to emit

Return: the number of lines, always >= 1

Greedy: each line takes as much as fits, breaking at the last N_WRAP_SPACE that fits or, when the line holds no space, immediately before the item that overflows. An item that cannot fit even alone is placed alone rather than looping.

function n_wrap_index_at

size_t n_wrap_index_at(
    const n_wrap_item * items,
    size_t count,
    double max_width,
    double line_height,
    double x,
    double y
)

The item boundary nearest a point: the inverse of n_wrap_layout(), for turning a click or a drag into a caret or a selection end.

Parameters:

  • items the items given to n_wrap_layout(); may be NULL if count is 0
  • count number of items
  • max_width the width they were wrapped to
  • line_height height of one line; a value <= 0 resolves every point to the first line
  • x point, from the left edge
  • y point, from the top edge

Return: a boundary index in [0, count]: "before item i", count = after the last

The point is in the laid-out text's own coordinates (0,0 = the first line's top-left), with every line line_height tall. The layout is the one n_wrap_layout() produces for the same items and max_width, so a hit can never disagree with what was drawn.

Within a line, a point on the left half of an item resolves to the boundary before it and on the right half to the boundary after it, so an atomic item (an emoji cell, an inline image) is selected whole or not at all. A point past a line's end resolves to that line's end; above the first line to 0; below the last line to count.

Macros Documentation

define N_WRAP_SPACE

#define N_WRAP_SPACE (1u << 0)

the item is a legal break point (typically a space); it is DROPPED when a line breaks there, appearing on neither line

define N_WRAP_NEWLINE

#define N_WRAP_NEWLINE (1u << 1)

the item is a hard line break; it ends the line and occupies no cell

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#ifndef N_WRAP_H
#define N_WRAP_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stddef.h>

#define N_WRAP_SPACE (1u << 0)
#define N_WRAP_NEWLINE (1u << 1)

typedef struct n_wrap_item {
    double width;
    unsigned int flags;
} n_wrap_item;

typedef void (*n_wrap_emit)(size_t first, size_t last, size_t line, double width, void* user);

size_t n_wrap_layout(const n_wrap_item* items, size_t count, double max_width, n_wrap_emit emit, void* user);

typedef void (*n_wrap_rect_emit)(size_t line, double x0, double x1, void* user);

size_t n_wrap_index_at(const n_wrap_item* items, size_t count, double max_width, double line_height, double x, double y);

size_t n_wrap_span_rects(const n_wrap_item* items, size_t count, double max_width, size_t lo, size_t hi, n_wrap_rect_emit emit, void* user);

#ifdef __cplusplus
}
#endif

#endif /* N_WRAP_H */