Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_wrap.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
26
27#include "nilorea/n_wrap.h"
28
30#define WRAP_NO_SPACE ((size_t)-1)
31
50size_t n_wrap_layout(const n_wrap_item* items, size_t count, double max_width, n_wrap_emit emit, void* user) {
51 size_t line_start = 0, k = 0, lines = 0;
52 size_t last_space = WRAP_NO_SPACE;
53 double cur = 0.0; /* width of [line_start, k) */
54 double at_space = 0.0; /* cur including the break-point item */
55 double pre_space = 0.0; /* cur excluding it: the emitted line's width */
56
57 if (!items)
58 count = 0;
59
60 while (k < count) {
61 const n_wrap_item* it = &items[k];
62
63 if (it->flags & N_WRAP_NEWLINE) {
64 if (emit)
65 emit(line_start, k, lines, cur, user);
66 lines++;
67 k++;
68 line_start = k;
69 cur = 0.0;
70 last_space = WRAP_NO_SPACE;
71 continue;
72 }
73
74 /* Overflow. The k > line_start guard is what makes an over-wide item
75 * terminate: alone on a line it is placed, not broken before. */
76 if (cur + it->width > max_width && k > line_start) {
77 if (it->flags & N_WRAP_SPACE) {
78 /* The overflowing item is itself a break point, so break HERE
79 * and drop it. Backtracking to the previous space instead
80 * would push a word that DID fit onto the next line, which is
81 * what a trailing space must never do. */
82 if (emit)
83 emit(line_start, k, lines, cur, user);
84 lines++;
85 k++;
86 line_start = k;
87 cur = 0.0;
88 last_space = WRAP_NO_SPACE;
89 continue;
90 }
91 if (last_space != WRAP_NO_SPACE && last_space > line_start) {
92 /* break at the space, which belongs to neither line */
93 if (emit)
94 emit(line_start, last_space, lines, pre_space, user);
95 line_start = last_space + 1;
96 cur -= at_space;
97 } else {
98 if (emit)
99 emit(line_start, k, lines, cur, user);
100 line_start = k;
101 cur = 0.0;
102 }
103 lines++;
104 last_space = WRAP_NO_SPACE;
105 continue; /* re-judge this item against the new line */
106 }
107
108 if (it->flags & N_WRAP_SPACE) {
109 last_space = k;
110 pre_space = cur;
111 }
112 cur += it->width;
113 if (it->flags & N_WRAP_SPACE)
114 at_space = cur;
115 k++;
116 }
117
118 if (emit && count > line_start)
119 emit(line_start, count, lines, cur, user);
120 lines++; /* the line just closed, or the single line of empty input */
121 return lines;
122}
123
125typedef struct {
127 size_t want; /* the line the point falls on */
128 double x; /* the point, from the line's start */
129 size_t result; /* the boundary found, set when that line is emitted */
130 int found;
131} wrap_hit;
132
133static void wrap_hit_line(size_t first, size_t last, size_t line, double width, void* user) {
134 wrap_hit* h = (wrap_hit*)user;
135 double acc = 0.0;
136 size_t k;
137 (void)width;
138 if (line != h->want)
139 return;
140 h->found = 1;
141 for (k = first; k < last; k++) {
142 /* nearer the item's left half: the boundary before it */
143 if (h->x < acc + h->items[k].width / 2.0) {
144 h->result = k;
145 return;
146 }
147 acc += h->items[k].width;
148 }
149 h->result = last;
150}
151
176size_t n_wrap_index_at(const n_wrap_item* items, size_t count, double max_width, double line_height, double x, double y) {
177 wrap_hit h;
178 size_t lines;
179 if (!items || count == 0)
180 return 0;
181 if (y < 0.0)
182 return 0;
183 lines = n_wrap_layout(items, count, max_width, NULL, NULL);
184 h.items = items;
185 h.want = 0;
186 if (line_height > 0.0) {
187 double l = y / line_height;
188 if (l >= (double)lines)
189 return count;
190 h.want = (size_t)l;
191 }
192 h.x = x;
193 h.result = count;
194 h.found = 0;
195 (void)n_wrap_layout(items, count, max_width, wrap_hit_line, &h);
196 /* a line that was counted but not emitted is the empty one after a
197 * trailing hard break: the only boundary on it is the end */
198 return h.found ? h.result : count;
199}
200
202typedef struct {
204 size_t lo, hi;
206 void* user;
207 size_t n;
208} wrap_span;
209
210static void wrap_span_line(size_t first, size_t last, size_t line, double width, void* user) {
211 wrap_span* sp = (wrap_span*)user;
212 size_t s = first > sp->lo ? first : sp->lo;
213 size_t e = last < sp->hi ? last : sp->hi;
214 double x0 = 0.0, x1;
215 size_t k;
216 (void)width;
217 if (s >= e)
218 return; /* the span does not touch this line */
219 for (k = first; k < s; k++)
220 x0 += sp->items[k].width;
221 x1 = x0;
222 for (k = s; k < e; k++)
223 x1 += sp->items[k].width;
224 if (x1 <= x0)
225 return; /* zero-width coverage (a hard break): nothing to paint */
226 if (sp->emit)
227 sp->emit(line, x0, x1, sp->user);
228 sp->n++;
229}
230
249size_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) {
250 wrap_span sp;
251 if (!items || count == 0)
252 return 0;
253 if (lo > hi) {
254 size_t t = lo;
255 lo = hi;
256 hi = t;
257 }
258 if (hi > count)
259 hi = count;
260 if (lo >= hi)
261 return 0;
262 sp.items = items;
263 sp.lo = lo;
264 sp.hi = hi;
265 sp.emit = emit;
266 sp.user = user;
267 sp.n = 0;
268 (void)n_wrap_layout(items, count, max_width, wrap_span_line, &sp);
269 return sp.n;
270}
static N_GUI_MOUSE_EVENT last
the last event the widget was told of, and how many
void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a line
static void wrap_hit_line(size_t first, size_t last, size_t line, double width, void *user)
Definition n_wrap.c:133
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.
Definition n_wrap.c:50
#define WRAP_NO_SPACE
no break point seen on the current line
Definition n_wrap.c:30
size_t hi
Definition n_wrap.c:204
size_t n
Definition n_wrap.c:207
int found
Definition n_wrap.c:130
size_t want
Definition n_wrap.c:127
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,...
Definition n_wrap.c:249
size_t result
Definition n_wrap.c:129
static void wrap_span_line(size_t first, size_t last, size_t line, double width, void *user)
Definition n_wrap.c:210
void * user
Definition n_wrap.c:206
size_t lo
Definition n_wrap.c:204
const n_wrap_item * items
Definition n_wrap.c:126
n_wrap_rect_emit emit
Definition n_wrap.c:205
const n_wrap_item * items
Definition n_wrap.c:203
double x
Definition n_wrap.c:128
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...
Definition n_wrap.c:176
state for n_wrap_index_at's per-line callback
Definition n_wrap.c:125
state for n_wrap_span_rects' per-line callback
Definition n_wrap.c:202
Greedy word-wrap over pre-measured, styled items.
#define N_WRAP_SPACE
the item is a legal break point (typically a space); it is DROPPED when a line breaks there,...
Definition n_wrap.h:70
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().
Definition n_wrap.h:102
#define N_WRAP_NEWLINE
the item is a hard line break; it ends the line and occupies no cell
Definition n_wrap.h:72
double width
advance width in the caller's units (pixels, columns, ...)
Definition n_wrap.h:78
unsigned int flags
N_WRAP_SPACE / N_WRAP_NEWLINE, or 0
Definition n_wrap.h:80
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.
Definition n_wrap.h:92
One pre-measured layout item: a glyph, an emoji cell, an inline image, or a whole word,...
Definition n_wrap.h:76