aboutsummaryrefslogtreecommitdiff
path: root/font_api
diff options
context:
space:
mode:
Diffstat (limited to 'font_api')
-rw-r--r--font_api/API.md145
-rw-r--r--font_api/LICENSE.txt166
-rw-r--r--font_api/README.md23
-rw-r--r--font_api/copyright.txt3
-rw-r--r--font_api/depends.txt0
-rw-r--r--font_api/doc/font.svg463
-rw-r--r--font_api/doc/lines.svg544
-rw-r--r--font_api/fallbacks.lua176
-rw-r--r--font_api/font.lua272
-rw-r--r--font_api/fontform.lua167
-rw-r--r--font_api/init.lua71
-rw-r--r--font_api/registry.lua162
-rw-r--r--font_api/settingtypes.txt1
-rw-r--r--font_api/textures/font_api_center.pngbin0 -> 224 bytes
-rw-r--r--font_api/textures/font_api_font.pngbin0 -> 238 bytes
-rw-r--r--font_api/textures/font_api_left.pngbin0 -> 220 bytes
-rw-r--r--font_api/textures/font_api_right.pngbin0 -> 222 bytes
-rwxr-xr-xfont_api/tools/make_font_lua.sh71
-rwxr-xr-xfont_api/tools/make_font_textures.sh111
19 files changed, 2375 insertions, 0 deletions
diff --git a/font_api/API.md b/font_api/API.md
new file mode 100644
index 0000000..9900a80
--- /dev/null
+++ b/font_api/API.md
@@ -0,0 +1,145 @@
+# Font Lib API
+This document describes Font Lib API. Font Lib creates textures for font display on entities.
+
+## Settings
+### default_font
+Name of the font to be used when no font is given. The font should be registered.
+
+If no default\_font given or if default\_font given but not registered, the first registered font will be used as default.
+
+## Provided methods
+
+### font_api.get_default_font_name()
+Returns de default font name.
+
+### font_api.register_font(font_name, font_def)
+Register a new font.
+**font_name**: Name of the font to register. If registering different sizes of the same font, add size in the font name (e.g. times_10, times_12...).
+**font_def**: Font definition table (see **Font definition table** below).
+
+### font_api.on_display_update(pos, objref)
+Standard on_display_update entity callback.
+
+**pos**: Node position
+
+**objref**: Object reference of entity
+
+Node should have a corresponding display_entity with size, resolution and maxlines fields and optionally halign, valign and color fields.
+
+### Font definition table
+Font definition table used by **font_api.register_font** and **font\_api.Font:new** may/can contain following elements:
+
+* **height** (required): Font height in pixels (all font textures should have the same height) .
+* **widths** (required): Array of character widths in pixels, indexed by UTF codepoints.
+* **margintop** (optional): Margin (in texture pixels) added on top of each char texture.
+* **marginbottom** (optional): Margin (in texture pixels) added at bottom of each char texture.
+* **linespacing** (optional): Spacing (in texture pixels) between each lines.
+
+**margintop**, **marginbottom** and **linespacing** can be negative numbers (default 0) and are to be used to adjust various font styles to each other.
+
+Font must have a char 0 which will be used to display any unknown char.
+
+All textures corresponding to the indexes in widths array should be present in textures directory with a name matching the pattern :
+
+> font\_**{font_name}**_**{utf_code}**.png
+
+**{font\_name}**: Name of the font as given in the first argument
+
+**{utf\_code}**: UTF code of the char in 4 hexadecimal digits
+
+Example : font_courrier_0041.png is for the "A" char in the "courrier" font.
+
+To ease that declaration (specially to build the **widths** array), a shell is provided to build a {font\_name}.lua file from the texture files (see provided tools).
+
+## Provided tools
+
+Still in early stage of development, these tools are helpers to create font mods.
+
+### make_font_texture.sh
+
+This scripts takes a .ttf file as input and create one .png file per char, that can be used as font texture. Launch it from your future font mod directory.
+
+__Advice__
+
+This script works much better with pixels font, providing the correct height. There is no antialiasing at all, vector fonts and bad heights gives very ugly results.
+
+__Syntax__
+
+**make\_font\_texture.sh {fontfile} {fontname} {fontsize}**
+
+**{fontfile}**: A TTF font file to use to create textures.
+**{fontname}**: The font name to be used in font_api (should be simple, with no spaces).
+**{fontsize}**: Font height to be rendered.
+
+### make_font_lua.sh
+
+This script analyses textures in textures directory and creates a font\_{font\_name}.lua files with a call to register_font with images information. Launch it from your future font mod directory.
+
+Once the font\_{font\_name}.lua created, it can be included by a init.lua file or directly renamed to init.lua if you are creating a simple font mod.
+
+__Syntax__
+
+**make\_font_lua.sh {fontname}**
+
+**{fontname}**: The font name to be used in font_api (same as given to make\_font\_texture.sh)
+
+### An exemple generating a font mod
+
+ mkdir font_myfont
+ cd font_myfont
+ /<path_to_font_api>/tools/make_font_texture.sh myfont.ttf myfont 12
+ /<path_to_font_api>/tools/make_font_lua.sh myfont
+ mv font_myfont.lua init.lua
+
+## Font class
+A font usable with font API. This class is supposed to be for internal use but who knows.
+
+### font\_api.Font:new(def)
+Create a new font object.
+
+**def** is a table containing font definition. See **Font definition table** above.
+
+### font:get_char_width(char)
+Returns the width of char **char** in texture pixels.
+
+**char**: Unicode codepoint of char.
+
+### font:get_height(nb_of_lines)
+Returns line(s) height. Takes care of top and bottom margins and line spacing.
+
+**nb_of_lines**: Number of lines in the text.
+
+### font:get_width(line)
+
+Returns the width of a text line. Beware, if line contains any new line char, they are ignored.
+
+**line**: Line of text which the width will be computed.
+
+### font:make_line_texture(line, texturew, x, y)
+Create a texture for a text line.
+
+**line**: Line of text to be rendered in texture.
+
+**texturew**: Width of the texture (extra text is not rendered).
+
+**x**: Starting x position in texture.
+
+**y**: Vertical position of the line in texture.
+
+### font:make_text_texture(text, texturew, textureh, maxlines, halign, valign, color)
+Builds texture for a multiline colored text.
+
+**text**: Text to be rendered.
+
+**texturew**: Width of the texture (extra text will be truncated).
+
+**textureh**: Height of the texture.
+
+**maxlines**: Maximum number of lines.
+
+**halign**: Horizontal text align ("left"/"center"/"right") (optional).
+
+**valign**: Vertical text align ("top"/"center"/"bottom") (optional).
+
+**color**: Color of the text (optional).
+
diff --git a/font_api/LICENSE.txt b/font_api/LICENSE.txt
new file mode 100644
index 0000000..341c30b
--- /dev/null
+++ b/font_api/LICENSE.txt
@@ -0,0 +1,166 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
+
diff --git a/font_api/README.md b/font_api/README.md
new file mode 100644
index 0000000..33af92b
--- /dev/null
+++ b/font_api/README.md
@@ -0,0 +1,23 @@
+# Font API
+
+This library for font display on entities (to be used with display_api for sign creation).
+
+**Dependancies**: default
+
+**License**: LGPL
+
+(Default font taken from VanessaE's homedecor/signs_lib, originally under WTFPL)
+
+**API**: See [API.md](https://github.com/pyrollo/display_modpack/blob/master/font_api/API.md) document please.
+
+For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?t=13563) at the Minetest forums.
+
+## Extra fonts
+
+You can add fonts by installing fonts mod. Be aware that each font comes with numerous textures. This can result in slowing media downloading and/or client display.
+
+Font mods can be found here:
+
+ * [Metro](https://github.com/pyrollo/display_modpack/tree/master/font_metro): A multipurpose font with many chars (uppercase, lowercase and accentuated latin letters, usual signs, cyrillic and greek letters).
+ * [OldWizard](https://github.com/pyrollo/font_oldwizard): An old style gothic font.
+ * [Botic](https://github.com/pyrollo/font_botic): A scifi style font.
diff --git a/font_api/copyright.txt b/font_api/copyright.txt
new file mode 100644
index 0000000..ceb5446
--- /dev/null
+++ b/font_api/copyright.txt
@@ -0,0 +1,3 @@
+Code by Pierre-Yves Rollo (pyrollo)
+Contributors:
+Andrzej Pieńkowski (apienk): Unicode support and tool for creating texturess
diff --git a/font_api/depends.txt b/font_api/depends.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/font_api/depends.txt
diff --git a/font_api/doc/font.svg b/font_api/doc/font.svg
new file mode 100644
index 0000000..e4e8757
--- /dev/null
+++ b/font_api/doc/font.svg
@@ -0,0 +1,463 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="120mm"
+ height="110mm"
+ viewBox="0 0 120 110"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
+ sodipodi:docname="font.svg">
+ <defs
+ id="defs2">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker7159"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path7157"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker7071"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path7069"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path886"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path883"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path880"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path877"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-3"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-6"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-7"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-5"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-9"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-1"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-2"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-7"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-9-9"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-1-3"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-2-6"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-7-0"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-1"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-2"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-9"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-3"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-94"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-7"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-8"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-4"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.98994949"
+ inkscape:cx="-151.37352"
+ inkscape:cy="445.54867"
+ inkscape:document-units="mm"
+ inkscape:current-layer="g8201"
+ showgrid="true"
+ inkscape:lockguides="true"
+ inkscape:window-width="1877"
+ inkscape:window-height="1052"
+ inkscape:window-x="1409"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ showguides="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid826"
+ spacingx="1"
+ units="mm"
+ spacingy="1" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-187)">
+ <g
+ id="g7357"
+ transform="translate(65,-15)"
+ style="opacity:0.5">
+ <rect
+ y="202"
+ x="170"
+ height="70"
+ width="70"
+ id="rect828-9"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.98000004;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:0.25098039;fill-rule:nonzero;stroke:#999999;stroke-width:0.30550504;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ d="m 220,252 h 10 v 10 h -10 z m -50,0 h 10 v 10 h -10 z m 5,-10 h 50 v 10 h -50 z m 4.99998,-10 h 10 v 10 h -10 z M 210,232 h 10 v 10 h -10 z m -5,-10 h 10 v 10 h -10 z m -20,0 h 10 v 10 h -10 z m 5,-10.00002 h 20 v 10 H 190 Z M 195,202 h 10 v 10 h -10 z"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.98000004;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32659864;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect854-2"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g8201"
+ transform="translate(0,190)">
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.26499998, 1.05999994;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect1594"
+ width="120"
+ height="109.99999"
+ x="0"
+ y="-3" />
+ <g
+ transform="translate(-75,-120)"
+ id="g7361">
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.98000004;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:0.25098039;fill-rule:nonzero;stroke:#0000ff;stroke-width:0.30550504;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect828"
+ width="70"
+ height="70"
+ x="100"
+ y="137" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect854"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.98000004;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32659864;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 150,187 h 10 v 10 h -10 z m -50,0 h 10 v 10 h -10 z m 5,-10 h 50 v 10 h -50 z m 4.99999,-10 h 10 v 10 h -10 z M 140,167 h 10 v 10 h -10 z m -5,-10 h 10 v 10 h -10 z m -20,0 h 10 v 10 h -10 z m 5,-10.00002 h 20 v 10 H 120 Z M 125,137 h 10 v 10 h -10 z" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ id="path875"
+ d="M 19.999999,17 V 87"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Lstart);marker-end:url(#Arrow1Lend)" />
+ <text
+ inkscape:transform-center-y="5.2916667"
+ inkscape:transform-center-x="1.889881"
+ transform="rotate(-90)"
+ id="text1193"
+ y="17.496948"
+ x="-52.43602"
+ style="font-style:normal;font-weight:normal;font-size:3.96875px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="17.496948"
+ x="-52.43602"
+ id="tspan1191"
+ sodipodi:role="line">Texture Height</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ id="path875-3"
+ d="M 95,97 H 25"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Lstart-3);marker-end:url(#Arrow1Lend-7)" />
+ <text
+ inkscape:transform-center-y="-1.8898824"
+ inkscape:transform-center-x="5.2916602"
+ id="text1193-5"
+ y="101.69528"
+ x="60.176346"
+ style="font-style:normal;font-weight:normal;font-size:3.96875px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="101.69528"
+ x="60.176346"
+ id="tspan1191-6"
+ sodipodi:role="line">Texture Width</tspan></text>
+ <rect
+ y="7"
+ x="25"
+ height="85"
+ width="70"
+ id="rect828-2"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.98000004;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ff00ff;stroke-width:0.33665016;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path875-0"
+ d="M 20,7 V 17"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker7071);marker-end:url(#marker7159)" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path875-0-6"
+ d="m 20,87 v 5"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Mstart);marker-end:url(#Arrow1Mend)" />
+ <text
+ inkscape:transform-center-y="-1.8898824"
+ inkscape:transform-center-x="5.2916678"
+ id="text1193-2"
+ y="10.614426"
+ x="17.348068"
+ style="font-style:normal;font-weight:normal;font-size:3.96875px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="text-align:end;text-anchor:end;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="10.614426"
+ x="17.348068"
+ id="tspan1191-61"
+ sodipodi:role="line">Margin</tspan><tspan
+ id="tspan8103"
+ style="text-align:end;text-anchor:end;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="15.575363"
+ x="17.348068"
+ sodipodi:role="line">Top</tspan></text>
+ <text
+ inkscape:transform-center-y="-1.8898824"
+ inkscape:transform-center-x="5.2916678"
+ id="text1193-2-8"
+ y="88.4991"
+ x="17.240334"
+ style="font-style:normal;font-weight:normal;font-size:3.96875px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="text-align:end;text-anchor:end;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="88.4991"
+ x="18.503822"
+ id="tspan1191-61-7"
+ sodipodi:role="line">Margin </tspan><tspan
+ id="tspan8105"
+ style="text-align:end;text-anchor:end;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="93.460037"
+ x="17.240334"
+ sodipodi:role="line">Bottom</tspan></text>
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path875-1"
+ d="M 100,7 V 92"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Lstart-1);marker-end:url(#Arrow1Lend-9)" />
+ <text
+ inkscape:transform-center-y="5.2916667"
+ inkscape:transform-center-x="1.889881"
+ transform="rotate(-90)"
+ id="text1193-0"
+ y="104.35954"
+ x="-49.646301"
+ style="font-style:normal;font-weight:normal;font-size:3.96875px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="104.35954"
+ x="-49.646301"
+ id="tspan1191-3"
+ sodipodi:role="line">Line Height</tspan></text>
+ </g>
+ </g>
+</svg>
diff --git a/font_api/doc/lines.svg b/font_api/doc/lines.svg
new file mode 100644
index 0000000..f75880f
--- /dev/null
+++ b/font_api/doc/lines.svg
@@ -0,0 +1,544 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="120mm"
+ height="225mm"
+ viewBox="0 0 120 225"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
+ sodipodi:docname="lines.svg">
+ <defs
+ id="defs2">
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker7159"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path7157"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="marker7071"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path7069"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path886"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path883"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.4,0,0,0.4,4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path880"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path877"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-3"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-6"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-7"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-5"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-9"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-1"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-2"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-7"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-9-9"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-1-3"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-2-6"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-7-0"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-1"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-2"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-9"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-3"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-94"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-7"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-8"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-4"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-1-2"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-2-0"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-9-6"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-3-1"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lstart-1-5"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path877-2-4"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(0.8,0,0,0.8,10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Lend-9-7"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ inkscape:connector-curvature="0"
+ id="path880-3-6"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+ </marker>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.7"
+ inkscape:cx="577.84911"
+ inkscape:cy="466.83879"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:lockguides="true"
+ inkscape:window-width="1877"
+ inkscape:window-height="1052"
+ inkscape:window-x="1409"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ showguides="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid826"
+ spacingx="1"
+ units="mm"
+ spacingy="1" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-72)">
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.26499998, 1.05999993999999997;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect890"
+ width="120"
+ height="224.99998"
+ x="0"
+ y="72" />
+ <g
+ id="g10629"
+ transform="translate(-5,-54.999998)">
+ <g
+ id="g8263">
+ <g
+ transform="translate(-40,39.999996)"
+ id="g8268">
+ <rect
+ y="102"
+ x="60"
+ height="70"
+ width="70"
+ id="rect828-9"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:0.25098039;fill-rule:nonzero;stroke:#999999;stroke-width:0.30550504;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ d="m 110,152 h 10 v 10 h -10 z m -50,0 h 10 v 10 H 60 Z m 5,-10 h 50 v 10 H 65 Z m 4.99998,-10 h 10 v 10 h -10 z M 100,132 h 10 v 10 h -10 z m -5,-10 h 10 v 10 H 95 Z m -20,0 h 10 v 10 H 75 Z m 5,-10.00002 h 20 v 10 H 80 Z M 85,102 h 10 v 10 H 85 Z"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32659864;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect854-2"
+ inkscape:connector-curvature="0" />
+ <rect
+ y="92"
+ x="60"
+ height="85.000008"
+ width="70"
+ id="rect828-2-6"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.33665016;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ <g
+ transform="translate(-40,170)"
+ id="g8268-1">
+ <rect
+ y="102"
+ x="60"
+ height="70"
+ width="70"
+ id="rect828-9-0"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:0.25098039;fill-rule:nonzero;stroke:#999999;stroke-width:0.30550504;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ d="m 110,152 h 10 v 10 h -10 z m -50,0 h 10 v 10 H 60 Z m 5,-10 h 50 v 10 H 65 Z m 4.99998,-10 h 10 v 10 h -10 z M 100,132 h 10 v 10 h -10 z m -5,-10 h 10 v 10 H 95 Z m -20,0 h 10 v 10 H 75 Z m 5,-10.00002 h 20 v 10 H 80 Z M 85,102 h 10 v 10 H 85 Z"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32659864;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect854-2-6"
+ inkscape:connector-curvature="0" />
+ <rect
+ y="92"
+ x="60"
+ height="85.000008"
+ width="70"
+ id="rect828-2-6-3"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#808080;stroke-width:0.33665016;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+ <g
+ transform="translate(-75,70)"
+ id="g7361">
+ <rect
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.98000004;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:0.25098039;fill-rule:nonzero;stroke:#0000ff;stroke-width:0.30550504;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ id="rect828"
+ width="70"
+ height="70"
+ x="100"
+ y="137" />
+ <path
+ inkscape:connector-curvature="0"
+ id="rect854"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.98000004;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.32659864;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 150,187 h 10 v 10 h -10 z m -50,0 h 10 v 10 h -10 z m 5,-10 h 50 v 10 h -50 z m 4.99999,-10 h 10 v 10 h -10 z M 140,167 h 10 v 10 h -10 z m -5,-10 h 10 v 10 h -10 z m -20,0 h 10 v 10 h -10 z m 5,-10.00002 h 20 v 10 H 120 Z M 125,137 h 10 v 10 h -10 z" />
+ </g>
+ <rect
+ y="197"
+ x="25"
+ height="85"
+ width="70"
+ id="rect828-2"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.98000004;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ff00ff;stroke-width:0.33665016;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path875-1"
+ d="m 100,197 v 85"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Lstart-1);marker-end:url(#Arrow1Lend-9)" />
+ <text
+ inkscape:transform-center-y="5.2916667"
+ inkscape:transform-center-x="1.889881"
+ transform="rotate(-90)"
+ id="text1193-0"
+ y="104.35954"
+ x="-239.6463"
+ style="font-style:normal;font-weight:normal;font-size:3.96875px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="104.35954"
+ x="-239.6463"
+ id="tspan1191-3"
+ sodipodi:role="line">Line Height</tspan></text>
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path875-1-5"
+ d="m 15,197 v 20"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Lstart-1-2);marker-end:url(#Arrow1Lend-9-6)" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path875-1-56"
+ d="M 110,132 V 347"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#Arrow1Lstart-1-5);marker-end:url(#Arrow1Lend-9-7)" />
+ <text
+ inkscape:transform-center-y="5.2916667"
+ inkscape:transform-center-x="1.889881"
+ transform="rotate(-90)"
+ id="text1193-0-9"
+ y="115.82679"
+ x="-239.6463"
+ style="font-style:normal;font-weight:normal;font-size:3.96875px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="115.82679"
+ x="-239.6463"
+ id="tspan1191-3-3"
+ sodipodi:role="line">Text Height</tspan></text>
+ <text
+ inkscape:transform-center-y="5.2916667"
+ inkscape:transform-center-x="1.889881"
+ transform="rotate(-90)"
+ id="text1193-0-7"
+ y="11.713711"
+ x="-206.76294"
+ style="font-style:normal;font-weight:normal;font-size:3.96875px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ xml:space="preserve"><tspan
+ style="text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke-width:0.26458332px"
+ y="11.713711"
+ x="-206.76294"
+ id="tspan1191-3-4"
+ sodipodi:role="line">Line Spacing</tspan></text>
+ <path
+ inkscape:connector-curvature="0"
+ id="path10512"
+ d="M 25,197 H 15 v 0"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.26499999, 1.05999996;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10512-5"
+ d="M 25,217 H 15 v 0"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.26499999, 1.05999996;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path10512-2"
+ d="M 110,132 H 90"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.26499999, 1.05999996;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path10512-2-5"
+ d="M 110,347 H 90"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.26499999, 1.05999997;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path10512-2-5-4"
+ d="M 100,282 H 95"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.26499999, 1.05999998;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path10512-2-5-4-7"
+ d="M 100,197 H 95"
+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.26499999, 1.05999999;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/font_api/fallbacks.lua b/font_api/fallbacks.lua
new file mode 100644
index 0000000..480d865
--- /dev/null
+++ b/font_api/fallbacks.lua
@@ -0,0 +1,176 @@
+--[[
+ font_api mod for Minetest - Library to add font display capability
+ to display_api mod.
+ (c) Pierre-Yves Rollo
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+--]]
+
+-- This is the unicode char fallback map. If a char is not present in
+-- font, this maps indicates which char to try to use instead next.
+
+return {
+ -- Lowercase chars
+ ['a'] = 'A', ['b'] = 'B', ['c'] = 'C', ['d'] = 'D',
+ ['e'] = 'E', ['f'] = 'F', ['g'] = 'G', ['h'] = 'H',
+ ['i'] = 'I', ['j'] = 'J', ['k'] = 'K', ['l'] = 'L',
+ ['m'] = 'M', ['n'] = 'N', ['o'] = 'O', ['p'] = 'P',
+ ['q'] = 'Q', ['r'] = 'R', ['s'] = 'S', ['t'] = 'T',
+ ['u'] = 'U', ['v'] = 'V', ['w'] = 'W', ['x'] = 'X',
+ ['y'] = 'Y', ['z'] = 'Z',
+
+ -- Special
+ ['¢'] = 'c', ['£'] = 'L', ['¥'] = 'Y', ['€'] = 'E',
+ ['©'] = '(C)', ['®'] = '(R)', ['™'] = 'TM',
+ ['ª'] = 'a', ['º'] = 'o',
+ ['«'] = '"', ['»'] = '"', ['´'] = '\'',
+ ['¹'] = '1', ['²'] = '2', ['³'] = '3',
+ ['µ'] = 'u', ['¤'] = 'o',
+ ['¼'] = '1/4', ['½'] = '1/2', ['¾'] = '3/4',
+ ['⅛'] = '1/8', ['⅜'] = '3/8', ['⅝'] = '5/8', ['⅞'] = '7/8',
+ ['¿'] = '?',
+
+ -- Upper case accents
+ ['À'] = 'A', ['Á'] = 'A', ['Â'] = 'A', ['Ã'] = 'A',
+ ['Ä'] = 'A', ['Å'] = 'A',
+ ['Æ'] = 'AE', ['Ç'] = 'C',
+ ['È'] = 'E', ['É'] = 'E', ['Ê'] = 'E', ['Ë'] = 'E',
+ ['Ì'] = 'I', ['Í'] = 'I', ['Î'] = 'I', ['Ï'] = 'I',
+ ['Ð'] = 'D', ['Ñ'] = 'N',
+ ['Ò'] = 'O', ['Ó'] = 'O', ['Ô'] = 'O', ['Õ'] = 'O',
+ ['Ö'] = 'O', ['Ø'] = 'O',
+ ['Ú'] = 'U', ['Ù'] = 'U', ['Û'] = 'U', ['Ü'] = 'U',
+ ['×'] = 'x', ['Ý'] = 'Y',
+
+ -- Lower case accents
+ ['à'] = 'a', ['à'] = 'a', ['á'] = 'a', ['â'] = 'a',
+ ['ã'] = 'a', ['ä'] = 'a', ['å'] = 'a',
+ ['æ'] = 'ae', ['ç'] = 'c',
+ ['è'] = 'e', ['é'] = 'e', ['ê'] = 'e', ['ë'] = 'e',
+ ['ì'] = 'i', ['í'] = 'i', ['î'] = 'i', ['ï'] = 'i',
+ ['ð'] = 'd', ['ñ'] = 'n',
+ ['ò'] = 'o', ['ó'] = 'o', ['ô'] = 'o', ['õ'] = 'o',
+ ['ö'] = 'o', ['ø'] = 'o',
+ ['ù'] = 'u', ['ú'] = 'u', ['û'] = 'u', ['ü'] = 'u',
+ ['ý'] = 'y', ['ÿ'] = 'y',
+
+ -- Extended latin A
+
+ ['Ā'] = 'A', ['ā'] = 'a', ['Ă'] = 'A', ['ă'] = 'a',
+ ['Ą'] = 'A', ['ą'] = 'a', ['Ć'] = 'C', ['ć'] = 'c',
+ ['Ĉ'] = 'C', ['ĉ'] = 'c', ['Ċ'] = 'C', ['ċ'] = 'c',
+ ['Č'] = 'C', ['č'] = 'c', ['Ď'] = 'D', ['ď'] = 'd',
+ ['Đ'] = 'D', ['đ'] = 'd', ['Ē'] = 'E', ['ē'] = 'e',
+ ['Ĕ'] = 'E', ['ĕ'] = 'e', ['Ė'] = 'E', ['ė'] = 'e',
+ ['Ę'] = 'E', ['ę'] = 'e', ['Ě'] = 'E', ['ě'] = 'e',
+ ['Ĝ'] = 'G', ['Ğ'] = 'G', ['ğ'] = 'g', ['ĝ'] = 'g',
+ ['Ġ'] = 'G', ['ġ'] = 'g', ['Ģ'] = 'G', ['ģ'] = 'g',
+ ['Ĥ'] = 'H', ['ĥ'] = 'h', ['Ħ'] = 'H', ['ħ'] = 'h',
+ ['Ĩ'] = 'I', ['ĩ'] = 'i', ['Ī'] = 'I', ['ī'] = 'i',
+ ['Ĭ'] = 'I', ['ĭ'] = 'i', ['Į'] = 'I', ['į'] = 'i',
+ ['ı'] = 'i', ['İ'] = 'I', ['IJ'] = 'IJ', ['ij'] = 'ij',
+ ['Ĵ'] = 'J', ['ĵ'] = 'j', ['ķ'] = 'k', ['Ķ'] = 'K',
+ ['ĸ'] = 'k',
+ ['Ĺ'] = 'L', ['ĺ'] = 'l', ['Ļ'] = 'L', ['ļ'] = 'l',
+ ['Ľ'] = 'L', ['ľ'] = 'l', ['Ŀ'] = 'L', ['ŀ'] = 'l',
+ ['Ł'] = 'L', ['ł'] = 'l', ['Ń'] = 'N', ['ń'] = 'n',
+ ['Ņ'] = 'N', ['ņ'] = 'n', ['Ň'] = 'N', ['ň'] = 'n',
+ ['ʼn'] = 'n', ['Ŋ'] = 'n', ['ŋ'] = 'n',
+ ['Ō'] = 'O', ['ō'] = 'o', ['Ŏ'] = 'O', ['ŏ'] = 'o',
+ ['ő'] = 'o', ['Ő'] = 'O', ['œ'] = 'oe', ['Œ'] = 'OE',
+ ['Ŕ'] = 'R', ['ŕ'] = 'r', ['Ŗ'] = 'R', ['ŗ'] = 'r',
+ ['Ř'] = 'R', ['ř'] = 'r', ['Ś'] = 'S', ['ś'] = 's',
+ ['Ŝ'] = 'S', ['ŝ'] = 's', ['Ş'] = 'S', ['ş'] = 's',
+ ['Š'] = 'S', ['š'] = 's', ['Ţ'] = 'T', ['ţ'] = 't',
+ ['ť'] = 't', ['Ŧ'] = 'T', ['Ť'] = 'T', ['ŧ'] = 't',
+ ['Ũ'] = 'U', ['ũ'] = 'u', ['Ū'] = 'U', ['ū'] = 'u',
+ ['Ŭ'] = 'U', ['ŭ'] = 'u', ['Ů'] = 'U', ['ů'] = 'u',
+ ['Ű'] = 'U', ['ű'] = 'u', ['Ų'] = 'U', ['ų'] = 'u',
+ ['Ŵ'] = 'W', ['ŵ'] = 'w', ['Ŷ'] = 'Y', ['ŷ'] = 'y',
+ ['Ÿ'] = 'Y',
+ ['Ź'] = 'Z', ['ź'] = 'z', ['Ż'] = 'Z', ['ż'] = 'z',
+ ['Ž'] = 'Z', ['ž'] = 'z', ['ſ'] = 's',
+
+ -- Extended latin B
+ ['ƀ'] = 'b', ['Ɓ'] = 'B', ['Ƃ'] = 'B', ['ƃ'] = 'b',
+ ['Ɔ'] = 'O',
+ ['Ƈ'] = 'C', ['ƈ'] = 'c', ['Ɖ'] = 'D', ['Ɗ'] = 'D',
+ ['Ƌ'] = 'D', ['ƌ'] = 'd', ['Ǝ'] = 'E', ['Ə'] = 'e',
+ ['Ɛ'] = 'E',
+ ['Ƒ'] = 'F', ['ƒ'] = 'f', ['Ɠ'] = 'G',
+ ['ƕ'] = 'hv', ['Ɨ'] = 'I', ['Ƙ'] = 'K', ['ƙ'] = 'k',
+ ['ƚ'] = 'l', ['Ɯ'] = 'M', ['Ɲ'] = 'N', ['ƞ'] = 'n',
+ ['Ɵ'] = 'O',
+ ['Ơ'] = 'O', ['ơ'] = 'o', ['Ƣ'] = 'OI', ['ƣ'] = 'oi',
+ ['Ƥ'] = 'P', ['ƥ'] = 'p', ['Ʀ'] = 'YR',
+ ['Ƨ'] = 'S', ['ƨ'] = 's', ['ƫ'] = 't',
+ ['Ƭ'] = 'T', ['ƭ'] = 't', ['Ʈ'] = 'T',
+ ['Ư'] = 'U', ['ư'] = 'u', ['Ʋ'] = 'V',
+ ['Ƴ'] = 'Y', ['ƴ'] = 'y', ['Ƶ'] = 'Z', ['ƶ'] = 'z',
+ ['ƻ'] = '2', ['Ƽ'] = '5', ['ƽ'] = '5',
+ ['DŽ'] = 'DZ', ['Dž'] = 'Dz', ['dž'] = 'dz',
+ ['LJ'] = 'LJ', ['Lj'] = 'Lj', ['lj'] = 'lj',
+ ['NJ'] = 'NJ', ['Nj'] = 'Nj', ['nj'] = 'nj',
+ ['Ǎ'] = 'A', ['ǎ'] = 'a', ['Ǐ'] = 'I', ['ǐ'] = 'i',
+ ['Ǒ'] = 'O', ['ǒ'] = 'o', ['Ǔ'] = 'U', ['ǔ'] = 'u',
+ ['Ǖ'] = 'U', ['ǖ'] = 'u', ['Ǘ'] = 'U', ['ǘ'] = 'u',
+ ['Ǚ'] = 'U', ['ǚ'] = 'u', ['Ǜ'] = 'U', ['ǜ'] = 'u',
+ ['ǝ'] = 'e',
+ ['Ǟ'] = 'A', ['ǟ'] = 'a', ['Ǡ'] = 'A', ['ǡ'] = 'a',
+ ['Ǣ'] = 'Æ', ['ǣ'] = 'æ', ['Ǥ'] = 'G', ['ǥ'] = 'g',
+ ['Ǧ'] = 'G', ['ǧ'] = 'g', ['Ǩ'] = 'K', ['ǩ'] = 'k',
+ ['Ǫ'] = 'Q', ['ǫ'] = 'q', ['Ǭ'] = 'Q', ['ǭ'] = 'q',
+ ['ǰ'] = 'J',
+ ['DZ'] = 'DZ', ['Dz'] = 'Dz', ['dz'] = 'dz',
+ ['Ǵ'] = 'G', ['ǵ'] = 'g', ['Ƕ'] = 'H',
+ ['Ǹ'] = 'N', ['ǹ'] = 'n', ['Ǻ'] = 'A', ['ǻ'] = 'a',
+ ['Ǽ'] = 'Æ', ['ǽ'] = 'æ', ['Ǿ'] = 'Ø', ['ǿ'] = 'ø',
+ ['Ȁ'] = 'A', ['ȁ'] = 'a', ['Ȃ'] = 'A', ['ȃ'] = 'a',
+ ['Ȅ'] = 'E', ['ȅ'] = 'e', ['Ȇ'] = 'E', ['ȇ'] = 'e',
+ ['Ȉ'] = 'I', ['ȉ'] = 'i', ['Ȋ'] = 'I', ['ȋ'] = 'i',
+ ['Ȍ'] = 'O', ['ȍ'] = 'o', ['Ȏ'] = 'O', ['ȏ'] = 'o',
+ ['Ȑ'] = 'R', ['ȑ'] = 'r', ['Ȓ'] = 'R', ['ȓ'] = 'r',
+ ['Ȕ'] = 'U', ['ȕ'] = 'u', ['Ȗ'] = 'U', ['ȗ'] = 'u',
+ ['Ș'] = 'S', ['ș'] = 's', ['Ț'] = 'T', ['ț'] = 't',
+ ['Ȟ'] = 'H', ['ȟ'] = 'h', ['Ƞ'] = 'N',
+ ['ȡ'] = 'd',
+ ['Ȣ'] = 'OU', ['ȣ'] = 'ou', ['Ȥ'] = 'Z', ['ȥ'] = 'z',
+ ['Ȧ'] = 'A', ['ȧ'] = 'a', ['Ȩ'] = 'E', ['ȩ'] = 'e',
+ ['Ȫ'] = 'O', ['ȫ'] = 'o', ['Ȭ'] = 'O', ['ȭ'] = 'o',
+ ['Ȯ'] = 'O', ['ȯ'] = 'o', ['Ȱ'] = 'O', ['ȱ'] = 'o',
+ ['Ȳ'] = 'Y', ['ȳ'] = 'y', ['ȴ'] = 'l',
+ ['ȵ'] = 'n', ['ȶ'] = 't', ['ȷ'] = 'j',
+ ['ȸ'] = 'db', ['ȹ'] = 'qp', ['Ⱥ'] = 'A',
+ ['Ȼ'] = 'C', ['ȼ'] = 'c', ['Ƚ'] = 'L',
+ ['Ⱦ'] = 'T', ['ȿ'] = 's', ['ɀ'] = 'z',
+ ['Ƀ'] = 'B', ['Ʉ'] = 'U', ['Ʌ'] = 'V',
+ ['Ɇ'] = 'E', ['ɇ'] = 'e', ['Ɉ'] = 'J', ['ɉ'] = 'j',
+ ['Ɋ'] = 'Q', ['ɋ'] = 'q', ['Ɍ'] = 'R', ['ɍ'] = 'r',
+ ['Ɏ'] = 'Y', ['ɏ'] = 'y', ['ɐ'] = 'a',
+ ['ɓ'] = 'b', ['ɔ'] = 'o',
+ ['ɕ'] = 'c', ['ɖ'] = 'd', ['ɗ'] = 'd',
+ ['ɘ'] = 'e', ['ə'] = 'e', ['ɚ'] = 'e',
+ ['ɛ'] = 'e', ['ɜ'] = 'e', ['ɝ'] = 'e', ['ɞ'] = 'e',
+ ['ɟ'] = 'j',
+ ['ɠ'] = 'g', ['ɡ'] = 'g', ['ɢ'] = 'G',
+ ['ɥ'] = 'h', ['ɦ'] = 'h', ['ɧ'] = 'h',
+ ['ɨ'] = 'i', ['ɪ'] = 'I',
+ ['ɫ'] = 'l', ['ɬ'] = 'l', ['ɭ'] = 'l',
+ ['ɮ'] = 'lz',
+ ['ɯ'] = 'm', ['ɰ'] = 'm', ['ɱ'] = 'm',
+ ['ɲ'] = 'n', ['ɳ'] = 'n', ['ɴ'] = 'N',
+ ['ɵ'] = 'o', ['ɶ'] = 'Œ',
+ ['ɹ'] = 'r', ['ɺ'] = 'r', ['ɻ'] = 'r',
+ ['ɼ'] = 'r', ['ɽ'] = 'r', ['ɾ'] = 'r', ['ɿ'] = 'r',
+}
diff --git a/font_api/font.lua b/font_api/font.lua
new file mode 100644
index 0000000..e12bb4b
--- /dev/null
+++ b/font_api/font.lua
@@ -0,0 +1,272 @@
+--[[
+ font_api mod for Minetest - Library to add font display capability
+ to display_api mod.
+ (c) Pierre-Yves Rollo
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+--]]
+
+-- Fallback table
+local fallbacks = dofile(font_api.path.."/fallbacks.lua")
+
+-- Local functions
+------------------
+
+-- Returns number of UTF8 bytes of the first char of the string
+local function get_char_bytes(str)
+ local msb = str:byte(1)
+ if msb ~= nil then
+ if msb < 0x80 then return 1 end
+ if msb >= 0xF0 then return 4 end
+ if msb >= 0xE0 then return 3 end
+ if msb >= 0xC2 then return 2 end
+ end
+end
+
+-- Returns the unicode codepoint of the first char of the string
+local function char_to_codepoint(str)
+ local bytes = get_char_bytes(str)
+ if bytes == 1 then
+ return str:byte(1)
+ elseif bytes == 2 then
+ return (str:byte(1) - 0xC2) * 0x40
+ + str:byte(2)
+ elseif bytes == 3 then
+ return (str:byte(1) - 0xE0) * 0x1000
+ + str:byte(2) % 0x40 * 0x40
+ + str:byte(3) % 0x40
+ elseif bytes == 4 then -- Not tested
+ return (str:byte(1) - 0xF0) * 0x40000
+ + str:byte(2) % 0x40 * 0x1000
+ + str:byte(3) % 0x40 * 0x40
+ + str:byte(4) % 0x40
+ end
+end
+
+-- Split multiline text into array of lines, with <maxlines> maximum lines.
+-- Can not use minetest string.split as it has bug if first line(s) empty
+local function split_lines(text, maxlines)
+ local lines = {}
+ local pos = 1
+ repeat
+ local found = string.find(text, "\n", pos)
+ found = found or #text + 1
+ lines[#lines + 1] = string.sub(text, pos, found - 1)
+ pos = found + 1
+ until (maxlines and (#lines >= maxlines)) or (pos > (#text + 1))
+ return lines
+end
+
+--------------------------------------------------------------------------------
+--- Font class
+
+local Font = {}
+font_api.Font = Font
+
+function Font:new(def)
+
+ if type(def) ~= "table" then
+ minetest.log("error",
+ "[font_api] Font definition must be a table.")
+ return nil
+ end
+
+ if def.height == nil or def.height <= 0 then
+ minetest.log("error",
+ "[font_api] Font definition must have a positive height.")
+ return nil
+ end
+
+ if type(def.widths) ~= "table" then
+ minetest.log("error",
+ "[font_api] Font definition must have a widths array.")
+ return nil
+ end
+
+ if def.widths[0] == nil then
+ minetest.log("error",
+ "[font_api] Font must have a char with codepoint 0 (=unknown char).")
+ return nil
+ end
+
+ local font = table.copy(def)
+ setmetatable(font, self)
+ self.__index = self
+ return font
+end
+
+--- Gets the next char of a text
+-- @return Codepoint of first char,
+-- @return Remaining string without this first char
+
+function Font:get_next_char(text)
+ local bytes = get_char_bytes(text)
+
+ if bytes == nil then
+ minetest.log("warning",
+ "[font_api] Encountered a non UTF char, not displaying text.")
+ return nil, ''
+ end
+
+ local codepoint = char_to_codepoint(text)
+
+ -- Fallback mechanism
+ if self.widths[codepoint] == nil then
+ local char = text:sub(1, bytes)
+
+ if fallbacks[char] then
+ return self:get_next_char(fallbacks[char]..text:sub(bytes+1))
+ else
+ return 0, text:sub(bytes+1) -- Ultimate fallback
+ end
+ else
+ return codepoint, text:sub(bytes+1)
+ end
+end
+
+--- Returns the width of a given char
+-- @param char : codepoint of the char
+-- @return Char width
+
+function Font:get_char_width(char)
+ -- Replace chars with no texture by the NULL(0) char
+ if self.widths[char] ~= nil then
+ return self.widths[char]
+ else
+ return self.widths[0]
+ end
+end
+
+--- Text height for multiline text including margins and line spacing
+-- @param nb_of_lines : number of text lines (default 1)
+-- @return Text height
+
+function Font:get_height(nb_of_lines)
+ if nb_of_lines == nil then nb_of_lines = 1 end
+
+ if nb_of_lines > 0 then
+ return
+ (
+ (self.height or 0) +
+ (self.margintop or 0) +
+ (self.marginbottom or 0)
+ ) * nb_of_lines +
+ (self.linespacing or 0) * (nb_of_lines -1)
+ else
+ return nb_of_lines == 0 and 0 or nil
+ end
+end
+
+--- Computes text width for a given text (ignores new lines)
+-- @param line Line of text which the width will be computed.
+-- @return Text width
+
+function Font:get_width(line)
+ local codepoint
+ local width = 0
+ line = line or ''
+
+ while line ~= "" do
+ codepoint, line = self:get_next_char(line)
+ width = width + self:get_char_width(codepoint)
+ end
+
+ return width
+end
+
+--- Builds texture part for a text line
+-- @param line Text line to be rendered
+-- @param texturew Width of the texture (extra text is not rendered)
+-- @param x Starting x position in texture
+-- @param y Vertical position of the line in texture
+-- @return Texture string
+
+function Font:make_line_texture(line, texturew, x, y)
+ local codepoint
+ local texture = ""
+ line = line or ''
+
+ while line ~= '' do
+ codepoint, line = self:get_next_char(line)
+
+ -- Add image only if it is visible (at least partly)
+ if x + self.widths[codepoint] >= 0 and x <= texturew then
+ texture = texture..
+ string.format(":%d,%d=font_%s_%04x.png",
+ x, y, self.name, codepoint)
+ end
+ x = x + self.widths[codepoint]
+ end
+
+ return texture
+end
+
+--- Builds texture for a multiline colored text
+-- @param text Text to be rendered
+-- @param texturew Width of the texture (extra text will be truncated)
+-- @param textureh Height of the texture
+-- @param maxlines Maximum number of lines
+-- @param halign Horizontal text align ("left"/"center"/"right") (optional)
+-- @param valign Vertical text align ("top"/"center"/"bottom") (optional)
+-- @param color Color of the text (optional)
+-- @return Texture string
+
+function Font:make_text_texture(text, texturew, textureh, maxlines,
+ halign, valign, color)
+ local texture = ""
+ local lines = {}
+ local textheight = 0
+ local y
+
+ -- Split text into lines (limited to maxlines fist lines)
+ for num, line in pairs(split_lines(text, maxlines)) do
+ lines[num] = { text = line, width = self:get_width(line) }
+ end
+
+ textheight = self:get_height(#lines)
+
+ if #lines then
+ if valign == "top" then
+ y = 0
+ elseif valign == "bottom" then
+ y = textureh - textheight
+ else
+ y = (textureh - textheight) / 2
+ end
+ end
+
+ y = y + (self.margintop or 0)
+
+ for _, line in pairs(lines) do
+ if halign == "left" then
+ texture = texture..
+ self:make_line_texture(line.text, texturew,
+ 0, y)
+ elseif halign == "right" then
+ texture = texture..
+ self:make_line_texture(line.text, texturew,
+ texturew - line.width, y)
+ else
+ texture = texture..
+ self:make_line_texture(line.text, texturew,
+ (texturew - line.width) / 2, y)
+ end
+
+ y = y + self:get_height() + (self.linespacing or 0)
+ end
+
+ texture = string.format("[combine:%dx%d", texturew, textureh)..texture
+ if color then texture = texture.."^[colorize:"..color end
+ return texture
+end
diff --git a/font_api/fontform.lua b/font_api/fontform.lua
new file mode 100644
index 0000000..e4952ed
--- /dev/null
+++ b/font_api/fontform.lua
@@ -0,0 +1,167 @@
+--[[
+ font_api mod for Minetest - Library to add font display capability
+ to display_api mod.
+ (c) Pierre-Yves Rollo
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+--]]
+
+local modname = minetest.get_current_modname()
+
+local contexts = {}
+
+minetest.register_on_leaveplayer(function(player)
+ if minetest.is_player(player) then
+ contexts[player:get_player_name()] = nil
+ end
+end)
+
+local function get_context(playername)
+ if not contexts[playername] then
+ contexts[playername] = { playername = playername }
+ end
+ return contexts[playername]
+end
+
+-- Show node formspec functions
+local function show_node_formspec(playername, pos)
+ local meta = minetest.get_meta(pos)
+
+ -- Decontextualize formspec
+ local fs = meta:get_string('formspec')
+
+ if not fs then
+ return
+ end
+
+ -- Change context and currrent_name references to nodemeta references
+ -- Change context and currrent_name references to nodemeta references
+ local nodemeta = string.format("nodemeta:%i,%i,%i", pos.x, pos.y ,pos.z)
+ fs = fs:gsub("current_name", nodemeta)
+ fs = fs:gsub("context", nodemeta)
+
+ -- Change all ${} to their corresponding metadata values
+ local s, e
+ repeat
+ s, e = fs:find('%${.*}')
+ if s and e then
+ fs = fs:sub(1, s-1)..
+ minetest.formspec_escape(meta:get_string(fs:sub(s+2,e-1)))..
+ fs:sub(e+1)
+ end
+ until s == nil
+
+ local context = get_context(playername)
+ context.node_pos = pos
+
+ -- Find node on_receive_fields
+ local ndef = minetest.registered_nodes[minetest.get_node(pos).name]
+ if ndef and ndef.on_receive_fields then
+ context.on_receive_fields = ndef.on_receive_fields
+ end
+
+ -- Show formspec
+ minetest.show_formspec(playername, modname..':context_formspec', fs)
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ if formname ~= modname..':context_formspec' then
+ return
+ end
+
+ if not minetest.is_player(player) then
+ return true
+ end
+
+ local context = get_context(player:get_player_name())
+ if context.on_receive_fields then
+ context.on_receive_fields(context.pos, '', fields, player)
+ end
+ return true
+end)
+
+-- Specific functions
+
+local function show_font_formspec(playername)
+ local context = get_context(playername)
+ local fonts = {}
+ for name, _ in pairs(font_api.registered_fonts) do
+ fonts[#fonts+1] = name
+ end
+ table.sort(fonts)
+
+ local fs = string.format(
+ "size[4,%s]%s%s%sbutton_exit[0,%s;4,1;cancel;Cancel]",
+ #fonts + 0.8, default.gui_bg, default.gui_bg_img, default.gui_slots,
+ #fonts)
+
+ for line = 1, #fonts do
+ local font = font_api.get_font(fonts[line])
+ local texture = font:make_text_texture(font.name, font:get_height()*5,
+ font:get_height()*1.2, 1, "center", "top", "#fff")
+ fs = string.format(
+ "%simage[0.1,%s;4.5,0.8;%s]button_exit[0,%s;4,1;font_%s;]",
+ fs, line-0.9, texture, line-1, font.name)
+ end
+ minetest.show_formspec(context.playername, modname..':font_list', fs)
+end
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ if formname ~= modname..':font_list' then
+ return
+ end
+
+ if not minetest.is_player(player) then
+ return true
+ end
+
+ local playername = player:get_player_name()
+ local context = get_context(playername)
+
+ if not context.pos
+ or minetest.is_protected(context.pos, playername) then
+ return true
+ end
+
+ if fields.quit == 'true' then
+ for name, _ in pairs(font_api.registered_fonts) do
+ if fields['font_'..name] then
+ local meta = minetest.get_meta(context.pos)
+ meta:set_string("font", name)
+ display_api.update_entities(context.pos)
+ end
+ end
+
+ if context.callback and type(context.callback) == "function" then
+ -- Using after to avoid the "double close" bug
+ minetest.after(0, context.callback, playername, context.pos)
+ else
+ -- Using after to avoid the "double close" bug
+ minetest.after(0, show_node_formspec, playername, context.pos)
+ end
+ end
+ return true
+end)
+
+-- @param player Player viewing the form
+-- @param pos Node pos
+-- @param callback function(playername, pos) to be called on form close
+function font_api.show_font_list(player, pos, callback)
+ if minetest.is_player(player) then
+ local context = get_context(player:get_player_name())
+ context.pos = pos
+ context.callback = callback
+ show_font_formspec(player:get_player_name())
+ end
+end
diff --git a/font_api/init.lua b/font_api/init.lua
new file mode 100644
index 0000000..2c8f4f3
--- /dev/null
+++ b/font_api/init.lua
@@ -0,0 +1,71 @@
+--[[
+ font_api mod for Minetest - Library to add font display capability
+ to display_api mod.
+ (c) Pierre-Yves Rollo
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+--]]
+
+-- Global variables
+-------------------
+
+font_api = {}
+font_api.name = minetest.get_current_modname()
+font_api.path = minetest.get_modpath(font_api.name)
+
+-- Inclusions
+-------------
+
+dofile(font_api.path.."/font.lua")
+dofile(font_api.path.."/registry.lua")
+dofile(font_api.path.."/fontform.lua")
+
+--- Standard on_display_update entity callback.
+-- Node should have a corresponding display_entity with size, resolution and
+-- maxlines fields and optionally halign, valign and color fields
+-- @param pos Node position
+-- @param objref Object reference of entity
+
+function font_api.on_display_update(pos, objref)
+ local meta = minetest.get_meta(pos)
+ local text = meta:get_string("display_text")
+ local ndef = minetest.registered_nodes[minetest.get_node(pos).name]
+ local entity = objref:get_luaentity()
+
+ -- If orwell96's modified signs_lib version is available and sign macros are active,
+ -- replace them in display_lib's text too.
+ if signs_lib and signs_lib.replace_macros then
+ text = signs_lib.replace_macros(text)
+ end
+
+ if entity and ndef.display_entities[entity.name] then
+ local def = ndef.display_entities[entity.name]
+ local font = font_api.get_font(meta:get_string("font") ~= ""
+ and meta:get_string("font") or def.font_name)
+
+ local maxlines = def.maxlines or 1 -- TODO:How to do w/o maxlines ?
+
+ objref:set_properties({
+ textures={font:make_text_texture(text,
+ font:get_height(maxlines) * def.size.x / def.size.y
+ / (def.aspect_ratio or 1),
+ font:get_height(maxlines),
+ def.maxlines, def.halign, def.valign, def.color)},
+ visual_size = def.size
+ })
+ end
+end
+
+-- Compatibility
+font_lib = font_api
diff --git a/font_api/registry.lua b/font_api/registry.lua
new file mode 100644
index 0000000..4b49b1e
--- /dev/null
+++ b/font_api/registry.lua
@@ -0,0 +1,162 @@
+--[[
+ font_api mod for Minetest - Library to add font display capability
+ to display_api mod.
+ (c) Pierre-Yves Rollo
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+--]]
+
+-- Global variables
+-------------------
+
+font_api.registered_fonts = {}
+font_api.registered_fonts_number = 0
+
+-- Local variables
+------------------
+
+local default_font = false
+
+-- Local functions
+------------------
+
+-- Gets a default (settings or fist font)
+local function get_default_font()
+ -- First call
+ if default_font == false then
+ default_font = nil
+
+ -- First, try with settings
+ local settings_font = minetest.settings:get("default_font")
+
+ if settings_font ~= nil and settings_font ~= "" then
+ default_font = font_api.registered_fonts[settings_font]
+
+ if default_font == nil then
+ minetest.log("warning", "Default font in settings (\""..
+ settings_font.."\") is not registered.")
+ end
+ end
+
+ -- If failed, choose first font without default = false
+ if default_font == nil then
+ for _, font in pairs(font_api.registered_fonts) do
+ if font.default then
+ default_font = font
+ break
+ end
+ end
+ end
+
+ -- If failed, chose first font
+ if default_font == nil then
+ for _, font in pairs(font_api.registered_fonts) do
+ default_font = font
+ break
+ end
+ end
+
+ -- Error, no font registered
+ if default_font == nil then
+ minetest.log("error",
+ "No font registred, unable to choose a default font.")
+ end
+ end
+
+ return default_font
+end
+
+--- Returns font object to be used according to font_name
+-- @param font_name: Name of the font
+-- @return Font object if font found (or default font)
+
+function font_api.get_font(font_name)
+ local font = font_api.registered_fonts[font_name]
+
+ if font == nil then
+ local message
+
+ if font_name == nil then
+ message = "No font given"
+ else
+ message = "Font \""..font_name.."\" unregistered"
+ end
+
+ font = get_default_font()
+
+ if font ~= nil then
+ minetest.log("info", message..", using font \""..font.name.."\".")
+ end
+ end
+
+ return font
+end
+
+-- API functions
+----------------
+
+--- Returns de default font name
+-- @return Default font name
+
+function font_api.get_default_font_name()
+ return get_default_font().name
+end
+
+--- Register a new font
+-- Textures corresponding to the font should be named after following patern :
+-- font_<name>_<code>.png
+-- <name> : name of the font
+-- <code> : 4 digit hexadecimal unicode of the char
+-- @param font_name Name of the font to register
+-- If registering different sizes of the same font, add size in the font name
+-- (e.g. times_10, times_12...).
+-- @param def font definition. A associative array with following keys :
+-- @key default True (by default) if this font may be used as default font
+-- @key height (mandatory) Height in pixels of all font textures
+-- @key widths (mandatory) Array of character widths in pixels, indexed by
+-- UTF codepoints
+-- @key margintop (optional) Margin (in texture pixels) added on top of each
+-- char texture.
+-- @key marginbottom (optional) dded at bottom of each char texture.
+-- @key linespacing (optional) Spacing (in texture pixels) between each lines.
+-- margintop, marginbottom and linespacing can be negative numbers (default 0)
+-- and are to be used to adjust various font styles to each other.
+
+-- TODO: Add something to remove common accent if not defined in font
+
+function font_api.register_font(font_name, font_def)
+
+ if font_api.registered_fonts[font_name] ~= nil then
+ minetest.log("error", "Font \""..font_name.."\" already registered.")
+ return
+ end
+
+ local font = font_api.Font:new(font_def)
+
+ if font == nil then
+ minetest.log("error", "Unable to register font \""..font_name.."\".")
+ return
+ end
+
+ font.name = font_name
+ font_api.registered_fonts[font_name] = font
+ font_api.registered_fonts_number = font_api.registered_fonts_number + 1
+
+ -- Force to choose again default font
+ -- (allows use of fonts registered after start)
+ default_font = false
+
+ minetest.log("action", "New font registered in font_api: "..font_name..".")
+end
+
diff --git a/font_api/settingtypes.txt b/font_api/settingtypes.txt
new file mode 100644
index 0000000..d111159
--- /dev/null
+++ b/font_api/settingtypes.txt
@@ -0,0 +1 @@
+default_font(Default font) string
diff --git a/font_api/textures/font_api_center.png b/font_api/textures/font_api_center.png
new file mode 100644
index 0000000..967db6a
--- /dev/null
+++ b/font_api/textures/font_api_center.png
Binary files differ
diff --git a/font_api/textures/font_api_font.png b/font_api/textures/font_api_font.png
new file mode 100644
index 0000000..e450338
--- /dev/null
+++ b/font_api/textures/font_api_font.png
Binary files differ
diff --git a/font_api/textures/font_api_left.png b/font_api/textures/font_api_left.png
new file mode 100644
index 0000000..727410b
--- /dev/null
+++ b/font_api/textures/font_api_left.png
Binary files differ
diff --git a/font_api/textures/font_api_right.png b/font_api/textures/font_api_right.png
new file mode 100644
index 0000000..16401d3
--- /dev/null
+++ b/font_api/textures/font_api_right.png
Binary files differ
diff --git a/font_api/tools/make_font_lua.sh b/font_api/tools/make_font_lua.sh
new file mode 100755
index 0000000..e858360
--- /dev/null
+++ b/font_api/tools/make_font_lua.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+
+scriptname=$(basename $0)
+identify="identify"
+
+usage() {
+ echo "Usage: $0 fontname"
+ echo "fontname: The name of the font. Must correspond to existing texture/font_<fontname>_????.png files"
+}
+
+if [ $# -ne 1 ]
+then
+ usage
+ exit 1
+fi
+
+font_name=$1
+
+for f in textures/font_${font_name}_????.png
+do
+ if [[ $f =~ textures/font_${font_name}_([0-9a-fA-F]{4}).png ]]
+ then
+ code=$((16#${BASH_REMATCH[1]}))
+ size=$(identify $f | cut -d " " -f 3)
+ w=$(echo $size | cut -d "x" -f 1)
+ h=$(echo $size | cut -d "x" -f 2)
+
+ if [ -z "$font_height" ]
+ then
+ font_height=$h
+ else
+ if [ $font_height -ne $h ]
+ then
+ echo "Error : $f as height of $h pixels, previous textures have a height of $font_height pixels. All textures should have the same height."
+ fi
+ fi
+
+ if [ -z "$font_widths" ]
+ then
+ font_widths="[$code]=$w"
+ else
+ font_widths="$font_widths, [$code]=$w"
+ fi
+ fi
+done
+
+echo "--[[
+
+$luafile generated by $scriptname $(LANG=en_US date)
+
+--]]
+
+font_api.register_font(
+ '$font_name',
+ {
+ height = $font_height,
+ widths = {
+ $font_widths
+ },
+ }
+);
+" > font_$font_name.lua
+
+if grep -q font_api depends.txt &>/dev/null
+then
+ echo "font_api already in depends.txt."
+else
+ echo "adding font_api to depends.txt."
+ echo "font_api" >> depends.txt
+fi
+
diff --git a/font_api/tools/make_font_textures.sh b/font_api/tools/make_font_textures.sh
new file mode 100755
index 0000000..4a3191c
--- /dev/null
+++ b/font_api/tools/make_font_textures.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+
+# This program generates a bitmap font for font_api mod for Minetest game.
+# (c) Andrzej Pieńkowski <pienkowski.andrzej@gmail.com>
+# (c) Pierre-Yves Rollo <dev@pyrollo.com>
+# License: GPL
+
+usage() {
+ echo "Usage: $0 fontfile fontname fontsize"
+ echo "fontfile: A TTF font file to use to create textures."
+ echo "fontname: The font name to be used in font_api (should be simple, with no spaces)."
+ echo "fontsize: Font height to be rendered."
+}
+
+if [ $# -ne 3 ]
+then
+ usage
+ exit 1
+fi
+
+fontfile=$1
+fontname=$2
+fontsize=$3
+
+if [ ! -r "$fontfile" ]
+then
+ echo "$fontfile not readable."
+ exit 1
+fi
+
+# check imagemagick
+hash convert &>/dev/null
+if [ $? -eq 1 ]; then
+ echo -e "Error: This program requires convert from ImageMagick! Please install it by typing 'sudo apt-get install imagemagick' in terminal."
+ abort=1
+fi
+
+# check ttx
+hash ttx &>/dev/null
+if [ $? -eq 1 ]; then
+ echo -e "Error: This program requires ttx from FontTools! Please install it by typing 'sudo apt-get install fonttools' in terminal."
+ abort=1
+fi
+
+if [ $abort ]
+then
+ exit 1
+fi
+
+generate() {
+ for i in $(seq $((0x$1)) $((0x$2)))
+ do
+ if echo "$codepoints" | grep -qi $(printf "0x%x" $i)
+ then
+ hex=$(printf "%04x" $i)
+ echo -e "Generating textures/font_${fontname}_$hex.png file for \"\\U$hex\" char."
+ if [[ "$hex" == "005c" ]] # Backslash char
+ then
+ convert -background none -fill black -font "$fontfile" -pointsize $fontsize label:"\\\\" -colorspace gray -channel alpha -threshold 50% textures/font_${fontname}_$hex.png
+ else
+ convert -background none -fill black -font "$fontfile" -pointsize $fontsize label:"$(echo -en "\\U$hex")" -colorspace gray -channel alpha -threshold 50% textures/font_${fontname}_$hex.png
+ fi
+ fi
+ done
+}
+
+mkdir textures
+
+# Reads all available code points in the font.
+codepoints=$(ttx -o - "$fontfile" | grep "<map code=" | cut -d \" -f 2)
+
+# Mandatory chars
+generate 0020 007f
+
+# generate space and null characters
+if [ ! -f "textures/font_${fontname}_0030.png" ]; then
+ echo "Error: Something wrong happened! Font was not generated!"
+ exit 1
+else
+ size=$(identify textures/font_${fontname}_0030.png | cut -d " " -f 3)
+ if ! [[ $fontHeight =~ $re ]] ; then
+ echo "Error: Something wrong happened! Generated files might be broken!"
+ exit 1
+ else
+ w=$(expr $(echo $size | cut -d x -f 1) - 1)
+ h=$(expr $(echo $size | cut -d x -f 2) - 1)
+ # Space char
+ convert -size $size xc:transparent -colorspace gray textures/font_${fontname}_0020.png
+ # Null char
+ convert -size $size xc:transparent -colorspace gray -stroke black -fill transparent -strokewidth 1 -draw "rectangle 0,0 $w,$h" textures/font_${fontname}_0000.png
+ fi
+fi
+
+# Optional Unicode pages (see https://en.wikipedia.org/wiki/Unicode) :
+
+# 00a0-00ff Latin-1 Supplement (full)
+generate 00a0 00ff
+# 0100-017f Latin Extended-A (full)
+generate 0100 017f
+# 0370-03ff Greek (full)
+generate 0370 03ff
+# 0400-04ff Cyrilic (full)
+generate 0400 04ff
+# 2000-206f General Punctuation (Limited to Dashes)
+generate 2010 2015
+# 2000-206f General Punctuation (Limited to Quotes)
+generate 2018 201F
+# 20a0-20cf Currency Symbols (Limited to Euro symbol)
+generate 20ac 20ac
+
+