Class: RGame::Core::Font

Inherits:
Object
  • Object
show all
Defined in:
lib/rgame/core/font.rb,
ext/rgame_core/ruby/font_ext.c

Overview

A typeface at one pixel size, with a glyph atlas behind it.

font = RGame::Core::Font.new(app, 18)
font.height                    # => 18
font.text_width('Score: 1200') # => 78.4

renderer.text('Score: 1200', 10, 10, font: font)

Two sizes are two fonts. Glyphs are rasterised the first time they are drawn and kept afterwards, so what a font costs is bounded by the characters a game actually uses — a score that changes every frame is free after the first ten digits.

Measuring works anywhere; drawing, like everything else, only inside draw. That split is deliberate: laying out a menu happens while updating.

The default font

Passing no path uses the one the engine ships — Liberation Sans, which covers Western European languages including ß, , accents, «» and . There is no font-name lookup and no system font database: a font is a file. That is what makes text render identically on every machine, which a system lookup cannot promise.

RGame::Core::Font.new(app, 18, path: 'assets/pixel.ttf')

Scripts outside the shipped font's coverage — CJK, Arabic, Hebrew — need their own file. No font of this size covers them.

Constant Summary collapse

DEFAULT_PATH =

Shipped with the gem, alongside its SIL OFL 1.1 licence. Resolved from this file's location so it works the same from a checkout and from an installed gem.

File.expand_path('../fonts/LiberationSans-Regular.ttf', __dir__)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, pixel_height, path) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'ext/rgame_core/ruby/font_ext.c', line 87

static VALUE font_initialize(VALUE self, VALUE app, VALUE pixel_height, VALUE path) {
    rgame_font_ref *ref;
    TypedData_Get_Struct(self, rgame_font_ref, &font_data_type, ref);

    rgame_app *engine_app = rgame_app_unwrap(app); /* raises TypeError otherwise */
    const char *path_str = StringValueCStr(path);
    int height = NUM2INT(pixel_height);

    char error[256] = {0};
    rgame_font *font = rgame_font_load(engine_app, path_str, height, error, sizeof(error));
    if (!font) {
        rb_raise(rb_const_get(cFont, rb_intern("LoadError")), "%s", error);
    }

    ref->font = font;
    ref->app_object = app;
    return self;
}

Class Method Details

.debug_live_pagesObject



139
140
141
142
# File 'ext/rgame_core/ruby/font_ext.c', line 139

static VALUE font_s_debug_live_pages(VALUE klass) {
    (void)klass;
    return LONG2NUM(rgame_font_live_pages());
}

.new(app, pixel_height, path: DEFAULT_PATH) ⇒ Object

path: is a keyword for callers but positional for the C initialize, which has no business knowing where a gem installs its data.



44
45
46
# File 'lib/rgame/core/font.rb', line 44

def self.new(app, pixel_height, path: DEFAULT_PATH)
  super(app, pixel_height, path)
end

Instance Method Details

#heightObject



107
108
109
# File 'ext/rgame_core/ruby/font_ext.c', line 107

static VALUE font_height(VALUE self) {
    return INT2NUM(rgame_font_height(font_unwrap(self)->font));
}

#inspectObject



144
145
146
147
148
149
150
151
152
153
# File 'ext/rgame_core/ruby/font_ext.c', line 144

static VALUE font_inspect(VALUE self) {
    rgame_font_ref *ref;
    TypedData_Get_Struct(self, rgame_font_ref, &font_data_type, ref);
    if (!ref->font) {
        return rb_sprintf("#<%" PRIsVALUE " (uninitialized)>", rb_obj_class(self));
    }

    return rb_sprintf("#<%" PRIsVALUE " %dpx>", rb_obj_class(self),
                      rgame_font_height(ref->font));
}

#text_width(string) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'ext/rgame_core/ruby/font_ext.c', line 117

static VALUE font_text_width(VALUE self, VALUE string) {
    rgame_font_ref *ref = font_unwrap(self);

    /* Raises TypeError rather than letting RSTRING_PTR read a non-String's
     * innards as a char*, which segfaults. See renderer_ext.c's #draw_text. */
    StringValue(string);

    /* The bytes as Ruby holds them. No copy, no per-call allocation, and the
     * codepoint walk happens in C where it costs nothing. */
    const char *text = RSTRING_PTR(string);
    long length = RSTRING_LEN(string);
    double width = rgame_font_measure(ref->font, text, (size_t)length);

    /* Keeps `string` alive across the call above, which matters because
     * RSTRING_PTR hands out a pointer the GC does not know we are holding. */
    RB_GC_GUARD(string);
    return DBL2NUM(width);
}