42 std::vector<std::string> lines;
56 if (lines.size() > 1 && lines.back().empty()) {
68 if (coverage <= 0.0f || color.a <= 0.0f) {
74 const float sa = std::clamp(color.a, 0.0f, 1.0f) * coverage;
81 const float dst_alpha =
static_cast<float>(
da) / 255.0f;
88 const float r = (color.r *
sa + (
static_cast<float>(
dr) / 255.0f) *
keep) *
inv_alpha;
89 const float g = (color.g *
sa + (
static_cast<float>(
dg) / 255.0f) *
keep) *
inv_alpha;
90 const float b = (color.b *
sa + (
static_cast<float>(
db) / 255.0f) *
keep) *
inv_alpha;
100 const Color &color) {
101 if (glyph.empty() || color.a <= 0.0f) {
104 for (
int y = 0;
y < glyph.height; ++
y) {
109 for (
int x = 0;
x < glyph.width; ++
x) {
130 const float max_u =
static_cast<float>(glyph.width - 1);
131 const float max_v =
static_cast<float>(glyph.height - 1);
132 const float fu = std::clamp(
u - 0.5f, 0.0f,
max_u);
133 const float fv = std::clamp(
v - 0.5f, 0.0f,
max_v);
134 const int x0 =
static_cast<int>(std::floor(
fu));
135 const int y0 =
static_cast<int>(std::floor(
fv));
136 const int x1 = std::min(
x0 + 1, glyph.width - 1);
137 const int y1 = std::min(
y0 + 1, glyph.height - 1);
138 const float sx =
fu -
static_cast<float>(
x0);
139 const float sy =
fv -
static_cast<float>(
y0);
140 const float c00 = glyph.at(
x0,
y0);
141 const float c10 = glyph.at(
x1,
y0);
142 const float c01 = glyph.at(
x0,
y1);
143 const float c11 = glyph.at(
x1,
y1);
159 float pivot_y,
const Color &color) {
160 if (glyph.empty() || color.a <= 0.0f) {
174 origin_y +
static_cast<float>(glyph.height),
175 origin_y +
static_cast<float>(glyph.height)};
176 float min_x = std::numeric_limits<float>::max();
177 float min_y = std::numeric_limits<float>::max();
178 float max_x = std::numeric_limits<float>::lowest();
179 float max_y = std::numeric_limits<float>::lowest();
180 for (
int i = 0;
i < 4; ++
i) {
191 const int x_start = std::max(0,
static_cast<int>(std::floor(
min_x)) - 1);
192 const int x_end = std::min(
image.width - 1,
static_cast<int>(std::ceil(
max_x)) + 1);
193 const int y_start = std::max(0,
static_cast<int>(std::floor(
min_y)) - 1);
194 const int y_end = std::min(
image.height - 1,
static_cast<int>(std::ceil(
max_y)) + 1);
200 const float dx =
static_cast<float>(
px) + 0.5f -
pivot_x;
201 const float dy =
static_cast<float>(
py) + 0.5f -
pivot_y;
208 v >=
static_cast<float>(glyph.height)) {
212 if (coverage <= 0.002f) {
239 std::vector<PlacedGlyph>
placed;
249 if (glyph !=
nullptr) {
250 if (!glyph->empty()) {
253 pg.x =
static_cast<int>(std::lround(
pen)) + glyph->x_offset;
254 pg.y =
static_cast<int>(std::lround(
baseline_y)) + glyph->y_offset;
257 pen += glyph->advance;
263 const bool rotated = std::abs(
style.rotation) > 1.0e-4f;
265 const Color &color) {
271 static_cast<int>(std::lround(
glyph_y)), color);
275 if (
style.halo_color.a > 0.0f &&
style.halo_width > 0.0f) {
276 const int width = std::max(1,
static_cast<int>(std::lround(
style.halo_width)));
278 for (
const PlacedGlyph &
pg :
placed) {
281 if (
dx == 0 &&
dy == 0) {
288 static_cast<float>(
pg.y +
dy),
style.halo_color);
294 for (
const PlacedGlyph &
pg :
placed) {
295 stamp(*
pg.bitmap,
static_cast<float>(
pg.x),
static_cast<float>(
pg.y),
305 float pixel_scale,
const std::vector<float> &z_buffer,
306 const Color &default_color) {
307 if (layer.empty() ||
output.width <= 0 ||
output.height <= 0) {
316 const FontMetrics metrics =
font.metrics();
317 const float line_step = metrics.box_height() * std::max(layer.line_spacing, 0.0f);
319 const bool depth_test = layer.depth_test && (layer.space ==
TextSpace::WORLD) &&
322 for (
size_t i = 0;
i < layer.count(); ++
i) {
323 const std::string &
text = layer.strings[
i];
327 const Color color = layer.color_or(
i, default_color);
328 if (color.a <= 0.0f) {
338 if (
clip.w <= 1.0e-6f) {
341 float sx = 0.0f,
sy = 0.0f, depth = 0.0f;
347 const Vec3 position = layer.position_or(
i);
362 const int px =
static_cast<int>(std::floor(
anchor_x));
363 const int py =
static_cast<int>(std::floor(
anchor_y));
368 z_buffer[
static_cast<size_t>(
py) *
static_cast<size_t>(
output.width) +
369 static_cast<size_t>(
px)];
378 for (
const std::string &
line : lines) {
382 metrics.box_height() +
static_cast<float>(lines.size() - 1) *
line_step;
389 style.halo_color = layer.halo_color;
391 style.line_spacing = layer.line_spacing;
392 style.rotation = layer.rotation;
409 throw std::invalid_argument(
"scimesh: draw_text() needs a valid font");
411 const float line_step =
font.metrics().box_height() * std::max(
style.line_spacing, 0.0f);
413 for (
size_t i = 0;
i < lines.size(); ++
i) {
423 const std::string &font_file,
float line_spacing) {
424 if (!(size > 0.0f)) {
425 throw std::invalid_argument(
"scimesh: text size must be > 0");
433 for (
const std::string &
line : lines) {
438 static_cast<float>(lines.size() - 1) *
line_step;
450 const std::vector<float> &z_buffer,
451 const Color &default_color) {
453 if (node.layer ==
nullptr) {
A TrueType font, loaded at a fixed pixel size, with a glyph cache.
static std::vector< uint32_t > decode_utf8(const std::string &text)
Decode a UTF-8 string into Unicode code points.
Low-level math utilities for the rendering pipeline.
void render_text_layers(const std::vector< TextNodeRef > &layers, Image &output, const Mat4 &view_projection, float pixel_scale, const std::vector< float > &z_buffer, const Color &default_color)
Draw all text layers into an already rendered image.
void draw_text(Image &image, const std::string &text, const Font &font, float x, float baseline_y, const TextDrawStyle &style)
Draw text into an image at a given position and baseline.
TextExtent measure_text(const std::string &text, float size, const std::string &font_file, float line_spacing)
Measure a (possibly multi-line) string in a given font and size.
void ndc_to_screen(const Vec3 &ndc, int width, int height, float &screen_x, float &screen_y, float &depth)
Convert from normalized device coordinates (NDC) to screen (pixel) coordinates.
@ WORLD
3D world coordinates, projected with the scene camera.
glm::mat4 Mat4
4×4 floating-point matrix.
glm::vec3 Vec3
3-component floating-point vector (xyz).
Vec3 perspective_divide(const Vec4 &clip)
Perform perspective division: divide xyz by w.
Font cached_font(const std::string &path, float pixel_size)
Load a font, re-using an already loaded one when possible.
Vec4 transform_point_homogeneous(const Mat4 &m, const Vec3 &p)
Transform a point by a 4×4 matrix, returning the full Vec4 result.
glm::vec4 Vec4
4-component floating-point vector (xyzw).
An RGBA color with floating-point components.
Vertical metrics of a font at a given pixel size.
float ascent
Distance from the baseline to the top of the line, in pixels.
float box_height() const
Height of the glyph box of one line, in pixels (ascent + descent).
How to draw text onto an image with draw_text().
Size of a (possibly multi-line) piece of text.
size_t line_count
Number of lines (a trailing newline does not add one).
A non-owning reference to one text layer in a scene, together with its placement transform (and optio...
int y
Top edge of the bitmap, in image pixels.
int x
Left edge of the bitmap, in image pixels.
const GlyphBitmap * bitmap
Text labels — the TextLayer.