diff --git a/data/verysmall.font b/data/verysmall.font new file mode 100644 index 00000000..864d9220 Binary files /dev/null and b/data/verysmall.font differ diff --git a/include/library/customfont.hpp b/include/library/customfont.hpp new file mode 100644 index 00000000..b7576027 --- /dev/null +++ b/include/library/customfont.hpp @@ -0,0 +1,56 @@ +#ifndef _customfont__hpp__included__ +#define _customfont__hpp__included__ + +#include +#include +#include +#include +#include +#include +#include "framebuffer.hpp" + +class ligature_key +{ +public: + ligature_key(const std::vector& key) throw(std::bad_alloc); + const std::vector& get() const throw() { return ikey; } + size_t length() const throw() { return ikey.size(); } + bool operator<(const ligature_key& key) const throw(); + bool operator<=(const ligature_key& key) const throw() { return !(key < *this); } + bool operator==(const ligature_key& key) const throw(); + bool operator!=(const ligature_key& key) const throw() { return !(key == *this); } + bool operator>=(const ligature_key& key) const throw() { return !(*this < key); } + bool operator>(const ligature_key& key) const throw() { return key < *this; } +private: + std::vector ikey; +}; + +struct font_glyph_data +{ + font_glyph_data(); + font_glyph_data(std::istream& s); + unsigned width; + unsigned height; + unsigned stride; + std::vector glyph; //Bitpacked, element breaks between rows. + void render(framebuffer& fb, int32_t x, int32_t y, premultiplied_color fg, premultiplied_color bg) + const; + void render(framebuffer& fb, int32_t x, int32_t y, premultiplied_color fg, premultiplied_color bg) const; +}; + +struct custom_font +{ +public: + custom_font(); + custom_font(const std::string& file); + void add(const ligature_key& key, const font_glyph_data& glyph) throw(std::bad_alloc); + ligature_key best_ligature_match(const std::vector& codepoints, size_t start) const + throw(std::bad_alloc); + const font_glyph_data& lookup_glyph(const ligature_key& key) const throw(); + unsigned get_rowadvance() const throw() { return rowadvance; } +private: + std::map glyphs; + unsigned rowadvance; +}; + +#endif \ No newline at end of file diff --git a/manual.lyx b/manual.lyx index bb3f942e..6ed2bd50 100644 --- a/manual.lyx +++ b/manual.lyx @@ -2099,6 +2099,23 @@ Run specified render queue, copying the objects to current render queue. Warning: Don't try to run the current render queue. \end_layout +\begin_layout Subsubsection +gui.loadfont(string filename) +\end_layout + +\begin_layout Standard +Loads font from specified file (CUSTOMFONT object). +\end_layout + +\begin_layout Subsubsection +CUSTOMFONT(number x, number y, string text[, number fgc[, number bgc]]) +\end_layout + +\begin_layout Standard +Draw string with custom font to screen. + The parameters are the same as in gui.text. +\end_layout + \begin_layout Subsection table input \end_layout diff --git a/manual.txt b/manual.txt index 13c5720f..8519a5d4 100644 --- a/manual.txt +++ b/manual.txt @@ -1051,6 +1051,16 @@ queue. • Warning: Don't try to run the current render queue. +8.3.33 gui.loadfont(string filename) + +Loads font from specified file (CUSTOMFONT object). + +8.3.34 CUSTOMFONT(number x, number y, string text[, number fgc[, + number bgc]]) + +Draw string with custom font to screen. The parameters are the +same as in gui.text. + 8.4 table input Input handling. Only available in on_input callback. diff --git a/src/library/customfont.cpp b/src/library/customfont.cpp new file mode 100644 index 00000000..f0d4a7cb --- /dev/null +++ b/src/library/customfont.cpp @@ -0,0 +1,278 @@ +#include "customfont.hpp" +#include "serialization.hpp" +#include +#include "zip.hpp" +#include "string.hpp" + +ligature_key::ligature_key(const std::vector& key) throw(std::bad_alloc) +{ + ikey = key; +} + +bool ligature_key::operator<(const ligature_key& key) const throw() +{ + for(size_t i = 0; i < ikey.size() && i < key.ikey.size(); i++) + if(ikey[i] < key.ikey[i]) + return true; + else if(ikey[i] > key.ikey[i]) + return false; + return (ikey.size() < key.ikey.size()); +} + +bool ligature_key::operator==(const ligature_key& key) const throw() +{ + for(size_t i = 0; i < ikey.size() && i < key.ikey.size(); i++) + if(ikey[i] != key.ikey[i]) + return false; + return (ikey.size() == key.ikey.size()); +} + +namespace +{ + void bound(int32_t c, uint32_t odim, uint32_t dim, uint32_t& dc, uint32_t& off, uint32_t& size) + { + if(c >= dim || c + odim <= 0) { + //Outside the screen. + dc = 0; + off = 0; + size = 0; + } else if(c >= 0) { + dc = c; + off = 0; + size = odim; + } else { + dc = 0; + off = -c; + size = odim + c; + } + if(dc + size > dim) + size = dim - dc; + } + + template void _render(const font_glyph_data& glyph, framebuffer& fb, int32_t x, int32_t y, + premultiplied_color fg, premultiplied_color bg) + { + uint32_t xdc, xoff, xsize; + uint32_t ydc, yoff, ysize; + bound(x, glyph.width, fb.get_width(), xdc, xoff, xsize); + bound(y, glyph.height, fb.get_height(), ydc, yoff, ysize); + if(!xsize || !ysize) + return; + for(unsigned i = 0; i < ysize; i++) { + auto p = fb.rowptr(i + ydc); + for(unsigned j = 0; j < xsize; j++) { + size_t ge = (i + yoff) * glyph.stride + ((j + xoff) / 32); + size_t gb = 31 - (j + xoff) % 32; + if((glyph.glyph[ge] >> gb) & 1) + fg.apply(p[j + xdc]); + else + bg.apply(p[j + xdc]); + } + } + } +} + +font_glyph_data::font_glyph_data() +{ + stride = width = height = 0; +} + +font_glyph_data::font_glyph_data(std::istream& s) +{ + char header[40]; + bool old = true; + bool upside_down = true; + size_t rcount = 26; + s.read(header, 26); + if(!s) + throw std::runtime_error("Can't read glyph bitmap header"); + if(read16ule(header + 0) != 0x4D42) + throw std::runtime_error("Bad glyph BMP magic"); + if(read16ule(header + 14) != 12) { + //Not OS/2 format. + old = false; + rcount = 40; + s.read(header + 26, 14); + if(!s) + throw std::runtime_error("Can't read glyph bitmap header"); + } + + uint32_t startoff = read32ule(header + 10); + if(old) { + width = read16ule(header + 18); + height = read16ule(header + 20); + if(read16ule(header + 22) != 1) + throw std::runtime_error("Bad glyph BMP planecount"); + if(read16ule(header + 24) != 1) + throw std::runtime_error("Bad glyph BMP bitdepth"); + if(startoff < 26) + throw std::runtime_error("Glyph BMP data can't overlap header"); + } else { + long _width = read32sle(header + 18); + long _height = read32sle(header + 22); + if(_width < 0) + throw std::runtime_error("Bad glyph BMP size"); + if(_height < 0) + upside_down = false; + width = _width; + height = (_height >= 0) ? height : -height; + + if(read16ule(header + 26) != 1) + throw std::runtime_error("Bad glyph BMP planecount"); + if(read16ule(header + 28) != 1) + throw std::runtime_error("Bad glyph BMP bitdepth"); + if(read32ule(header + 30) != 0) + throw std::runtime_error("Bad glyph BMP compression method"); + if(startoff < 40) + throw std::runtime_error("Glyph BMP data can't overlap header"); + } + //Discard data until start of bitmap. + while(rcount < startoff) { + s.get(); + if(!s) + throw std::runtime_error("EOF while skipping to BMP data"); + rcount++; + } + stride = (width + 31) / 32; + glyph.resize(stride * height); + memset(&glyph[0], 0, sizeof(uint32_t) * glyph.size()); + size_t toskip = (4 - ((width + 7) / 8) % 4) % 4; + for(size_t i = 0; i < height; i++) { + size_t y = upside_down ? (height - i - 1) : i; + size_t bpos = y * stride * 32; + for(size_t j = 0; j < width; j += 8) { + size_t e = (bpos + j) / 32; + size_t b = (bpos + j) % 32; + int c = s.get(); + if(!s) + throw std::runtime_error("EOF while reading BMP data"); + glyph[e] |= ((uint32_t)c << (24 - b)); + } + for(size_t j = 0; j < toskip; j++) { + s.get(); + if(!s) + throw std::runtime_error("EOF while reading BMP data"); + } + } +} + +void font_glyph_data::render(framebuffer& fb, int32_t x, int32_t y, premultiplied_color fg, + premultiplied_color bg) const +{ + _render(*this, fb, x, y, fg, bg); +} + +void font_glyph_data::render(framebuffer& fb, int32_t x, int32_t y, premultiplied_color fg, + premultiplied_color bg) const +{ + _render(*this, fb, x, y, fg, bg); +} + + +custom_font::custom_font() +{ + rowadvance = 0; +} + +custom_font::custom_font(const std::string& file) +{ + std::istream* toclose = NULL; + rowadvance = 0; + try { + zip_reader r(file); + for(auto member : r) { + //Parse the key out of filename. + std::vector k; + std::string tname = member; + std::string tmp; + if(tname == "bad") { + //Special, no key. + } else if(regex_match("[0-9]+(-[0-9]+)*", tname)) + while(tname != "") { + extract_token(tname, tmp, "-"); + k.push_back(parse_value(tmp)); + } + else { + delete toclose; + toclose = NULL; + continue; + } + ligature_key key(k); + std::istream& s = r[member]; + toclose = &s; + try { + add(key, font_glyph_data(s)); + } catch(std::bad_alloc& e) { + throw; + } catch(std::exception& e) { + throw std::runtime_error(tname + std::string(": ") + e.what()); + } + delete toclose; + toclose = NULL; + } + } catch(std::bad_alloc& e) { + if(toclose) + delete toclose; + throw; + } catch(std::exception& e) { + if(toclose) + delete toclose; + throw std::runtime_error(std::string("Error reading font: ") + e.what()); + } +} + +std::ostream& operator<<(std::ostream& os, const ligature_key& lkey) +{ + if(!lkey.length()) + return (os << "bad"); + for(size_t i = 0; i < lkey.length(); i++) { + if(i) + os << "-"; + os << lkey.get()[i]; + } + return os; +} + +void custom_font::add(const ligature_key& key, const font_glyph_data& glyph) throw(std::bad_alloc) +{ + glyphs[key] = glyph; + if(glyph.height > rowadvance) + rowadvance = glyph.height; +} + +ligature_key custom_font::best_ligature_match(const std::vector& codepoints, size_t start) const + throw(std::bad_alloc) +{ + std::vector tmp; + if(start >= codepoints.size()) + return ligature_key(tmp); //Bad. + ligature_key best(tmp); + for(size_t i = 1; i <= codepoints.size() - start; i++) { + tmp.push_back(codepoints[start + i - 1]); + ligature_key lkey(tmp); + if(glyphs.count(lkey)) + best = lkey; + auto j = glyphs.lower_bound(lkey); + //If lower_bound is greater than equivalent length of string, there can be no better match. + if(j == glyphs.end()) + break; + const std::vector& tmp2 = j->first.get(); + bool best_found = false; + for(size_t k = 0; k < tmp2.size() && start + k < codepoints.size(); k++) + if(tmp2[k] > codepoints[start + k]) { + best_found = true; + break; + } else if(tmp2[k] < codepoints[start + k]) + break; + if(best_found) + break; + } + return best; +} + +const font_glyph_data& custom_font::lookup_glyph(const ligature_key& key) const throw() +{ + static font_glyph_data empty_glyph; + auto i = glyphs.find(key); + return (i == glyphs.end()) ? empty_glyph : i->second; +} diff --git a/src/lua/gui-text-cf.cpp b/src/lua/gui-text-cf.cpp new file mode 100644 index 00000000..56755aa4 --- /dev/null +++ b/src/lua/gui-text-cf.cpp @@ -0,0 +1,130 @@ +#include "lua/internal.hpp" +#include "fonts/wrapper.hpp" +#include "library/framebuffer.hpp" +#include "library/customfont.hpp" +#include "library/utf8.hpp" + + +namespace +{ + struct lua_customfont + { + public: + lua_customfont(lua_State* LS, const std::string& filename); + ~lua_customfont() throw(); + int draw(lua_State* LS); + const custom_font& get_font() { return font; } + private: + custom_font font; + }; +} + +DECLARE_LUACLASS(lua_customfont, "CUSTOMFONT"); + +namespace +{ + std::vector decode_utf8(const std::string& t) + { + std::vector x; + size_t ts = t.length(); + uint16_t s = utf8_initial_state; + for(size_t i = 0; i <= ts; i++) { + int ch = (i < ts) ? (int)(unsigned char)t[i] : -1; + int32_t cp = utf8_parse_byte(ch, s); + if(cp >= 0) + x.push_back(cp); + } + return x; + } + + struct render_object_text_cf : public render_object + { + render_object_text_cf(int32_t _x, int32_t _y, const std::string& _text, premultiplied_color _fg, + premultiplied_color _bg, lua_obj_pin* _font) throw() + : x(_x), y(_y), text(_text), fg(_fg), bg(_bg), font(_font) {} + ~render_object_text_cf() throw() {} + template void op(struct framebuffer& scr) throw() + { + fg.set_palette(scr); + bg.set_palette(scr); + const custom_font& fdata = font->object()->get_font(); + std::vector _text = decode_utf8(text); + int32_t orig_x = x; + int32_t drawx = x; + int32_t drawy = y; + for(size_t i = 0; i < _text.size();) { + uint32_t cp = _text[i]; + ligature_key k = fdata.best_ligature_match(_text, i); + const font_glyph_data& glyph = fdata.lookup_glyph(k); + if(k.length()) + i += k.length(); + else + i++; + if(cp == 9) { + drawx = (drawx + 64) >> 6 << 6; + } else if(cp == 10) { + drawx = orig_x; + drawy += fdata.get_rowadvance(); + } else { + glyph.render(scr, drawx, drawy, fg, bg); + drawx += glyph.width; + } + } + + } + void operator()(struct framebuffer& scr) throw() { op(scr); } + void operator()(struct framebuffer& scr) throw() { op(scr); } + private: + int32_t x; + int32_t y; + premultiplied_color fg; + premultiplied_color bg; + std::string text; + lua_obj_pin* font; + }; + + lua_customfont::lua_customfont(lua_State* LS, const std::string& filename) + : font(filename) + { + static bool done = false; + if(!done) { + objclass().bind(LS, "__call", &lua_customfont::draw); + done = true; + } + } + + lua_customfont::~lua_customfont() throw() {} + + int lua_customfont::draw(lua_State* LS) + { + std::string fname = "CUSTOMFONT::__call"; + if(!lua_render_ctx) + return 0; + int64_t fgc = 0xFFFFFFU; + int64_t bgc = -1; + int32_t _x = get_numeric_argument(LS, 2, fname.c_str()); + int32_t _y = get_numeric_argument(LS, 3, fname.c_str()); + get_numeric_argument(LS, 5, fgc, fname.c_str()); + get_numeric_argument(LS, 6, bgc, fname.c_str()); + std::string text = get_string_argument(LS, 4, fname.c_str()); + auto f = lua_class::pin(LS, 1, fname.c_str()); + premultiplied_color fg(fgc); + premultiplied_color bg(bgc); + lua_render_ctx->queue->create_add(_x, _y, text, fg, bg, f); + return 0; + } + + function_ptr_luafun gui_text_cf("gui.loadfont", [](lua_State* LS, const std::string& fname) -> int { + std::string filename = get_string_argument(LS, 1, fname.c_str()); + try { + lua_customfont* b = lua_class::create(LS, LS, filename); + return 1; + } catch(std::exception& e) { + lua_pushstring(LS, e.what()); + lua_error(LS); + return 0; + } + + }); +} +