Skip to content

Commit

Permalink
karm-ui: Multiline label.
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepy-monax committed Dec 12, 2023
1 parent 8ccea64 commit db87dbd
Show file tree
Hide file tree
Showing 20 changed files with 527 additions and 582 deletions.
20 changes: 12 additions & 8 deletions src/libs/karm-gfx/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,29 +298,29 @@ void Context::fill(Math::Vec2i pos, Media::Icon icon) {
_useSpaa = false;
}

void Context::stroke(Math::Vec2f baseline, Media::Glyph rune) {
void Context::stroke(Math::Vec2f baseline, Media::Glyph glyph) {
auto f = textFont();

_useSpaa = true;
save();
begin();
origin(baseline.cast<isize>());
scale(f.scale());
f.fontface->contour(*this, rune);
f.fontface->contour(*this, glyph);
stroke();
restore();
_useSpaa = false;
}

void Context::fill(Math::Vec2f baseline, Media::Glyph rune) {
void Context::fill(Math::Vec2f baseline, Media::Glyph glyph) {
auto f = textFont();

_useSpaa = true;
save();
begin();
origin(baseline.cast<isize>());
scale(f.scale());
f.fontface->contour(*this, rune);
f.fontface->contour(*this, glyph);
fill();
restore();
_useSpaa = false;
Expand All @@ -343,9 +343,11 @@ void Context::stroke(Math::Vec2f baseline, Str str) {
auto curr = f.glyph(rune);
if (not first)
baseline.x += f.kern(prev, curr);
else
first = false;

stroke(baseline, curr);
baseline.x += f.advance(curr);
first = false;
prev = curr;
}
}
Expand All @@ -359,9 +361,11 @@ void Context::fill(Math::Vec2f baseline, Str str) {
auto curr = f.glyph(rune);
if (not first)
baseline.x += f.kern(prev, curr);
else
first = false;

fill(baseline, curr);
baseline.x += f.advance(curr);
first = false;
prev = curr;
}
}
Expand Down Expand Up @@ -409,7 +413,7 @@ void Context::debugRect(Math::Recti rect, Color color) {
}

void Context::debugArrow(Math::Vec2i from, Math::Vec2i to, Color color) {
const isize SIZE = 16;
isize const SIZE = 16;

Math::Vec2i dir = to - from;
Math::Vec2i perp = {-dir.y, dir.x};
Expand All @@ -427,7 +431,7 @@ void Context::debugArrow(Math::Vec2i from, Math::Vec2i to, Color color) {
}

void Context::debugDoubleArrow(Math::Vec2i from, Math::Vec2i to, Color color) {
const isize SIZE = 8;
isize const SIZE = 8;

Math::Vec2f dir = (to - from).cast<f64>();
Math::Vec2f perp = {-dir.y, dir.x};
Expand Down
296 changes: 296 additions & 0 deletions src/libs/karm-gfx/text.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
#pragma once

#include <karm-media/font.h>

#include "context.h"

namespace Karm::Gfx {

enum struct TextAlign {
LEFT,
CENTER,
RIGHT,
};

struct TextStyle {
Media::Font font;
TextAlign align = TextAlign::LEFT;
Opt<Color> color = NONE;
bool wordwrap = true;
bool multiline = true;

TextStyle withSize(f64 size) const {
TextStyle style = *this;
style.font.fontsize = size;
return style;
}

TextStyle withLineHeight(f64 height) const {
TextStyle style = *this;
style.font.lineheight = height;
return style;
}

TextStyle withAlign(TextAlign align) const {
TextStyle style = *this;
style.align = align;
return style;
}

TextStyle withColor(Color color) const {
TextStyle style = *this;
style.color = color;
return style;
}

TextStyle withWordwrap(bool wordwrap) const {
TextStyle style = *this;
style.wordwrap = wordwrap;
return style;
}

TextStyle withMultiline(bool multiline) const {
TextStyle style = *this;
style.multiline = multiline;
return style;
}
};

struct Text {
TextStyle _style;

struct Cell {
Media::Glyph glyph;
f64 pos = 0; // Position of the glyph within the block
};

Vec<Cell> _cells;

struct Block {
USizeRange cells;
size_t spaces = 0;
bool newline = false;

f64 pos = 0; // Position of the block within the line
f64 width = 0;

bool empty() const {
return cells.empty() and not spaces and not newline;
}

f64 fullWidth(Text const &text) const {
return width + spaces * text._spaceWidth;
}

bool hasWhitespace() const {
return spaces > 0 or newline;
}
};

Vec<Block> _blocks;
bool _blocksMesured = false;

struct Line {
USizeRange blocks;
f64 baseline = 0; // Baseline of the line within the text
f64 width = 0;
};

Vec<Line> _lines;

f64 _spaceWidth{};

Text(TextStyle style, Str str = "") : _style(style) {
clear();
_spaceWidth = _style.font.advance(_style.font.glyph(' '));
append(str);
}

void _beginBlock() {
_blocks.pushBack({{_cells.len(), 0}});
}

void _appendRune(Rune rune) {
if (last(_blocks).newline)
_beginBlock();

if (rune == ' ') {
last(_blocks).spaces++;
} else if (rune == '\t') {
// HACK: tab width is 4 spaces
last(_blocks).spaces += 4;
} else if (rune == '\n') {
last(_blocks).newline = true;
} else {
if (last(_blocks).hasWhitespace())
_beginBlock();

auto glyph = _style.font.glyph(rune);
_cells.pushBack({glyph});
last(_blocks).cells.size++;
}
}

void clear() {
_cells.clear();
_blocks.clear();
_blocksMesured = false;
_blocks.pushBack({{0, 0}});
_lines.clear();
}

template <typename E>
void append(_Str<E> str) {
for (auto rune : iterRunes(str)) {
_appendRune(rune);
}
}

void _mesureBlocks() {
for (auto &block : _blocks) {
auto adv = 0;
bool first = true;
Media::Glyph prev{0};
for (usize i = block.cells.start; i < block.cells.end(); i++) {
auto &cell = _cells[i];
if (not first)
adv += _style.font.kern(prev, cell.glyph);
else
first = false;

cell.pos = adv;
adv += _style.font.advance(cell.glyph);
prev = cell.glyph;
}
block.width = adv;
}
}

void _wrapLines(f64 width) {
_lines.clear();

Line line{{0, 0}};
bool first = true;
f64 adv = 0;
for (usize i = 0; i < _blocks.len(); i++) {
auto &block = _blocks[i];
auto fullWidth = block.fullWidth(_style);
if (adv + block.width > width and _style.wordwrap and not first) {
_lines.pushBack(line);
line = {{i, 1}};
adv = fullWidth;

if (block.newline) {
_lines.pushBack(line);
line = {{i + 1, 0}};
adv = 0;
}
} else {
line.blocks.size++;

if (block.newline) {
_lines.pushBack(line);
line = {{i + 1, 0}};
adv = 0;
} else {
adv += fullWidth;
}
}
first = false;
}
_lines.pushBack(line);
}

f64 _layoutVerticaly() {
f64 baseline = 0;
auto m = _style.font.metrics();
for (auto &line : _lines) {
baseline += m.ascend;
line.baseline = baseline;
baseline += m.linegap + m.descend;
}
return baseline;
}

f64 _layoutHorizontaly(f64 width) {
f64 maxWidth = 0;
for (auto &line : _lines) {
if (not line.blocks.any())
continue;

f64 pos = 0;
for (usize i = line.blocks.start; i < line.blocks.end(); i++) {
auto &block = _blocks[i];
block.pos = pos;
pos += block.fullWidth(*this);
}

auto lastBlock = _blocks[line.blocks.end() - 1];
line.width = lastBlock.pos + lastBlock.width;
maxWidth = max(maxWidth, line.width);
auto free = width - line.width;

switch (_style.align) {
case TextAlign::LEFT:
break;

case TextAlign::CENTER:
for (usize i = line.blocks.start; i < line.blocks.end(); i++) {
auto &block = _blocks[i];
block.pos += free / 2;
}
break;

case TextAlign::RIGHT:
for (usize i = line.blocks.start; i < line.blocks.end(); i++) {
auto &block = _blocks[i];
block.pos += free;
}
break;
}
}

return maxWidth;
}

Math::Vec2f layout(f64 width) {
if (isEmpty(_blocks)) {
return {};
}

if (not _blocksMesured) {
_mesureBlocks();
_blocksMesured = true;
}

_wrapLines(width);
auto textHeight = _layoutVerticaly();
auto textWidth = _layoutHorizontaly(width);
return {textWidth, textHeight};
}

void paint(Context &ctx) const {
auto m = _style.font.metrics();
auto baseline = m.ascend;

ctx.save();
ctx.textFont(_style.font);
if (_style.color)
ctx.fillStyle(*_style.color);
for (auto const &line : _lines) {
for (usize b = line.blocks.start; b < line.blocks.end(); b++) {
auto const &block = _blocks[b];

for (usize c = block.cells.start; c < block.cells.end(); c++) {
auto const &cell = _cells[c];

ctx.fill({block.pos + cell.pos, baseline}, cell.glyph);
}
}
baseline += m.linegap + m.descend + m.ascend;
}
ctx.restore();
}
};

} // namespace Karm::Gfx
1 change: 0 additions & 1 deletion src/libs/karm-io/funcs.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <karm-base/string.h>

#include "impls.h"
#include "traits.h"

namespace Karm::Io {

Expand Down
4 changes: 2 additions & 2 deletions src/libs/karm-math/bigint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,9 @@ void _fromF64(Frac &frac, f64 value) {
UBig dec = 0_ubig;
while (value >= __DBL_EPSILON__ || currPow >= 0) {
frac._num *= TEN;
usize digit = (u64)(value * ::pow(0.1, (double)currPow)) % 10;
usize digit = (u64)(value * ::pow(0.1, (f64)currPow)) % 10;
frac._num += IBig{digit};
value -= digit * ::pow(10.0, (double)currPow);
value -= digit * ::pow(10.0, (f64)currPow);
if (currPow < 0) {
++dec;
_pow(TEN.value(), dec, frac._den);
Expand Down
Loading

0 comments on commit db87dbd

Please sign in to comment.