src/n_wrap.c
Greedy word-wrap over pre-measured, styled items. More...
Structures
| Name | |
|---|---|
| struct | wrap_span state for n_wrap_span_rects' per-line callback |
| struct | wrap_hit state for n_wrap_index_at's per-line callback |
Functions
| Name | |
|---|---|
| void | wrap_span_line(size_t first, size_t last, size_t line, double width, void * user) |
| void | wrap_hit_line(size_t first, size_t last, size_t line, double width, void * user) |
| 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 | |
|---|---|
| WRAP_NO_SPACE no break point seen on the current line |
Detailed Description
Greedy word-wrap over pre-measured, styled items.
Author: Castagnier Mickael
Version: 1.0
Functions Documentation
function wrap_span_line
static void wrap_span_line(
size_t first,
size_t last,
size_t line,
double width,
void * user
)
function wrap_hit_line
static void wrap_hit_line(
size_t first,
size_t last,
size_t line,
double width,
void * user
)
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
countis 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
countis 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 WRAP_NO_SPACE
#define WRAP_NO_SPACE ((size_t)-1)
no break point seen on the current line
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
*/
#include "nilorea/n_wrap.h"
#define WRAP_NO_SPACE ((size_t)-1)
size_t n_wrap_layout(const n_wrap_item* items, size_t count, double max_width, n_wrap_emit emit, void* user) {
size_t line_start = 0, k = 0, lines = 0;
size_t last_space = WRAP_NO_SPACE;
double cur = 0.0; /* width of [line_start, k) */
double at_space = 0.0; /* cur including the break-point item */
double pre_space = 0.0; /* cur excluding it: the emitted line's width */
if (!items)
count = 0;
while (k < count) {
const n_wrap_item* it = &items[k];
if (it->flags & N_WRAP_NEWLINE) {
if (emit)
emit(line_start, k, lines, cur, user);
lines++;
k++;
line_start = k;
cur = 0.0;
last_space = WRAP_NO_SPACE;
continue;
}
/* Overflow. The k > line_start guard is what makes an over-wide item
* terminate: alone on a line it is placed, not broken before. */
if (cur + it->width > max_width && k > line_start) {
if (it->flags & N_WRAP_SPACE) {
/* The overflowing item is itself a break point, so break HERE
* and drop it. Backtracking to the previous space instead
* would push a word that DID fit onto the next line, which is
* what a trailing space must never do. */
if (emit)
emit(line_start, k, lines, cur, user);
lines++;
k++;
line_start = k;
cur = 0.0;
last_space = WRAP_NO_SPACE;
continue;
}
if (last_space != WRAP_NO_SPACE && last_space > line_start) {
/* break at the space, which belongs to neither line */
if (emit)
emit(line_start, last_space, lines, pre_space, user);
line_start = last_space + 1;
cur -= at_space;
} else {
if (emit)
emit(line_start, k, lines, cur, user);
line_start = k;
cur = 0.0;
}
lines++;
last_space = WRAP_NO_SPACE;
continue; /* re-judge this item against the new line */
}
if (it->flags & N_WRAP_SPACE) {
last_space = k;
pre_space = cur;
}
cur += it->width;
if (it->flags & N_WRAP_SPACE)
at_space = cur;
k++;
}
if (emit && count > line_start)
emit(line_start, count, lines, cur, user);
lines++; /* the line just closed, or the single line of empty input */
return lines;
}
typedef struct {
const n_wrap_item* items;
size_t want; /* the line the point falls on */
double x; /* the point, from the line's start */
size_t result; /* the boundary found, set when that line is emitted */
int found;
} wrap_hit;
static void wrap_hit_line(size_t first, size_t last, size_t line, double width, void* user) {
wrap_hit* h = (wrap_hit*)user;
double acc = 0.0;
size_t k;
(void)width;
if (line != h->want)
return;
h->found = 1;
for (k = first; k < last; k++) {
/* nearer the item's left half: the boundary before it */
if (h->x < acc + h->items[k].width / 2.0) {
h->result = k;
return;
}
acc += h->items[k].width;
}
h->result = last;
}
size_t n_wrap_index_at(const n_wrap_item* items, size_t count, double max_width, double line_height, double x, double y) {
wrap_hit h;
size_t lines;
if (!items || count == 0)
return 0;
if (y < 0.0)
return 0;
lines = n_wrap_layout(items, count, max_width, NULL, NULL);
h.items = items;
h.want = 0;
if (line_height > 0.0) {
double l = y / line_height;
if (l >= (double)lines)
return count;
h.want = (size_t)l;
}
h.x = x;
h.result = count;
h.found = 0;
(void)n_wrap_layout(items, count, max_width, wrap_hit_line, &h);
/* a line that was counted but not emitted is the empty one after a
* trailing hard break: the only boundary on it is the end */
return h.found ? h.result : count;
}
typedef struct {
const n_wrap_item* items;
size_t lo, hi;
n_wrap_rect_emit emit;
void* user;
size_t n;
} wrap_span;
static void wrap_span_line(size_t first, size_t last, size_t line, double width, void* user) {
wrap_span* sp = (wrap_span*)user;
size_t s = first > sp->lo ? first : sp->lo;
size_t e = last < sp->hi ? last : sp->hi;
double x0 = 0.0, x1;
size_t k;
(void)width;
if (s >= e)
return; /* the span does not touch this line */
for (k = first; k < s; k++)
x0 += sp->items[k].width;
x1 = x0;
for (k = s; k < e; k++)
x1 += sp->items[k].width;
if (x1 <= x0)
return; /* zero-width coverage (a hard break): nothing to paint */
if (sp->emit)
sp->emit(line, x0, x1, sp->user);
sp->n++;
}
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) {
wrap_span sp;
if (!items || count == 0)
return 0;
if (lo > hi) {
size_t t = lo;
lo = hi;
hi = t;
}
if (hi > count)
hi = count;
if (lo >= hi)
return 0;
sp.items = items;
sp.lo = lo;
sp.hi = hi;
sp.emit = emit;
sp.user = user;
sp.n = 0;
(void)n_wrap_layout(items, count, max_width, wrap_span_line, &sp);
return sp.n;
}
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB