Discussion:
[U-Boot] [PATCH 00/19] video: Introduce support for anti-aliased outline fonts
Simon Glass
2016-01-15 01:10:33 UTC
Permalink
The existing 8x16 font is adequate for most purposes. It is small and fast.
However for boot screens where information must be presented to the user,
the console font is not ideal. Common requirements are larger and
better-looking fonts. In many systems U-Boot is 'behind the scenes' and
does not display user-facing data. For those situations where this is not
the case, we need to present firmware screens with visually attractive
menus.

This series adds a console driver which uses TrueType fonts built into
U-Boot. It can render them at any size. This can be used in scripts to
place text as needed on the display.

This driver is not really designed to operate with the command line. Much
of U-Boot expects a fixed-width font. But to keep things working correctly,
rudimentary support for the console is provided. The main missing feature is
support for command-line editing.

The TrueType implementation is STB, a fairly light-weight TrueType-rendering
implementation from http://nothings.org/. This integrates fairly easily with
U-Boot.

The driver is tested on sandbox and a few ARM hardware devices. It should
be possible to use it on any hardware. The main new dependency is floating
point which is available on many modern systems. Care is taken to ensure
this dependency is isolated to this one driver.


Simon Glass (19):
video: Add stb TrueType font renderer
Makefile: Add rules to build in .ttf files
video kconfig console_normal
video: Use fractional units for X coordinates
video: Handle the 'bell' character
video: Provide a left margin for the text console
video: Provide a signal when a new console line is started
video: Provide a backspace method
video: Add a console driver that uses TrueType fonts
video: Add the Nimbus sans font
video: Add the AnkaCoder mono-spaced font
video: Add the Rufscript handwriting font
video: Add the Cantoraone decorative font
License: Add the Open Font License
video: Allow selection of the driver and font size
video: sandbox: Allow selection of font size and console name
video: sandbox: Enable truetype fonts for sandbox
video: test: Add console tests for truetype
video: Correct 'tor' typo in comment

Licenses/OFL.txt | 97 +
Licenses/README | 1 +
configs/sandbox_defconfig | 4 +-
drivers/video/Kconfig | 36 +-
drivers/video/Makefile | 6 +-
drivers/video/console_normal.c | 24 +-
drivers/video/console_rotate.c | 66 +-
drivers/video/console_truetype.c | 550 +++++
drivers/video/fonts/Kconfig | 51 +
drivers/video/fonts/Makefile | 11 +
drivers/video/fonts/ankacoder_c75_r.ttf | Bin 0 -> 65596 bytes
drivers/video/fonts/cantoraone_regular.ttf | Bin 0 -> 163116 bytes
drivers/video/fonts/nimbus_sans_l_regular.ttf | Bin 0 -> 61660 bytes
drivers/video/fonts/rufscript010.ttf | Bin 0 -> 23080 bytes
drivers/video/sandbox_sdl.c | 2 +
drivers/video/stb_truetype.h | 3240 +++++++++++++++++++++++++
drivers/video/vidconsole-uclass.c | 84 +-
drivers/video/video-uclass.c | 29 +-
include/dm/test.h | 2 +
include/video.h | 7 +-
include/video_console.h | 70 +-
scripts/Makefile.lib | 21 +
test/dm/video.c | 90 +-
23 files changed, 4326 insertions(+), 65 deletions(-)
create mode 100644 Licenses/OFL.txt
create mode 100644 drivers/video/console_truetype.c
create mode 100644 drivers/video/fonts/Kconfig
create mode 100644 drivers/video/fonts/Makefile
create mode 100644 drivers/video/fonts/ankacoder_c75_r.ttf
create mode 100644 drivers/video/fonts/cantoraone_regular.ttf
create mode 100644 drivers/video/fonts/nimbus_sans_l_regular.ttf
create mode 100644 drivers/video/fonts/rufscript010.ttf
create mode 100644 drivers/video/stb_truetype.h
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:35 UTC
Permalink
Add rules to allow TrueType files to be compiled into U-Boot for use on
the video console.

Signed-off-by: Simon Glass <***@chromium.org>
---

scripts/Makefile.lib | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)

diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index ed30bf5..cff84cf 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -297,6 +297,27 @@ $(obj)/%.dtb: $(src)/%.dts FORCE

dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp)

+# Fonts
+# ---------------------------------------------------------------------------
+
+# Generate an assembly file to wrap the font data
+quiet_cmd_S_ttf= TTF $@
+# Modified for U-Boot
+cmd_S_ttf= \
+( \
+ echo '.section .rodata.ttf.init,"a"'; \
+ echo '.balign 16'; \
+ echo '.global __ttf_$(*F)_begin'; \
+ echo '__ttf_$(*F)_begin:'; \
+ echo '.incbin "$<" '; \
+ echo '__ttf_$(*F)_end:'; \
+ echo '.global __ttf_$(*F)_end'; \
+ echo '.balign 16'; \
+) > $@
+
+$(obj)/%.S: $(src)/%.ttf
+ $(call cmd,S_ttf)
+
# ACPI
# ---------------------------------------------------------------------------
quiet_cmd_acpi_c_asl= ASL $@
--
2.6.0.rc2.230.g3dd15c0
Tom Rini
2016-01-21 19:11:19 UTC
Permalink
Post by Simon Glass
Add rules to allow TrueType files to be compiled into U-Boot for use on
the video console.
Reviewed-by: Tom Rini <***@konsulko.com>
--
Tom
Simon Glass
2016-01-15 01:10:38 UTC
Permalink
This can be sent when to many characters are entered. Make sure it is
ignored and does not cause a character to be displayed.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/vidconsole-uclass.c | 3 +++
test/dm/video.c | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index da92ee8..d9a9615 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -91,6 +91,9 @@ int vidconsole_put_char(struct udevice *dev, char ch)
int ret;

switch (ch) {
+ case '\a':
+ /* beep */
+ break;
case '\r':
priv->xcur_frac = 0;
break;
diff --git a/test/dm/video.c b/test/dm/video.c
index 3540271..cd00c96 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -124,7 +124,7 @@ DM_TEST(dm_test_video_text, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
static int dm_test_video_chars(struct unit_test_state *uts)
{
struct udevice *dev, *con;
- const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very modest \bman\n\t\tand Has much to\b\bto be modest about.";
+ const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very \amodest \bman\n\t\tand Has much to\b\bto be modest about.";
const char *s;

ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:36 UTC
Permalink
Signed-off-by: Simon Glass <***@chromium.org>
---

configs/sandbox_defconfig | 2 +-
drivers/video/Kconfig | 12 +++++++++++-
drivers/video/Makefile | 5 +++--
3 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index b55d5e5..aa92726 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -77,7 +77,7 @@ CONFIG_USB_STORAGE=y
CONFIG_USB_KEYBOARD=y
CONFIG_SYS_USB_EVENT_POLL=y
CONFIG_DM_VIDEO=y
-CONFIG_VIDEO_ROTATION=y
+CONFIG_CONSOLE_ROTATION=y
CONFIG_VIDEO_SANDBOX_SDL=y
CONFIG_SYS_VSNPRINTF=y
CONFIG_CMD_DHRYSTONE=y
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 35fe192..29ddde2 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -44,7 +44,17 @@ config VIDEO_BPP32
this option, such displays will not be supported and console output
will be empty.

-config VIDEO_ROTATION
+config CONSOLE_NORMAL
+ bool "Support a simple text console"
+ depends on DM_VIDEO
+ default y if DM_VIDEO
+ help
+ Support drawing text on the frame buffer console so that it can be
+ used as a console. Rotation is not supported by this driver (see
+ CONFIG_CONSOLE_ROTATION for that). A built-in 8x16 font is used
+ for the display.
+
+config CONSOLE_ROTATION
bool "Support rotated displays"
depends on DM_VIDEO
help
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index f6f1106..1a76655 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -9,12 +9,13 @@ ifdef CONFIG_DM
obj-$(CONFIG_DISPLAY) += display-uclass.o
obj-$(CONFIG_DM_VIDEO) += backlight-uclass.o
obj-$(CONFIG_DM_VIDEO) += panel-uclass.o simple_panel.o
-obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o console_normal.o
+obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o
obj-$(CONFIG_DM_VIDEO) += video_bmp.o
ifdef CONFIG_DM_VIDEO
obj-$(CONFIG_DM_PWM) += pwm_backlight.o
endif
-obj-$(CONFIG_VIDEO_ROTATION) += console_rotate.o
+obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o
+obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o
endif

obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o
--
2.6.0.rc2.230.g3dd15c0
Anatolij Gustschin
2016-01-23 00:14:44 UTC
Permalink
From: Simon Glass <***@chromium.org>

Signed-off-by: Simon Glass <***@chromium.org>
Signed-off-by: Anatolij Gustschin <***@denx.de>
---
Changes in v2: rebased

configs/sandbox_defconfig | 2 +-
drivers/video/Kconfig | 12 +++++++++++-
drivers/video/Makefile | 5 +++--
3 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 09ced01..2ebcba0 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -77,7 +77,7 @@ CONFIG_USB_STORAGE=y
CONFIG_USB_KEYBOARD=y
CONFIG_SYS_USB_EVENT_POLL=y
CONFIG_DM_VIDEO=y
-CONFIG_VIDEO_ROTATION=y
+CONFIG_CONSOLE_ROTATION=y
CONFIG_VIDEO_SANDBOX_SDL=y
CONFIG_CMD_DHRYSTONE=y
CONFIG_TPM=y
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index ae122da..f06ecfe 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -44,7 +44,17 @@ config VIDEO_BPP32
this option, such displays will not be supported and console output
will be empty.

-config VIDEO_ROTATION
+config CONSOLE_NORMAL
+ bool "Support a simple text console"
+ depends on DM_VIDEO
+ default y if DM_VIDEO
+ help
+ Support drawing text on the frame buffer console so that it can be
+ used as a console. Rotation is not supported by this driver (see
+ CONFIG_CONSOLE_ROTATION for that). A built-in 8x16 font is used
+ for the display.
+
+config CONSOLE_ROTATION
bool "Support rotated displays"
depends on DM_VIDEO
help
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index ee04629..01f4be5 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -7,9 +7,10 @@

ifdef CONFIG_DM
obj-$(CONFIG_DISPLAY_PORT) += dp-uclass.o
-obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o console_normal.o
+obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o
obj-$(CONFIG_DM_VIDEO) += video_bmp.o
-obj-$(CONFIG_VIDEO_ROTATION) += console_rotate.o
+obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o
+obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o
endif

obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o
--
1.7.9.5
Simon Glass
2016-01-15 01:10:40 UTC
Permalink
When we start a new line (due to the user pressing return), signal this to
the driver so that it can flush its buffer of character positions.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/vidconsole-uclass.c | 14 ++++++++++++++
include/video_console.h | 14 ++++++++++++++
2 files changed, 28 insertions(+)

diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index fec4a60..182aaed 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -47,6 +47,15 @@ int vidconsole_set_row(struct udevice *dev, uint row, int clr)
return ops->set_row(dev, row, clr);
}

+static int vidconsole_entry_start(struct udevice *dev)
+{
+ struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+
+ if (!ops->entry_start)
+ return -ENOSYS;
+ return ops->entry_start(dev);
+}
+
/* Move backwards one space */
static void vidconsole_back(struct udevice *dev)
{
@@ -82,6 +91,8 @@ static void vidconsole_newline(struct udevice *dev)
vid_priv->colour_bg);
priv->ycur -= rows * priv->y_charsize;
}
+ priv->last_ch = 0;
+
video_sync(dev->parent);
}

@@ -99,6 +110,7 @@ int vidconsole_put_char(struct udevice *dev, char ch)
break;
case '\n':
vidconsole_newline(dev);
+ vidconsole_entry_start(dev);
break;
case '\t': /* Tab (8 chars alignment) */
priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac)
@@ -109,6 +121,7 @@ int vidconsole_put_char(struct udevice *dev, char ch)
break;
case '\b':
vidconsole_back(dev);
+ priv->last_ch = 0;
break;
default:
/*
@@ -125,6 +138,7 @@ int vidconsole_put_char(struct udevice *dev, char ch)
if (ret < 0)
return ret;
priv->xcur_frac += ret;
+ priv->last_ch = ch;
if (priv->xcur_frac >= priv->xsize_frac)
vidconsole_newline(dev);
break;
diff --git a/include/video_console.h b/include/video_console.h
index 3bfc37f..80bc948 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -28,6 +28,7 @@
* @tab_width_frac: Tab width in fractional units
* @xsize_frac: Width of the display in fractional units
* @xstart_frac: Left margin for the text console in fractional units
+ * @last_ch: Last character written to the text console on this line
*/
struct vidconsole_priv {
struct stdio_dev sdev;
@@ -40,6 +41,7 @@ struct vidconsole_priv {
int tab_width_frac;
int xsize_frac;
int xstart_frac;
+ int last_ch;
};

/**
@@ -87,6 +89,18 @@ struct vidconsole_ops {
* @return 0 if OK, -ve on error
*/
int (*set_row)(struct udevice *dev, uint row, int clr);
+
+ /**
+ * entry_start() - Indicate that text entry is starting afresh
+ *
+ * Consoles which use proportional fonts need to track the position of
+ * each character output so that backspace will return to the correct
+ * place. This method signals to the console driver that a new entry
+ * line is being start (e.g. the user pressed return to start a new
+ * command). The driver can use this signal to empty its list of
+ * positions.
+ */
+ int (*entry_start)(struct udevice *dev);
};

/* Get a pointer to the driver operations for a video console device */
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:39 UTC
Permalink
Allow the left margin to be set so that text does not have to be right up
against the left side. On some panels this makes it hard to read.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/vidconsole-uclass.c | 6 +++---
include/video_console.h | 2 ++
2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index d9a9615..fec4a60 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -53,7 +53,7 @@ static void vidconsole_back(struct udevice *dev)
struct vidconsole_priv *priv = dev_get_uclass_priv(dev);

priv->xcur_frac -= VID_TO_POS(priv->x_charsize);
- if (priv->xcur_frac < 0) {
+ if (priv->xcur_frac < priv->xstart_frac) {
priv->xcur_frac = (priv->cols - 1) *
VID_TO_POS(priv->x_charsize);
priv->ycur -= priv->y_charsize;
@@ -71,7 +71,7 @@ static void vidconsole_newline(struct udevice *dev)
const int rows = CONFIG_CONSOLE_SCROLL_LINES;
int i;

- priv->xcur_frac = 0;
+ priv->xcur_frac = priv->xstart_frac;
priv->ycur += priv->y_charsize;

/* Check if we need to scroll the terminal */
@@ -95,7 +95,7 @@ int vidconsole_put_char(struct udevice *dev, char ch)
/* beep */
break;
case '\r':
- priv->xcur_frac = 0;
+ priv->xcur_frac = priv->xstart_frac;
break;
case '\n':
vidconsole_newline(dev);
diff --git a/include/video_console.h b/include/video_console.h
index 2f7012e..3bfc37f 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -27,6 +27,7 @@
* @y_charsize: Character height in pixels
* @tab_width_frac: Tab width in fractional units
* @xsize_frac: Width of the display in fractional units
+ * @xstart_frac: Left margin for the text console in fractional units
*/
struct vidconsole_priv {
struct stdio_dev sdev;
@@ -38,6 +39,7 @@ struct vidconsole_priv {
int y_charsize;
int tab_width_frac;
int xsize_frac;
+ int xstart_frac;
};

/**
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:37 UTC
Permalink
With anti-aliased fonts we need a more fine-grained horizontal position
than a single pixel. Characters can be positioned to start part-way through
a pixel, with anti-aliasing (greyscale edges) taking care of the visual
effect.

To cope with this, use fractional units (1/256 pixel) for horizontal
positions in the text console.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/console_normal.c | 24 ++++++++++++--
drivers/video/console_rotate.c | 66 +++++++++++++++++++++++++++++----------
drivers/video/vidconsole-uclass.c | 57 +++++++++++++++++++--------------
include/video_console.h | 40 ++++++++++++++++++------
test/dm/video.c | 4 +--
5 files changed, 138 insertions(+), 53 deletions(-)

diff --git a/drivers/video/console_normal.c b/drivers/video/console_normal.c
index d1031c8..89a55dd 100644
--- a/drivers/video/console_normal.c
+++ b/drivers/video/console_normal.c
@@ -71,13 +71,18 @@ static int console_normal_move_rows(struct udevice *dev, uint rowdst,
return 0;
}

-static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch)
+static int console_normal_putc_xy(struct udevice *dev, uint x_frac, uint y,
+ char ch)
{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);
int i, row;
void *line = vid_priv->fb + y * vid_priv->line_length +
- x * VNBYTES(vid_priv->bpix);
+ VID_TO_PIXEL(x_frac) * VNBYTES(vid_priv->bpix);
+
+ if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac)
+ return -EAGAIN;

for (row = 0; row < VIDEO_FONT_HEIGHT; row++) {
uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row];
@@ -125,6 +130,20 @@ static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch)
line += vid_priv->line_length;
}

+ return VID_TO_POS(VIDEO_FONT_WIDTH);
+}
+
+static int console_normal_probe(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+
+ vc_priv->x_charsize = VIDEO_FONT_WIDTH;
+ vc_priv->y_charsize = VIDEO_FONT_HEIGHT;
+ vc_priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH;
+ vc_priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT;
+
return 0;
}

@@ -138,4 +157,5 @@ U_BOOT_DRIVER(vidconsole_normal) = {
.name = "vidconsole0",
.id = UCLASS_VIDEO_CONSOLE,
.ops = &console_normal_ops,
+ .probe = console_normal_probe,
};
diff --git a/drivers/video/console_rotate.c b/drivers/video/console_rotate.c
index 4c5c4ef..227141d 100644
--- a/drivers/video/console_rotate.c
+++ b/drivers/video/console_rotate.c
@@ -82,17 +82,22 @@ static int console_move_rows_1(struct udevice *dev, uint rowdst, uint rowsrc,
return 0;
}

-static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch)
+static int console_putc_xy_1(struct udevice *dev, uint x_frac, uint y, char ch)
{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);
int pbytes = VNBYTES(vid_priv->bpix);
int i, col;
int mask = 0x80;
- void *line = vid_priv->fb + (x + 1) * vid_priv->line_length -
- (y + 1) * pbytes;
+ void *line;
uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT;

+ line = vid_priv->fb + (VID_TO_PIXEL(x_frac) + 1) *
+ vid_priv->line_length - (y + 1) * pbytes;
+ if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac)
+ return -EAGAIN;
+
for (col = 0; col < VIDEO_FONT_HEIGHT; col++) {
switch (vid_priv->bpix) {
#ifdef CONFIG_VIDEO_BPP8
@@ -135,7 +140,7 @@ static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch)
mask >>= 1;
}

- return 0;
+ return VID_TO_POS(VIDEO_FONT_WIDTH);
}


@@ -201,16 +206,21 @@ static int console_move_rows_2(struct udevice *dev, uint rowdst, uint rowsrc,
return 0;
}

-static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch)
+static int console_putc_xy_2(struct udevice *dev, uint x_frac, uint y, char ch)
{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);
int i, row;
void *line;

- line = vid_priv->fb + (vid_priv->ysize - y - 1) * vid_priv->line_length +
- (vid_priv->xsize - x - VIDEO_FONT_WIDTH - 1) *
- VNBYTES(vid_priv->bpix);
+ if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac)
+ return -EAGAIN;
+
+ line = vid_priv->fb + (vid_priv->ysize - y - 1) *
+ vid_priv->line_length +
+ (vid_priv->xsize - VID_TO_PIXEL(x_frac) -
+ VIDEO_FONT_WIDTH - 1) * VNBYTES(vid_priv->bpix);

for (row = 0; row < VIDEO_FONT_HEIGHT; row++) {
uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row];
@@ -258,7 +268,7 @@ static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch)
line -= vid_priv->line_length;
}

- return 0;
+ return VID_TO_POS(VIDEO_FONT_WIDTH);
}

static int console_set_row_3(struct udevice *dev, uint row, int clr)
@@ -328,17 +338,22 @@ static int console_move_rows_3(struct udevice *dev, uint rowdst, uint rowsrc,
return 0;
}

-static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch)
+static int console_putc_xy_3(struct udevice *dev, uint x_frac, uint y, char ch)
{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);
int pbytes = VNBYTES(vid_priv->bpix);
int i, col;
int mask = 0x80;
- void *line = vid_priv->fb + (vid_priv->ysize - x - 1) *
+ void *line = vid_priv->fb +
+ (vid_priv->ysize - VID_TO_PIXEL(x_frac) - 1) *
vid_priv->line_length + y * pbytes;
uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT;

+ if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac)
+ return -EAGAIN;
+
for (col = 0; col < VIDEO_FONT_HEIGHT; col++) {
switch (vid_priv->bpix) {
#ifdef CONFIG_VIDEO_BPP8
@@ -381,17 +396,35 @@ static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch)
mask >>= 1;
}

- return 0;
+ return VID_TO_POS(VIDEO_FONT_WIDTH);
}


+static int console_probe_2(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+
+ vc_priv->x_charsize = VIDEO_FONT_WIDTH;
+ vc_priv->y_charsize = VIDEO_FONT_HEIGHT;
+ vc_priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH;
+ vc_priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT;
+
+ return 0;
+}
+
static int console_probe_1_3(struct udevice *dev)
{
- struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
- struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);

- priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH;
- priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT;
+ vc_priv->x_charsize = VIDEO_FONT_WIDTH;
+ vc_priv->y_charsize = VIDEO_FONT_HEIGHT;
+ vc_priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH;
+ vc_priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT;
+ vc_priv->xsize_frac = VID_TO_POS(vid_priv->ysize);

return 0;
}
@@ -425,6 +458,7 @@ U_BOOT_DRIVER(vidconsole_2) = {
.name = "vidconsole2",
.id = UCLASS_VIDEO_CONSOLE,
.ops = &console_ops_2,
+ .probe = console_probe_2,
};

U_BOOT_DRIVER(vidconsole_3) = {
diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index a4c919e..da92ee8 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -52,14 +52,14 @@ static void vidconsole_back(struct udevice *dev)
{
struct vidconsole_priv *priv = dev_get_uclass_priv(dev);

- if (--priv->curr_col < 0) {
- priv->curr_col = priv->cols - 1;
- if (--priv->curr_row < 0)
- priv->curr_row = 0;
+ priv->xcur_frac -= VID_TO_POS(priv->x_charsize);
+ if (priv->xcur_frac < 0) {
+ priv->xcur_frac = (priv->cols - 1) *
+ VID_TO_POS(priv->x_charsize);
+ priv->ycur -= priv->y_charsize;
+ if (priv->ycur < 0)
+ priv->ycur = 0;
}
-
- vidconsole_putc_xy(dev, priv->curr_col * VIDEO_FONT_WIDTH,
- priv->curr_row * VIDEO_FONT_HEIGHT, ' ');
}

/* Move to a newline, scrolling the display if necessary */
@@ -71,15 +71,16 @@ static void vidconsole_newline(struct udevice *dev)
const int rows = CONFIG_CONSOLE_SCROLL_LINES;
int i;

- priv->curr_col = 0;
+ priv->xcur_frac = 0;
+ priv->ycur += priv->y_charsize;

/* Check if we need to scroll the terminal */
- if (++priv->curr_row >= priv->rows) {
+ if ((priv->ycur + priv->y_charsize) / priv->y_charsize > priv->rows) {
vidconsole_move_rows(dev, 0, rows, priv->rows - rows);
for (i = 0; i < rows; i++)
vidconsole_set_row(dev, priv->rows - i - 1,
vid_priv->colour_bg);
- priv->curr_row -= rows;
+ priv->ycur -= rows * priv->y_charsize;
}
video_sync(dev->parent);
}
@@ -91,16 +92,16 @@ int vidconsole_put_char(struct udevice *dev, char ch)

switch (ch) {
case '\r':
- priv->curr_col = 0;
+ priv->xcur_frac = 0;
break;
case '\n':
vidconsole_newline(dev);
break;
case '\t': /* Tab (8 chars alignment) */
- priv->curr_col += 8;
- priv->curr_col &= ~7;
+ priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac)
+ + 1) * priv->tab_width_frac;

- if (priv->curr_col >= priv->cols)
+ if (priv->xcur_frac >= priv->xsize_frac)
vidconsole_newline(dev);
break;
case '\b':
@@ -112,13 +113,16 @@ int vidconsole_put_char(struct udevice *dev, char ch)
* colour depth. Check this and return an error to help with
* diagnosis.
*/
- ret = vidconsole_putc_xy(dev,
- priv->curr_col * VIDEO_FONT_WIDTH,
- priv->curr_row * VIDEO_FONT_HEIGHT,
- ch);
- if (ret)
+ ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch);
+ if (ret == -EAGAIN) {
+ vidconsole_newline(dev);
+ ret = vidconsole_putc_xy(dev, priv->xcur_frac,
+ priv->ycur, ch);
+ }
+ if (ret < 0)
return ret;
- if (++priv->curr_col >= priv->cols)
+ priv->xcur_frac += ret;
+ if (priv->xcur_frac >= priv->xsize_frac)
vidconsole_newline(dev);
break;
}
@@ -149,8 +153,7 @@ static int vidconsole_pre_probe(struct udevice *dev)
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);

- priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT;
- priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH;
+ priv->xsize_frac = VID_TO_POS(vid_priv->xsize);

return 0;
}
@@ -162,12 +165,16 @@ static int vidconsole_post_probe(struct udevice *dev)
struct stdio_dev *sdev = &priv->sdev;
int ret;

+ if (!priv->tab_width_frac)
+ priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8;
+
if (dev->seq) {
snprintf(sdev->name, sizeof(sdev->name), "vidconsole%d",
dev->seq);
} else {
strcpy(sdev->name, "vidconsole");
}
+
sdev->flags = DEV_FLAGS_OUTPUT;
sdev->putc = vidconsole_putc;
sdev->puts = vidconsole_puts;
@@ -190,9 +197,11 @@ UCLASS_DRIVER(vidconsole) = {
void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row)
{
struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);

- priv->curr_col = min_t(short, col, priv->cols - 1);
- priv->curr_row = min_t(short, row, priv->rows - 1);
+ priv->xcur_frac = VID_TO_POS(min_t(short, col, vid_priv->xsize - 1));
+ priv->ycur = min_t(short, row, vid_priv->ysize - 1);
}

static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc,
diff --git a/include/video_console.h b/include/video_console.h
index d4acbc8..2f7012e 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -7,21 +7,37 @@
#ifndef __video_console_h
#define __video_console_h

+#define VID_FRAC_DIV 256
+
+#define VID_TO_PIXEL(x) ((x) / VID_FRAC_DIV)
+#define VID_TO_POS(x) ((x) * VID_FRAC_DIV)
+
/**
* struct vidconsole_priv - uclass-private data about a console device
*
+ * Drivers must set up @rows, @cols, @x_charsize, @y_charsize in their probe()
+ * method. Drivers may set up @xstart_frac if desired.
+ *
* @sdev: stdio device, acting as an output sink
- * @curr_col: Current text column (0=left)
- * @curr_row: Current row (0=top)
+ * @xcur_frac: Current X position, in fractional units (VID_TO_POS(x))
+ * @curr_row: Current Y position in pixels (0=top)
* @rows: Number of text rows
* @cols: Number of text columns
+ * @x_charsize: Character width in pixels
+ * @y_charsize: Character height in pixels
+ * @tab_width_frac: Tab width in fractional units
+ * @xsize_frac: Width of the display in fractional units
*/
struct vidconsole_priv {
struct stdio_dev sdev;
- int curr_col;
- int curr_row;
+ int xcur_frac;
+ int ycur;
int rows;
int cols;
+ int x_charsize;
+ int y_charsize;
+ int tab_width_frac;
+ int xsize_frac;
};

/**
@@ -36,12 +52,15 @@ struct vidconsole_ops {
* putc_xy() - write a single character to a position
*
* @dev: Device to write to
- * @x: Pixel X position (0=left-most pixel)
+ * @x_frac: Fractional pixel X position (0=left-most pixel) which
+ * is the X position multipled by VID_FRAC_DIV.
* @y: Pixel Y position (0=top-most pixel)
* @ch: Character to write
- * @return 0 if OK, -ve on error
+ * @return number of fractional pixels that the cursor should move,
+ * if all is OK, -EAGAIN if we ran out of space on this line, other -ve
+ * on error
*/
- int (*putc_xy)(struct udevice *dev, uint x, uint y, char ch);
+ int (*putc_xy)(struct udevice *dev, uint x_frac, uint y, char ch);

/**
* move_rows() - Move text rows from one place to another
@@ -75,10 +94,13 @@ struct vidconsole_ops {
* vidconsole_putc_xy() - write a single character to a position
*
* @dev: Device to write to
- * @x: Pixel X position (0=left-most pixel)
+ * @x_frac: Fractional pixel X position (0=left-most pixel) which
+ * is the X position multipled by VID_FRAC_DIV.
* @y: Pixel Y position (0=top-most pixel)
* @ch: Character to write
- * @return 0 if OK, -ve on error
+ * @return number of fractional pixels that the cursor should move,
+ * if all is OK, -EAGAIN if we ran out of space on this line, other -ve
+ * on error
*/
int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch);

diff --git a/test/dm/video.c b/test/dm/video.c
index 52aba2e..3540271 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -106,14 +106,14 @@ static int dm_test_video_text(struct unit_test_state *uts)
ut_asserteq(46, compress_frame_buffer(dev));

for (i = 0; i < 20; i++)
- vidconsole_putc_xy(con, i * 8, 0, ' ' + i);
+ vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i);
ut_asserteq(273, compress_frame_buffer(dev));

vidconsole_set_row(con, 0, WHITE);
ut_asserteq(46, compress_frame_buffer(dev));

for (i = 0; i < 20; i++)
- vidconsole_putc_xy(con, i * 8, 0, ' ' + i);
+ vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i);
ut_asserteq(273, compress_frame_buffer(dev));

return 0;
--
2.6.0.rc2.230.g3dd15c0
Anatolij Gustschin
2016-01-23 00:19:34 UTC
Permalink
From: Simon Glass <***@chromium.org>

With anti-aliased fonts we need a more fine-grained horizontal position
than a single pixel. Characters can be positioned to start part-way through
a pixel, with anti-aliasing (greyscale edges) taking care of the visual
effect.

To cope with this, use fractional units (1/256 pixel) for horizontal
positions in the text console.

Signed-off-by: Simon Glass <***@chromium.org>
Signed-off-by: Anatolij Gustschin <***@denx.de>
---
Changes in v2: rebased

drivers/video/console_normal.c | 24 ++++++++++++--
drivers/video/console_rotate.c | 65 ++++++++++++++++++++++++++++---------
drivers/video/vidconsole-uclass.c | 56 ++++++++++++++++++--------------
include/video_console.h | 40 ++++++++++++++++++-----
test/dm/video.c | 4 +--
5 files changed, 136 insertions(+), 53 deletions(-)

diff --git a/drivers/video/console_normal.c b/drivers/video/console_normal.c
index d1031c8..89a55dd 100644
--- a/drivers/video/console_normal.c
+++ b/drivers/video/console_normal.c
@@ -71,13 +71,18 @@ static int console_normal_move_rows(struct udevice *dev, uint rowdst,
return 0;
}

-static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch)
+static int console_normal_putc_xy(struct udevice *dev, uint x_frac, uint y,
+ char ch)
{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);
int i, row;
void *line = vid_priv->fb + y * vid_priv->line_length +
- x * VNBYTES(vid_priv->bpix);
+ VID_TO_PIXEL(x_frac) * VNBYTES(vid_priv->bpix);
+
+ if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac)
+ return -EAGAIN;

for (row = 0; row < VIDEO_FONT_HEIGHT; row++) {
uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row];
@@ -125,6 +130,20 @@ static int console_normal_putc_xy(struct udevice *dev, uint x, uint y, char ch)
line += vid_priv->line_length;
}

+ return VID_TO_POS(VIDEO_FONT_WIDTH);
+}
+
+static int console_normal_probe(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+
+ vc_priv->x_charsize = VIDEO_FONT_WIDTH;
+ vc_priv->y_charsize = VIDEO_FONT_HEIGHT;
+ vc_priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH;
+ vc_priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT;
+
return 0;
}

@@ -138,4 +157,5 @@ U_BOOT_DRIVER(vidconsole_normal) = {
.name = "vidconsole0",
.id = UCLASS_VIDEO_CONSOLE,
.ops = &console_normal_ops,
+ .probe = console_normal_probe,
};
diff --git a/drivers/video/console_rotate.c b/drivers/video/console_rotate.c
index ebb31d8..227141d 100644
--- a/drivers/video/console_rotate.c
+++ b/drivers/video/console_rotate.c
@@ -82,17 +82,22 @@ static int console_move_rows_1(struct udevice *dev, uint rowdst, uint rowsrc,
return 0;
}

-static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch)
+static int console_putc_xy_1(struct udevice *dev, uint x_frac, uint y, char ch)
{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);
int pbytes = VNBYTES(vid_priv->bpix);
int i, col;
int mask = 0x80;
- void *line = vid_priv->fb + (x + 1) * vid_priv->line_length -
- (y + 1) * pbytes;
+ void *line;
uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT;

+ line = vid_priv->fb + (VID_TO_PIXEL(x_frac) + 1) *
+ vid_priv->line_length - (y + 1) * pbytes;
+ if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac)
+ return -EAGAIN;
+
for (col = 0; col < VIDEO_FONT_HEIGHT; col++) {
switch (vid_priv->bpix) {
#ifdef CONFIG_VIDEO_BPP8
@@ -135,7 +140,7 @@ static int console_putc_xy_1(struct udevice *dev, uint x, uint y, char ch)
mask >>= 1;
}

- return 0;
+ return VID_TO_POS(VIDEO_FONT_WIDTH);
}


@@ -201,17 +206,21 @@ static int console_move_rows_2(struct udevice *dev, uint rowdst, uint rowsrc,
return 0;
}

-static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch)
+static int console_putc_xy_2(struct udevice *dev, uint x_frac, uint y, char ch)
{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);
int i, row;
void *line;

+ if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac)
+ return -EAGAIN;
+
line = vid_priv->fb + (vid_priv->ysize - y - 1) *
- vid_priv->line_length +
- (vid_priv->xsize - x - VIDEO_FONT_WIDTH - 1) *
- VNBYTES(vid_priv->bpix);
+ vid_priv->line_length +
+ (vid_priv->xsize - VID_TO_PIXEL(x_frac) -
+ VIDEO_FONT_WIDTH - 1) * VNBYTES(vid_priv->bpix);

for (row = 0; row < VIDEO_FONT_HEIGHT; row++) {
uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row];
@@ -259,7 +268,7 @@ static int console_putc_xy_2(struct udevice *dev, uint x, uint y, char ch)
line -= vid_priv->line_length;
}

- return 0;
+ return VID_TO_POS(VIDEO_FONT_WIDTH);
}

static int console_set_row_3(struct udevice *dev, uint row, int clr)
@@ -329,17 +338,22 @@ static int console_move_rows_3(struct udevice *dev, uint rowdst, uint rowsrc,
return 0;
}

-static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch)
+static int console_putc_xy_3(struct udevice *dev, uint x_frac, uint y, char ch)
{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);
int pbytes = VNBYTES(vid_priv->bpix);
int i, col;
int mask = 0x80;
- void *line = vid_priv->fb + (vid_priv->ysize - x - 1) *
+ void *line = vid_priv->fb +
+ (vid_priv->ysize - VID_TO_PIXEL(x_frac) - 1) *
vid_priv->line_length + y * pbytes;
uchar *pfont = video_fontdata + ch * VIDEO_FONT_HEIGHT;

+ if (x_frac + VID_TO_POS(vc_priv->x_charsize) > vc_priv->xsize_frac)
+ return -EAGAIN;
+
for (col = 0; col < VIDEO_FONT_HEIGHT; col++) {
switch (vid_priv->bpix) {
#ifdef CONFIG_VIDEO_BPP8
@@ -382,17 +396,35 @@ static int console_putc_xy_3(struct udevice *dev, uint x, uint y, char ch)
mask >>= 1;
}

- return 0;
+ return VID_TO_POS(VIDEO_FONT_WIDTH);
}


+static int console_probe_2(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+
+ vc_priv->x_charsize = VIDEO_FONT_WIDTH;
+ vc_priv->y_charsize = VIDEO_FONT_HEIGHT;
+ vc_priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH;
+ vc_priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT;
+
+ return 0;
+}
+
static int console_probe_1_3(struct udevice *dev)
{
- struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
- struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);

- priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH;
- priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT;
+ vc_priv->x_charsize = VIDEO_FONT_WIDTH;
+ vc_priv->y_charsize = VIDEO_FONT_HEIGHT;
+ vc_priv->cols = vid_priv->ysize / VIDEO_FONT_WIDTH;
+ vc_priv->rows = vid_priv->xsize / VIDEO_FONT_HEIGHT;
+ vc_priv->xsize_frac = VID_TO_POS(vid_priv->ysize);

return 0;
}
@@ -426,6 +458,7 @@ U_BOOT_DRIVER(vidconsole_2) = {
.name = "vidconsole2",
.id = UCLASS_VIDEO_CONSOLE,
.ops = &console_ops_2,
+ .probe = console_probe_2,
};

U_BOOT_DRIVER(vidconsole_3) = {
diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index ea10189..d997186 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -52,14 +52,14 @@ static void vidconsole_back(struct udevice *dev)
{
struct vidconsole_priv *priv = dev_get_uclass_priv(dev);

- if (--priv->curr_col < 0) {
- priv->curr_col = priv->cols - 1;
- if (--priv->curr_row < 0)
- priv->curr_row = 0;
+ priv->xcur_frac -= VID_TO_POS(priv->x_charsize);
+ if (priv->xcur_frac < 0) {
+ priv->xcur_frac = (priv->cols - 1) *
+ VID_TO_POS(priv->x_charsize);
+ priv->ycur -= priv->y_charsize;
+ if (priv->ycur < 0)
+ priv->ycur = 0;
}
-
- vidconsole_putc_xy(dev, priv->curr_col * VIDEO_FONT_WIDTH,
- priv->curr_row * VIDEO_FONT_HEIGHT, ' ');
}

/* Move to a newline, scrolling the display if necessary */
@@ -71,15 +71,16 @@ static void vidconsole_newline(struct udevice *dev)
const int rows = CONFIG_CONSOLE_SCROLL_LINES;
int i;

- priv->curr_col = 0;
+ priv->xcur_frac = 0;
+ priv->ycur += priv->y_charsize;

/* Check if we need to scroll the terminal */
- if (++priv->curr_row >= priv->rows) {
+ if ((priv->ycur + priv->y_charsize) / priv->y_charsize > priv->rows) {
vidconsole_move_rows(dev, 0, rows, priv->rows - rows);
for (i = 0; i < rows; i++)
vidconsole_set_row(dev, priv->rows - i - 1,
vid_priv->colour_bg);
- priv->curr_row -= rows;
+ priv->ycur -= rows * priv->y_charsize;
}
video_sync(dev->parent);
}
@@ -91,16 +92,16 @@ int vidconsole_put_char(struct udevice *dev, char ch)

switch (ch) {
case '\r':
- priv->curr_col = 0;
+ priv->xcur_frac = 0;
break;
case '\n':
vidconsole_newline(dev);
break;
case '\t': /* Tab (8 chars alignment) */
- priv->curr_col += 8;
- priv->curr_col &= ~7;
+ priv->xcur_frac = ((priv->xcur_frac / priv->tab_width_frac)
+ + 1) * priv->tab_width_frac;

- if (priv->curr_col >= priv->cols)
+ if (priv->xcur_frac >= priv->xsize_frac)
vidconsole_newline(dev);
break;
case '\b':
@@ -112,13 +113,16 @@ int vidconsole_put_char(struct udevice *dev, char ch)
* colour depth. Check this and return an error to help with
* diagnosis.
*/
- ret = vidconsole_putc_xy(dev,
- priv->curr_col * VIDEO_FONT_WIDTH,
- priv->curr_row * VIDEO_FONT_HEIGHT,
- ch);
- if (ret)
+ ret = vidconsole_putc_xy(dev, priv->xcur_frac, priv->ycur, ch);
+ if (ret == -EAGAIN) {
+ vidconsole_newline(dev);
+ ret = vidconsole_putc_xy(dev, priv->xcur_frac,
+ priv->ycur, ch);
+ }
+ if (ret < 0)
return ret;
- if (++priv->curr_col >= priv->cols)
+ priv->xcur_frac += ret;
+ if (priv->xcur_frac >= priv->xsize_frac)
vidconsole_newline(dev);
break;
}
@@ -148,8 +152,7 @@ static int vidconsole_pre_probe(struct udevice *dev)
struct udevice *vid = dev->parent;
struct video_priv *vid_priv = dev_get_uclass_priv(vid);

- priv->rows = vid_priv->ysize / VIDEO_FONT_HEIGHT;
- priv->cols = vid_priv->xsize / VIDEO_FONT_WIDTH;
+ priv->xsize_frac = VID_TO_POS(vid_priv->xsize);

return 0;
}
@@ -161,6 +164,9 @@ static int vidconsole_post_probe(struct udevice *dev)
struct stdio_dev *sdev = &priv->sdev;
int ret;

+ if (!priv->tab_width_frac)
+ priv->tab_width_frac = VID_TO_POS(priv->x_charsize) * 8;
+
strlcpy(sdev->name, dev->name, sizeof(sdev->name));
sdev->flags = DEV_FLAGS_OUTPUT;
sdev->putc = vidconsole_putc;
@@ -184,9 +190,11 @@ UCLASS_DRIVER(vidconsole) = {
void vidconsole_position_cursor(struct udevice *dev, unsigned col, unsigned row)
{
struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);

- priv->curr_col = min_t(short, col, priv->cols - 1);
- priv->curr_row = min_t(short, row, priv->rows - 1);
+ priv->xcur_frac = VID_TO_POS(min_t(short, col, vid_priv->xsize - 1));
+ priv->ycur = min_t(short, row, vid_priv->ysize - 1);
}

static int do_video_setcursor(cmd_tbl_t *cmdtp, int flag, int argc,
diff --git a/include/video_console.h b/include/video_console.h
index c0fc792..eeba368 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -7,21 +7,37 @@
#ifndef __video_console_h
#define __video_console_h

+#define VID_FRAC_DIV 256
+
+#define VID_TO_PIXEL(x) ((x) / VID_FRAC_DIV)
+#define VID_TO_POS(x) ((x) * VID_FRAC_DIV)
+
/**
* struct vidconsole_priv - uclass-private data about a console device
*
+ * Drivers must set up @rows, @cols, @x_charsize, @y_charsize in their probe()
+ * method. Drivers may set up @xstart_frac if desired.
+ *
* @sdev: stdio device, acting as an output sink
- * @curr_col: Current text column (0=left)
- * @curr_row: Current row (0=top)
+ * @xcur_frac: Current X position, in fractional units (VID_TO_POS(x))
+ * @curr_row: Current Y position in pixels (0=top)
* @rows: Number of text rows
* @cols: Number of text columns
+ * @x_charsize: Character width in pixels
+ * @y_charsize: Character height in pixels
+ * @tab_width_frac: Tab width in fractional units
+ * @xsize_frac: Width of the display in fractional units
*/
struct vidconsole_priv {
struct stdio_dev sdev;
- int curr_col;
- int curr_row;
+ int xcur_frac;
+ int ycur;
int rows;
int cols;
+ int x_charsize;
+ int y_charsize;
+ int tab_width_frac;
+ int xsize_frac;
};

/**
@@ -36,12 +52,15 @@ struct vidconsole_ops {
* putc_xy() - write a single character to a position
*
* @dev: Device to write to
- * @x: Pixel X position (0=left-most pixel)
+ * @x_frac: Fractional pixel X position (0=left-most pixel) which
+ * is the X position multipled by VID_FRAC_DIV.
* @y: Pixel Y position (0=top-most pixel)
* @ch: Character to write
- * @return 0 if OK, -ve on error
+ * @return number of fractional pixels that the cursor should move,
+ * if all is OK, -EAGAIN if we ran out of space on this line, other -ve
+ * on error
*/
- int (*putc_xy)(struct udevice *dev, uint x, uint y, char ch);
+ int (*putc_xy)(struct udevice *dev, uint x_frac, uint y, char ch);

/**
* move_rows() - Move text rows from one place to another
@@ -75,10 +94,13 @@ struct vidconsole_ops {
* vidconsole_putc_xy() - write a single character to a position
*
* @dev: Device to write to
- * @x: Pixel X position (0=left-most pixel)
+ * @x_frac: Fractional pixel X position (0=left-most pixel) which
+ * is the X position multipled by VID_FRAC_DIV.
* @y: Pixel Y position (0=top-most pixel)
* @ch: Character to write
- * @return 0 if OK, -ve on error
+ * @return number of fractional pixels that the cursor should move,
+ * if all is OK, -EAGAIN if we ran out of space on this line, other -ve
+ * on error
*/
int vidconsole_putc_xy(struct udevice *dev, uint x, uint y, char ch);

diff --git a/test/dm/video.c b/test/dm/video.c
index 9f5e7fc..be94633 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -106,14 +106,14 @@ static int dm_test_video_text(struct unit_test_state *uts)
ut_asserteq(46, compress_frame_buffer(dev));

for (i = 0; i < 20; i++)
- vidconsole_putc_xy(con, i * 8, 0, ' ' + i);
+ vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i);
ut_asserteq(273, compress_frame_buffer(dev));

vidconsole_set_row(con, 0, WHITE);
ut_asserteq(46, compress_frame_buffer(dev));

for (i = 0; i < 20; i++)
- vidconsole_putc_xy(con, i * 8, 0, ' ' + i);
+ vidconsole_putc_xy(con, VID_TO_POS(i * 8), 0, ' ' + i);
ut_asserteq(273, compress_frame_buffer(dev));

return 0;
--
1.7.9.5
Simon Glass
2016-01-15 01:10:41 UTC
Permalink
With proportional fonts the vidconsole uclass cannot itself erase the
previous character. Provide an optional method so that the driver can
handle this operation.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/vidconsole-uclass.c | 12 +++++++++++-
include/video_console.h | 14 ++++++++++++++
2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/drivers/video/vidconsole-uclass.c b/drivers/video/vidconsole-uclass.c
index 182aaed..832e90a 100644
--- a/drivers/video/vidconsole-uclass.c
+++ b/drivers/video/vidconsole-uclass.c
@@ -57,9 +57,17 @@ static int vidconsole_entry_start(struct udevice *dev)
}

/* Move backwards one space */
-static void vidconsole_back(struct udevice *dev)
+static int vidconsole_back(struct udevice *dev)
{
struct vidconsole_priv *priv = dev_get_uclass_priv(dev);
+ struct vidconsole_ops *ops = vidconsole_get_ops(dev);
+ int ret;
+
+ if (ops->backspace) {
+ ret = ops->backspace(dev);
+ if (ret != -ENOSYS)
+ return ret;
+ }

priv->xcur_frac -= VID_TO_POS(priv->x_charsize);
if (priv->xcur_frac < priv->xstart_frac) {
@@ -69,6 +77,8 @@ static void vidconsole_back(struct udevice *dev)
if (priv->ycur < 0)
priv->ycur = 0;
}
+
+ return 0;
}

/* Move to a newline, scrolling the display if necessary */
diff --git a/include/video_console.h b/include/video_console.h
index 80bc948..1345748 100644
--- a/include/video_console.h
+++ b/include/video_console.h
@@ -101,6 +101,20 @@ struct vidconsole_ops {
* positions.
*/
int (*entry_start)(struct udevice *dev);
+
+ /**
+ * backspace() - Handle erasing the last character
+ *
+ * With proportional fonts the vidconsole uclass cannot itself erase
+ * the previous character. This optional method will be called when
+ * a backspace is needed. The driver should erase the previous
+ * character and update the cursor position (xcur_frac, ycur) to the
+ * start of the previous character.
+ *
+ * If not implement, default behaviour will work for fixed-width
+ * characters.
+ */
+ int (*backspace)(struct udevice *dev);
};

/* Get a pointer to the driver operations for a video console device */
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:45 UTC
Permalink
This can be used when a a friendly 'hand-writing' font is needed. It helps
to make the device feel familiar.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/console_truetype.c | 4 ++++
drivers/video/fonts/Kconfig | 9 +++++++++
drivers/video/fonts/Makefile | 1 +
drivers/video/fonts/rufscript010.ttf | Bin 0 -> 23080 bytes
4 files changed, 14 insertions(+)
create mode 100644 drivers/video/fonts/rufscript010.ttf

diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index a18611b..405e065 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -446,6 +446,7 @@ struct font_info {

FONT_DECL(nimbus_sans_l_regular);
FONT_DECL(ankacoder_c75_r);
+FONT_DECL(rufscript010);

static struct font_info font_table[] = {
#ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS
@@ -454,6 +455,9 @@ static struct font_info font_table[] = {
#ifdef CONFIG_CONSOLE_TRUETYPE_ANKACODER
FONT_ENTRY(ankacoder_c75_r),
#endif
+#ifdef CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT
+ FONT_ENTRY(rufscript010),
+#endif
{} /* sentinel */
};

diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig
index ba1ccca..d3bf742 100644
--- a/drivers/video/fonts/Kconfig
+++ b/drivers/video/fonts/Kconfig
@@ -29,4 +29,13 @@ config CONSOLE_TRUETYPE_ANKACODER
License: SIL Open Font Licence
http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL

+config CONSOLE_TRUETYPE_RUFSCRIPT
+ bool "Ruf Script"
+ depends on CONSOLE_TRUETYPE
+ help
+ A laid-back handwritten font.
+ Font: https://fontlibrary.org/en/font/rufscript
+ License: GPL with font exception
+ http://www.gnu.org/copyleft/gpl.html
+
endmenu
diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile
index 58b1813..a3f6ea3 100644
--- a/drivers/video/fonts/Makefile
+++ b/drivers/video/fonts/Makefile
@@ -7,3 +7,4 @@

obj-$(CONFIG_CONSOLE_TRUETYPE_NIMBUS) += nimbus_sans_l_regular.o
obj-$(CONFIG_CONSOLE_TRUETYPE_ANKACODER) += ankacoder_c75_r.o
+obj-$(CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT) += rufscript010.o
diff --git a/drivers/video/fonts/rufscript010.ttf b/drivers/video/fonts/rufscript010.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..887a4496f3c8dbdb62efe1c95dfd9b2604cb732c
GIT binary patch
literal 23080
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

literal 0
HcmV?d00001
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:46 UTC
Permalink
This font is a little more ornate than normal. Example uses are on security
screens where a feeling of formality is required.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/console_truetype.c | 4 ++++
drivers/video/fonts/Kconfig | 10 ++++++++++
drivers/video/fonts/Makefile | 1 +
drivers/video/fonts/cantoraone_regular.ttf | Bin 0 -> 163116 bytes
4 files changed, 15 insertions(+)
create mode 100644 drivers/video/fonts/cantoraone_regular.ttf

diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index 405e065..c249f04 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -447,6 +447,7 @@ struct font_info {
FONT_DECL(nimbus_sans_l_regular);
FONT_DECL(ankacoder_c75_r);
FONT_DECL(rufscript010);
+FONT_DECL(cantoraone_regular);

static struct font_info font_table[] = {
#ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS
@@ -458,6 +459,9 @@ static struct font_info font_table[] = {
#ifdef CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT
FONT_ENTRY(rufscript010),
#endif
+#ifdef CONFIG_CONSOLE_TRUETYPE_CANTORAONE
+ FONT_ENTRY(cantoraone_regular),
+#endif
{} /* sentinel */
};

diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig
index d3bf742..3f1398d 100644
--- a/drivers/video/fonts/Kconfig
+++ b/drivers/video/fonts/Kconfig
@@ -38,4 +38,14 @@ config CONSOLE_TRUETYPE_RUFSCRIPT
License: GPL with font exception
http://www.gnu.org/copyleft/gpl.html

+config CONSOLE_TRUETYPE_CANTORAONE
+ bool "Cantoraone"
+ depends on CONSOLE_TRUETYPE
+ help
+ Cantora is a friendly semi formal, semi condensed, semi sans-serif
+ with a hint of handwriting. Perfect for headlines.
+ From https://fontlibrary.org/en/font/cantora
+ License: SIL Open Font Licence
+ http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
+
endmenu
diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile
index a3f6ea3..46137f4 100644
--- a/drivers/video/fonts/Makefile
+++ b/drivers/video/fonts/Makefile
@@ -8,3 +8,4 @@
obj-$(CONFIG_CONSOLE_TRUETYPE_NIMBUS) += nimbus_sans_l_regular.o
obj-$(CONFIG_CONSOLE_TRUETYPE_ANKACODER) += ankacoder_c75_r.o
obj-$(CONFIG_CONSOLE_TRUETYPE_RUFSCRIPT) += rufscript010.o
+obj-$(CONFIG_CONSOLE_TRUETYPE_CANTORAONE) += cantoraone_regular.o
diff --git a/drivers/video/fonts/cantoraone_regular.ttf b/drivers/video/fonts/cantoraone_regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..57446a27a0372942cc3321ba3e14123b0a1be1d6
GIT binary patch
literal 163116
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
WcDDszEb5M}&u5Cg1oM#=&iDfFy53I!

literal 0
HcmV?d00001
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:49 UTC
Permalink
For testing it is useful to be able to select the font size and the console
driver for sandbox. Add this information to platform data and copy it to
the video device when needed.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/sandbox_sdl.c | 2 ++
include/dm/test.h | 2 ++
2 files changed, 4 insertions(+)

diff --git a/drivers/video/sandbox_sdl.c b/drivers/video/sandbox_sdl.c
index 21448a1..dc5a220 100644
--- a/drivers/video/sandbox_sdl.c
+++ b/drivers/video/sandbox_sdl.c
@@ -35,6 +35,8 @@ static int sandbox_sdl_probe(struct udevice *dev)
uc_priv->ysize = plat->yres;
uc_priv->bpix = plat->bpix;
uc_priv->rot = plat->rot;
+ uc_priv->vidconsole_drv_name = plat->vidconsole_drv_name;
+ uc_priv->font_size = plat->font_size;

return 0;
}
diff --git a/include/dm/test.h b/include/dm/test.h
index ca924d9..cba5049 100644
--- a/include/dm/test.h
+++ b/include/dm/test.h
@@ -161,6 +161,8 @@ struct sandbox_sdl_plat {
int yres;
int bpix;
int rot;
+ const char *vidconsole_drv_name;
+ int font_size;
};

/* Declare ping methods for the drivers */
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:47 UTC
Permalink
This is used by two of the font files. Add this license to permit tracking
of this. The copyright text cannot be added to the .ttf files, so put it
here.

Signed-off-by: Simon Glass <***@chromium.org>
---

Licenses/OFL.txt | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Licenses/README | 1 +
2 files changed, 98 insertions(+)
create mode 100644 Licenses/OFL.txt

diff --git a/Licenses/OFL.txt b/Licenses/OFL.txt
new file mode 100644
index 0000000..07c881f
--- /dev/null
+++ b/Licenses/OFL.txt
@@ -0,0 +1,97 @@
+Copyright (c) 2010, Andrey Makarov (***@bmstu.ru, mka-at-***@mail.ru),
+with Reserved Font Name Anka/Coder Narrow.
+
+Copyright (c) 2011, Pablo Impallari (www.impallari.com|***@gmail.com),
+Rodrigo Fuenzalida (www.rfuenzalida.com) with Reserved Font Name Cantora.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/Licenses/README b/Licenses/README
index 731d45c..b850c70 100644
--- a/Licenses/README
+++ b/Licenses/README
@@ -67,4 +67,5 @@ BSD 3-clause "New" or "Revised" License BSD-3-Clause Y bsd-3-clause.txt http:/
IBM PIBS (PowerPC Initialization and IBM-pibs ibm-pibs.txt
Boot Software) license
ISC License ISC Y isc.txt https://spdx.org/licenses/ISC
+SIL OPEN FONT LICENSE (OFL-1.1) OFL-1.1 Y https://opensource.org/licenses/OFL-1.1
X11 License X11 x11.txt https://spdx.org/licenses/X11.html
--
2.6.0.rc2.230.g3dd15c0
Tom Rini
2016-01-21 19:11:25 UTC
Permalink
Post by Simon Glass
This is used by two of the font files. Add this license to permit tracking
of this. The copyright text cannot be added to the .ttf files, so put it
here.
[snip]
Post by Simon Glass
diff --git a/Licenses/README b/Licenses/README
index 731d45c..b850c70 100644
--- a/Licenses/README
+++ b/Licenses/README
@@ -67,4 +67,5 @@ BSD 3-clause "New" or "Revised" License BSD-3-Clause Y bsd-3-clause.txt http:/
IBM PIBS (PowerPC Initialization and IBM-pibs ibm-pibs.txt
Boot Software) license
ISC License ISC Y isc.txt https://spdx.org/licenses/ISC
+SIL OPEN FONT LICENSE (OFL-1.1) OFL-1.1 Y https://opensource.org/licenses/OFL-1.1
X11 License X11 x11.txt https://spdx.org/licenses/X11.html
I'd like to keep using either gnu.org or spdx.org for the URL so can you
please use https://spdx.org/licenses/OFL-1.1.html instead? Thanks!
--
Tom
Anatolij Gustschin
2016-01-23 00:23:44 UTC
Permalink
From: Simon Glass <***@chromium.org>

This is used by two of the font files. Add this license to permit tracking
of this. The copyright text cannot be added to the .ttf files, so put it
here.

Signed-off-by: Simon Glass <***@chromium.org>
Signed-off-by: Anatolij Gustschin <***@denx.de>
---
Changes in v2: replaced the license URL as suggested by Tom
and added OFL.txt file name

Licenses/OFL.txt | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
Licenses/README | 1 +
2 files changed, 98 insertions(+)
create mode 100644 Licenses/OFL.txt

diff --git a/Licenses/OFL.txt b/Licenses/OFL.txt
new file mode 100644
index 0000000..07c881f
--- /dev/null
+++ b/Licenses/OFL.txt
@@ -0,0 +1,97 @@
+Copyright (c) 2010, Andrey Makarov (***@bmstu.ru, mka-at-***@mail.ru),
+with Reserved Font Name Anka/Coder Narrow.
+
+Copyright (c) 2011, Pablo Impallari (www.impallari.com|***@gmail.com),
+Rodrigo Fuenzalida (www.rfuenzalida.com) with Reserved Font Name Cantora.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/Licenses/README b/Licenses/README
index 731d45c..5ad921d 100644
--- a/Licenses/README
+++ b/Licenses/README
@@ -67,4 +67,5 @@ BSD 3-clause "New" or "Revised" License BSD-3-Clause Y bsd-3-clause.txt http:/
IBM PIBS (PowerPC Initialization and IBM-pibs ibm-pibs.txt
Boot Software) license
ISC License ISC Y isc.txt https://spdx.org/licenses/ISC
+SIL OPEN FONT LICENSE (OFL-1.1) OFL-1.1 Y OFL.txt https://spdx.org/licenses/OFL-1.1.html
X11 License X11 x11.txt https://spdx.org/licenses/X11.html
--
1.7.9.5
Simon Glass
2016-01-15 01:10:48 UTC
Permalink
Provide a way for the video console driver to be selected. This is
controlled by the video driver's private data. This can be set up when the
driver is probed so that it is ready for the video_post_probe() method.

The font size is provided as well. The console driver may or may not support
this depending on its capability.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/video-uclass.c | 20 ++++++++++++++++----
include/video.h | 5 +++++
2 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 2189fce..b6dd0f5 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -180,6 +180,7 @@ static int video_post_probe(struct udevice *dev)
struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);
struct video_priv *priv = dev_get_uclass_priv(dev);
char name[30], drv[15], *str;
+ const char *drv_name = drv;
struct udevice *cons;
int ret;

@@ -197,11 +198,19 @@ static int video_post_probe(struct udevice *dev)
video_clear(dev);

/*
- * Create a text console devices. For now we always do this, although
+ * Create a text console device. For now we always do this, although
* it might be useful to support only bitmap drawing on the device
- * for boards that don't need to display text.
+ * for boards that don't need to display text. We create a TrueType
+ * console if enabled, a rotated console if the video driver requests
+ * it, otherwise a normal console.
+ *
+ * The console can be override by setting vidconsole_drv_name before
+ * probing this video driver, or in the probe() method.
+ *
+ * TrueType does not support rotation at present so fall back to the
+ * rotated console in that case.
*/
- if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) {
+ if (!priv->rot && IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) {
snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name);
strcpy(drv, "vidconsole_tt");
} else {
@@ -213,11 +222,14 @@ static int video_post_probe(struct udevice *dev)
str = strdup(name);
if (!str)
return -ENOMEM;
- ret = device_bind_driver(dev, drv, str, &cons);
+ if (priv->vidconsole_drv_name)
+ drv_name = priv->vidconsole_drv_name;
+ ret = device_bind_driver(dev, drv_name, str, &cons);
if (ret) {
debug("%s: Cannot bind console driver\n", __func__);
return ret;
}
+
ret = device_probe(cons);
if (ret) {
debug("%s: Cannot probe console driver\n", __func__);
diff --git a/include/video.h b/include/video.h
index 41e3cbf..5ed0999 100644
--- a/include/video.h
+++ b/include/video.h
@@ -56,6 +56,9 @@ enum video_log2_bpp {
* @ysize: Number of pixels rows (e.g.. 768)
* @tor: Display rotation (0=none, 1=90 degrees clockwise, etc.)
* @bpix: Encoded bits per pixel
+ * @vidconsole_drv_name: Driver to use for the text console, NULL to
+ * select automatically
+ * @font_size: Font size in pixels (0 to use a default value)
* @fb: Frame buffer
* @fb_size: Frame buffer size
* @fb_size: Frame buffer size
@@ -72,6 +75,8 @@ struct video_priv {
ushort ysize;
ushort rot;
enum video_log2_bpp bpix;
+ const char *vidconsole_drv_name;
+ int font_size;

/*
* Things that are private to the uclass: don't use these in the
--
2.6.0.rc2.230.g3dd15c0
Anatolij Gustschin
2016-01-23 00:24:54 UTC
Permalink
From: Simon Glass <***@chromium.org>

Provide a way for the video console driver to be selected. This is
controlled by the video driver's private data. This can be set up when the
driver is probed so that it is ready for the video_post_probe() method.

The font size is provided as well. The console driver may or may not support
this depending on its capability.

Signed-off-by: Simon Glass <***@chromium.org>
Signed-off-by: Anatolij Gustschin <***@denx.de>
---
Changes in v2: rebased

drivers/video/video-uclass.c | 20 ++++++++++++++++----
include/video.h | 5 +++++
2 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 13e8a88..2b5e0f9 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -173,6 +173,7 @@ static int video_post_probe(struct udevice *dev)
struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);
struct video_priv *priv = dev_get_uclass_priv(dev);
char name[30], drv[15], *str;
+ const char *drv_name = drv;
struct udevice *cons;
int ret;

@@ -190,11 +191,19 @@ static int video_post_probe(struct udevice *dev)
video_clear(dev);

/*
- * Create a text console devices. For now we always do this, although
+ * Create a text console device. For now we always do this, although
* it might be useful to support only bitmap drawing on the device
- * for boards that don't need to display text.
+ * for boards that don't need to display text. We create a TrueType
+ * console if enabled, a rotated console if the video driver requests
+ * it, otherwise a normal console.
+ *
+ * The console can be override by setting vidconsole_drv_name before
+ * probing this video driver, or in the probe() method.
+ *
+ * TrueType does not support rotation at present so fall back to the
+ * rotated console in that case.
*/
- if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) {
+ if (!priv->rot && IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) {
snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name);
strcpy(drv, "vidconsole_tt");
} else {
@@ -206,11 +215,14 @@ static int video_post_probe(struct udevice *dev)
str = strdup(name);
if (!str)
return -ENOMEM;
- ret = device_bind_driver(dev, drv, str, &cons);
+ if (priv->vidconsole_drv_name)
+ drv_name = priv->vidconsole_drv_name;
+ ret = device_bind_driver(dev, drv_name, str, &cons);
if (ret) {
debug("%s: Cannot bind console driver\n", __func__);
return ret;
}
+
ret = device_probe(cons);
if (ret) {
debug("%s: Cannot probe console driver\n", __func__);
diff --git a/include/video.h b/include/video.h
index b20f06f..20fdf54 100644
--- a/include/video.h
+++ b/include/video.h
@@ -51,6 +51,9 @@ enum video_log2_bpp {
* @ysize: Number of pixels rows (e.g.. 768)
* @tor: Display rotation (0=none, 1=90 degrees clockwise, etc.)
* @bpix: Encoded bits per pixel
+ * @vidconsole_drv_name: Driver to use for the text console, NULL to
+ * select automatically
+ * @font_size: Font size in pixels (0 to use a default value)
* @fb: Frame buffer
* @fb_size: Frame buffer size
* @line_length: Length of each frame buffer line, in bytes
@@ -66,6 +69,8 @@ struct video_priv {
ushort ysize;
ushort rot;
enum video_log2_bpp bpix;
+ const char *vidconsole_drv_name;
+ int font_size;

/*
* Things that are private to the uclass: don't use these in the
--
1.7.9.5
Simon Glass
2016-01-15 01:10:42 UTC
Permalink
The existing 8x16 font is adequate for most purposes. It is small and fast.
However for boot screens where information must be presented to the user,
the console font is not ideal. Common requirements are larger and
better-looking fonts.

This console driver can use TrueType fonts built into U-Boot, and render
them at any size. This can be used in scripts to place text as needed on
the display.

This driver is not really designed to operate with the command line. Much
of U-Boot expects a fixed-width font. But to keep things working correctly,
rudimentary support for the console is provided. The main missing feature is
support for command-line editing.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/Kconfig | 24 ++
drivers/video/Makefile | 1 +
drivers/video/console_truetype.c | 533 +++++++++++++++++++++++++++++++++++++++
drivers/video/fonts/Kconfig | 7 +
drivers/video/fonts/Makefile | 6 +
drivers/video/video-uclass.c | 11 +-
6 files changed, 580 insertions(+), 2 deletions(-)
create mode 100644 drivers/video/console_truetype.c
create mode 100644 drivers/video/fonts/Kconfig
create mode 100644 drivers/video/fonts/Makefile

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 29ddde2..2826082 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -67,6 +67,30 @@ config CONSOLE_ROTATION
struct video_priv: 0=unrotated, 1=90 degrees clockwise, 2=180
degrees, 3=270 degrees.

+config CONSOLE_TRUETYPE
+ bool "Support a console that uses TrueType fonts"
+ depends on DM_VIDEO
+ help
+ TrueTrype fonts can provide outline-drawing capability rather than
+ needing to provide a bitmap for each font and size that is needed.
+ With this option you can adjust the text size and use a variety of
+ fonts. Note that this is noticeably slower than with normal console.
+
+config CONSOLE_TRUETYPE_SIZE
+ int "TrueType font size"
+ depends on CONSOLE_TRUETYPE
+ default 18
+ help
+ This sets the font size for the console. The size is measured in
+ pixels and is the nominal height of a character. Note that fonts
+ are commonly measured in 'points', being 1/72 inch (about 3.52mm).
+ However that measurement depends on the size of your display and
+ there is no standard display density. At present there is not a
+ method to select the display's physical size, which would allow
+ U-Boot to calculate the correct font size.
+
+source "drivers/video/fonts/Kconfig"
+
config VIDEO_VESA
bool "Enable VESA video driver support"
default n
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 1a76655..c9056cb 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_DM_PWM) += pwm_backlight.o
endif
obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o
obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o
+obj-$(CONFIG_CONSOLE_TRUETYPE) += console_truetype.o fonts/
endif

obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o
diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
new file mode 100644
index 0000000..b770ad4
--- /dev/null
+++ b/drivers/video/console_truetype.c
@@ -0,0 +1,533 @@
+/*
+ * Copyright (c) 2016 Google, Inc
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <video.h>
+#include <video_console.h>
+
+/* Functions needed by stb_truetype.h */
+static int tt_floor(double val)
+{
+ if (val < 0)
+ return (int)(val - 0.999);
+
+ return (int)val;
+}
+
+static int tt_ceil(double val)
+{
+ if (val < 0)
+ return (int)val;
+
+ return (int)(val + 0.999);
+}
+
+static double frac(double val)
+{
+ return val - tt_floor(val);
+}
+
+static double tt_fabs(double x)
+{
+ return x < 0 ? -x : x;
+}
+
+ /*
+ * Simple square root algorithm. This is from:
+ * http://stackoverflow.com/questions/1623375/writing-your-own-square-root-function
+ * Written by Chihung Yu
+ * Creative Commons license
+ * http://creativecommons.org/licenses/by-sa/3.0/legalcode
+ * It has been modified to compile correctly, and for U-Boot style.
+ */
+static double tt_sqrt(double value)
+{
+ double lo = 1.0;
+ double hi = value;
+
+ while (hi - lo > 0.00001) {
+ double mid = lo + (hi - lo) / 2;
+
+ if (mid * mid - value > 0.00001)
+ hi = mid;
+ else
+ lo = mid;
+ }
+
+ return lo;
+}
+
+#define STBTT_ifloor tt_floor
+#define STBTT_iceil tt_ceil
+#define STBTT_fabs tt_fabs
+#define STBTT_sqrt tt_sqrt
+#define STBTT_malloc(size, u) ((void)(u), malloc(size))
+#define STBTT_free(size, u) ((void)(u), free(size))
+#define STBTT_assert(x)
+#define STBTT_strlen(x) strlen(x)
+#define STBTT_memcpy memcpy
+#define STBTT_memset memset
+
+#define STB_TRUETYPE_IMPLEMENTATION
+#include "stb_truetype.h"
+
+/**
+ * struct pos_info - Records a cursor position
+ *
+ * @xpos_frac: Fractional X position in pixels (multiplied by VID_FRAC_DIV)
+ * @ypos: Y position (pixels from the top)
+ */
+struct pos_info {
+ int xpos_frac;
+ int ypos;
+};
+
+/*
+ * Allow one for each character on the command line plus one for each newline.
+ * This is just an estimate, but it should not be exceeded.
+ */
+#define POS_HISTORY_SIZE (CONFIG_SYS_CBSIZE * 11 / 10)
+
+/**
+ * struct console_tt_priv - Private data for this driver
+ *
+ * @font_size: Vertical font size in pixels
+ * @font_data: Pointer to TrueType font file contents
+ * @font: TrueType font information for the current font
+ * @pos: List of cursor positions for each character written. This is
+ * used to handle backspace. We clear the frame buffer between
+ * the last position and the current position, thus erasing the
+ * last character. We record enough characters to go back to the
+ * start of the current command line.
+ * @pos_ptr: Current position in the position history
+ * @baseline: Pixel offset of the font's baseline from the cursor position.
+ * This is the 'ascent' of the font, scaled to pixel coordinates.
+ * It measures the distance from the baseline to the top of the
+ * font.
+ * @scale: Scale of the font. This is calculated from the pixel height
+ * of the font. It is used by the STB library to generate images
+ * of the correct size.
+ */
+struct console_tt_priv {
+ int font_size;
+ u8 *font_data;
+ stbtt_fontinfo font;
+ struct pos_info pos[POS_HISTORY_SIZE];
+ int pos_ptr;
+ int baseline;
+ double scale;
+};
+
+static int console_truetype_set_row(struct udevice *dev, uint row, int clr)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ void *line;
+ int pixels = priv->font_size * vid_priv->line_length;
+ int i;
+
+ line = vid_priv->fb + row * priv->font_size * vid_priv->line_length;
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+ case VIDEO_BPP8: {
+ uint8_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+ case VIDEO_BPP32: {
+ uint32_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+static int console_truetype_move_rows(struct udevice *dev, uint rowdst,
+ uint rowsrc, uint count)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ void *dst;
+ void *src;
+ int i, diff;
+
+ dst = vid_priv->fb + rowdst * priv->font_size * vid_priv->line_length;
+ src = vid_priv->fb + rowsrc * priv->font_size * vid_priv->line_length;
+ memmove(dst, src, priv->font_size * vid_priv->line_length * count);
+
+ /* Scroll up our position history */
+ diff = (rowsrc - rowdst) * priv->font_size;
+ for (i = 0; i < priv->pos_ptr; i++)
+ priv->pos[i].ypos -= diff;
+
+ return 0;
+}
+
+static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
+ char ch)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ stbtt_fontinfo *font = &priv->font;
+ int width, height, xoff, yoff;
+ double xpos, x_shift;
+ int lsb;
+ int width_frac, linenum;
+ struct pos_info *pos;
+ u8 *bits, *data;
+ int advance;
+ void *line;
+ int row;
+
+ /* First get some basic metrics about this character */
+ stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb);
+
+ /*
+ * First out our current X position in fractional pixels. If we wrote
+ * a character previously, using kerning to fine-tune the position of
+ * this character */
+ xpos = frac(VID_TO_PIXEL((double)x));
+ if (vc_priv->last_ch) {
+ xpos += priv->scale * stbtt_GetCodepointKernAdvance(font,
+ vc_priv->last_ch, ch);
+ }
+
+ /*
+ * Figure out where the cursor will move to after this character, and
+ * abort if we are out of space on this line. Also calculate the
+ * effective width of this character, which will be our return value:
+ * it dictates how much the cursor will move forward on the line.
+ */
+ x_shift = xpos - (double)tt_floor(xpos);
+ xpos += advance * priv->scale;
+ width_frac = (int)VID_TO_POS(xpos);
+ if (x + width_frac >= vc_priv->xsize_frac)
+ return -EAGAIN;
+
+ /* Write the current cursor position into history */
+ if (priv->pos_ptr < POS_HISTORY_SIZE) {
+ pos = &priv->pos[priv->pos_ptr];
+ pos->xpos_frac = vc_priv->xcur_frac;
+ pos->ypos = vc_priv->ycur;
+ priv->pos_ptr++;
+ }
+
+ /*
+ * Figure out how much past the start of a pixel we are, and pass this
+ * information into the render, which will return a 8-bit-per-pixel
+ * image of the character. For empty characters, like ' ', data will
+ * return NULL;
+ */
+ data = stbtt_GetCodepointBitmapSubpixel(font, priv->scale, priv->scale,
+ x_shift, 0, ch, &width, &height,
+ &xoff, &yoff);
+ if (!data)
+ return width_frac;
+
+ /* Figure out where to write the character in the frame buffer */
+ bits = data;
+ line = vid_priv->fb + y * vid_priv->line_length +
+ VID_TO_PIXEL(x) * VNBYTES(vid_priv->bpix);
+ linenum = priv->baseline + yoff;
+ if (linenum > 0)
+ line += linenum * vid_priv->line_length;
+
+ /*
+ * Write a row at a time, converting the 8bpp image into the colour
+ * depth of the display. We only expect white-on-black or the reverse
+ * so the code only handles this simple case.
+ */
+ for (row = 0; row < height; row++) {
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = (uint16_t *)line + xoff;
+ int i;
+
+ for (i = 0; i < width; i++) {
+ int val = *bits;
+ int out;
+
+ if (vid_priv->colour_bg)
+ val = 255 - val;
+ out = val >> 3 |
+ (val >> 2) << 5 |
+ (val >> 3) << 11;
+ if (vid_priv->colour_fg)
+ *dst++ |= out;
+ else
+ *dst++ &= out;
+ bits++;
+ }
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+
+ line += vid_priv->line_length;
+ }
+ free(data);
+
+ return width_frac;
+}
+
+/**
+ * console_truetype_erase() - Erase a character
+ *
+ * This is used for backspace. We erase a square of the display within the
+ * given bounds.
+ *
+ * @dev: Device to update
+ * @xstart: X start position in pixels from the left
+ * @ystart: Y start position in pixels from the top
+ * @xend: X end position in pixels from the left
+ * @yend: Y end position in pixels from the top
+ * @clr: Value to write
+ * @return 0 if OK, -ENOSYS if the display depth is not supported
+ */
+static int console_truetype_erase(struct udevice *dev, int xstart, int ystart,
+ int xend, int yend, int clr)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ void *line;
+ int pixels = xend - xstart;
+ int row, i;
+
+ line = vid_priv->fb + ystart * vid_priv->line_length;
+ line += xstart * VNBYTES(vid_priv->bpix);
+ for (row = ystart; row < yend; row++) {
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+ case VIDEO_BPP8: {
+ uint8_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+ case VIDEO_BPP32: {
+ uint32_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+ line += vid_priv->line_length;
+ }
+
+ return 0;
+}
+
+/**
+ * console_truetype_backspace() - Handle a backspace operation
+ *
+ * This clears the previous character so that the console looks as if it had
+ * not been entered.
+ *
+ * @dev: Device to update
+ * @return 0 if OK, -ENOSYS if not supported
+ */
+static int console_truetype_backspace(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+ struct pos_info *pos;
+ int xend;
+
+ /*
+ * This indicates a very strange error higher in the stack. The caller
+ * has sent out n character and n + 1 backspaces.
+ */
+ if (!priv->pos_ptr)
+ return -ENOSYS;
+
+ /* Pop the last cursor position off the stack */
+ pos = &priv->pos[--priv->pos_ptr];
+
+ /*
+ * Figure out the end position for clearing. Normlly it is the current
+ * cursor position, but if we are clearing a character on the previous
+ * line, we clear from the end of the line.
+ */
+ if (pos->ypos == vc_priv->ycur)
+ xend = VID_TO_PIXEL(vc_priv->xcur_frac);
+ else
+ xend = vid_priv->xsize;
+
+ console_truetype_erase(dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos,
+ xend, pos->ypos + vc_priv->y_charsize,
+ vid_priv->colour_bg);
+
+ /* Move the cursor back to where it was when we pushed this record */
+ vc_priv->xcur_frac = pos->xpos_frac;
+ vc_priv->ycur = pos->ypos;
+
+ return 0;
+}
+
+static int console_truetype_entry_start(struct udevice *dev)
+{
+ struct console_tt_priv *priv = dev_get_priv(dev);
+
+ /* A new input line has start, so clear our history */
+ priv->pos_ptr = 0;
+
+ return 0;
+}
+
+/*
+ * Provides a list of fonts which can be obtained at run-time in U-Boot. These
+ * are compiled in by the Makefile.
+ *
+ * At present there is no mechanism to select a particular font - the first
+ * one found is the one that is used. But the build system and the code here
+ * supports multiple fonts, which may be useful for certain firmware screens.
+ */
+struct font_info {
+ char *name;
+ u8 *begin;
+ u8 *end;
+};
+
+#define FONT_DECL(_name) \
+ extern u8 __ttf_ ## _name ## _begin[]; \
+ extern u8 __ttf_ ## _name ## _end[];
+
+#define FONT_ENTRY(_name) { \
+ .name = #_name, \
+ .begin = __ttf_ ## _name ## _begin, \
+ .end = __ttf_ ## _name ## _end, \
+ }
+
+static struct font_info font_table[] = {
+ {} /* sentinel */
+};
+
+#define FONT_BEGIN(name) __ttf_ ## name ## _begin
+#define FONT_END(name) __ttf_ ## name ## _end
+#define FONT_IS_VALID(name) (abs(FONT_END(name) - FONT_BEGIN) > 4)
+
+/**
+ * console_truetype_find_font() - Find a suitable font
+ *
+ * This searched for the first available font.
+ *
+ * @return pointer to the font, or NULL if none is found
+ */
+static u8 *console_truetype_find_font(void)
+{
+ struct font_info *tab;
+
+ for (tab = font_table; tab->begin; tab++) {
+ if (abs(tab->begin - tab->end) > 4) {
+ debug("%s: Font '%s', at %p, size %lx\n", __func__,
+ tab->name, tab->begin,
+ (ulong)(tab->end - tab->begin));
+ return tab->begin;
+ }
+ }
+
+ return NULL;
+}
+
+static int console_truetype_probe(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+ stbtt_fontinfo *font = &priv->font;
+ int ascent;
+
+ debug("%s: start\n", __func__);
+ if (vid_priv->font_size)
+ priv->font_size = vid_priv->font_size;
+ else
+ priv->font_size = CONFIG_CONSOLE_TRUETYPE_SIZE;
+ priv->font_data = console_truetype_find_font();
+ if (!priv->font_data) {
+ debug("%s: Could not find any fonts\n", __func__);
+ return -EBFONT;
+ }
+
+ vc_priv->x_charsize = priv->font_size;
+ vc_priv->y_charsize = priv->font_size;
+ vc_priv->xstart_frac = VID_TO_POS(2);
+ vc_priv->cols = vid_priv->xsize / priv->font_size;
+ vc_priv->rows = vid_priv->ysize / priv->font_size;
+ vc_priv->tab_width_frac = VID_TO_POS(priv->font_size) * 8 / 2;
+
+ if (!stbtt_InitFont(font, priv->font_data, 0)) {
+ debug("%s: Font init failed\n", __func__);
+ return -EPERM;
+ }
+
+ /* Pre-calculate some things we will need regularly */
+ priv->scale = stbtt_ScaleForPixelHeight(font, priv->font_size);
+ stbtt_GetFontVMetrics(font, &ascent, 0, 0);
+ priv->baseline = (int)(ascent * priv->scale);
+ debug("%s: ready\n", __func__);
+
+ return 0;
+}
+
+struct vidconsole_ops console_truetype_ops = {
+ .putc_xy = console_truetype_putc_xy,
+ .move_rows = console_truetype_move_rows,
+ .set_row = console_truetype_set_row,
+ .backspace = console_truetype_backspace,
+ .entry_start = console_truetype_entry_start,
+};
+
+U_BOOT_DRIVER(vidconsole_truetype) = {
+ .name = "vidconsole_tt",
+ .id = UCLASS_VIDEO_CONSOLE,
+ .ops = &console_truetype_ops,
+ .probe = console_truetype_probe,
+ .priv_auto_alloc_size = sizeof(struct console_tt_priv),
+};
diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig
new file mode 100644
index 0000000..ad16ce6
--- /dev/null
+++ b/drivers/video/fonts/Kconfig
@@ -0,0 +1,7 @@
+#
+# Video fonts
+#
+
+menu "TrueType Fonts"
+
+endmenu
diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile
new file mode 100644
index 0000000..6ab4647
--- /dev/null
+++ b/drivers/video/fonts/Makefile
@@ -0,0 +1,6 @@
+#
+# (C) Copyright 2000-2007
+# Wolfgang Denk, DENX Software Engineering, ***@denx.de.
+#
+# SPDX-License-Identifier: GPL-2.0+
+#
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 24d537e..2189fce 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -201,11 +201,18 @@ static int video_post_probe(struct udevice *dev)
* it might be useful to support only bitmap drawing on the device
* for boards that don't need to display text.
*/
- snprintf(name, sizeof(name), "%s.vidconsole", dev->name);
+ if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) {
+ snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name);
+ strcpy(drv, "vidconsole_tt");
+ } else {
+ snprintf(name, sizeof(name), "%s.vidconsole%d", dev->name,
+ priv->rot);
+ snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot);
+ }
+
str = strdup(name);
if (!str)
return -ENOMEM;
- snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot);
ret = device_bind_driver(dev, drv, str, &cons);
if (ret) {
debug("%s: Cannot bind console driver\n", __func__);
--
2.6.0.rc2.230.g3dd15c0
Anatolij Gustschin
2016-01-23 00:21:50 UTC
Permalink
From: Simon Glass <***@chromium.org>

The existing 8x16 font is adequate for most purposes. It is small and fast.
However for boot screens where information must be presented to the user,
the console font is not ideal. Common requirements are larger and
better-looking fonts.

This console driver can use TrueType fonts built into U-Boot, and render
them at any size. This can be used in scripts to place text as needed on
the display.

This driver is not really designed to operate with the command line. Much
of U-Boot expects a fixed-width font. But to keep things working correctly,
rudimentary support for the console is provided. The main missing feature is
support for command-line editing.

Signed-off-by: Simon Glass <***@chromium.org>
Signed-off-by: Anatolij Gustschin <***@denx.de>
---
Changes in v2: rebased

drivers/video/Kconfig | 24 ++
drivers/video/Makefile | 1 +
drivers/video/console_truetype.c | 533 ++++++++++++++++++++++++++++++++++++++
drivers/video/fonts/Kconfig | 7 +
drivers/video/fonts/Makefile | 6 +
drivers/video/video-uclass.c | 11 +-
6 files changed, 580 insertions(+), 2 deletions(-)
create mode 100644 drivers/video/console_truetype.c
create mode 100644 drivers/video/fonts/Kconfig
create mode 100644 drivers/video/fonts/Makefile

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index f06ecfe..279c5f9 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -67,6 +67,30 @@ config CONSOLE_ROTATION
struct video_priv: 0=unrotated, 1=90 degrees clockwise, 2=180
degrees, 3=270 degrees.

+config CONSOLE_TRUETYPE
+ bool "Support a console that uses TrueType fonts"
+ depends on DM_VIDEO
+ help
+ TrueTrype fonts can provide outline-drawing capability rather than
+ needing to provide a bitmap for each font and size that is needed.
+ With this option you can adjust the text size and use a variety of
+ fonts. Note that this is noticeably slower than with normal console.
+
+config CONSOLE_TRUETYPE_SIZE
+ int "TrueType font size"
+ depends on CONSOLE_TRUETYPE
+ default 18
+ help
+ This sets the font size for the console. The size is measured in
+ pixels and is the nominal height of a character. Note that fonts
+ are commonly measured in 'points', being 1/72 inch (about 3.52mm).
+ However that measurement depends on the size of your display and
+ there is no standard display density. At present there is not a
+ method to select the display's physical size, which would allow
+ U-Boot to calculate the correct font size.
+
+source "drivers/video/fonts/Kconfig"
+
config VIDEO_VESA
bool "Enable VESA video driver support"
default n
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 01f4be5..b2ee40d 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o
obj-$(CONFIG_DM_VIDEO) += video_bmp.o
obj-$(CONFIG_CONSOLE_NORMAL) += console_normal.o
obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o
+obj-$(CONFIG_CONSOLE_TRUETYPE) += console_truetype.o fonts/
endif

obj-$(CONFIG_ATI_RADEON_FB) += ati_radeon_fb.o videomodes.o
diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
new file mode 100644
index 0000000..b770ad4
--- /dev/null
+++ b/drivers/video/console_truetype.c
@@ -0,0 +1,533 @@
+/*
+ * Copyright (c) 2016 Google, Inc
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <video.h>
+#include <video_console.h>
+
+/* Functions needed by stb_truetype.h */
+static int tt_floor(double val)
+{
+ if (val < 0)
+ return (int)(val - 0.999);
+
+ return (int)val;
+}
+
+static int tt_ceil(double val)
+{
+ if (val < 0)
+ return (int)val;
+
+ return (int)(val + 0.999);
+}
+
+static double frac(double val)
+{
+ return val - tt_floor(val);
+}
+
+static double tt_fabs(double x)
+{
+ return x < 0 ? -x : x;
+}
+
+ /*
+ * Simple square root algorithm. This is from:
+ * http://stackoverflow.com/questions/1623375/writing-your-own-square-root-function
+ * Written by Chihung Yu
+ * Creative Commons license
+ * http://creativecommons.org/licenses/by-sa/3.0/legalcode
+ * It has been modified to compile correctly, and for U-Boot style.
+ */
+static double tt_sqrt(double value)
+{
+ double lo = 1.0;
+ double hi = value;
+
+ while (hi - lo > 0.00001) {
+ double mid = lo + (hi - lo) / 2;
+
+ if (mid * mid - value > 0.00001)
+ hi = mid;
+ else
+ lo = mid;
+ }
+
+ return lo;
+}
+
+#define STBTT_ifloor tt_floor
+#define STBTT_iceil tt_ceil
+#define STBTT_fabs tt_fabs
+#define STBTT_sqrt tt_sqrt
+#define STBTT_malloc(size, u) ((void)(u), malloc(size))
+#define STBTT_free(size, u) ((void)(u), free(size))
+#define STBTT_assert(x)
+#define STBTT_strlen(x) strlen(x)
+#define STBTT_memcpy memcpy
+#define STBTT_memset memset
+
+#define STB_TRUETYPE_IMPLEMENTATION
+#include "stb_truetype.h"
+
+/**
+ * struct pos_info - Records a cursor position
+ *
+ * @xpos_frac: Fractional X position in pixels (multiplied by VID_FRAC_DIV)
+ * @ypos: Y position (pixels from the top)
+ */
+struct pos_info {
+ int xpos_frac;
+ int ypos;
+};
+
+/*
+ * Allow one for each character on the command line plus one for each newline.
+ * This is just an estimate, but it should not be exceeded.
+ */
+#define POS_HISTORY_SIZE (CONFIG_SYS_CBSIZE * 11 / 10)
+
+/**
+ * struct console_tt_priv - Private data for this driver
+ *
+ * @font_size: Vertical font size in pixels
+ * @font_data: Pointer to TrueType font file contents
+ * @font: TrueType font information for the current font
+ * @pos: List of cursor positions for each character written. This is
+ * used to handle backspace. We clear the frame buffer between
+ * the last position and the current position, thus erasing the
+ * last character. We record enough characters to go back to the
+ * start of the current command line.
+ * @pos_ptr: Current position in the position history
+ * @baseline: Pixel offset of the font's baseline from the cursor position.
+ * This is the 'ascent' of the font, scaled to pixel coordinates.
+ * It measures the distance from the baseline to the top of the
+ * font.
+ * @scale: Scale of the font. This is calculated from the pixel height
+ * of the font. It is used by the STB library to generate images
+ * of the correct size.
+ */
+struct console_tt_priv {
+ int font_size;
+ u8 *font_data;
+ stbtt_fontinfo font;
+ struct pos_info pos[POS_HISTORY_SIZE];
+ int pos_ptr;
+ int baseline;
+ double scale;
+};
+
+static int console_truetype_set_row(struct udevice *dev, uint row, int clr)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ void *line;
+ int pixels = priv->font_size * vid_priv->line_length;
+ int i;
+
+ line = vid_priv->fb + row * priv->font_size * vid_priv->line_length;
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+ case VIDEO_BPP8: {
+ uint8_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+ case VIDEO_BPP32: {
+ uint32_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+static int console_truetype_move_rows(struct udevice *dev, uint rowdst,
+ uint rowsrc, uint count)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ void *dst;
+ void *src;
+ int i, diff;
+
+ dst = vid_priv->fb + rowdst * priv->font_size * vid_priv->line_length;
+ src = vid_priv->fb + rowsrc * priv->font_size * vid_priv->line_length;
+ memmove(dst, src, priv->font_size * vid_priv->line_length * count);
+
+ /* Scroll up our position history */
+ diff = (rowsrc - rowdst) * priv->font_size;
+ for (i = 0; i < priv->pos_ptr; i++)
+ priv->pos[i].ypos -= diff;
+
+ return 0;
+}
+
+static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
+ char ch)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct udevice *vid = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ stbtt_fontinfo *font = &priv->font;
+ int width, height, xoff, yoff;
+ double xpos, x_shift;
+ int lsb;
+ int width_frac, linenum;
+ struct pos_info *pos;
+ u8 *bits, *data;
+ int advance;
+ void *line;
+ int row;
+
+ /* First get some basic metrics about this character */
+ stbtt_GetCodepointHMetrics(font, ch, &advance, &lsb);
+
+ /*
+ * First out our current X position in fractional pixels. If we wrote
+ * a character previously, using kerning to fine-tune the position of
+ * this character */
+ xpos = frac(VID_TO_PIXEL((double)x));
+ if (vc_priv->last_ch) {
+ xpos += priv->scale * stbtt_GetCodepointKernAdvance(font,
+ vc_priv->last_ch, ch);
+ }
+
+ /*
+ * Figure out where the cursor will move to after this character, and
+ * abort if we are out of space on this line. Also calculate the
+ * effective width of this character, which will be our return value:
+ * it dictates how much the cursor will move forward on the line.
+ */
+ x_shift = xpos - (double)tt_floor(xpos);
+ xpos += advance * priv->scale;
+ width_frac = (int)VID_TO_POS(xpos);
+ if (x + width_frac >= vc_priv->xsize_frac)
+ return -EAGAIN;
+
+ /* Write the current cursor position into history */
+ if (priv->pos_ptr < POS_HISTORY_SIZE) {
+ pos = &priv->pos[priv->pos_ptr];
+ pos->xpos_frac = vc_priv->xcur_frac;
+ pos->ypos = vc_priv->ycur;
+ priv->pos_ptr++;
+ }
+
+ /*
+ * Figure out how much past the start of a pixel we are, and pass this
+ * information into the render, which will return a 8-bit-per-pixel
+ * image of the character. For empty characters, like ' ', data will
+ * return NULL;
+ */
+ data = stbtt_GetCodepointBitmapSubpixel(font, priv->scale, priv->scale,
+ x_shift, 0, ch, &width, &height,
+ &xoff, &yoff);
+ if (!data)
+ return width_frac;
+
+ /* Figure out where to write the character in the frame buffer */
+ bits = data;
+ line = vid_priv->fb + y * vid_priv->line_length +
+ VID_TO_PIXEL(x) * VNBYTES(vid_priv->bpix);
+ linenum = priv->baseline + yoff;
+ if (linenum > 0)
+ line += linenum * vid_priv->line_length;
+
+ /*
+ * Write a row at a time, converting the 8bpp image into the colour
+ * depth of the display. We only expect white-on-black or the reverse
+ * so the code only handles this simple case.
+ */
+ for (row = 0; row < height; row++) {
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = (uint16_t *)line + xoff;
+ int i;
+
+ for (i = 0; i < width; i++) {
+ int val = *bits;
+ int out;
+
+ if (vid_priv->colour_bg)
+ val = 255 - val;
+ out = val >> 3 |
+ (val >> 2) << 5 |
+ (val >> 3) << 11;
+ if (vid_priv->colour_fg)
+ *dst++ |= out;
+ else
+ *dst++ &= out;
+ bits++;
+ }
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+
+ line += vid_priv->line_length;
+ }
+ free(data);
+
+ return width_frac;
+}
+
+/**
+ * console_truetype_erase() - Erase a character
+ *
+ * This is used for backspace. We erase a square of the display within the
+ * given bounds.
+ *
+ * @dev: Device to update
+ * @xstart: X start position in pixels from the left
+ * @ystart: Y start position in pixels from the top
+ * @xend: X end position in pixels from the left
+ * @yend: Y end position in pixels from the top
+ * @clr: Value to write
+ * @return 0 if OK, -ENOSYS if the display depth is not supported
+ */
+static int console_truetype_erase(struct udevice *dev, int xstart, int ystart,
+ int xend, int yend, int clr)
+{
+ struct video_priv *vid_priv = dev_get_uclass_priv(dev->parent);
+ void *line;
+ int pixels = xend - xstart;
+ int row, i;
+
+ line = vid_priv->fb + ystart * vid_priv->line_length;
+ line += xstart * VNBYTES(vid_priv->bpix);
+ for (row = ystart; row < yend; row++) {
+ switch (vid_priv->bpix) {
+#ifdef CONFIG_VIDEO_BPP8
+ case VIDEO_BPP8: {
+ uint8_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP16
+ case VIDEO_BPP16: {
+ uint16_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+#ifdef CONFIG_VIDEO_BPP32
+ case VIDEO_BPP32: {
+ uint32_t *dst = line;
+
+ for (i = 0; i < pixels; i++)
+ *dst++ = clr;
+ break;
+ }
+#endif
+ default:
+ return -ENOSYS;
+ }
+ line += vid_priv->line_length;
+ }
+
+ return 0;
+}
+
+/**
+ * console_truetype_backspace() - Handle a backspace operation
+ *
+ * This clears the previous character so that the console looks as if it had
+ * not been entered.
+ *
+ * @dev: Device to update
+ * @return 0 if OK, -ENOSYS if not supported
+ */
+static int console_truetype_backspace(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+ struct pos_info *pos;
+ int xend;
+
+ /*
+ * This indicates a very strange error higher in the stack. The caller
+ * has sent out n character and n + 1 backspaces.
+ */
+ if (!priv->pos_ptr)
+ return -ENOSYS;
+
+ /* Pop the last cursor position off the stack */
+ pos = &priv->pos[--priv->pos_ptr];
+
+ /*
+ * Figure out the end position for clearing. Normlly it is the current
+ * cursor position, but if we are clearing a character on the previous
+ * line, we clear from the end of the line.
+ */
+ if (pos->ypos == vc_priv->ycur)
+ xend = VID_TO_PIXEL(vc_priv->xcur_frac);
+ else
+ xend = vid_priv->xsize;
+
+ console_truetype_erase(dev, VID_TO_PIXEL(pos->xpos_frac), pos->ypos,
+ xend, pos->ypos + vc_priv->y_charsize,
+ vid_priv->colour_bg);
+
+ /* Move the cursor back to where it was when we pushed this record */
+ vc_priv->xcur_frac = pos->xpos_frac;
+ vc_priv->ycur = pos->ypos;
+
+ return 0;
+}
+
+static int console_truetype_entry_start(struct udevice *dev)
+{
+ struct console_tt_priv *priv = dev_get_priv(dev);
+
+ /* A new input line has start, so clear our history */
+ priv->pos_ptr = 0;
+
+ return 0;
+}
+
+/*
+ * Provides a list of fonts which can be obtained at run-time in U-Boot. These
+ * are compiled in by the Makefile.
+ *
+ * At present there is no mechanism to select a particular font - the first
+ * one found is the one that is used. But the build system and the code here
+ * supports multiple fonts, which may be useful for certain firmware screens.
+ */
+struct font_info {
+ char *name;
+ u8 *begin;
+ u8 *end;
+};
+
+#define FONT_DECL(_name) \
+ extern u8 __ttf_ ## _name ## _begin[]; \
+ extern u8 __ttf_ ## _name ## _end[];
+
+#define FONT_ENTRY(_name) { \
+ .name = #_name, \
+ .begin = __ttf_ ## _name ## _begin, \
+ .end = __ttf_ ## _name ## _end, \
+ }
+
+static struct font_info font_table[] = {
+ {} /* sentinel */
+};
+
+#define FONT_BEGIN(name) __ttf_ ## name ## _begin
+#define FONT_END(name) __ttf_ ## name ## _end
+#define FONT_IS_VALID(name) (abs(FONT_END(name) - FONT_BEGIN) > 4)
+
+/**
+ * console_truetype_find_font() - Find a suitable font
+ *
+ * This searched for the first available font.
+ *
+ * @return pointer to the font, or NULL if none is found
+ */
+static u8 *console_truetype_find_font(void)
+{
+ struct font_info *tab;
+
+ for (tab = font_table; tab->begin; tab++) {
+ if (abs(tab->begin - tab->end) > 4) {
+ debug("%s: Font '%s', at %p, size %lx\n", __func__,
+ tab->name, tab->begin,
+ (ulong)(tab->end - tab->begin));
+ return tab->begin;
+ }
+ }
+
+ return NULL;
+}
+
+static int console_truetype_probe(struct udevice *dev)
+{
+ struct vidconsole_priv *vc_priv = dev_get_uclass_priv(dev);
+ struct console_tt_priv *priv = dev_get_priv(dev);
+ struct udevice *vid_dev = dev->parent;
+ struct video_priv *vid_priv = dev_get_uclass_priv(vid_dev);
+ stbtt_fontinfo *font = &priv->font;
+ int ascent;
+
+ debug("%s: start\n", __func__);
+ if (vid_priv->font_size)
+ priv->font_size = vid_priv->font_size;
+ else
+ priv->font_size = CONFIG_CONSOLE_TRUETYPE_SIZE;
+ priv->font_data = console_truetype_find_font();
+ if (!priv->font_data) {
+ debug("%s: Could not find any fonts\n", __func__);
+ return -EBFONT;
+ }
+
+ vc_priv->x_charsize = priv->font_size;
+ vc_priv->y_charsize = priv->font_size;
+ vc_priv->xstart_frac = VID_TO_POS(2);
+ vc_priv->cols = vid_priv->xsize / priv->font_size;
+ vc_priv->rows = vid_priv->ysize / priv->font_size;
+ vc_priv->tab_width_frac = VID_TO_POS(priv->font_size) * 8 / 2;
+
+ if (!stbtt_InitFont(font, priv->font_data, 0)) {
+ debug("%s: Font init failed\n", __func__);
+ return -EPERM;
+ }
+
+ /* Pre-calculate some things we will need regularly */
+ priv->scale = stbtt_ScaleForPixelHeight(font, priv->font_size);
+ stbtt_GetFontVMetrics(font, &ascent, 0, 0);
+ priv->baseline = (int)(ascent * priv->scale);
+ debug("%s: ready\n", __func__);
+
+ return 0;
+}
+
+struct vidconsole_ops console_truetype_ops = {
+ .putc_xy = console_truetype_putc_xy,
+ .move_rows = console_truetype_move_rows,
+ .set_row = console_truetype_set_row,
+ .backspace = console_truetype_backspace,
+ .entry_start = console_truetype_entry_start,
+};
+
+U_BOOT_DRIVER(vidconsole_truetype) = {
+ .name = "vidconsole_tt",
+ .id = UCLASS_VIDEO_CONSOLE,
+ .ops = &console_truetype_ops,
+ .probe = console_truetype_probe,
+ .priv_auto_alloc_size = sizeof(struct console_tt_priv),
+};
diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig
new file mode 100644
index 0000000..ad16ce6
--- /dev/null
+++ b/drivers/video/fonts/Kconfig
@@ -0,0 +1,7 @@
+#
+# Video fonts
+#
+
+menu "TrueType Fonts"
+
+endmenu
diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile
new file mode 100644
index 0000000..6ab4647
--- /dev/null
+++ b/drivers/video/fonts/Makefile
@@ -0,0 +1,6 @@
+#
+# (C) Copyright 2000-2007
+# Wolfgang Denk, DENX Software Engineering, ***@denx.de.
+#
+# SPDX-License-Identifier: GPL-2.0+
+#
diff --git a/drivers/video/video-uclass.c b/drivers/video/video-uclass.c
index 63d0d9d..13e8a88 100644
--- a/drivers/video/video-uclass.c
+++ b/drivers/video/video-uclass.c
@@ -194,11 +194,18 @@ static int video_post_probe(struct udevice *dev)
* it might be useful to support only bitmap drawing on the device
* for boards that don't need to display text.
*/
- snprintf(name, sizeof(name), "%s.vidconsole", dev->name);
+ if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE)) {
+ snprintf(name, sizeof(name), "%s.vidconsole_tt", dev->name);
+ strcpy(drv, "vidconsole_tt");
+ } else {
+ snprintf(name, sizeof(name), "%s.vidconsole%d", dev->name,
+ priv->rot);
+ snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot);
+ }
+
str = strdup(name);
if (!str)
return -ENOMEM;
- snprintf(drv, sizeof(drv), "vidconsole%d", priv->rot);
ret = device_bind_driver(dev, drv, str, &cons);
if (ret) {
debug("%s: Cannot bind console driver\n", __func__);
--
1.7.9.5
Simon Glass
2016-01-15 01:10:51 UTC
Permalink
This adds tests for the different character types, line wrap, scrolling and
backspace.

Signed-off-by: Simon Glass <***@chromium.org>
---

test/dm/video.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)

diff --git a/test/dm/video.c b/test/dm/video.c
index 86e6f20..44fd628 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -288,3 +288,66 @@ static int dm_test_video_bmp_comp(struct unit_test_state *uts)
return 0;
}
DM_TEST(dm_test_video_bmp_comp, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test TrueType console */
+static int dm_test_video_truetype(struct unit_test_state *uts)
+{
+ struct udevice *dev, *con;
+ const char *test_string = "Criticism may not be agreeable, but it is necessary. It fulfils the same function as pain in the human body. It calls attention to an unhealthy state of things. Some see private enterprise as a predatory target to be shot, others as a cow to be milked, but few are those who see it as a sturdy horse pulling the wagon. The \aprice OF\b\bof greatness\n\tis responsibility.\n\nBye";
+ const char *s;
+
+ ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+ ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
+ for (s = test_string; *s; s++)
+ vidconsole_put_char(con, *s);
+ ut_asserteq(12619, compress_frame_buffer(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_video_truetype, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test scrolling TrueType console */
+static int dm_test_video_truetype_scroll(struct unit_test_state *uts)
+{
+ struct sandbox_sdl_plat *plat;
+ struct udevice *dev, *con;
+ const char *test_string = "Criticism may not be agreeable, but it is necessary. It fulfils the same function as pain in the human body. It calls attention to an unhealthy state of things. Some see private enterprise as a predatory target to be shot, others as a cow to be milked, but few are those who see it as a sturdy horse pulling the wagon. The \aprice OF\b\bof greatness\n\tis responsibility.\n\nBye";
+ const char *s;
+
+ ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev));
+ ut_assert(!device_active(dev));
+ plat = dev_get_platdata(dev);
+ plat->font_size = 100;
+
+ ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+ ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
+ for (s = test_string; *s; s++)
+ vidconsole_put_char(con, *s);
+ ut_asserteq(33849, compress_frame_buffer(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_video_truetype_scroll, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
+
+/* Test TrueType backspace, within and across lines */
+static int dm_test_video_truetype_bs(struct unit_test_state *uts)
+{
+ struct sandbox_sdl_plat *plat;
+ struct udevice *dev, *con;
+ const char *test_string = "...Criticism may or may\b\b\b\b\b\bnot be agreeable, but seldom it is necessary\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bit is necessary. It fulfils the same function as pain in the human body. It calls attention to an unhealthy state of things.";
+ const char *s;
+
+ ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev));
+ ut_assert(!device_active(dev));
+ plat = dev_get_platdata(dev);
+ plat->font_size = 100;
+
+ ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
+ ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
+ for (s = test_string; *s; s++)
+ vidconsole_put_char(con, *s);
+ ut_asserteq(34871, compress_frame_buffer(dev));
+
+ return 0;
+}
+DM_TEST(dm_test_video_truetype_bs, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:52 UTC
Permalink
This should be 'rot', not 'tor'.

Signed-off-by: Simon Glass <***@chromium.org>
---

include/video.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/video.h b/include/video.h
index 5ed0999..cc2b35a 100644
--- a/include/video.h
+++ b/include/video.h
@@ -54,7 +54,7 @@ enum video_log2_bpp {
*
* @xsize: Number of pixel columns (e.g. 1366)
* @ysize: Number of pixels rows (e.g.. 768)
- * @tor: Display rotation (0=none, 1=90 degrees clockwise, etc.)
+ * @rot: Display rotation (0=none, 1=90 degrees clockwise, etc.)
* @bpix: Encoded bits per pixel
* @vidconsole_drv_name: Driver to use for the text console, NULL to
* select automatically
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:43 UTC
Permalink
This provides a good-looking font for user prompts.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/console_truetype.c | 5 +++++
drivers/video/fonts/Kconfig | 13 +++++++++++++
drivers/video/fonts/Makefile | 2 ++
drivers/video/fonts/nimbus_sans_l_regular.ttf | Bin 0 -> 61660 bytes
4 files changed, 20 insertions(+)
create mode 100644 drivers/video/fonts/nimbus_sans_l_regular.ttf

diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index b770ad4..46c5205 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -444,7 +444,12 @@ struct font_info {
.end = __ttf_ ## _name ## _end, \
}

+FONT_DECL(nimbus_sans_l_regular);
+
static struct font_info font_table[] = {
+#ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS
+ FONT_ENTRY(nimbus_sans_l_regular),
+#endif
{} /* sentinel */
};

diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig
index ad16ce6..ded3e5e 100644
--- a/drivers/video/fonts/Kconfig
+++ b/drivers/video/fonts/Kconfig
@@ -4,4 +4,17 @@

menu "TrueType Fonts"

+config CONSOLE_TRUETYPE_NIMBUS
+ bool "Nimbus Sans Regular"
+ depends on CONSOLE_TRUETYPE
+ help
+ Nimbus Sans L is a version of Nimbus Sans using Adobe font sources.
+ It was designed in 1987. A subset of Nimbus Sans L were released
+ under the GPL. Although the characters are not exactly the same,
+ Nimbus Sans L has metrics almost identical to Helvetica and Arial.
+ (From Wikipedia, the free encyclopedia)
+ From: https://fontlibrary.org/en/font/nimbus-sans-l
+ License: GNU GPL v3
+ http://www.gnu.org/copyleft/gpl.html
+
endmenu
diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile
index 6ab4647..68f4c3b 100644
--- a/drivers/video/fonts/Makefile
+++ b/drivers/video/fonts/Makefile
@@ -4,3 +4,5 @@
#
# SPDX-License-Identifier: GPL-2.0+
#
+
+obj-$(CONFIG_CONSOLE_TRUETYPE_NIMBUS) += nimbus_sans_l_regular.o
diff --git a/drivers/video/fonts/nimbus_sans_l_regular.ttf b/drivers/video/fonts/nimbus_sans_l_regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..3bd694d8ee55d8eaf468abad40bf072896a757d1
GIT binary patch
literal 61660
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
***@xSEV^uGWA

literal 0
HcmV?d00001
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:50 UTC
Permalink
Enable this feature so that truetype fonts can be used on the sandbox
console. Update the tests to select the normal/rotated console when needed.

Signed-off-by: Simon Glass <***@chromium.org>
---

configs/sandbox_defconfig | 2 ++
test/dm/video.c | 21 ++++++++++++++++++++-
2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index aa92726..119cee6 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -78,6 +78,8 @@ CONFIG_USB_KEYBOARD=y
CONFIG_SYS_USB_EVENT_POLL=y
CONFIG_DM_VIDEO=y
CONFIG_CONSOLE_ROTATION=y
+CONFIG_CONSOLE_TRUETYPE=y
+CONFIG_CONSOLE_TRUETYPE_CANTORAONE=y
CONFIG_VIDEO_SANDBOX_SDL=y
CONFIG_SYS_VSNPRINTF=y
CONFIG_CMD_DHRYSTONE=y
diff --git a/test/dm/video.c b/test/dm/video.c
index cd00c96..86e6f20 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -86,6 +86,20 @@ static void __maybe_unused see_output(void)
while (1);
}

+/* Select the video console driver to use for a video device */
+static int select_vidconsole(struct unit_test_state *uts, const char *drv_name)
+{
+ struct sandbox_sdl_plat *plat;
+ struct udevice *dev;
+
+ ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev));
+ ut_assert(!device_active(dev));
+ plat = dev_get_platdata(dev);
+ plat->vidconsole_drv_name = "vidconsole0";
+
+ return 0;
+}
+
/* Test text output works on the video console */
static int dm_test_video_text(struct unit_test_state *uts)
{
@@ -95,6 +109,7 @@ static int dm_test_video_text(struct unit_test_state *uts)
#define WHITE 0xffff
#define SCROLL_LINES 100

+ ut_assertok(select_vidconsole(uts, "vidconsole0"));
ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
ut_asserteq(46, compress_frame_buffer(dev));

@@ -127,6 +142,7 @@ static int dm_test_video_chars(struct unit_test_state *uts)
const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very \amodest \bman\n\t\tand Has much to\b\bto be modest about.";
const char *s;

+ ut_assertok(select_vidconsole(uts, "vidconsole0"));
ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
for (s = test_string; *s; s++)
@@ -185,7 +201,10 @@ static int check_vidconsole_output(struct unit_test_state *uts, int rot,
/* Test text output through the console uclass */
static int dm_test_video_context(struct unit_test_state *uts)
{
- return check_vidconsole_output(uts, 0, 788, 453);
+ ut_assertok(select_vidconsole(uts, "vidconsole0"));
+ ut_assertok(check_vidconsole_output(uts, 0, 788, 453));
+
+ return 0;
}
DM_TEST(dm_test_video_context, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
--
2.6.0.rc2.230.g3dd15c0
Anatolij Gustschin
2016-01-23 00:32:18 UTC
Permalink
From: Simon Glass <***@chromium.org>

Enable this feature so that truetype fonts can be used on the sandbox
console. Update the tests to select the normal/rotated console when needed.

Signed-off-by: Simon Glass <***@chromium.org>
Signed-off-by: Anatolij Gustschin <***@denx.de>
---
Changes in v2: rebased

configs/sandbox_defconfig | 2 ++
test/dm/video.c | 21 ++++++++++++++++++++-
2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 2ebcba0..b5b81ca 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -78,6 +78,8 @@ CONFIG_USB_KEYBOARD=y
CONFIG_SYS_USB_EVENT_POLL=y
CONFIG_DM_VIDEO=y
CONFIG_CONSOLE_ROTATION=y
+CONFIG_CONSOLE_TRUETYPE=y
+CONFIG_CONSOLE_TRUETYPE_CANTORAONE=y
CONFIG_VIDEO_SANDBOX_SDL=y
CONFIG_CMD_DHRYSTONE=y
CONFIG_TPM=y
diff --git a/test/dm/video.c b/test/dm/video.c
index 4e3f9d5..a7e44e0 100644
--- a/test/dm/video.c
+++ b/test/dm/video.c
@@ -86,6 +86,20 @@ static void __maybe_unused see_output(void)
while (1);
}

+/* Select the video console driver to use for a video device */
+static int select_vidconsole(struct unit_test_state *uts, const char *drv_name)
+{
+ struct sandbox_sdl_plat *plat;
+ struct udevice *dev;
+
+ ut_assertok(uclass_find_device(UCLASS_VIDEO, 0, &dev));
+ ut_assert(!device_active(dev));
+ plat = dev_get_platdata(dev);
+ plat->vidconsole_drv_name = "vidconsole0";
+
+ return 0;
+}
+
/* Test text output works on the video console */
static int dm_test_video_text(struct unit_test_state *uts)
{
@@ -95,6 +109,7 @@ static int dm_test_video_text(struct unit_test_state *uts)
#define WHITE 0xffff
#define SCROLL_LINES 100

+ ut_assertok(select_vidconsole(uts, "vidconsole0"));
ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
ut_asserteq(46, compress_frame_buffer(dev));

@@ -127,6 +142,7 @@ static int dm_test_video_chars(struct unit_test_state *uts)
const char *test_string = "Well\b\b\b\bxhe is\r \n\ta very \amodest \bman\n\t\tand Has much to\b\bto be modest about.";
const char *s;

+ ut_assertok(select_vidconsole(uts, "vidconsole0"));
ut_assertok(uclass_get_device(UCLASS_VIDEO, 0, &dev));
ut_assertok(uclass_get_device(UCLASS_VIDEO_CONSOLE, 0, &con));
for (s = test_string; *s; s++)
@@ -185,7 +201,10 @@ static int check_vidconsole_output(struct unit_test_state *uts, int rot,
/* Test text output through the console uclass */
static int dm_test_video_context(struct unit_test_state *uts)
{
- return check_vidconsole_output(uts, 0, 788, 453);
+ ut_assertok(select_vidconsole(uts, "vidconsole0"));
+ ut_assertok(check_vidconsole_output(uts, 0, 788, 453));
+
+ return 0;
}
DM_TEST(dm_test_video_context, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
--
1.7.9.5
Simon Glass
2016-01-15 01:10:44 UTC
Permalink
This can be used when a mono-space font is needed, but the console font
is too small (such as with high-DPI displays).

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/console_truetype.c | 4 ++++
drivers/video/fonts/Kconfig | 12 ++++++++++++
drivers/video/fonts/Makefile | 1 +
drivers/video/fonts/ankacoder_c75_r.ttf | Bin 0 -> 65596 bytes
4 files changed, 17 insertions(+)
create mode 100644 drivers/video/fonts/ankacoder_c75_r.ttf

diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index 46c5205..a18611b 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -445,11 +445,15 @@ struct font_info {
}

FONT_DECL(nimbus_sans_l_regular);
+FONT_DECL(ankacoder_c75_r);

static struct font_info font_table[] = {
#ifdef CONFIG_CONSOLE_TRUETYPE_NIMBUS
FONT_ENTRY(nimbus_sans_l_regular),
#endif
+#ifdef CONFIG_CONSOLE_TRUETYPE_ANKACODER
+ FONT_ENTRY(ankacoder_c75_r),
+#endif
{} /* sentinel */
};

diff --git a/drivers/video/fonts/Kconfig b/drivers/video/fonts/Kconfig
index ded3e5e..ba1ccca 100644
--- a/drivers/video/fonts/Kconfig
+++ b/drivers/video/fonts/Kconfig
@@ -17,4 +17,16 @@ config CONSOLE_TRUETYPE_NIMBUS
License: GNU GPL v3
http://www.gnu.org/copyleft/gpl.html

+config CONSOLE_TRUETYPE_ANKACODER
+ bool "Anka Coder Narrow"
+ depends on CONSOLE_TRUETYPE
+ help
+ The Anka/Coder family is a monospaced, courier-width font for source
+ code and terminals, in two styles and weights. Anka/Coder Narrow was
+ developed for printing source code.
+ https://code.google.com/p/anka-coder-fonts/
+ From: https://fontlibrary.org/en/font/anka-coder-narrow
+ License: SIL Open Font Licence
+ http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
+
endmenu
diff --git a/drivers/video/fonts/Makefile b/drivers/video/fonts/Makefile
index 68f4c3b..58b1813 100644
--- a/drivers/video/fonts/Makefile
+++ b/drivers/video/fonts/Makefile
@@ -6,3 +6,4 @@
#

obj-$(CONFIG_CONSOLE_TRUETYPE_NIMBUS) += nimbus_sans_l_regular.o
+obj-$(CONFIG_CONSOLE_TRUETYPE_ANKACODER) += ankacoder_c75_r.o
diff --git a/drivers/video/fonts/ankacoder_c75_r.ttf b/drivers/video/fonts/ankacoder_c75_r.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..3b73dcf00e79b8b5b40f65b003680ded3cdfb592
GIT binary patch
literal 65596
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

literal 0
HcmV?d00001
--
2.6.0.rc2.230.g3dd15c0
Simon Glass
2016-01-15 01:10:34 UTC
Permalink
This is a header file which provides a fairly light-weight TrueType
rendering implementation. It is pulled from http://nothings.org/. The code
style does not comply with U-Boot but I think it is best to leave alone to
permit the source to be synced later if needed.

The only change is to fix a reference to fabs() which should route through
a macro to allow U-Boot to provide its own version.

Signed-off-by: Simon Glass <***@chromium.org>
---

drivers/video/stb_truetype.h | 3240 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 3240 insertions(+)
create mode 100644 drivers/video/stb_truetype.h

diff --git a/drivers/video/stb_truetype.h b/drivers/video/stb_truetype.h
new file mode 100644
index 0000000..91d8e6f
--- /dev/null
+++ b/drivers/video/stb_truetype.h
@@ -0,0 +1,3240 @@
+// stb_truetype.h - v1.08 - public domain
+// authored from 2009-2015 by Sean Barrett / RAD Game Tools
+//
+// This library processes TrueType files:
+// parse files
+// extract glyph metrics
+// extract glyph shapes
+// render glyphs to one-channel bitmaps with antialiasing (box filter)
+//
+// Todo:
+// non-MS cmaps
+// crashproof on bad data
+// hinting? (no longer patented)
+// cleartype-style AA?
+// optimize: use simple memory allocator for intermediates
+// optimize: build edge-list directly from curves
+// optimize: rasterize directly from curves?
+//
+// ADDITIONAL CONTRIBUTORS
+//
+// Mikko Mononen: compound shape support, more cmap formats
+// Tor Andersson: kerning, subpixel rendering
+//
+// Bug/warning reports/fixes:
+// "Zer" on mollyrocket (with fix)
+// Cass Everitt
+// stoiko (Haemimont Games)
+// Brian Hook
+// Walter van Niftrik
+// David Gow
+// David Given
+// Ivan-Assen Ivanov
+// Anthony Pesch
+// Johan Duparc
+// Hou Qiming
+// Fabian "ryg" Giesen
+// Martins Mozeiko
+// Cap Petschulat
+// Omar Cornut
+// github:aloucks
+// Peter LaValle
+// Sergey Popov
+// Giumo X. Clanjor
+// Higor Euripedes
+//
+// Misc other:
+// Ryan Gordon
+//
+// VERSION HISTORY
+//
+// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
+// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
+// variant PackFontRanges to pack and render in separate phases;
+// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);
+// fixed an assert() bug in the new rasterizer
+// replace assert() with STBTT_assert() in new rasterizer
+// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine)
+// also more precise AA rasterizer, except if shapes overlap
+// remove need for STBTT_sort
+// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC
+// 1.04 (2015-04-15) typo in example
+// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes
+//
+// Full history can be found at the end of this file.
+//
+// LICENSE
+//
+// This software is in the public domain. Where that dedication is not
+// recognized, you are granted a perpetual, irrevocable license to copy,
+// distribute, and modify this file as you see fit.
+//
+// USAGE
+//
+// Include this file in whatever places neeed to refer to it. In ONE C/C++
+// file, write:
+// #define STB_TRUETYPE_IMPLEMENTATION
+// before the #include of this file. This expands out the actual
+// implementation into that C/C++ file.
+//
+// To make the implementation private to the file that generates the implementation,
+// #define STBTT_STATIC
+//
+// Simple 3D API (don't ship this, but it's fine for tools and quick start)
+// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture
+// stbtt_GetBakedQuad() -- compute quad to draw for a given char
+//
+// Improved 3D API (more shippable):
+// #include "stb_rect_pack.h" -- optional, but you really want it
+// stbtt_PackBegin()
+// stbtt_PackSetOversample() -- for improved quality on small fonts
+// stbtt_PackFontRanges() -- pack and renders
+// stbtt_PackEnd()
+// stbtt_GetPackedQuad()
+//
+// "Load" a font file from a memory buffer (you have to keep the buffer loaded)
+// stbtt_InitFont()
+// stbtt_GetFontOffsetForIndex() -- use for TTC font collections
+//
+// Render a unicode codepoint to a bitmap
+// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap
+// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide
+// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be
+//
+// Character advance/positioning
+// stbtt_GetCodepointHMetrics()
+// stbtt_GetFontVMetrics()
+// stbtt_GetCodepointKernAdvance()
+//
+// Starting with version 1.06, the rasterizer was replaced with a new,
+// faster and generally-more-precise rasterizer. The new rasterizer more
+// accurately measures pixel coverage for anti-aliasing, except in the case
+// where multiple shapes overlap, in which case it overestimates the AA pixel
+// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If
+// this turns out to be a problem, you can re-enable the old rasterizer with
+// #define STBTT_RASTERIZER_VERSION 1
+// which will incur about a 15% speed hit.
+//
+// ADDITIONAL DOCUMENTATION
+//
+// Immediately after this block comment are a series of sample programs.
+//
+// After the sample programs is the "header file" section. This section
+// includes documentation for each API function.
+//
+// Some important concepts to understand to use this library:
+//
+// Codepoint
+// Characters are defined by unicode codepoints, e.g. 65 is
+// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is
+// the hiragana for "ma".
+//
+// Glyph
+// A visual character shape (every codepoint is rendered as
+// some glyph)
+//
+// Glyph index
+// A font-specific integer ID representing a glyph
+//
+// Baseline
+// Glyph shapes are defined relative to a baseline, which is the
+// bottom of uppercase characters. Characters extend both above
+// and below the baseline.
+//
+// Current Point
+// As you draw text to the screen, you keep track of a "current point"
+// which is the origin of each character. The current point's vertical
+// position is the baseline. Even "baked fonts" use this model.
+//
+// Vertical Font Metrics
+// The vertical qualities of the font, used to vertically position
+// and space the characters. See docs for stbtt_GetFontVMetrics.
+//
+// Font Size in Pixels or Points
+// The preferred interface for specifying font sizes in stb_truetype
+// is to specify how tall the font's vertical extent should be in pixels.
+// If that sounds good enough, skip the next paragraph.
+//
+// Most font APIs instead use "points", which are a common typographic
+// measurement for describing font size, defined as 72 points per inch.
+// stb_truetype provides a point API for compatibility. However, true
+// "per inch" conventions don't make much sense on computer displays
+// since they different monitors have different number of pixels per
+// inch. For example, Windows traditionally uses a convention that
+// there are 96 pixels per inch, thus making 'inch' measurements have
+// nothing to do with inches, and thus effectively defining a point to
+// be 1.333 pixels. Additionally, the TrueType font data provides
+// an explicit scale factor to scale a given font's glyphs to points,
+// but the author has observed that this scale factor is often wrong
+// for non-commercial fonts, thus making fonts scaled in points
+// according to the TrueType spec incoherently sized in practice.
+//
+// ADVANCED USAGE
+//
+// Quality:
+//
+// - Use the functions with Subpixel at the end to allow your characters
+// to have subpixel positioning. Since the font is anti-aliased, not
+// hinted, this is very import for quality. (This is not possible with
+// baked fonts.)
+//
+// - Kerning is now supported, and if you're supporting subpixel rendering
+// then kerning is worth using to give your text a polished look.
+//
+// Performance:
+//
+// - Convert Unicode codepoints to glyph indexes and operate on the glyphs;
+// if you don't do this, stb_truetype is forced to do the conversion on
+// every call.
+//
+// - There are a lot of memory allocations. We should modify it to take
+// a temp buffer and allocate from the temp buffer (without freeing),
+// should help performance a lot.
+//
+// NOTES
+//
+// The system uses the raw data found in the .ttf file without changing it
+// and without building auxiliary data structures. This is a bit inefficient
+// on little-endian systems (the data is big-endian), but assuming you're
+// caching the bitmaps or glyph shapes this shouldn't be a big deal.
+//
+// It appears to be very hard to programmatically determine what font a
+// given file is in a general way. I provide an API for this, but I don't
+// recommend it.
+//
+//
+// SOURCE STATISTICS (based on v0.6c, 2050 LOC)
+//
+// Documentation & header file 520 LOC \___ 660 LOC documentation
+// Sample code 140 LOC /
+// Truetype parsing 620 LOC ---- 620 LOC TrueType
+// Software rasterization 240 LOC \ .
+// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation
+// Bitmap management 100 LOC /
+// Baked bitmap interface 70 LOC /
+// Font name matching & access 150 LOC ---- 150
+// C runtime library abstraction 60 LOC ---- 60
+//
+//
+// PERFORMANCE MEASUREMENTS FOR 1.06:
+//
+// 32-bit 64-bit
+// Previous release: 8.83 s 7.68 s
+// Pool allocations: 7.72 s 6.34 s
+// Inline sort : 6.54 s 5.65 s
+// New rasterizer : 5.63 s 5.00 s
+
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+////
+//// SAMPLE PROGRAMS
+////
+//
+// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless
+//
+#if 0
+#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation
+#include "stb_truetype.h"
+
+unsigned char ttf_buffer[1<<20];
+unsigned char temp_bitmap[512*512];
+
+stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs
+GLuint ftex;
+
+void my_stbtt_initfont(void)
+{
+ fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb"));
+ stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits!
+ // can free ttf_buffer at this point
+ glGenTextures(1, &ftex);
+ glBindTexture(GL_TEXTURE_2D, ftex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap);
+ // can free temp_bitmap at this point
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+void my_stbtt_print(float x, float y, char *text)
+{
+ // assume orthographic projection with units = screen pixels, origin at top left
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, ftex);
+ glBegin(GL_QUADS);
+ while (*text) {
+ if (*text >= 32 && *text < 128) {
+ stbtt_aligned_quad q;
+ stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9
+ glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0);
+ glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0);
+ glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1);
+ glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1);
+ }
+ ++text;
+ }
+ glEnd();
+}
+#endif
+//
+//
+//////////////////////////////////////////////////////////////////////////////
+//
+// Complete program (this compiles): get a single bitmap, print as ASCII art
+//
+#if 0
+#include <stdio.h>
+#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation
+#include "stb_truetype.h"
+
+char ttf_buffer[1<<25];
+
+int main(int argc, char **argv)
+{
+ stbtt_fontinfo font;
+ unsigned char *bitmap;
+ int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20);
+
+ fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb"));
+
+ stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0));
+ bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0);
+
+ for (j=0; j < h; ++j) {
+ for (i=0; i < w; ++i)
+ putchar(" .:ioVM@"[bitmap[j*w+i]>>5]);
+ putchar('\n');
+ }
+ return 0;
+}
+#endif
+//
+// Output:
+//
+// .ii.
+// @@@@@@.
+// ***@Mio@@o
+// :i. ***@V
+// :oM@@M
+// :@@@***@M
+// @@o ***@M
+// :@@. ***@M
+// @@@o@@@@
+// :M@@V:@@.
+//
+//////////////////////////////////////////////////////////////////////////////
+//
+// Complete program: print "Hello World!" banner, with bugs
+//
+#if 0
+char buffer[24<<20];
+unsigned char screen[20][79];
+
+int main(int arg, char **argv)
+{
+ stbtt_fontinfo font;
+ int i,j,ascent,baseline,ch=0;
+ float scale, xpos=2; // leave a little padding in case the character extends left
+ char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness
+
+ fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb"));
+ stbtt_InitFont(&font, buffer, 0);
+
+ scale = stbtt_ScaleForPixelHeight(&font, 15);
+ stbtt_GetFontVMetrics(&font, &ascent,0,0);
+ baseline = (int) (ascent*scale);
+
+ while (text[ch]) {
+ int advance,lsb,x0,y0,x1,y1;
+ float x_shift = xpos - (float) floor(xpos);
+ stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb);
+ stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1);
+ stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]);
+ // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong
+ // because this API is really for baking character bitmaps into textures. if you want to render
+ // a sequence of characters, you really need to render each bitmap to a temp buffer, then
+ // "alpha blend" that into the working buffer
+ xpos += (advance * scale);
+ if (text[ch+1])
+ xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]);
+ ++ch;
+ }
+
+ for (j=0; j < 20; ++j) {
+ for (i=0; i < 78; ++i)
+ putchar(" .:ioVM@"[screen[j][i]>>5]);
+ putchar('\n');
+ }
+
+ return 0;
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+////
+//// INTEGRATION WITH YOUR CODEBASE
+////
+//// The following sections allow you to supply alternate definitions
+//// of C library functions used by stb_truetype.
+
+#ifdef STB_TRUETYPE_IMPLEMENTATION
+ // #define your own (u)stbtt_int8/16/32 before including to override this
+ #ifndef stbtt_uint8
+ typedef unsigned char stbtt_uint8;
+ typedef signed char stbtt_int8;
+ typedef unsigned short stbtt_uint16;
+ typedef signed short stbtt_int16;
+ typedef unsigned int stbtt_uint32;
+ typedef signed int stbtt_int32;
+ #endif
+
+ typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1];
+ typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1];
+
+ // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h
+ #ifndef STBTT_ifloor
+ #include <math.h>
+ #define STBTT_ifloor(x) ((int) floor(x))
+ #define STBTT_iceil(x) ((int) ceil(x))
+ #endif
+
+ #ifndef STBTT_sqrt
+ #include <math.h>
+ #define STBTT_sqrt(x) sqrt(x)
+ #endif
+
+ #ifndef STBTT_fabs
+ #include <math.h>
+ #define STBTT_fabs(x) fabs(x)
+ #endif
+
+ // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h
+ #ifndef STBTT_malloc
+ #include <stdlib.h>
+ #define STBTT_malloc(x,u) ((void)(u),malloc(x))
+ #define STBTT_free(x,u) ((void)(u),free(x))
+ #endif
+
+ #ifndef STBTT_assert
+ #include <assert.h>
+ #define STBTT_assert(x) assert(x)
+ #endif
+
+ #ifndef STBTT_strlen
+ #include <string.h>
+ #define STBTT_strlen(x) strlen(x)
+ #endif
+
+ #ifndef STBTT_memcpy
+ #include <memory.h>
+ #define STBTT_memcpy memcpy
+ #define STBTT_memset memset
+ #endif
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+////
+//// INTERFACE
+////
+////
+
+#ifndef __STB_INCLUDE_STB_TRUETYPE_H__
+#define __STB_INCLUDE_STB_TRUETYPE_H__
+
+#ifdef STBTT_STATIC
+#define STBTT_DEF static
+#else
+#define STBTT_DEF extern
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// TEXTURE BAKING API
+//
+// If you use this API, you only have to call two functions ever.
+//
+
+typedef struct
+{
+ unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap
+ float xoff,yoff,xadvance;
+} stbtt_bakedchar;
+
+STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf)
+ float pixel_height, // height of font in pixels
+ unsigned char *pixels, int pw, int ph, // bitmap to be filled in
+ int first_char, int num_chars, // characters to bake
+ stbtt_bakedchar *chardata); // you allocate this, it's num_chars long
+// if return is positive, the first unused row of the bitmap
+// if return is negative, returns the negative of the number of characters that fit
+// if return is 0, no characters fit and no rows were used
+// This uses a very crappy packing.
+
+typedef struct
+{
+ float x0,y0,s0,t0; // top-left
+ float x1,y1,s1,t1; // bottom-right
+} stbtt_aligned_quad;
+
+STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above
+ int char_index, // character to display
+ float *xpos, float *ypos, // pointers to current position in screen pixel space
+ stbtt_aligned_quad *q, // output: quad to draw
+ int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier
+// Call GetBakedQuad with char_index = 'character - first_char', and it
+// creates the quad you need to draw and advances the current position.
+//
+// The coordinate system used assumes y increases downwards.
+//
+// Characters will extend both above and below the current position;
+// see discussion of "BASELINE" above.
+//
+// It's inefficient; you might want to c&p it and optimize it.
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// NEW TEXTURE BAKING API
+//
+// This provides options for packing multiple fonts into one atlas, not
+// perfectly but better than nothing.
+
+typedef struct
+{
+ unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap
+ float xoff,yoff,xadvance;
+ float xoff2,yoff2;
+} stbtt_packedchar;
+
+typedef struct stbtt_pack_context stbtt_pack_context;
+typedef struct stbtt_fontinfo stbtt_fontinfo;
+#ifndef STB_RECT_PACK_VERSION
+typedef struct stbrp_rect stbrp_rect;
+#endif
+
+STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context);
+// Initializes a packing context stored in the passed-in stbtt_pack_context.
+// Future calls using this context will pack characters into the bitmap passed
+// in here: a 1-channel bitmap that is weight x height. stride_in_bytes is
+// the distance from one row to the next (or 0 to mean they are packed tightly
+// together). "padding" is the amount of padding to leave between each
+// character (normally you want '1' for bitmaps you'll use as textures with
+// bilinear filtering).
+//
+// Returns 0 on failure, 1 on success.
+
+STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc);
+// Cleans up the packing context and frees all memory.
+
+#define STBTT_POINT_SIZE(x) (-(x))
+
+STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size,
+ int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range);
+// Creates character bitmaps from the font_index'th font found in fontdata (use
+// font_index=0 if you don't know what that is). It creates num_chars_in_range
+// bitmaps for characters with unicode values starting at first_unicode_char_in_range
+// and increasing. Data for how to render them is stored in chardata_for_range;
+// pass these to stbtt_GetPackedQuad to get back renderable quads.
+//
+// font_size is the full height of the character from ascender to descender,
+// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed
+// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE()
+// and pass that result as 'font_size':
+// ..., 20 , ... // font max minus min y is 20 pixels tall
+// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall
+
+typedef struct
+{
+ float font_size;
+ int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint
+ int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints
+ int num_chars;
+ stbtt_packedchar *chardata_for_range; // output
+ unsigned char h_oversample, v_oversample; // don't set these, they're used internally
+} stbtt_pack_range;
+
+STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges);
+// Creates character bitmaps from multiple ranges of characters stored in
+// ranges. This will usually create a better-packed bitmap than multiple
+// calls to stbtt_PackFontRange. Note that you can call this multiple
+// times within a single PackBegin/PackEnd.
+
+STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample);
+// Oversampling a font increases the quality by allowing higher-quality subpixel
+// positioning, and is especially valuable at smaller text sizes.
+//
+// This function sets the amount of oversampling for all following calls to
+// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given
+// pack context. The default (no oversampling) is achieved by h_oversample=1
+// and v_oversample=1. The total number of pixels required is
+// h_oversample*v_oversample larger than the default; for example, 2x2
+// oversampling requires 4x the storage of 1x1. For best results, render
+// oversampled textures with bilinear filtering. Look at the readme in
+// stb/tests/oversample for information about oversampled fonts
+//
+// To use with PackFontRangesGather etc., you must set it before calls
+// call to PackFontRangesGatherRects.
+
+STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, // same data as above
+ int char_index, // character to display
+ float *xpos, float *ypos, // pointers to current position in screen pixel space
+ stbtt_aligned_quad *q, // output: quad to draw
+ int align_to_integer);
+
+STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);
+STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects);
+STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);
+// Calling these functions in sequence is roughly equivalent to calling
+// stbtt_PackFontRanges(). If you more control over the packing of multiple
+// fonts, or if you want to pack custom data into a font texture, take a look
+// at the source to of stbtt_PackFontRanges() and create a custom version
+// using these functions, e.g. call GatherRects multiple times,
+// building up a single array of rects, then call PackRects once,
+// then call RenderIntoRects repeatedly. This may result in a
+// better packing than calling PackFontRanges multiple times
+// (or it may not).
+
+// this is an opaque structure that you shouldn't mess with which holds
+// all the context needed from PackBegin to PackEnd.
+struct stbtt_pack_context {
+ void *user_allocator_context;
+ void *pack_info;
+ int width;
+ int height;
+ int stride_in_bytes;
+ int padding;
+ unsigned int h_oversample, v_oversample;
+ unsigned char *pixels;
+ void *nodes;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// FONT LOADING
+//
+//
+
+STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index);
+// Each .ttf/.ttc file may have more than one font. Each font has a sequential
+// index number starting from 0. Call this function to get the font offset for
+// a given index; it returns -1 if the index is out of range. A regular .ttf
+// file will only define one font and it always be at offset 0, so it will
+// return '0' for index 0, and -1 for all other indices. You can just skip
+// this step if you know it's that kind of font.
+
+
+// The following structure is defined publically so you can declare one on
+// the stack or as a global or etc, but you should treat it as opaque.
+typedef struct stbtt_fontinfo
+{
+ void * userdata;
+ unsigned char * data; // pointer to .ttf file
+ int fontstart; // offset of start of font
+
+ int numGlyphs; // number of glyphs, needed for range checking
+
+ int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf
+ int index_map; // a cmap mapping for our chosen character encoding
+ int indexToLocFormat; // format needed to map from glyph index to glyph
+} stbtt_fontinfo;
+
+STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset);
+// Given an offset into the file that defines a font, this function builds
+// the necessary cached info for the rest of the system. You must allocate
+// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't
+// need to do anything special to free it, because the contents are pure
+// value data with no additional data structures. Returns 0 on failure.
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// CHARACTER TO GLYPH-INDEX CONVERSIOn
+
+STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint);
+// If you're going to perform multiple operations on the same character
+// and you want a speed-up, call this function with the character you're
+// going to process, then use glyph-based functions instead of the
+// codepoint-based functions.
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// CHARACTER PROPERTIES
+//
+
+STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels);
+// computes a scale factor to produce a font whose "height" is 'pixels' tall.
+// Height is measured as the distance from the highest ascender to the lowest
+// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics
+// and computing:
+// scale = pixels / (ascent - descent)
+// so if you prefer to measure height by the ascent only, use a similar calculation.
+
+STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels);
+// computes a scale factor to produce a font whose EM size is mapped to
+// 'pixels' tall. This is probably what traditional APIs compute, but
+// I'm not positive.
+
+STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap);
+// ascent is the coordinate above the baseline the font extends; descent
+// is the coordinate below the baseline the font extends (i.e. it is typically negative)
+// lineGap is the spacing between one row's descent and the next row's ascent...
+// so you should advance the vertical position by "*ascent - *descent + *lineGap"
+// these are expressed in unscaled coordinates, so you must multiply by
+// the scale factor for a given size
+
+STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1);
+// the bounding box around all possible characters
+
+STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing);
+// leftSideBearing is the offset from the current horizontal position to the left edge of the character
+// advanceWidth is the offset from the current horizontal position to the next horizontal position
+// these are expressed in unscaled coordinates
+
+STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2);
+// an additional amount to add to the 'advance' value between ch1 and ch2
+
+STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1);
+// Gets the bounding box of the visible part of the glyph, in unscaled coordinates
+
+STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing);
+STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2);
+STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);
+// as above, but takes one or more glyph indices for greater efficiency
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// GLYPH SHAPES (you probably don't need these, but they have to go before
+// the bitmaps for C declaration-order reasons)
+//
+
+#ifndef STBTT_vmove // you can predefine these to use different values (but why?)
+ enum {
+ STBTT_vmove=1,
+ STBTT_vline,
+ STBTT_vcurve
+ };
+#endif
+
+#ifndef stbtt_vertex // you can predefine this to use different values
+ // (we share this with other code at RAD)
+ #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file
+ typedef struct
+ {
+ stbtt_vertex_type x,y,cx,cy;
+ unsigned char type,padding;
+ } stbtt_vertex;
+#endif
+
+STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index);
+// returns non-zero if nothing is drawn for this glyph
+
+STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices);
+STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices);
+// returns # of vertices and fills *vertices with the pointer to them
+// these are expressed in "unscaled" coordinates
+//
+// The shape is a series of countours. Each one starts with
+// a STBTT_moveto, then consists of a series of mixed
+// STBTT_lineto and STBTT_curveto segments. A lineto
+// draws a line from previous endpoint to its x,y; a curveto
+// draws a quadratic bezier from previous endpoint to
+// its x,y, using cx,cy as the bezier control point.
+
+STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices);
+// frees the data allocated above
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// BITMAP RENDERING
+//
+
+STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata);
+// frees the bitmap allocated below
+
+STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff);
+// allocates a large-enough single-channel 8bpp bitmap and renders the
+// specified character/glyph at the specified scale into it, with
+// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque).
+// *width & *height are filled out with the width & height of the bitmap,
+// which is stored left-to-right, top-to-bottom.
+//
+// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap
+
+STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff);
+// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel
+// shift for the character
+
+STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);
+// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap
+// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap
+// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the
+// width and height and positioning info for it first.
+
+STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint);
+// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel
+// shift for the character
+
+STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);
+// get the bbox of the bitmap centered around the glyph origin; so the
+// bitmap width is ix1-ix0, height is iy1-iy0, and location to place
+// the bitmap top left is (leftSideBearing*scale,iy0).
+// (Note that the bitmap uses y-increases-down, but the shape uses
+// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.)
+
+STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1);
+// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel
+// shift for the character
+
+// the following functions are equivalent to the above functions, but operate
+// on glyph indices instead of Unicode codepoints (for efficiency)
+STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff);
+STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff);
+STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph);
+STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph);
+STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);
+STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1);
+
+
+// @TODO: don't expose this structure
+typedef struct
+{
+ int w,h,stride;
+ unsigned char *pixels;
+} stbtt__bitmap;
+
+// rasterize a shape with quadratic beziers into a bitmap
+STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into
+ float flatness_in_pixels, // allowable error of curve in pixels
+ stbtt_vertex *vertices, // array of vertices defining shape
+ int num_verts, // number of vertices in above array
+ float scale_x, float scale_y, // scale applied to input vertices
+ float shift_x, float shift_y, // translation applied to input vertices
+ int x_off, int y_off, // another translation applied to input
+ int invert, // if non-zero, vertically flip shape
+ void *userdata); // context for to STBTT_MALLOC
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Finding the right font...
+//
+// You should really just solve this offline, keep your own tables
+// of what font is what, and don't try to get it out of the .ttf file.
+// That's because getting it out of the .ttf file is really hard, because
+// the names in the file can appear in many possible encodings, in many
+// possible languages, and e.g. if you need a case-insensitive comparison,
+// the details of that depend on the encoding & language in a complex way
+// (actually underspecified in truetype, but also gigantic).
+//
+// But you can use the provided functions in two possible ways:
+// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on
+// unicode-encoded names to try to find the font you want;
+// you can run this before calling stbtt_InitFont()
+//
+// stbtt_GetFontNameString() lets you get any of the various strings
+// from the file yourself and do your own comparisons on them.
+// You have to have called stbtt_InitFont() first.
+
+
+STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags);
+// returns the offset (not index) of the font that matches, or -1 if none
+// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold".
+// if you use any other flag, use a font name like "Arial"; this checks
+// the 'macStyle' header field; i don't know if fonts set this consistently
+#define STBTT_MACSTYLE_DONTCARE 0
+#define STBTT_MACSTYLE_BOLD 1
+#define STBTT_MACSTYLE_ITALIC 2
+#define STBTT_MACSTYLE_UNDERSCORE 4
+#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0
+
+STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2);
+// returns 1/0 whether the first string interpreted as utf8 is identical to
+// the second string interpreted as big-endian utf16... useful for strings from next func
+
+STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID);
+// returns the string (which may be big-endian double byte, e.g. for unicode)
+// and puts the length in bytes in *length.
+//
+// some of the values for the IDs are below; for more see the truetype spec:
+// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html
+// http://www.microsoft.com/typography/otspec/name.htm
+
+enum { // platformID
+ STBTT_PLATFORM_ID_UNICODE =0,
+ STBTT_PLATFORM_ID_MAC =1,
+ STBTT_PLATFORM_ID_ISO =2,
+ STBTT_PLATFORM_ID_MICROSOFT =3
+};
+
+enum { // encodingID for STBTT_PLATFORM_ID_UNICODE
+ STBTT_UNICODE_EID_UNICODE_1_0 =0,
+ STBTT_UNICODE_EID_UNICODE_1_1 =1,
+ STBTT_UNICODE_EID_ISO_10646 =2,
+ STBTT_UNICODE_EID_UNICODE_2_0_BMP=3,
+ STBTT_UNICODE_EID_UNICODE_2_0_FULL=4
+};
+
+enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT
+ STBTT_MS_EID_SYMBOL =0,
+ STBTT_MS_EID_UNICODE_BMP =1,
+ STBTT_MS_EID_SHIFTJIS =2,
+ STBTT_MS_EID_UNICODE_FULL =10
+};
+
+enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes
+ STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4,
+ STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5,
+ STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6,
+ STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7
+};
+
+enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID...
+ // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs
+ STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410,
+ STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411,
+ STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412,
+ STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419,
+ STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409,
+ STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D
+};
+
+enum { // languageID for STBTT_PLATFORM_ID_MAC
+ STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11,
+ STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23,
+ STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32,
+ STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 ,
+ STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 ,
+ STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33,
+ STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __STB_INCLUDE_STB_TRUETYPE_H__
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+////
+//// IMPLEMENTATION
+////
+////
+
+#ifdef STB_TRUETYPE_IMPLEMENTATION
+
+#ifndef STBTT_MAX_OVERSAMPLE
+#define STBTT_MAX_OVERSAMPLE 8
+#endif
+
+#if STBTT_MAX_OVERSAMPLE > 255
+#error "STBTT_MAX_OVERSAMPLE cannot be > 255"
+#endif
+
+typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1];
+
+#ifndef STBTT_RASTERIZER_VERSION
+#define STBTT_RASTERIZER_VERSION 2
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+//
+// accessors to parse data from file
+//
+
+// on platforms that don't allow misaligned reads, if we want to allow
+// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE
+
+#define ttBYTE(p) (* (stbtt_uint8 *) (p))
+#define ttCHAR(p) (* (stbtt_int8 *) (p))
+#define ttFixed(p) ttLONG(p)
+
+#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE)
+
+ #define ttUSHORT(p) (* (stbtt_uint16 *) (p))
+ #define ttSHORT(p) (* (stbtt_int16 *) (p))
+ #define ttULONG(p) (* (stbtt_uint32 *) (p))
+ #define ttLONG(p) (* (stbtt_int32 *) (p))
+
+#else
+
+ static stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; }
+ static stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; }
+ static stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
+ static stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
+
+#endif
+
+#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3))
+#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3])
+
+static int stbtt__isfont(const stbtt_uint8 *font)
+{
+ // check the version number
+ if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1
+ if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this!
+ if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF
+ if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0
+ return 0;
+}
+
+// @OPTIMIZE: binary search
+static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag)
+{
+ stbtt_int32 num_tables = ttUSHORT(data+fontstart+4);
+ stbtt_uint32 tabledir = fontstart + 12;
+ stbtt_int32 i;
+ for (i=0; i < num_tables; ++i) {
+ stbtt_uint32 loc = tabledir + 16*i;
+ if (stbtt_tag(data+loc+0, tag))
+ return ttULONG(data+loc+8);
+ }
+ return 0;
+}
+
+STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index)
+{
+ // if it's just a font, there's only one valid index
+ if (stbtt__isfont(font_collection))
+ return index == 0 ? 0 : -1;
+
+ // check if it's a TTC
+ if (stbtt_tag(font_collection, "ttcf")) {
+ // version 1?
+ if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) {
+ stbtt_int32 n = ttLONG(font_collection+8);
+ if (index >= n)
+ return -1;
+ return ttULONG(font_collection+12+index*4);
+ }
+ }
+ return -1;
+}
+
+STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart)
+{
+ stbtt_uint8 *data = (stbtt_uint8 *) data2;
+ stbtt_uint32 cmap, t;
+ stbtt_int32 i,numTables;
+
+ info->data = data;
+ info->fontstart = fontstart;
+
+ cmap = stbtt__find_table(data, fontstart, "cmap"); // required
+ info->loca = stbtt__find_table(data, fontstart, "loca"); // required
+ info->head = stbtt__find_table(data, fontstart, "head"); // required
+ info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required
+ info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required
+ info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required
+ info->kern = stbtt__find_table(data, fontstart, "kern"); // not required
+ if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx)
+ return 0;
+
+ t = stbtt__find_table(data, fontstart, "maxp");
+ if (t)
+ info->numGlyphs = ttUSHORT(data+t+4);
+ else
+ info->numGlyphs = 0xffff;
+
+ // find a cmap encoding table we understand *now* to avoid searching
+ // later. (todo: could make this installable)
+ // the same regardless of glyph.
+ numTables = ttUSHORT(data + cmap + 2);
+ info->index_map = 0;
+ for (i=0; i < numTables; ++i) {
+ stbtt_uint32 encoding_record = cmap + 4 + 8 * i;
+ // find an encoding we understand:
+ switch(ttUSHORT(data+encoding_record)) {
+ case STBTT_PLATFORM_ID_MICROSOFT:
+ switch (ttUSHORT(data+encoding_record+2)) {
+ case STBTT_MS_EID_UNICODE_BMP:
+ case STBTT_MS_EID_UNICODE_FULL:
+ // MS/Unicode
+ info->index_map = cmap + ttULONG(data+encoding_record+4);
+ break;
+ }
+ break;
+ case STBTT_PLATFORM_ID_UNICODE:
+ // Mac/iOS has these
+ // all the encodingIDs are unicode, so we don't bother to check it
+ info->index_map = cmap + ttULONG(data+encoding_record+4);
+ break;
+ }
+ }
+ if (info->index_map == 0)
+ return 0;
+
+ info->indexToLocFormat = ttUSHORT(data+info->head + 50);
+ return 1;
+}
+
+STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint)
+{
+ stbtt_uint8 *data = info->data;
+ stbtt_uint32 index_map = info->index_map;
+
+ stbtt_uint16 format = ttUSHORT(data + index_map + 0);
+ if (format == 0) { // apple byte encoding
+ stbtt_int32 bytes = ttUSHORT(data + index_map + 2);
+ if (unicode_codepoint < bytes-6)
+ return ttBYTE(data + index_map + 6 + unicode_codepoint);
+ return 0;
+ } else if (format == 6) {
+ stbtt_uint32 first = ttUSHORT(data + index_map + 6);
+ stbtt_uint32 count = ttUSHORT(data + index_map + 8);
+ if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count)
+ return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2);
+ return 0;
+ } else if (format == 2) {
+ STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean
+ return 0;
+ } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges
+ stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1;
+ stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1;
+ stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10);
+ stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1;
+
+ // do a binary search of the segments
+ stbtt_uint32 endCount = index_map + 14;
+ stbtt_uint32 search = endCount;
+
+ if (unicode_codepoint > 0xffff)
+ return 0;
+
+ // they lie from endCount .. endCount + segCount
+ // but searchRange is the nearest power of two, so...
+ if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2))
+ search += rangeShift*2;
+
+ // now decrement to bias correctly to find smallest
+ search -= 2;
+ while (entrySelector) {
+ stbtt_uint16 end;
+ searchRange >>= 1;
+ end = ttUSHORT(data + search + searchRange*2);
+ if (unicode_codepoint > end)
+ search += searchRange*2;
+ --entrySelector;
+ }
+ search += 2;
+
+ {
+ stbtt_uint16 offset, start;
+ stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1);
+
+ STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item));
+ start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item);
+ if (unicode_codepoint < start)
+ return 0;
+
+ offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item);
+ if (offset == 0)
+ return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item));
+
+ return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item);
+ }
+ } else if (format == 12 || format == 13) {
+ stbtt_uint32 ngroups = ttULONG(data+index_map+12);
+ stbtt_int32 low,high;
+ low = 0; high = (stbtt_int32)ngroups;
+ // Binary search the right group.
+ while (low < high) {
+ stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high
+ stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12);
+ stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4);
+ if ((stbtt_uint32) unicode_codepoint < start_char)
+ high = mid;
+ else if ((stbtt_uint32) unicode_codepoint > end_char)
+ low = mid+1;
+ else {
+ stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8);
+ if (format == 12)
+ return start_glyph + unicode_codepoint-start_char;
+ else // format == 13
+ return start_glyph;
+ }
+ }
+ return 0; // not found
+ }
+ // @TODO
+ STBTT_assert(0);
+ return 0;
+}
+
+STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices)
+{
+ return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices);
+}
+
+static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy)
+{
+ v->type = type;
+ v->x = (stbtt_int16) x;
+ v->y = (stbtt_int16) y;
+ v->cx = (stbtt_int16) cx;
+ v->cy = (stbtt_int16) cy;
+}
+
+static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index)
+{
+ int g1,g2;
+
+ if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range
+ if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format
+
+ if (info->indexToLocFormat == 0) {
+ g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2;
+ g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2;
+ } else {
+ g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4);
+ g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4);
+ }
+
+ return g1==g2 ? -1 : g1; // if length is 0, return -1
+}
+
+STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)
+{
+ int g = stbtt__GetGlyfOffset(info, glyph_index);
+ if (g < 0) return 0;
+
+ if (x0) *x0 = ttSHORT(info->data + g + 2);
+ if (y0) *y0 = ttSHORT(info->data + g + 4);
+ if (x1) *x1 = ttSHORT(info->data + g + 6);
+ if (y1) *y1 = ttSHORT(info->data + g + 8);
+ return 1;
+}
+
+STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1)
+{
+ return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1);
+}
+
+STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index)
+{
+ stbtt_int16 numberOfContours;
+ int g = stbtt__GetGlyfOffset(info, glyph_index);
+ if (g < 0) return 1;
+ numberOfContours = ttSHORT(info->data + g);
+ return numberOfContours == 0;
+}
+
+static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off,
+ stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy)
+{
+ if (start_off) {
+ if (was_off)
+ stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy);
+ stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy);
+ } else {
+ if (was_off)
+ stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy);
+ else
+ stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0);
+ }
+ return num_vertices;
+}
+
+STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
+{
+ stbtt_int16 numberOfContours;
+ stbtt_uint8 *endPtsOfContours;
+ stbtt_uint8 *data = info->data;
+ stbtt_vertex *vertices=0;
+ int num_vertices=0;
+ int g = stbtt__GetGlyfOffset(info, glyph_index);
+
+ *pvertices = NULL;
+
+ if (g < 0) return 0;
+
+ numberOfContours = ttSHORT(data + g);
+
+ if (numberOfContours > 0) {
+ stbtt_uint8 flags=0,flagcount;
+ stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0;
+ stbtt_int32 x,y,cx,cy,sx,sy, scx,scy;
+ stbtt_uint8 *points;
+ endPtsOfContours = (data + g + 10);
+ ins = ttUSHORT(data + g + 10 + numberOfContours * 2);
+ points = data + g + 10 + numberOfContours * 2 + 2 + ins;
+
+ n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2);
+
+ m = n + 2*numberOfContours; // a loose bound on how many vertices we might need
+ vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata);
+ if (vertices == 0)
+ return 0;
+
+ next_move = 0;
+ flagcount=0;
+
+ // in first pass, we load uninterpreted data into the allocated array
+ // above, shifted to the end of the array so we won't overwrite it when
+ // we create our final data starting from the front
+
+ off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated
+
+ // first load flags
+
+ for (i=0; i < n; ++i) {
+ if (flagcount == 0) {
+ flags = *points++;
+ if (flags & 8)
+ flagcount = *points++;
+ } else
+ --flagcount;
+ vertices[off+i].type = flags;
+ }
+
+ // now load x coordinates
+ x=0;
+ for (i=0; i < n; ++i) {
+ flags = vertices[off+i].type;
+ if (flags & 2) {
+ stbtt_int16 dx = *points++;
+ x += (flags & 16) ? dx : -dx; // ???
+ } else {
+ if (!(flags & 16)) {
+ x = x + (stbtt_int16) (points[0]*256 + points[1]);
+ points += 2;
+ }
+ }
+ vertices[off+i].x = (stbtt_int16) x;
+ }
+
+ // now load y coordinates
+ y=0;
+ for (i=0; i < n; ++i) {
+ flags = vertices[off+i].type;
+ if (flags & 4) {
+ stbtt_int16 dy = *points++;
+ y += (flags & 32) ? dy : -dy; // ???
+ } else {
+ if (!(flags & 32)) {
+ y = y + (stbtt_int16) (points[0]*256 + points[1]);
+ points += 2;
+ }
+ }
+ vertices[off+i].y = (stbtt_int16) y;
+ }
+
+ // now convert them to our format
+ num_vertices=0;
+ sx = sy = cx = cy = scx = scy = 0;
+ for (i=0; i < n; ++i) {
+ flags = vertices[off+i].type;
+ x = (stbtt_int16) vertices[off+i].x;
+ y = (stbtt_int16) vertices[off+i].y;
+
+ if (next_move == i) {
+ if (i != 0)
+ num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
+
+ // now start the new one
+ start_off = !(flags & 1);
+ if (start_off) {
+ // if we start off with an off-curve point, then when we need to find a point on the curve
+ // where we can start, and we need to save some state for when we wraparound.
+ scx = x;
+ scy = y;
+ if (!(vertices[off+i+1].type & 1)) {
+ // next point is also a curve point, so interpolate an on-point curve
+ sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1;
+ sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1;
+ } else {
+ // otherwise just use the next point as our start point
+ sx = (stbtt_int32) vertices[off+i+1].x;
+ sy = (stbtt_int32) vertices[off+i+1].y;
+ ++i; // we're using point i+1 as the starting point, so skip it
+ }
+ } else {
+ sx = x;
+ sy = y;
+ }
+ stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0);
+ was_off = 0;
+ next_move = 1 + ttUSHORT(endPtsOfContours+j*2);
+ ++j;
+ } else {
+ if (!(flags & 1)) { // if it's a curve
+ if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint
+ stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy);
+ cx = x;
+ cy = y;
+ was_off = 1;
+ } else {
+ if (was_off)
+ stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy);
+ else
+ stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0);
+ was_off = 0;
+ }
+ }
+ }
+ num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
+ } else if (numberOfContours == -1) {
+ // Compound shapes.
+ int more = 1;
+ stbtt_uint8 *comp = data + g + 10;
+ num_vertices = 0;
+ vertices = 0;
+ while (more) {
+ stbtt_uint16 flags, gidx;
+ int comp_num_verts = 0, i;
+ stbtt_vertex *comp_verts = 0, *tmp = 0;
+ float mtx[6] = {1,0,0,1,0,0}, m, n;
+
+ flags = ttSHORT(comp); comp+=2;
+ gidx = ttSHORT(comp); comp+=2;
+
+ if (flags & 2) { // XY values
+ if (flags & 1) { // shorts
+ mtx[4] = ttSHORT(comp); comp+=2;
+ mtx[5] = ttSHORT(comp); comp+=2;
+ } else {
+ mtx[4] = ttCHAR(comp); comp+=1;
+ mtx[5] = ttCHAR(comp); comp+=1;
+ }
+ }
+ else {
+ // @TODO handle matching point
+ STBTT_assert(0);
+ }
+ if (flags & (1<<3)) { // WE_HAVE_A_SCALE
+ mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
+ mtx[1] = mtx[2] = 0;
+ } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE
+ mtx[0] = ttSHORT(comp)/16384.0f; comp+=2;
+ mtx[1] = mtx[2] = 0;
+ mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
+ } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO
+ mtx[0] = ttSHORT(comp)/16384.0f; comp+=2;
+ mtx[1] = ttSHORT(comp)/16384.0f; comp+=2;
+ mtx[2] = ttSHORT(comp)/16384.0f; comp+=2;
+ mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
+ }
+
+ // Find transformation scales.
+ m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]);
+ n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]);
+
+ // Get indexed glyph.
+ comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts);
+ if (comp_num_verts > 0) {
+ // Transform vertices.
+ for (i = 0; i < comp_num_verts; ++i) {
+ stbtt_vertex* v = &comp_verts[i];
+ stbtt_vertex_type x,y;
+ x=v->x; y=v->y;
+ v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4]));
+ v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5]));
+ x=v->cx; y=v->cy;
+ v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4]));
+ v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5]));
+ }
+ // Append vertices.
+ tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata);
+ if (!tmp) {
+ if (vertices) STBTT_free(vertices, info->userdata);
+ if (comp_verts) STBTT_free(comp_verts, info->userdata);
+ return 0;
+ }
+ if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex));
+ STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex));
+ if (vertices) STBTT_free(vertices, info->userdata);
+ vertices = tmp;
+ STBTT_free(comp_verts, info->userdata);
+ num_vertices += comp_num_verts;
+ }
+ // More components ?
+ more = flags & (1<<5);
+ }
+ } else if (numberOfContours < 0) {
+ // @TODO other compound variations?
+ STBTT_assert(0);
+ } else {
+ // numberOfCounters == 0, do nothing
+ }
+
+ *pvertices = vertices;
+ return num_vertices;
+}
+
+STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing)
+{
+ stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34);
+ if (glyph_index < numOfLongHorMetrics) {
+ if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index);
+ if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2);
+ } else {
+ if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1));
+ if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics));
+ }
+}
+
+STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)
+{
+ stbtt_uint8 *data = info->data + info->kern;
+ stbtt_uint32 needle, straw;
+ int l, r, m;
+
+ // we only look at the first table. it must be 'horizontal' and format 0.
+ if (!info->kern)
+ return 0;
+ if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
+ return 0;
+ if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
+ return 0;
+
+ l = 0;
+ r = ttUSHORT(data+10) - 1;
+ needle = glyph1 << 16 | glyph2;
+ while (l <= r) {
+ m = (l + r) >> 1;
+ straw = ttULONG(data+18+(m*6)); // note: unaligned read
+ if (needle < straw)
+ r = m - 1;
+ else if (needle > straw)
+ l = m + 1;
+ else
+ return ttSHORT(data+22+(m*6));
+ }
+ return 0;
+}
+
+STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2)
+{
+ if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs
+ return 0;
+ return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2));
+}
+
+STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing)
+{
+ stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing);
+}
+
+STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap)
+{
+ if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4);
+ if (descent) *descent = ttSHORT(info->data+info->hhea + 6);
+ if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8);
+}
+
+STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1)
+{
+ *x0 = ttSHORT(info->data + info->head + 36);
+ *y0 = ttSHORT(info->data + info->head + 38);
+ *x1 = ttSHORT(info->data + info->head + 40);
+ *y1 = ttSHORT(info->data + info->head + 42);
+}
+
+STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height)
+{
+ int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6);
+ return (float) height / fheight;
+}
+
+STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels)
+{
+ int unitsPerEm = ttUSHORT(info->data + info->head + 18);
+ return pixels / unitsPerEm;
+}
+
+STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v)
+{
+ STBTT_free(v, info->userdata);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// antialiasing software rasterizer
+//
+
+STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+ int x0,y0,x1,y1;
+ if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) {
+ // e.g. space character
+ if (ix0) *ix0 = 0;
+ if (iy0) *iy0 = 0;
+ if (ix1) *ix1 = 0;
+ if (iy1) *iy1 = 0;
+ } else {
+ // move to integral bboxes (treating pixels as little squares, what pixels get touched)?
+ if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x);
+ if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y);
+ if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x);
+ if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y);
+ }
+}
+
+STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+ stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1);
+}
+
+STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+ stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1);
+}
+
+STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+ stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Rasterizer
+
+typedef struct stbtt__hheap_chunk
+{
+ struct stbtt__hheap_chunk *next;
+} stbtt__hheap_chunk;
+
+typedef struct stbtt__hheap
+{
+ struct stbtt__hheap_chunk *head;
+ void *first_free;
+ int num_remaining_in_head_chunk;
+} stbtt__hheap;
+
+static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata)
+{
+ if (hh->first_free) {
+ void *p = hh->first_free;
+ hh->first_free = * (void **) p;
+ return p;
+ } else {
+ if (hh->num_remaining_in_head_chunk == 0) {
+ int count = (size < 32 ? 2000 : size < 128 ? 800 : 100);
+ stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata);
+ if (c == NULL)
+ return NULL;
+ c->next = hh->head;
+ hh->head = c;
+ hh->num_remaining_in_head_chunk = count;
+ }
+ --hh->num_remaining_in_head_chunk;
+ return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk;
+ }
+}
+
+static void stbtt__hheap_free(stbtt__hheap *hh, void *p)
+{
+ *(void **) p = hh->first_free;
+ hh->first_free = p;
+}
+
+static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata)
+{
+ stbtt__hheap_chunk *c = hh->head;
+ while (c) {
+ stbtt__hheap_chunk *n = c->next;
+ STBTT_free(c, userdata);
+ c = n;
+ }
+}
+
+typedef struct stbtt__edge {
+ float x0,y0, x1,y1;
+ int invert;
+} stbtt__edge;
+
+
+typedef struct stbtt__active_edge
+{
+ struct stbtt__active_edge *next;
+ #if STBTT_RASTERIZER_VERSION==1
+ int x,dx;
+ float ey;
+ int direction;
+ #elif STBTT_RASTERIZER_VERSION==2
+ float fx,fdx,fdy;
+ float direction;
+ float sy;
+ float ey;
+ #else
+ #error "Unrecognized value of STBTT_RASTERIZER_VERSION"
+ #endif
+} stbtt__active_edge;
+
+#if STBTT_RASTERIZER_VERSION == 1
+#define STBTT_FIXSHIFT 10
+#define STBTT_FIX (1 << STBTT_FIXSHIFT)
+#define STBTT_FIXMASK (STBTT_FIX-1)
+
+static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata)
+{
+ stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);
+ float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
+ if (!z) return z;
+
+ // round dx down to avoid overshooting
+ if (dxdy < 0)
+ z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy);
+ else
+ z->dx = STBTT_ifloor(STBTT_FIX * dxdy);
+
+ z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount
+ z->x -= off_x * STBTT_FIX;
+
+ z->ey = e->y1;
+ z->next = 0;
+ z->direction = e->invert ? 1 : -1;
+ return z;
+}
+#elif STBTT_RASTERIZER_VERSION == 2
+static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata)
+{
+ stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);
+ float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
+ //STBTT_assert(e->y0 <= start_point);
+ if (!z) return z;
+ z->fdx = dxdy;
+ z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f;
+ z->fx = e->x0 + dxdy * (start_point - e->y0);
+ z->fx -= off_x;
+ z->direction = e->invert ? 1.0f : -1.0f;
+ z->sy = e->y0;
+ z->ey = e->y1;
+ z->next = 0;
+ return z;
+}
+#else
+#error "Unrecognized value of STBTT_RASTERIZER_VERSION"
+#endif
+
+#if STBTT_RASTERIZER_VERSION == 1
+// note: this routine clips fills that extend off the edges... ideally this
+// wouldn't happen, but it could happen if the truetype glyph bounding boxes
+// are wrong, or if the user supplies a too-small bitmap
+static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight)
+{
+ // non-zero winding fill
+ int x0=0, w=0;
+
+ while (e) {
+ if (w == 0) {
+ // if we're currently at zero, we need to record the edge start point
+ x0 = e->x; w += e->direction;
+ } else {
+ int x1 = e->x; w += e->direction;
+ // if we went to zero, we need to draw
+ if (w == 0) {
+ int i = x0 >> STBTT_FIXSHIFT;
+ int j = x1 >> STBTT_FIXSHIFT;
+
+ if (i < len && j >= 0) {
+ if (i == j) {
+ // x0,x1 are the same pixel, so compute combined coverage
+ scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT);
+ } else {
+ if (i >= 0) // add antialiasing for x0
+ scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT);
+ else
+ i = -1; // clip
+
+ if (j < len) // add antialiasing for x1
+ scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT);
+ else
+ j = len; // clip
+
+ for (++i; i < j; ++i) // fill pixels between x0 and x1
+ scanline[i] = scanline[i] + (stbtt_uint8) max_weight;
+ }
+ }
+ }
+ }
+
+ e = e->next;
+ }
+}
+
+static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata)
+{
+ stbtt__hheap hh = { 0, 0, 0 };
+ stbtt__active_edge *active = NULL;
+ int y,j=0;
+ int max_weight = (255 / vsubsample); // weight per vertical scanline
+ int s; // vertical subsample index
+ unsigned char scanline_data[512], *scanline;
+
+ if (result->w > 512)
+ scanline = (unsigned char *) STBTT_malloc(result->w, userdata);
+ else
+ scanline = scanline_data;
+
+ y = off_y * vsubsample;
+ e[n].y0 = (off_y + result->h) * (float) vsubsample + 1;
+
+ while (j < result->h) {
+ STBTT_memset(scanline, 0, result->w);
+ for (s=0; s < vsubsample; ++s) {
+ // find center of pixel for this scanline
+ float scan_y = y + 0.5f;
+ stbtt__active_edge **step = &active;
+
+ // update all active edges;
+ // remove all active edges that terminate before the center of this scanline
+ while (*step) {
+ stbtt__active_edge * z = *step;
+ if (z->ey <= scan_y) {
+ *step = z->next; // delete from list
+ STBTT_assert(z->direction);
+ z->direction = 0;
+ stbtt__hheap_free(&hh, z);
+ } else {
+ z->x += z->dx; // advance to position for current scanline
+ step = &((*step)->next); // advance through list
+ }
+ }
+
+ // resort the list if needed
+ for(;;) {
+ int changed=0;
+ step = &active;
+ while (*step && (*step)->next) {
+ if ((*step)->x > (*step)->next->x) {
+ stbtt__active_edge *t = *step;
+ stbtt__active_edge *q = t->next;
+
+ t->next = q->next;
+ q->next = t;
+ *step = q;
+ changed = 1;
+ }
+ step = &(*step)->next;
+ }
+ if (!changed) break;
+ }
+
+ // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline
+ while (e->y0 <= scan_y) {
+ if (e->y1 > scan_y) {
+ stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata);
+ // find insertion point
+ if (active == NULL)
+ active = z;
+ else if (z->x < active->x) {
+ // insert at front
+ z->next = active;
+ active = z;
+ } else {
+ // find thing to insert AFTER
+ stbtt__active_edge *p = active;
+ while (p->next && p->next->x < z->x)
+ p = p->next;
+ // at this point, p->next->x is NOT < z->x
+ z->next = p->next;
+ p->next = z;
+ }
+ }
+ ++e;
+ }
+
+ // now process all active edges in XOR fashion
+ if (active)
+ stbtt__fill_active_edges(scanline, result->w, active, max_weight);
+
+ ++y;
+ }
+ STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w);
+ ++j;
+ }
+
+ stbtt__hheap_cleanup(&hh, userdata);
+
+ if (scanline != scanline_data)
+ STBTT_free(scanline, userdata);
+}
+
+#elif STBTT_RASTERIZER_VERSION == 2
+
+// the edge passed in here does not cross the vertical line at x or the vertical line at x+1
+// (i.e. it has already been clipped to those)
+static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1)
+{
+ if (y0 == y1) return;
+ STBTT_assert(y0 < y1);
+ STBTT_assert(e->sy <= e->ey);
+ if (y0 > e->ey) return;
+ if (y1 < e->sy) return;
+ if (y0 < e->sy) {
+ x0 += (x1-x0) * (e->sy - y0) / (y1-y0);
+ y0 = e->sy;
+ }
+ if (y1 > e->ey) {
+ x1 += (x1-x0) * (e->ey - y1) / (y1-y0);
+ y1 = e->ey;
+ }
+
+ if (x0 == x)
+ STBTT_assert(x1 <= x+1);
+ else if (x0 == x+1)
+ STBTT_assert(x1 >= x);
+ else if (x0 <= x)
+ STBTT_assert(x1 <= x);
+ else if (x0 >= x+1)
+ STBTT_assert(x1 >= x+1);
+ else
+ STBTT_assert(x1 >= x && x1 <= x+1);
+
+ if (x0 <= x && x1 <= x)
+ scanline[x] += e->direction * (y1-y0);
+ else if (x0 >= x+1 && x1 >= x+1)
+ ;
+ else {
+ STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1);
+ scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position
+ }
+}
+
+static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top)
+{
+ float y_bottom = y_top+1;
+
+ while (e) {
+ // brute force every pixel
+
+ // compute intersection points with top & bottom
+ STBTT_assert(e->ey >= y_top);
+
+ if (e->fdx == 0) {
+ float x0 = e->fx;
+ if (x0 < len) {
+ if (x0 >= 0) {
+ stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom);
+ stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom);
+ } else {
+ stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom);
+ }
+ }
+ } else {
+ float x0 = e->fx;
+ float dx = e->fdx;
+ float xb = x0 + dx;
+ float x_top, x_bottom;
+ float sy0,sy1;
+ float dy = e->fdy;
+ STBTT_assert(e->sy <= y_bottom && e->ey >= y_top);
+
+ // compute endpoints of line segment clipped to this scanline (if the
+ // line segment starts on this scanline. x0 is the intersection of the
+ // line with y_top, but that may be off the line segment.
+ if (e->sy > y_top) {
+ x_top = x0 + dx * (e->sy - y_top);
+ sy0 = e->sy;
+ } else {
+ x_top = x0;
+ sy0 = y_top;
+ }
+ if (e->ey < y_bottom) {
+ x_bottom = x0 + dx * (e->ey - y_top);
+ sy1 = e->ey;
+ } else {
+ x_bottom = xb;
+ sy1 = y_bottom;
+ }
+
+ if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) {
+ // from here on, we don't have to range check x values
+
+ if ((int) x_top == (int) x_bottom) {
+ float height;
+ // simple case, only spans one pixel
+ int x = (int) x_top;
+ height = sy1 - sy0;
+ STBTT_assert(x >= 0 && x < len);
+ scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height;
+ scanline_fill[x] += e->direction * height; // everything right of this pixel is filled
+ } else {
+ int x,x1,x2;
+ float y_crossing, step, sign, area;
+ // covers 2+ pixels
+ if (x_top > x_bottom) {
+ // flip scanline vertically; signed area is the same
+ float t;
+ sy0 = y_bottom - (sy0 - y_top);
+ sy1 = y_bottom - (sy1 - y_top);
+ t = sy0, sy0 = sy1, sy1 = t;
+ t = x_bottom, x_bottom = x_top, x_top = t;
+ dx = -dx;
+ dy = -dy;
+ t = x0, x0 = xb, xb = t;
+ }
+
+ x1 = (int) x_top;
+ x2 = (int) x_bottom;
+ // compute intersection with y axis at x1+1
+ y_crossing = (x1+1 - x0) * dy + y_top;
+
+ sign = e->direction;
+ // area of the rectangle covered from y0..y_crossing
+ area = sign * (y_crossing-sy0);
+ // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing)
+ scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2);
+
+ step = sign * dy;
+ for (x = x1+1; x < x2; ++x) {
+ scanline[x] += area + step/2;
+ area += step;
+ }
+ y_crossing += dy * (x2 - (x1+1));
+
+ STBTT_assert(fabs(area) <= 1.01f);
+
+ scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing);
+
+ scanline_fill[x2] += sign * (sy1-sy0);
+ }
+ } else {
+ // if edge goes outside of box we're drawing, we require
+ // clipping logic. since this does not match the intended use
+ // of this library, we use a different, very slow brute
+ // force implementation
+ int x;
+ for (x=0; x < len; ++x) {
+ // cases:
+ //
+ // there can be up to two intersections with the pixel. any intersection
+ // with left or right edges can be handled by splitting into two (or three)
+ // regions. intersections with top & bottom do not necessitate case-wise logic.
+ //
+ // the old way of doing this found the intersections with the left & right edges,
+ // then used some simple logic to produce up to three segments in sorted order
+ // from top-to-bottom. however, this had a problem: if an x edge was epsilon
+ // across the x border, then the corresponding y position might not be distinct
+ // from the other y segment, and it might ignored as an empty segment. to avoid
+ // that, we need to explicitly produce segments based on x positions.
+
+ // rename variables to clear pairs
+ float y0 = y_top;
+ float x1 = (float) (x);
+ float x2 = (float) (x+1);
+ float x3 = xb;
+ float y3 = y_bottom;
+ float y1,y2;
+
+ // x = e->x + e->dx * (y-y_top)
+ // (y-y_top) = (x - e->x) / e->dx
+ // y = (x - e->x) / e->dx + y_top
+ y1 = (x - x0) / dx + y_top;
+ y2 = (x+1 - x0) / dx + y_top;
+
+ if (x0 < x1 && x3 > x2) { // three segments descending down-right
+ stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
+ stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2);
+ stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+ } else if (x3 < x1 && x0 > x2) { // three segments descending down-left
+ stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
+ stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1);
+ stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
+ } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right
+ stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
+ stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
+ } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left
+ stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
+ stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
+ } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right
+ stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
+ stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+ } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left
+ stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
+ stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+ } else { // one segment
+ stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3);
+ }
+ }
+ }
+ }
+ e = e->next;
+ }
+}
+
+// directly AA rasterize edges w/o supersampling
+static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata)
+{
+ stbtt__hheap hh = { 0, 0, 0 };
+ stbtt__active_edge *active = NULL;
+ int y,j=0, i;
+ float scanline_data[129], *scanline, *scanline2;
+
+ if (result->w > 64)
+ scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata);
+ else
+ scanline = scanline_data;
+
+ scanline2 = scanline + result->w;
+
+ y = off_y;
+ e[n].y0 = (float) (off_y + result->h) + 1;
+
+ while (j < result->h) {
+ // find center of pixel for this scanline
+ float scan_y_top = y + 0.0f;
+ float scan_y_bottom = y + 1.0f;
+ stbtt__active_edge **step = &active;
+
+ STBTT_memset(scanline , 0, result->w*sizeof(scanline[0]));
+ STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0]));
+
+ // update all active edges;
+ // remove all active edges that terminate before the top of this scanline
+ while (*step) {
+ stbtt__active_edge * z = *step;
+ if (z->ey <= scan_y_top) {
+ *step = z->next; // delete from list
+ STBTT_assert(z->direction);
+ z->direction = 0;
+ stbtt__hheap_free(&hh, z);
+ } else {
+ step = &((*step)->next); // advance through list
+ }
+ }
+
+ // insert all edges that start before the bottom of this scanline
+ while (e->y0 <= scan_y_bottom) {
+ if (e->y0 != e->y1) {
+ stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata);
+ STBTT_assert(z->ey >= scan_y_top);
+ // insert at front
+ z->next = active;
+ active = z;
+ }
+ ++e;
+ }
+
+ // now process all active edges
+ if (active)
+ stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top);
+
+ {
+ float sum = 0;
+ for (i=0; i < result->w; ++i) {
+ float k;
+ int m;
+ sum += scanline2[i];
+ k = scanline[i] + sum;
+ k = (float) STBTT_fabs(k)*255 + 0.5f;
+ m = (int) k;
+ if (m > 255) m = 255;
+ result->pixels[j*result->stride + i] = (unsigned char) m;
+ }
+ }
+ // advance all the edges
+ step = &active;
+ while (*step) {
+ stbtt__active_edge *z = *step;
+ z->fx += z->fdx; // advance to position for current scanline
+ step = &((*step)->next); // advance through list
+ }
+
+ ++y;
+ ++j;
+ }
+
+ stbtt__hheap_cleanup(&hh, userdata);
+
+ if (scanline != scanline_data)
+ STBTT_free(scanline, userdata);
+}
+#else
+#error "Unrecognized value of STBTT_RASTERIZER_VERSION"
+#endif
+
+#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0)
+
+static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n)
+{
+ int i,j;
+ for (i=1; i < n; ++i) {
+ stbtt__edge t = p[i], *a = &t;
+ j = i;
+ while (j > 0) {
+ stbtt__edge *b = &p[j-1];
+ int c = STBTT__COMPARE(a,b);
+ if (!c) break;
+ p[j] = p[j-1];
+ --j;
+ }
+ if (i != j)
+ p[j] = t;
+ }
+}
+
+static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n)
+{
+ /* threshhold for transitioning to insertion sort */
+ while (n > 12) {
+ stbtt__edge t;
+ int c01,c12,c,m,i,j;
+
+ /* compute median of three */
+ m = n >> 1;
+ c01 = STBTT__COMPARE(&p[0],&p[m]);
+ c12 = STBTT__COMPARE(&p[m],&p[n-1]);
+ /* if 0 >= mid >= end, or 0 < mid < end, then use mid */
+ if (c01 != c12) {
+ /* otherwise, we'll need to swap something else to middle */
+ int z;
+ c = STBTT__COMPARE(&p[0],&p[n-1]);
+ /* 0>mid && mid<n: 0>n => n; 0<n => 0 */
+ /* 0<mid && mid>n: 0>n => 0; 0<n => n */
+ z = (c == c12) ? 0 : n-1;
+ t = p[z];
+ p[z] = p[m];
+ p[m] = t;
+ }
+ /* now p[m] is the median-of-three */
+ /* swap it to the beginning so it won't move around */
+ t = p[0];
+ p[0] = p[m];
+ p[m] = t;
+
+ /* partition loop */
+ i=1;
+ j=n-1;
+ for(;;) {
+ /* handling of equality is crucial here */
+ /* for sentinels & efficiency with duplicates */
+ for (;;++i) {
+ if (!STBTT__COMPARE(&p[i], &p[0])) break;
+ }
+ for (;;--j) {
+ if (!STBTT__COMPARE(&p[0], &p[j])) break;
+ }
+ /* make sure we haven't crossed */
+ if (i >= j) break;
+ t = p[i];
+ p[i] = p[j];
+ p[j] = t;
+
+ ++i;
+ --j;
+ }
+ /* recurse on smaller side, iterate on larger */
+ if (j < (n-i)) {
+ stbtt__sort_edges_quicksort(p,j);
+ p = p+i;
+ n = n-i;
+ } else {
+ stbtt__sort_edges_quicksort(p+i, n-i);
+ n = j;
+ }
+ }
+}
+
+static void stbtt__sort_edges(stbtt__edge *p, int n)
+{
+ stbtt__sort_edges_quicksort(p, n);
+ stbtt__sort_edges_ins_sort(p, n);
+}
+
+typedef struct
+{
+ float x,y;
+} stbtt__point;
+
+static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata)
+{
+ float y_scale_inv = invert ? -scale_y : scale_y;
+ stbtt__edge *e;
+ int n,i,j,k,m;
+#if STBTT_RASTERIZER_VERSION == 1
+ int vsubsample = result->h < 8 ? 15 : 5;
+#elif STBTT_RASTERIZER_VERSION == 2
+ int vsubsample = 1;
+#else
+ #error "Unrecognized value of STBTT_RASTERIZER_VERSION"
+#endif
+ // vsubsample should divide 255 evenly; otherwise we won't reach full opacity
+
+ // now we have to blow out the windings into explicit edge lists
+ n = 0;
+ for (i=0; i < windings; ++i)
+ n += wcount[i];
+
+ e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel
+ if (e == 0) return;
+ n = 0;
+
+ m=0;
+ for (i=0; i < windings; ++i) {
+ stbtt__point *p = pts + m;
+ m += wcount[i];
+ j = wcount[i]-1;
+ for (k=0; k < wcount[i]; j=k++) {
+ int a=k,b=j;
+ // skip the edge if horizontal
+ if (p[j].y == p[k].y)
+ continue;
+ // add edge from j to k to the list
+ e[n].invert = 0;
+ if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) {
+ e[n].invert = 1;
+ a=j,b=k;
+ }
+ e[n].x0 = p[a].x * scale_x + shift_x;
+ e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample;
+ e[n].x1 = p[b].x * scale_x + shift_x;
+ e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample;
+ ++n;
+ }
+ }
+
+ // now sort the edges by their highest point (should snap to integer, and then by x)
+ //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare);
+ stbtt__sort_edges(e, n);
+
+ // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule
+ stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata);
+
+ STBTT_free(e, userdata);
+}
+
+static void stbtt__add_point(stbtt__point *points, int n, float x, float y)
+{
+ if (!points) return; // during first pass, it's unallocated
+ points[n].x = x;
+ points[n].y = y;
+}
+
+// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching
+static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n)
+{
+ // midpoint
+ float mx = (x0 + 2*x1 + x2)/4;
+ float my = (y0 + 2*y1 + y2)/4;
+ // versus directly drawn line
+ float dx = (x0+x2)/2 - mx;
+ float dy = (y0+y2)/2 - my;
+ if (n > 16) // 65536 segments on one curve better be enough!
+ return 1;
+ if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA
+ stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1);
+ stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1);
+ } else {
+ stbtt__add_point(points, *num_points,x2,y2);
+ *num_points = *num_points+1;
+ }
+ return 1;
+}
+
+// returns number of contours
+static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata)
+{
+ stbtt__point *points=0;
+ int num_points=0;
+
+ float objspace_flatness_squared = objspace_flatness * objspace_flatness;
+ int i,n=0,start=0, pass;
+
+ // count how many "moves" there are to get the contour count
+ for (i=0; i < num_verts; ++i)
+ if (vertices[i].type == STBTT_vmove)
+ ++n;
+
+ *num_contours = n;
+ if (n == 0) return 0;
+
+ *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata);
+
+ if (*contour_lengths == 0) {
+ *num_contours = 0;
+ return 0;
+ }
+
+ // make two passes through the points so we don't need to realloc
+ for (pass=0; pass < 2; ++pass) {
+ float x=0,y=0;
+ if (pass == 1) {
+ points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata);
+ if (points == NULL) goto error;
+ }
+ num_points = 0;
+ n= -1;
+ for (i=0; i < num_verts; ++i) {
+ switch (vertices[i].type) {
+ case STBTT_vmove:
+ // start the next contour
+ if (n >= 0)
+ (*contour_lengths)[n] = num_points - start;
+ ++n;
+ start = num_points;
+
+ x = vertices[i].x, y = vertices[i].y;
+ stbtt__add_point(points, num_points++, x,y);
+ break;
+ case STBTT_vline:
+ x = vertices[i].x, y = vertices[i].y;
+ stbtt__add_point(points, num_points++, x, y);
+ break;
+ case STBTT_vcurve:
+ stbtt__tesselate_curve(points, &num_points, x,y,
+ vertices[i].cx, vertices[i].cy,
+ vertices[i].x, vertices[i].y,
+ objspace_flatness_squared, 0);
+ x = vertices[i].x, y = vertices[i].y;
+ break;
+ }
+ }
+ (*contour_lengths)[n] = num_points - start;
+ }
+
+ return points;
+error:
+ STBTT_free(points, userdata);
+ STBTT_free(*contour_lengths, userdata);
+ *contour_lengths = 0;
+ *num_contours = 0;
+ return NULL;
+}
+
+STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata)
+{
+ float scale = scale_x > scale_y ? scale_y : scale_x;
+ int winding_count, *winding_lengths;
+ stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata);
+ if (windings) {
+ stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata);
+ STBTT_free(winding_lengths, userdata);
+ STBTT_free(windings, userdata);
+ }
+}
+
+STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata)
+{
+ STBTT_free(bitmap, userdata);
+}
+
+STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff)
+{
+ int ix0,iy0,ix1,iy1;
+ stbtt__bitmap gbm;
+ stbtt_vertex *vertices;
+ int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
+
+ if (scale_x == 0) scale_x = scale_y;
+ if (scale_y == 0) {
+ if (scale_x == 0) return NULL;
+ scale_y = scale_x;
+ }
+
+ stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1);
+
+ // now we get the size
+ gbm.w = (ix1 - ix0);
+ gbm.h = (iy1 - iy0);
+ gbm.pixels = NULL; // in case we error
+
+ if (width ) *width = gbm.w;
+ if (height) *height = gbm.h;
+ if (xoff ) *xoff = ix0;
+ if (yoff ) *yoff = iy0;
+
+ if (gbm.w && gbm.h) {
+ gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata);
+ if (gbm.pixels) {
+ gbm.stride = gbm.w;
+
+ stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata);
+ }
+ }
+ STBTT_free(vertices, info->userdata);
+ return gbm.pixels;
+}
+
+STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff)
+{
+ return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff);
+}
+
+STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph)
+{
+ int ix0,iy0;
+ stbtt_vertex *vertices;
+ int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
+ stbtt__bitmap gbm;
+
+ stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0);
+ gbm.pixels = output;
+ gbm.w = out_w;
+ gbm.h = out_h;
+ gbm.stride = out_stride;
+
+ if (gbm.w && gbm.h)
+ stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata);
+
+ STBTT_free(vertices, info->userdata);
+}
+
+STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph)
+{
+ stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph);
+}
+
+STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
+{
+ return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff);
+}
+
+STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint)
+{
+ stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint));
+}
+
+STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
+{
+ return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff);
+}
+
+STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint)
+{
+ stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// bitmap baking
+//
+// This is SUPER-CRAPPY packing to keep source code small
+
+STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf)
+ float pixel_height, // height of font in pixels
+ unsigned char *pixels, int pw, int ph, // bitmap to be filled in
+ int first_char, int num_chars, // characters to bake
+ stbtt_bakedchar *chardata)
+{
+ float scale;
+ int x,y,bottom_y, i;
+ stbtt_fontinfo f;
+ if (!stbtt_InitFont(&f, data, offset))
+ return -1;
+ STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels
+ x=y=1;
+ bottom_y = 1;
+
+ scale = stbtt_ScaleForPixelHeight(&f, pixel_height);
+
+ for (i=0; i < num_chars; ++i) {
+ int advance, lsb, x0,y0,x1,y1,gw,gh;
+ int g = stbtt_FindGlyphIndex(&f, first_char + i);
+ stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb);
+ stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1);
+ gw = x1-x0;
+ gh = y1-y0;
+ if (x + gw + 1 >= pw)
+ y = bottom_y, x = 1; // advance to next row
+ if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row
+ return -i;
+ STBTT_assert(x+gw < pw);
+ STBTT_assert(y+gh < ph);
+ stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g);
+ chardata[i].x0 = (stbtt_int16) x;
+ chardata[i].y0 = (stbtt_int16) y;
+ chardata[i].x1 = (stbtt_int16) (x + gw);
+ chardata[i].y1 = (stbtt_int16) (y + gh);
+ chardata[i].xadvance = scale * advance;
+ chardata[i].xoff = (float) x0;
+ chardata[i].yoff = (float) y0;
+ x = x + gw + 1;
+ if (y+gh+1 > bottom_y)
+ bottom_y = y+gh+1;
+ }
+ return bottom_y;
+}
+
+STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule)
+{
+ float d3d_bias = opengl_fillrule ? 0 : -0.5f;
+ float ipw = 1.0f / pw, iph = 1.0f / ph;
+ stbtt_bakedchar *b = chardata + char_index;
+ int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f);
+ int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f);
+
+ q->x0 = round_x + d3d_bias;
+ q->y0 = round_y + d3d_bias;
+ q->x1 = round_x + b->x1 - b->x0 + d3d_bias;
+ q->y1 = round_y + b->y1 - b->y0 + d3d_bias;
+
+ q->s0 = b->x0 * ipw;
+ q->t0 = b->y0 * iph;
+ q->s1 = b->x1 * ipw;
+ q->t1 = b->y1 * iph;
+
+ *xpos += b->xadvance;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// rectangle packing replacement routines if you don't have stb_rect_pack.h
+//
+
+#ifndef STB_RECT_PACK_VERSION
+#ifdef _MSC_VER
+#define STBTT__NOTUSED(v) (void)(v)
+#else
+#define STBTT__NOTUSED(v) (void)sizeof(v)
+#endif
+
+typedef int stbrp_coord;
+
+////////////////////////////////////////////////////////////////////////////////////
+// //
+// //
+// COMPILER WARNING ?!?!? //
+// //
+// //
+// if you get a compile warning due to these symbols being defined more than //
+// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" //
+// //
+////////////////////////////////////////////////////////////////////////////////////
+
+typedef struct
+{
+ int width,height;
+ int x,y,bottom_y;
+} stbrp_context;
+
+typedef struct
+{
+ unsigned char x;
+} stbrp_node;
+
+struct stbrp_rect
+{
+ stbrp_coord x,y;
+ int id,w,h,was_packed;
+};
+
+static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes)
+{
+ con->width = pw;
+ con->height = ph;
+ con->x = 0;
+ con->y = 0;
+ con->bottom_y = 0;
+ STBTT__NOTUSED(nodes);
+ STBTT__NOTUSED(num_nodes);
+}
+
+static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects)
+{
+ int i;
+ for (i=0; i < num_rects; ++i) {
+ if (con->x + rects[i].w > con->width) {
+ con->x = 0;
+ con->y = con->bottom_y;
+ }
+ if (con->y + rects[i].h > con->height)
+ break;
+ rects[i].x = con->x;
+ rects[i].y = con->y;
+ rects[i].was_packed = 1;
+ con->x += rects[i].w;
+ if (con->y + rects[i].h > con->bottom_y)
+ con->bottom_y = con->y + rects[i].h;
+ }
+ for ( ; i < num_rects; ++i)
+ rects[i].was_packed = 0;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// bitmap baking
+//
+// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If
+// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy.
+
+STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context)
+{
+ stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context);
+ int num_nodes = pw - padding;
+ stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context);
+
+ if (context == NULL || nodes == NULL) {
+ if (context != NULL) STBTT_free(context, alloc_context);
+ if (nodes != NULL) STBTT_free(nodes , alloc_context);
+ return 0;
+ }
+
+ spc->user_allocator_context = alloc_context;
+ spc->width = pw;
+ spc->height = ph;
+ spc->pixels = pixels;
+ spc->pack_info = context;
+ spc->nodes = nodes;
+ spc->padding = padding;
+ spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw;
+ spc->h_oversample = 1;
+ spc->v_oversample = 1;
+
+ stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes);
+
+ if (pixels)
+ STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels
+
+ return 1;
+}
+
+STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc)
+{
+ STBTT_free(spc->nodes , spc->user_allocator_context);
+ STBTT_free(spc->pack_info, spc->user_allocator_context);
+}
+
+STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample)
+{
+ STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE);
+ STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE);
+ if (h_oversample <= STBTT_MAX_OVERSAMPLE)
+ spc->h_oversample = h_oversample;
+ if (v_oversample <= STBTT_MAX_OVERSAMPLE)
+ spc->v_oversample = v_oversample;
+}
+
+#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1)
+
+static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)
+{
+ unsigned char buffer[STBTT_MAX_OVERSAMPLE];
+ int safe_w = w - kernel_width;
+ int j;
+ for (j=0; j < h; ++j) {
+ int i;
+ unsigned int total;
+ STBTT_memset(buffer, 0, kernel_width);
+
+ total = 0;
+
+ // make kernel_width a constant in common cases so compiler can optimize out the divide
+ switch (kernel_width) {
+ case 2:
+ for (i=0; i <= safe_w; ++i) {
+ total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+ pixels[i] = (unsigned char) (total / 2);
+ }
+ break;
+ case 3:
+ for (i=0; i <= safe_w; ++i) {
+ total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+ pixels[i] = (unsigned char) (total / 3);
+ }
+ break;
+ case 4:
+ for (i=0; i <= safe_w; ++i) {
+ total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+ pixels[i] = (unsigned char) (total / 4);
+ }
+ break;
+ case 5:
+ for (i=0; i <= safe_w; ++i) {
+ total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+ pixels[i] = (unsigned char) (total / 5);
+ }
+ break;
+ default:
+ for (i=0; i <= safe_w; ++i) {
+ total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+ pixels[i] = (unsigned char) (total / kernel_width);
+ }
+ break;
+ }
+
+ for (; i < w; ++i) {
+ STBTT_assert(pixels[i] == 0);
+ total -= buffer[i & STBTT__OVER_MASK];
+ pixels[i] = (unsigned char) (total / kernel_width);
+ }
+
+ pixels += stride_in_bytes;
+ }
+}
+
+static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)
+{
+ unsigned char buffer[STBTT_MAX_OVERSAMPLE];
+ int safe_h = h - kernel_width;
+ int j;
+ for (j=0; j < w; ++j) {
+ int i;
+ unsigned int total;
+ STBTT_memset(buffer, 0, kernel_width);
+
+ total = 0;
+
+ // make kernel_width a constant in common cases so compiler can optimize out the divide
+ switch (kernel_width) {
+ case 2:
+ for (i=0; i <= safe_h; ++i) {
+ total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+ pixels[i*stride_in_bytes] = (unsigned char) (total / 2);
+ }
+ break;
+ case 3:
+ for (i=0; i <= safe_h; ++i) {
+ total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+ pixels[i*stride_in_bytes] = (unsigned char) (total / 3);
+ }
+ break;
+ case 4:
+ for (i=0; i <= safe_h; ++i) {
+ total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+ pixels[i*stride_in_bytes] = (unsigned char) (total / 4);
+ }
+ break;
+ case 5:
+ for (i=0; i <= safe_h; ++i) {
+ total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+ pixels[i*stride_in_bytes] = (unsigned char) (total / 5);
+ }
+ break;
+ default:
+ for (i=0; i <= safe_h; ++i) {
+ total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+ buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+ pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width);
+ }
+ break;
+ }
+
+ for (; i < h; ++i) {
+ STBTT_assert(pixels[i*stride_in_bytes] == 0);
+ total -= buffer[i & STBTT__OVER_MASK];
+ pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width);
+ }
+
+ pixels += 1;
+ }
+}
+
+static float stbtt__oversample_shift(int oversample)
+{
+ if (!oversample)
+ return 0.0f;
+
+ // The prefilter is a box filter of width "oversample",
+ // which shifts phase by (oversample - 1)/2 pixels in
+ // oversampled space. We want to shift in the opposite
+ // direction to counter this.
+ return (float)-(oversample - 1) / (2.0f * (float)oversample);
+}
+
+// rects array must be big enough to accommodate all characters in the given ranges
+STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
+{
+ int i,j,k;
+
+ k=0;
+ for (i=0; i < num_ranges; ++i) {
+ float fh = ranges[i].font_size;
+ float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh);
+ ranges[i].h_oversample = (unsigned char) spc->h_oversample;
+ ranges[i].v_oversample = (unsigned char) spc->v_oversample;
+ for (j=0; j < ranges[i].num_chars; ++j) {
+ int x0,y0,x1,y1;
+ int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
+ int glyph = stbtt_FindGlyphIndex(info, codepoint);
+ stbtt_GetGlyphBitmapBoxSubpixel(info,glyph,
+ scale * spc->h_oversample,
+ scale * spc->v_oversample,
+ 0,0,
+ &x0,&y0,&x1,&y1);
+ rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1);
+ rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1);
+ ++k;
+ }
+ }
+
+ return k;
+}
+
+// rects array must be big enough to accommodate all characters in the given ranges
+STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
+{
+ int i,j,k, return_value = 1;
+
+ // save current values
+ int old_h_over = spc->h_oversample;
+ int old_v_over = spc->v_oversample;
+
+ k = 0;
+ for (i=0; i < num_ranges; ++i) {
+ float fh = ranges[i].font_size;
+ float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh);
+ float recip_h,recip_v,sub_x,sub_y;
+ spc->h_oversample = ranges[i].h_oversample;
+ spc->v_oversample = ranges[i].v_oversample;
+ recip_h = 1.0f / spc->h_oversample;
+ recip_v = 1.0f / spc->v_oversample;
+ sub_x = stbtt__oversample_shift(spc->h_oversample);
+ sub_y = stbtt__oversample_shift(spc->v_oversample);
+ for (j=0; j < ranges[i].num_chars; ++j) {
+ stbrp_rect *r = &rects[k];
+ if (r->was_packed) {
+ stbtt_packedchar *bc = &ranges[i].chardata_for_range[j];
+ int advance, lsb, x0,y0,x1,y1;
+ int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
+ int glyph = stbtt_FindGlyphIndex(info, codepoint);
+ stbrp_coord pad = (stbrp_coord) spc->padding;
+
+ // pad on left and top
+ r->x += pad;
+ r->y += pad;
+ r->w -= pad;
+ r->h -= pad;
+ stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb);
+ stbtt_GetGlyphBitmapBox(info, glyph,
+ scale * spc->h_oversample,
+ scale * spc->v_oversample,
+ &x0,&y0,&x1,&y1);
+ stbtt_MakeGlyphBitmapSubpixel(info,
+ spc->pixels + r->x + r->y*spc->stride_in_bytes,
+ r->w - spc->h_oversample+1,
+ r->h - spc->v_oversample+1,
+ spc->stride_in_bytes,
+ scale * spc->h_oversample,
+ scale * spc->v_oversample,
+ 0,0,
+ glyph);
+
+ if (spc->h_oversample > 1)
+ stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,
+ r->w, r->h, spc->stride_in_bytes,
+ spc->h_oversample);
+
+ if (spc->v_oversample > 1)
+ stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,
+ r->w, r->h, spc->stride_in_bytes,
+ spc->v_oversample);
+
+ bc->x0 = (stbtt_int16) r->x;
+ bc->y0 = (stbtt_int16) r->y;
+ bc->x1 = (stbtt_int16) (r->x + r->w);
+ bc->y1 = (stbtt_int16) (r->y + r->h);
+ bc->xadvance = scale * advance;
+ bc->xoff = (float) x0 * recip_h + sub_x;
+ bc->yoff = (float) y0 * recip_v + sub_y;
+ bc->xoff2 = (x0 + r->w) * recip_h + sub_x;
+ bc->yoff2 = (y0 + r->h) * recip_v + sub_y;
+ } else {
+ return_value = 0; // if any fail, report failure
+ }
+
+ ++k;
+ }
+ }
+
+ // restore original values
+ spc->h_oversample = old_h_over;
+ spc->v_oversample = old_v_over;
+
+ return return_value;
+}
+
+STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects)
+{
+ stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects);
+}
+
+STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges)
+{
+ stbtt_fontinfo info;
+ int i,j,n, return_value = 1;
+ //stbrp_context *context = (stbrp_context *) spc->pack_info;
+ stbrp_rect *rects;
+
+ // flag all characters as NOT packed
+ for (i=0; i < num_ranges; ++i)
+ for (j=0; j < ranges[i].num_chars; ++j)
+ ranges[i].chardata_for_range[j].x0 =
+ ranges[i].chardata_for_range[j].y0 =
+ ranges[i].chardata_for_range[j].x1 =
+ ranges[i].chardata_for_range[j].y1 = 0;
+
+ n = 0;
+ for (i=0; i < num_ranges; ++i)
+ n += ranges[i].num_chars;
+
+ rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context);
+ if (rects == NULL)
+ return 0;
+
+ stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index));
+
+ n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects);
+
+ stbtt_PackFontRangesPackRects(spc, rects, n);
+
+ return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects);
+
+ STBTT_free(rects, spc->user_allocator_context);
+ return return_value;
+}
+
+STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size,
+ int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range)
+{
+ stbtt_pack_range range;
+ range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range;
+ range.array_of_unicode_codepoints = NULL;
+ range.num_chars = num_chars_in_range;
+ range.chardata_for_range = chardata_for_range;
+ range.font_size = font_size;
+ return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1);
+}
+
+STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer)
+{
+ float ipw = 1.0f / pw, iph = 1.0f / ph;
+ stbtt_packedchar *b = chardata + char_index;
+
+ if (align_to_integer) {
+ float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f);
+ float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f);
+ q->x0 = x;
+ q->y0 = y;
+ q->x1 = x + b->xoff2 - b->xoff;
+ q->y1 = y + b->yoff2 - b->yoff;
+ } else {
+ q->x0 = *xpos + b->xoff;
+ q->y0 = *ypos + b->yoff;
+ q->x1 = *xpos + b->xoff2;
+ q->y1 = *ypos + b->yoff2;
+ }
+
+ q->s0 = b->x0 * ipw;
+ q->t0 = b->y0 * iph;
+ q->s1 = b->x1 * ipw;
+ q->t1 = b->y1 * iph;
+
+ *xpos += b->xadvance;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// font name matching -- recommended not to use this
+//
+
+// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string
+static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2)
+{
+ stbtt_int32 i=0;
+
+ // convert utf16 to utf8 and compare the results while converting
+ while (len2) {
+ stbtt_uint16 ch = s2[0]*256 + s2[1];
+ if (ch < 0x80) {
+ if (i >= len1) return -1;
+ if (s1[i++] != ch) return -1;
+ } else if (ch < 0x800) {
+ if (i+1 >= len1) return -1;
+ if (s1[i++] != 0xc0 + (ch >> 6)) return -1;
+ if (s1[i++] != 0x80 + (ch & 0x3f)) return -1;
+ } else if (ch >= 0xd800 && ch < 0xdc00) {
+ stbtt_uint32 c;
+ stbtt_uint16 ch2 = s2[2]*256 + s2[3];
+ if (i+3 >= len1) return -1;
+ c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000;
+ if (s1[i++] != 0xf0 + (c >> 18)) return -1;
+ if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1;
+ if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1;
+ if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1;
+ s2 += 2; // plus another 2 below
+ len2 -= 2;
+ } else if (ch >= 0xdc00 && ch < 0xe000) {
+ return -1;
+ } else {
+ if (i+2 >= len1) return -1;
+ if (s1[i++] != 0xe0 + (ch >> 12)) return -1;
+ if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1;
+ if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1;
+ }
+ s2 += 2;
+ len2 -= 2;
+ }
+ return i;
+}
+
+STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2)
+{
+ return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2);
+}
+
+// returns results in whatever encoding you request... but note that 2-byte encodings
+// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare
+STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID)
+{
+ stbtt_int32 i,count,stringOffset;
+ stbtt_uint8 *fc = font->data;
+ stbtt_uint32 offset = font->fontstart;
+ stbtt_uint32 nm = stbtt__find_table(fc, offset, "name");
+ if (!nm) return NULL;
+
+ count = ttUSHORT(fc+nm+2);
+ stringOffset = nm + ttUSHORT(fc+nm+4);
+ for (i=0; i < count; ++i) {
+ stbtt_uint32 loc = nm + 6 + 12 * i;
+ if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2)
+ && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) {
+ *length = ttUSHORT(fc+loc+8);
+ return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10));
+ }
+ }
+ return NULL;
+}
+
+static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id)
+{
+ stbtt_int32 i;
+ stbtt_int32 count = ttUSHORT(fc+nm+2);
+ stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4);
+
+ for (i=0; i < count; ++i) {
+ stbtt_uint32 loc = nm + 6 + 12 * i;
+ stbtt_int32 id = ttUSHORT(fc+loc+6);
+ if (id == target_id) {
+ // find the encoding
+ stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4);
+
+ // is this a Unicode encoding?
+ if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) {
+ stbtt_int32 slen = ttUSHORT(fc+loc+8);
+ stbtt_int32 off = ttUSHORT(fc+loc+10);
+
+ // check if there's a prefix match
+ stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen);
+ if (matchlen >= 0) {
+ // check for target_id+1 immediately following, with same encoding & language
+ if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) {
+ slen = ttUSHORT(fc+loc+12+8);
+ off = ttUSHORT(fc+loc+12+10);
+ if (slen == 0) {
+ if (matchlen == nlen)
+ return 1;
+ } else if (matchlen < nlen && name[matchlen] == ' ') {
+ ++matchlen;
+ if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen))
+ return 1;
+ }
+ } else {
+ // if nothing immediately following
+ if (matchlen == nlen)
+ return 1;
+ }
+ }
+ }
+
+ // @TODO handle other encodings
+ }
+ }
+ return 0;
+}
+
+static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags)
+{
+ stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name);
+ stbtt_uint32 nm,hd;
+ if (!stbtt__isfont(fc+offset)) return 0;
+
+ // check italics/bold/underline flags in macStyle...
+ if (flags) {
+ hd = stbtt__find_table(fc, offset, "head");
+ if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0;
+ }
+
+ nm = stbtt__find_table(fc, offset, "name");
+ if (!nm) return 0;
+
+ if (flags) {
+ // if we checked the macStyle flags, then just check the family and ignore the subfamily
+ if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1;
+ if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1;
+ if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1;
+ } else {
+ if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1;
+ if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1;
+ if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1;
+ }
+
+ return 0;
+}
+
+STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags)
+{
+ stbtt_int32 i;
+ for (i=0;;++i) {
+ stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i);
+ if (off < 0) return off;
+ if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags))
+ return off;
+ }
+}
+
+#endif // STB_TRUETYPE_IMPLEMENTATION
+
+
+// FULL VERSION HISTORY
+//
+// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
+// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
+// allow PackFontRanges to pack and render in separate phases;
+// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);
+// fixed an assert() bug in the new rasterizer
+// replace assert() with STBTT_assert() in new rasterizer
+// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine)
+// also more precise AA rasterizer, except if shapes overlap
+// remove need for STBTT_sort
+// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC
+// 1.04 (2015-04-15) typo in example
+// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes
+// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++
+// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match
+// non-oversampled; STBTT_POINT_SIZE for packed case only
+// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling
+// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg)
+// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID
+// 0.8b (2014-07-07) fix a warning
+// 0.8 (2014-05-25) fix a few more warnings
+// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back
+// 0.6c (2012-07-24) improve documentation
+// 0.6b (2012-07-20) fix a few more warnings
+// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels,
+// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty
+// 0.5 (2011-12-09) bugfixes:
+// subpixel glyph renderer computed wrong bounding box
+// first vertex of shape can be off-curve (FreeSans)
+// 0.4b (2011-12-03) fixed an error in the font baking example
+// 0.4 (2011-12-01) kerning, subpixel rendering (tor)
+// bugfixes for:
+// codepoint-to-glyph conversion using table fmt=12
+// codepoint-to-glyph conversion using table fmt=4
+// stbtt_GetBakedQuad with non-square texture (Zer)
+// updated Hello World! sample to use kerning and subpixel
+// fixed some warnings
+// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM)
+// userdata, malloc-from-userdata, non-zero fill (stb)
+// 0.2 (2009-03-11) Fix unsigned/signed char warnings
+// 0.1 (2009-03-09) First public release
+//
--
2.6.0.rc2.230.g3dd15c0
Måns Rullgård
2016-01-15 01:50:55 UTC
Permalink
Post by Simon Glass
This is a header file which provides a fairly light-weight TrueType
rendering implementation. It is pulled from http://nothings.org/. The code
style does not comply with U-Boot but I think it is best to leave alone to
permit the source to be synced later if needed.
The only change is to fix a reference to fabs() which should route through
a macro to allow U-Boot to provide its own version.
This seems to be using floating-point quite a bit. Unless I missed a
recent change, that's not allowed in u-boot.
--
Måns Rullgård
Simon Glass
2016-01-15 01:56:35 UTC
Permalink
Hi Måns,
Post by Måns Rullgård
Post by Simon Glass
This is a header file which provides a fairly light-weight TrueType
rendering implementation. It is pulled from http://nothings.org/. The code
style does not comply with U-Boot but I think it is best to leave alone to
permit the source to be synced later if needed.
The only change is to fix a reference to fabs() which should route through
a macro to allow U-Boot to provide its own version.
This seems to be using floating-point quite a bit. Unless I missed a
recent change, that's not allowed in u-boot.
See the cover letter. It works OK in sandbox and I've tested it on
rockchip and exynos also. Is there a fixed-point implementation
somewhere? I remember Acorn had one years ago [1] but I believe it was
written in ARM assembler. Perhaps there is another one somewhere. The
floating point is limited to the one driver that uses this header, so
it doesn't bleed into the build system, etc. and is not built at all
unless that driver is enabled.

Regards,
Simon

[1] e.g. http://www.tofla.iconbar.com/tofla/gfx/fnt01/index.htm
Tom Rini
2016-01-21 16:56:23 UTC
Permalink
Post by Måns Rullgård
Post by Simon Glass
style does not comply with U-Boot but I think it is best to leave alone to
permit the source to be synced later if needed.
The only change is to fix a reference to fabs() which should route through
a macro to allow U-Boot to provide its own version.
This seems to be using floating-point quite a bit. Unless I missed a
recent change, that's not allowed in u-boot.
You are generally speaking, correct. I am wondering if we don't need to
make exceptions, from time to time. For example, when we can easily
correct general math problems by using something from <linux/math64.h>
that's one thing and should be done.

On the other hand, we have this, which is adding a nice looking font for
the cases where our console is not a serial port but a screen and in
some cases a rather nice high DPI one too. So under the assumption that
no, we can't find a font we can borrow that doesn't also use floating
point, maybe we allow this, BUT with some caveats needing to be added
such as noting that hey, what happens if you 'go' some benchmark that
does FP stuff? Well, it better be save/restoring, yes?
--
Tom
Måns Rullgård
2016-01-21 17:05:55 UTC
Permalink
Post by Tom Rini
Post by Måns Rullgård
Post by Simon Glass
style does not comply with U-Boot but I think it is best to leave alone to
permit the source to be synced later if needed.
The only change is to fix a reference to fabs() which should route through
a macro to allow U-Boot to provide its own version.
This seems to be using floating-point quite a bit. Unless I missed a
recent change, that's not allowed in u-boot.
You are generally speaking, correct. I am wondering if we don't need to
make exceptions, from time to time. For example, when we can easily
correct general math problems by using something from <linux/math64.h>
that's one thing and should be done.
On the other hand, we have this, which is adding a nice looking font for
the cases where our console is not a serial port but a screen and in
some cases a rather nice high DPI one too. So under the assumption that
no, we can't find a font we can borrow that doesn't also use floating
point, maybe we allow this, BUT with some caveats needing to be added
such as noting that hey, what happens if you 'go' some benchmark that
does FP stuff? Well, it better be save/restoring, yes?
On some CPUs you also need to explicitly enable the FPU, and some rely
on software completion of certain operations (usually involving
subnormals). Of course we shouldn't let that prevent other systems
having nice features. We just need to be a bit careful about when we
enable them.
--
Måns Rullgård
Anatolij Gustschin
2016-01-30 13:11:19 UTC
Permalink
Hi Simon,

On Thu, 14 Jan 2016 18:10:33 -0700
Simon Glass ***@chromium.org wrote:
...
Post by Simon Glass
video: Add stb TrueType font renderer
Makefile: Add rules to build in .ttf files
video kconfig console_normal
video: Use fractional units for X coordinates
video: Handle the 'bell' character
video: Provide a left margin for the text console
video: Provide a signal when a new console line is started
video: Provide a backspace method
video: Add a console driver that uses TrueType fonts
video: Add the Nimbus sans font
video: Add the AnkaCoder mono-spaced font
video: Add the Rufscript handwriting font
video: Add the Cantoraone decorative font
License: Add the Open Font License
video: Allow selection of the driver and font size
video: sandbox: Allow selection of font size and console name
video: sandbox: Enable truetype fonts for sandbox
video: test: Add console tests for truetype
video: Correct 'tor' typo in comment
Licenses/OFL.txt | 97 +
Licenses/README | 1 +
configs/sandbox_defconfig | 4 +-
drivers/video/Kconfig | 36 +-
drivers/video/Makefile | 6 +-
drivers/video/console_normal.c | 24 +-
drivers/video/console_rotate.c | 66 +-
drivers/video/console_truetype.c | 550 +++++
drivers/video/fonts/Kconfig | 51 +
drivers/video/fonts/Makefile | 11 +
drivers/video/fonts/ankacoder_c75_r.ttf | Bin 0 -> 65596 bytes
drivers/video/fonts/cantoraone_regular.ttf | Bin 0 -> 163116 bytes
drivers/video/fonts/nimbus_sans_l_regular.ttf | Bin 0 -> 61660 bytes
drivers/video/fonts/rufscript010.ttf | Bin 0 -> 23080 bytes
drivers/video/sandbox_sdl.c | 2 +
drivers/video/stb_truetype.h | 3240 +++++++++++++++++++++++++
drivers/video/vidconsole-uclass.c | 84 +-
drivers/video/video-uclass.c | 29 +-
include/dm/test.h | 2 +
include/video.h | 7 +-
include/video_console.h | 70 +-
scripts/Makefile.lib | 21 +
test/dm/video.c | 90 +-
23 files changed, 4326 insertions(+), 65 deletions(-)
create mode 100644 Licenses/OFL.txt
create mode 100644 drivers/video/console_truetype.c
create mode 100644 drivers/video/fonts/Kconfig
create mode 100644 drivers/video/fonts/Makefile
create mode 100644 drivers/video/fonts/ankacoder_c75_r.ttf
create mode 100644 drivers/video/fonts/cantoraone_regular.ttf
create mode 100644 drivers/video/fonts/nimbus_sans_l_regular.ttf
create mode 100644 drivers/video/fonts/rufscript010.ttf
create mode 100644 drivers/video/stb_truetype.h
Series applied to u-boot-video/master. Thanks!

Anatolij

Loading...