From d298d21e5d78a79a44ba50ad58c87785c9467c63 Mon Sep 17 00:00:00 2001
From: Amar Takhar <verm@aegisub.org>
Date: Mon, 13 Jul 2009 22:43:25 +0000
Subject: [PATCH] Update libass to 94d4104 from
 http://greg.geekmind.org/viewgit/.

Originally committed to SVN as r3123.
---
 aegisub/libass/Makefile.am                    |   3 -
 aegisub/libass/ass.c                          | 136 ++++++------
 aegisub/libass/ass.h                          |   5 +
 aegisub/libass/ass_bitmap.c                   | 199 ++++++++++++++----
 aegisub/libass/ass_bitmap.h                   |  10 +-
 aegisub/libass/ass_cache.c                    |  42 ++--
 aegisub/libass/ass_cache.h                    |  14 +-
 ..._cache_template.c => ass_cache_template.h} |   0
 aegisub/libass/ass_drawing.c                  |   5 +
 aegisub/libass/ass_drawing.h                  |   1 +
 aegisub/libass/ass_font.c                     |  39 ++--
 aegisub/libass/ass_fontconfig.c               | 102 ++++-----
 aegisub/libass/ass_fontconfig.h               |   7 +-
 aegisub/libass/ass_library.c                  |  34 ++-
 aegisub/libass/ass_library.h                  |   4 +
 aegisub/libass/ass_render.c                   | 155 +++++++-------
 aegisub/libass/ass_utils.c                    | 149 ++-----------
 aegisub/libass/ass_utils.h                    |  16 +-
 18 files changed, 505 insertions(+), 416 deletions(-)
 rename aegisub/libass/{ass_cache_template.c => ass_cache_template.h} (100%)

diff --git a/aegisub/libass/Makefile.am b/aegisub/libass/Makefile.am
index cdcfd3e28..9ac93bf4f 100644
--- a/aegisub/libass/Makefile.am
+++ b/aegisub/libass/Makefile.am
@@ -19,6 +19,3 @@ libass_aegisub_a_SOURCES = \
 
 libass_aegisub_a_SOURCES += \
 	*.h
-
-EXTRA_DIST= \
-	ass_cache_template.c
diff --git a/aegisub/libass/ass.c b/aegisub/libass/ass.c
index 5aef14479..9eaa7b01d 100644
--- a/aegisub/libass/ass.c
+++ b/aegisub/libass/ass.c
@@ -182,25 +182,26 @@ static int lookup_style(ass_track_t *track, char *name)
             return i;
     }
     i = track->default_style;
-    ass_msg(MSGL_WARN, MSGTR_LIBASS_NoStyleNamedXFoundUsingY,
-           track, name, track->styles[i].Name);
+    ass_msg(track->library, MSGL_WARN,
+            "[%p]: Warning: no style named '%s' found, using '%s'",
+            track, name, track->styles[i].Name);
     return i;                   // use the first style
 }
 
-static uint32_t string2color(char *p)
+static uint32_t string2color(ass_library_t *library, char *p)
 {
     uint32_t tmp;
-    (void) strtocolor(&p, &tmp);
+    (void) strtocolor(library, &p, &tmp);
     return tmp;
 }
 
-static long long string2timecode(char *p)
+static long long string2timecode(ass_library_t *library, char *p)
 {
     unsigned h, m, s, ms;
     long long tm;
     int res = sscanf(p, "%1d:%2d:%2d.%2d", &h, &m, &s, &ms);
     if (res < 4) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_BadTimestamp);
+        ass_msg(library, MSGL_WARN, "Bad timestamp");
         return 0;
     }
     tm = ((h * 60 + m) * 60 + s) * 1000 + ms * 10;
@@ -228,22 +229,30 @@ static int numpad2align(int val)
 #define ANYVAL(name,func) \
 	} else if (strcasecmp(tname, #name) == 0) { \
 		target->name = func(token); \
-		ass_msg(MSGL_DBG2, "%s = %s\n", #name, token);
+		ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
 
 #define STRVAL(name) \
 	} else if (strcasecmp(tname, #name) == 0) { \
 		if (target->name != NULL) free(target->name); \
 		target->name = strdup(token); \
-		ass_msg(MSGL_DBG2, "%s = %s\n", #name, token);
+		ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
+
+#define COLORVAL(name) \
+	} else if (strcasecmp(tname, #name) == 0) { \
+		target->name = string2color(track->library, token); \
+		ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
 
-#define COLORVAL(name) ANYVAL(name,string2color)
 #define INTVAL(name) ANYVAL(name,atoi)
 #define FPVAL(name) ANYVAL(name,atof)
-#define TIMEVAL(name) ANYVAL(name,string2timecode)
+#define TIMEVAL(name) \
+	} else if (strcasecmp(tname, #name) == 0) { \
+		target->name = string2timecode(track->library, token); \
+		ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
+
 #define STYLEVAL(name) \
 	} else if (strcasecmp(tname, #name) == 0) { \
 		target->name = lookup_style(track, token); \
-		ass_msg(MSGL_DBG2, "%s = %s\n", #name, token);
+		ass_msg(track->library, MSGL_DBG2, "%s = %s", #name, token);
 
 #define ALIAS(alias,name) \
 	if (strcasecmp(tname, #alias) == 0) {tname = #name;}
@@ -317,7 +326,7 @@ static int process_event_tail(ass_track_t *track, ass_event_t *event,
                 if (last >= event->Text && *last == '\r')
                     *last = 0;
             }
-            ass_msg(MSGL_DBG2, "Text = %s\n", event->Text);
+            ass_msg(track->library, MSGL_DBG2, "Text = %s", event->Text);
             event->Duration -= event->Start;
             free(format);
             return 0;           // "Text" is always the last
@@ -457,7 +466,7 @@ static int process_style(ass_track_t *track, char *str)
 
     q = format = strdup(track->style_format);
 
-    ass_msg(MSGL_V, "[%p] Style: %s\n", track, str);
+    ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
 
     sid = ass_alloc_style(track);
 
@@ -534,7 +543,7 @@ static int process_styles_line(ass_track_t *track, char *str)
         char *p = str + 7;
         skip_spaces(&p);
         track->style_format = strdup(p);
-        ass_msg(MSGL_DBG2, "Style format: %s\n",
+        ass_msg(track->library, MSGL_DBG2, "Style format: %s",
                track->style_format);
     } else if (!strncmp(str, "Style:", 6)) {
         char *p = str + 6;
@@ -564,14 +573,13 @@ static void event_format_fallback(ass_track_t *track)
 {
     track->parser_priv->state = PST_EVENTS;
     if (track->track_type == TRACK_TYPE_SSA)
-        track->event_format =
-            strdup
-            ("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
+        track->event_format = strdup("Format: Marked, Start, End, Style, "
+            "Name, MarginL, MarginR, MarginV, Effect, Text");
     else
-        track->event_format =
-            strdup
-            ("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text");
-    ass_msg(MSGL_V, "No event format found, using fallback.\n");
+        track->event_format = strdup("Format: Layer, Start, End, Style, "
+            "Actor, MarginL, MarginR, MarginV, Effect, Text");
+    ass_msg(track->library, MSGL_V,
+            "No event format found, using fallback");
 }
 
 static int process_events_line(ass_track_t *track, char *str)
@@ -580,8 +588,7 @@ static int process_events_line(ass_track_t *track, char *str)
         char *p = str + 7;
         skip_spaces(&p);
         track->event_format = strdup(p);
-        ass_msg(MSGL_DBG2, "Event format: %s\n",
-               track->event_format);
+        ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
     } else if (!strncmp(str, "Dialogue:", 9)) {
         // This should never be reached for embedded subtitles.
         // They have slightly different format and are parsed in ass_process_chunk,
@@ -601,7 +608,7 @@ static int process_events_line(ass_track_t *track, char *str)
 
         process_event_tail(track, event, str, 0);
     } else {
-        ass_msg(MSGL_V, "Not understood: %s  \n", str);
+        ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
     }
     return 0;
 }
@@ -636,11 +643,11 @@ static int decode_font(ass_track_t *track)
     int dsize;                  // decoded size
     unsigned char *buf = 0;
 
-    ass_msg(MSGL_V, "font: %d bytes encoded data \n",
-           track->parser_priv->fontdata_used);
+    ass_msg(track->library, MSGL_V, "Font: %d bytes encoded data",
+            track->parser_priv->fontdata_used);
     size = track->parser_priv->fontdata_used;
     if (size % 4 == 1) {
-        ass_msg(MSGL_ERR, MSGTR_LIBASS_BadEncodedDataSize);
+        ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
         goto error_decode_font;
     }
     buf = malloc(size / 4 * 3 + 2);
@@ -686,19 +693,20 @@ static int process_fonts_line(ass_track_t *track, char *str)
             decode_font(track);
         }
         track->parser_priv->fontname = strdup(p);
-        ass_msg(MSGL_V, "fontname: %s\n",
+        ass_msg(track->library, MSGL_V, "Fontname: %s",
                track->parser_priv->fontname);
         return 0;
     }
 
     if (!track->parser_priv->fontname) {
-        ass_msg(MSGL_V, "Not understood: %s  \n", str);
+        ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
         return 0;
     }
 
     len = strlen(str);
     if (len > 80) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_FontLineTooLong, len, str);
+        ass_msg(track->library, MSGL_WARN, "Font line too long: %d, %s",
+                len, str);
         return 0;
     }
     if (track->parser_priv->fontdata_used + len >
@@ -801,7 +809,7 @@ void ass_process_data(ass_track_t *track, char *data, int size)
     memcpy(str, data, size);
     str[size] = '\0';
 
-    ass_msg(MSGL_V, "event: %s\n", str);
+    ass_msg(track->library, MSGL_V, "Event: %s", str);
     process_text(track, str);
     free(str);
 }
@@ -852,14 +860,14 @@ void ass_process_chunk(ass_track_t *track, char *data, int size,
     ass_event_t *event;
 
     if (!track->event_format) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_EventFormatHeaderMissing);
+        ass_msg(track->library, MSGL_WARN, "Event format header missing");
         return;
     }
 
     str = malloc(size + 1);
     memcpy(str, data, size);
     str[size] = '\0';
-    ass_msg(MSGL_V, "event at %" PRId64 ", +%" PRId64 ": %s  \n",
+    ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
            (int64_t) timecode, (int64_t) duration, str);
 
     eid = ass_alloc_event(track);
@@ -898,7 +906,8 @@ void ass_process_chunk(ass_track_t *track, char *data, int size,
  * \param size buffer size
  * \return a pointer to recoded buffer, caller is responsible for freeing it
 **/
-static char *sub_recode(char *data, size_t size, char *codepage)
+static char *sub_recode(ass_library_t *library, char *data, size_t size,
+                        char *codepage)
 {
     iconv_t icdsc;
     char *tocp = "UTF-8";
@@ -913,15 +922,14 @@ static char *sub_recode(char *data, size_t size, char *codepage)
             || sscanf(codepage, "ENCA:%2s:%99s", enca_lang,
                       enca_fallback) == 2) {
             cp_tmp =
-                ass_guess_buffer_cp((unsigned char *) data, size, enca_lang,
-                                enca_fallback);
+                ass_guess_buffer_cp(library, (unsigned char *) data, size,
+                                    enca_lang, enca_fallback);
         }
 #endif
         if ((icdsc = iconv_open(tocp, cp_tmp)) != (iconv_t) (-1)) {
-            ass_msg(MSGL_V, "LIBSUB: opened iconv descriptor.\n");
+            ass_msg(library, MSGL_V, "Opened iconv descriptor");
         } else
-            ass_msg(MSGL_ERR,
-                   MSGTR_LIBASS_ErrorOpeningIconvDescriptor);
+            ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
     }
 
     {
@@ -952,8 +960,7 @@ static char *sub_recode(char *data, size_t size, char *codepage)
                     osize += size;
                     oleft += size;
                 } else {
-                    ass_msg(MSGL_WARN,
-                           MSGTR_LIBASS_ErrorRecodingFile);
+                    ass_msg(library, MSGL_WARN, "Error recoding file");
                     return NULL;
                 }
             } else if (clear)
@@ -965,7 +972,7 @@ static char *sub_recode(char *data, size_t size, char *codepage)
     if (icdsc != (iconv_t) (-1)) {
         (void) iconv_close(icdsc);
         icdsc = (iconv_t) (-1);
-        ass_msg(MSGL_V, "LIBSUB: closed iconv descriptor.\n");
+        ass_msg(library, MSGL_V, "Closed iconv descriptor");
     }
 
     return outbuf;
@@ -978,7 +985,7 @@ static char *sub_recode(char *data, size_t size, char *codepage)
  * \param bufsize out: file size
  * \return pointer to file contents. Caller is responsible for its deallocation.
  */
-static char *read_file(char *fname, size_t *bufsize)
+static char *read_file(ass_library_t *library, char *fname, size_t *bufsize)
 {
     int res;
     long sz;
@@ -987,12 +994,14 @@ static char *read_file(char *fname, size_t *bufsize)
 
     FILE *fp = fopen(fname, "rb");
     if (!fp) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_FopenFailed, fname);
+        ass_msg(library, MSGL_WARN,
+                "ass_read_file(%s): fopen failed", fname);
         return 0;
     }
     res = fseek(fp, 0, SEEK_END);
     if (res == -1) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_FseekFailed, fname);
+        ass_msg(library, MSGL_WARN,
+                "ass_read_file(%s): fseek failed", fname);
         fclose(fp);
         return 0;
     }
@@ -1001,13 +1010,14 @@ static char *read_file(char *fname, size_t *bufsize)
     rewind(fp);
 
     if (sz > 10 * 1024 * 1024) {
-        ass_msg(MSGL_INFO,
-               MSGTR_LIBASS_RefusingToLoadSubtitlesLargerThan10M, fname);
+        ass_msg(library, MSGL_INFO,
+               "ass_read_file(%s): Refusing to load subtitles "
+               "larger than 10MiB", fname);
         fclose(fp);
         return 0;
     }
 
-    ass_msg(MSGL_V, "file size: %ld\n", sz);
+    ass_msg(library, MSGL_V, "File size: %ld", sz);
 
     buf = malloc(sz + 1);
     assert(buf);
@@ -1015,8 +1025,8 @@ static char *read_file(char *fname, size_t *bufsize)
     do {
         res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
         if (res <= 0) {
-            ass_msg(MSGL_INFO, MSGTR_LIBASS_ReadFailed, errno,
-                   strerror(errno));
+            ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
+                    strerror(errno));
             fclose(fp);
             free(buf);
             return 0;
@@ -1081,7 +1091,7 @@ ass_track_t *ass_read_memory(ass_library_t *library, char *buf,
 
 #ifdef CONFIG_ICONV
     if (codepage)
-        buf = sub_recode(buf, bufsize, codepage);
+        buf = sub_recode(library, buf, bufsize, codepage);
     if (!buf)
         return 0;
     else
@@ -1093,22 +1103,24 @@ ass_track_t *ass_read_memory(ass_library_t *library, char *buf,
     if (!track)
         return 0;
 
-    ass_msg(MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileMemory,
-           track->n_styles, track->n_events);
+    ass_msg(library, MSGL_INFO, "Added subtitle file: "
+            "<memory> (%d styles, %d events)",
+            track->n_styles, track->n_events);
     return track;
 }
 
-static char *read_file_recode(char *fname, char *codepage, size_t *size)
+static char *read_file_recode(ass_library_t *library, char *fname,
+                              char *codepage, size_t *size)
 {
     char *buf;
     size_t bufsize;
 
-    buf = read_file(fname, &bufsize);
+    buf = read_file(library, fname, &bufsize);
     if (!buf)
         return 0;
 #ifdef CONFIG_ICONV
     if (codepage) {
-        char *tmpbuf = sub_recode(buf, bufsize, codepage);
+        char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
         free(buf);
         buf = tmpbuf;
     }
@@ -1133,7 +1145,7 @@ ass_track_t *ass_read_file(ass_library_t *library, char *fname,
     ass_track_t *track;
     size_t bufsize;
 
-    buf = read_file_recode(fname, codepage, &bufsize);
+    buf = read_file_recode(library, fname, codepage, &bufsize);
     if (!buf)
         return 0;
     track = parse_memory(library, buf);
@@ -1143,10 +1155,10 @@ ass_track_t *ass_read_file(ass_library_t *library, char *fname,
 
     track->name = strdup(fname);
 
-    ass_msg(MSGL_INFO, MSGTR_LIBASS_AddedSubtitleFileFname, fname,
-           track->n_styles, track->n_events);
+    ass_msg(library, MSGL_INFO,
+            "Added subtitle file: '%s' (%d styles, %d events)",
+            fname, track->n_styles, track->n_events);
 
-//      dump_events(forced_tid);
     return track;
 }
 
@@ -1159,13 +1171,13 @@ int ass_read_styles(ass_track_t *track, char *fname, char *codepage)
     parser_state_t old_state;
     size_t sz;
 
-    buf = read_file(fname, &sz);
+    buf = read_file(track->library, fname, &sz);
     if (!buf)
         return 1;
 #ifdef CONFIG_ICONV
     if (codepage) {
         char *tmpbuf;
-        tmpbuf = sub_recode(buf, sz, codepage);
+        tmpbuf = sub_recode(track->library, buf, sz, codepage);
         free(buf);
         buf = tmpbuf;
     }
diff --git a/aegisub/libass/ass.h b/aegisub/libass/ass.h
index d911e9911..0a03ec2a0 100644
--- a/aegisub/libass/ass.h
+++ b/aegisub/libass/ass.h
@@ -22,6 +22,7 @@
 #define LIBASS_ASS_H
 
 #include <stdio.h>
+#include <stdarg.h>
 #include "ass_types.h"
 
 /// Libass renderer object. Contents are private.
@@ -69,6 +70,10 @@ void ass_set_style_overrides(ass_library_t *priv, char **list);
 
 void ass_process_force_style(ass_track_t *track);
 
+void ass_set_message_cb(ass_library_t *priv,
+                        void (*msg_cb)(int, char *, va_list *, void *),
+                        void *data);
+
 /**
  * \brief initialize the renderer
  * \param priv library handle
diff --git a/aegisub/libass/ass_bitmap.c b/aegisub/libass/ass_bitmap.c
index 16d2b351e..3642be826 100644
--- a/aegisub/libass/ass_bitmap.c
+++ b/aegisub/libass/ass_bitmap.c
@@ -165,7 +165,7 @@ static bitmap_t *copy_bitmap(const bitmap_t *src)
     return dst;
 }
 
-static int check_glyph_area(FT_Glyph glyph)
+static int check_glyph_area(ass_library_t *library, FT_Glyph glyph)
 {
     FT_BBox bbox;
     long long dx, dy;
@@ -173,14 +173,15 @@ static int check_glyph_area(FT_Glyph glyph)
     dx = bbox.xMax - bbox.xMin;
     dy = bbox.yMax - bbox.yMin;
     if (dx * dy > 8000000) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_GlyphBBoxTooLarge,
+        ass_msg(library, MSGL_WARN, "Glyph bounding box too large: %dx%dpx",
                (int) dx, (int) dy);
         return 1;
     } else
         return 0;
 }
 
-static bitmap_t *glyph_to_bitmap_internal(FT_Glyph glyph, int bord)
+static bitmap_t *glyph_to_bitmap_internal(ass_library_t *library,
+                                          FT_Glyph glyph, int bord)
 {
     FT_BitmapGlyph bg;
     FT_Bitmap *bit;
@@ -191,11 +192,11 @@ static bitmap_t *glyph_to_bitmap_internal(FT_Glyph glyph, int bord)
     int i;
     int error;
 
-    if (check_glyph_area(glyph))
+    if (check_glyph_area(library, glyph))
         return 0;
     error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 0);
     if (error) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_FT_Glyph_To_BitmapError,
+        ass_msg(library, MSGL_WARN, "FT_Glyph_To_Bitmap error %d",
                error);
         return 0;
     }
@@ -203,7 +204,7 @@ static bitmap_t *glyph_to_bitmap_internal(FT_Glyph glyph, int bord)
     bg = (FT_BitmapGlyph) glyph;
     bit = &(bg->bitmap);
     if (bit->pixel_mode != FT_PIXEL_MODE_GRAY) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_UnsupportedPixelMode,
+        ass_msg(library, MSGL_WARN, "Unsupported pixel mode: %d",
                (int) (bit->pixel_mode));
         FT_Done_Glyph(glyph);
         return 0;
@@ -260,7 +261,7 @@ static bitmap_t *fix_outline_and_shadow(bitmap_t *bm_g, bitmap_t *bm_o)
             unsigned char c_g, c_o;
             c_g = g[x];
             c_o = o[x];
-            o[x] = (c_o > c_g) ? c_o - (c_g / 2) : 0;
+            o[x] = (c_o > (3 * c_g) / 5) ? c_o - (3 * c_g) / 5 : 0;
             s[x] = (c_o < 0xFF - c_g) ? c_o + c_g : 0xFF;
         }
         g += bm_g->w;
@@ -322,6 +323,127 @@ static void shift_bitmap(unsigned char *buf, int w, int h, int shift_x,
     }
 }
 
+/*
+ * Gaussian blur.  An fast pure C implementation from MPlayer.
+ */
+static void ass_gauss_blur(unsigned char *buffer, unsigned short *tmp2,
+                           int width, int height, int stride, int *m2,
+                           int r, int mwidth)
+{
+
+    int x, y;
+
+    unsigned char *s = buffer;
+    unsigned short *t = tmp2 + 1;
+    for (y = 0; y < height; y++) {
+        memset(t - 1, 0, (width + 1) * sizeof(short));
+
+        for (x = 0; x < r; x++) {
+            const int src = s[x];
+            if (src) {
+                register unsigned short *dstp = t + x - r;
+                int mx;
+                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
+                for (mx = r - x; mx < mwidth; mx++) {
+                    dstp[mx] += m3[mx];
+                }
+            }
+        }
+
+        for (; x < width - r; x++) {
+            const int src = s[x];
+            if (src) {
+                register unsigned short *dstp = t + x - r;
+                int mx;
+                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
+                for (mx = 0; mx < mwidth; mx++) {
+                    dstp[mx] += m3[mx];
+                }
+            }
+        }
+
+        for (; x < width; x++) {
+            const int src = s[x];
+            if (src) {
+                register unsigned short *dstp = t + x - r;
+                int mx;
+                const int x2 = r + width - x;
+                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
+                for (mx = 0; mx < x2; mx++) {
+                    dstp[mx] += m3[mx];
+                }
+            }
+        }
+
+        s += stride;
+        t += width + 1;
+    }
+
+    t = tmp2;
+    for (x = 0; x < width; x++) {
+        for (y = 0; y < r; y++) {
+            unsigned short *srcp = t + y * (width + 1) + 1;
+            int src = *srcp;
+            if (src) {
+                register unsigned short *dstp = srcp - 1 + width + 1;
+                const int src2 = (src + 128) >> 8;
+                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
+
+                int mx;
+                *srcp = 128;
+                for (mx = r - 1; mx < mwidth; mx++) {
+                    *dstp += m3[mx];
+                    dstp += width + 1;
+                }
+            }
+        }
+        for (; y < height - r; y++) {
+            unsigned short *srcp = t + y * (width + 1) + 1;
+            int src = *srcp;
+            if (src) {
+                register unsigned short *dstp = srcp - 1 - r * (width + 1);
+                const int src2 = (src + 128) >> 8;
+                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
+
+                int mx;
+                *srcp = 128;
+                for (mx = 0; mx < mwidth; mx++) {
+                    *dstp += m3[mx];
+                    dstp += width + 1;
+                }
+            }
+        }
+        for (; y < height; y++) {
+            unsigned short *srcp = t + y * (width + 1) + 1;
+            int src = *srcp;
+            if (src) {
+                const int y2 = r + height - y;
+                register unsigned short *dstp = srcp - 1 - r * (width + 1);
+                const int src2 = (src + 128) >> 8;
+                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
+
+                int mx;
+                *srcp = 128;
+                for (mx = 0; mx < y2; mx++) {
+                    *dstp += m3[mx];
+                    dstp += width + 1;
+                }
+            }
+        }
+        t++;
+    }
+
+    t = tmp2;
+    s = buffer;
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            s[x] = t[x] >> 8;
+        }
+        s += stride;
+        t += width + 1;
+    }
+}
+
 /**
  * \brief Blur with [[1,2,1]. [2,4,2], [1,2,1]] kernel
  * This blur is the same as the one employed by vsfilter.
@@ -350,14 +472,15 @@ static void be_blur(unsigned char *buf, int w, int h)
     }
 }
 
-int glyph_to_bitmap(ass_synth_priv_t *priv_blur,
+int glyph_to_bitmap(ass_library_t *library, ass_synth_priv_t *priv_blur,
                     FT_Glyph glyph, FT_Glyph outline_glyph,
                     bitmap_t **bm_g, bitmap_t **bm_o, bitmap_t **bm_s,
                     int be, double blur_radius, FT_Vector shadow_offset)
 {
-    int bord = be ? (be / 4 + 1) : 0;
     blur_radius *= 2;
-    bord = (blur_radius > 0.0) ? blur_radius + 1 : bord;
+    int bbord = be > 0 ? sqrt(2 * be) : 0;
+    int gbord = blur_radius > 0.0 ? blur_radius + 1 : 0;
+    int bord = FFMAX(bbord, gbord);
     if (bord == 0 && (shadow_offset.x || shadow_offset.y))
         bord = 1;
 
@@ -366,43 +489,45 @@ int glyph_to_bitmap(ass_synth_priv_t *priv_blur,
     *bm_g = *bm_o = *bm_s = 0;
 
     if (glyph)
-        *bm_g = glyph_to_bitmap_internal(glyph, bord);
+        *bm_g = glyph_to_bitmap_internal(library, glyph, bord);
     if (!*bm_g)
         return 1;
 
     if (outline_glyph) {
-        *bm_o = glyph_to_bitmap_internal(outline_glyph, bord);
+        *bm_o = glyph_to_bitmap_internal(library, outline_glyph, bord);
         if (!*bm_o) {
             ass_free_bitmap(*bm_g);
             return 1;
         }
     }
-    if (*bm_o)
-        resize_tmp(priv_blur, (*bm_o)->w, (*bm_o)->h);
-    resize_tmp(priv_blur, (*bm_g)->w, (*bm_g)->h);
-
-    if (be) {
-        while (be--) {
-            if (*bm_o)
-                be_blur((*bm_o)->buffer, (*bm_o)->w, (*bm_o)->h);
-            else
-                be_blur((*bm_g)->buffer, (*bm_g)->w, (*bm_g)->h);
-        }
-    } else {
-        if (blur_radius > 0.0) {
-            generate_tables(priv_blur, blur_radius);
-            if (*bm_o)
-                ass_gauss_blur((*bm_o)->buffer, priv_blur->tmp,
-                               (*bm_o)->w, (*bm_o)->h, (*bm_o)->w,
-                               (int *) priv_blur->gt2, priv_blur->g_r,
-                               priv_blur->g_w);
-            else
-                ass_gauss_blur((*bm_g)->buffer, priv_blur->tmp,
-                               (*bm_g)->w, (*bm_g)->h, (*bm_g)->w,
-                               (int *) priv_blur->gt2, priv_blur->g_r,
-                               priv_blur->g_w);
-        }
+
+    // Apply box blur (multiple passes, if requested)
+    while (be--) {
+        if (*bm_o)
+            be_blur((*bm_o)->buffer, (*bm_o)->w, (*bm_o)->h);
+        else
+            be_blur((*bm_g)->buffer, (*bm_g)->w, (*bm_g)->h);
+    }
+
+    // Apply gaussian blur
+    if (blur_radius > 0.0) {
+        if (*bm_o)
+            resize_tmp(priv_blur, (*bm_o)->w, (*bm_o)->h);
+        else
+            resize_tmp(priv_blur, (*bm_g)->w, (*bm_g)->h);
+        generate_tables(priv_blur, blur_radius);
+        if (*bm_o)
+            ass_gauss_blur((*bm_o)->buffer, priv_blur->tmp,
+                           (*bm_o)->w, (*bm_o)->h, (*bm_o)->w,
+                           (int *) priv_blur->gt2, priv_blur->g_r,
+                           priv_blur->g_w);
+        else
+            ass_gauss_blur((*bm_g)->buffer, priv_blur->tmp,
+                           (*bm_g)->w, (*bm_g)->h, (*bm_g)->w,
+                           (int *) priv_blur->gt2, priv_blur->g_r,
+                           priv_blur->g_w);
     }
+
     if (*bm_o)
         *bm_s = fix_outline_and_shadow(*bm_g, *bm_o);
     else
diff --git a/aegisub/libass/ass_bitmap.h b/aegisub/libass/ass_bitmap.h
index f6b138058..2e34a5c6d 100644
--- a/aegisub/libass/ass_bitmap.h
+++ b/aegisub/libass/ass_bitmap.h
@@ -24,6 +24,8 @@
 #include <ft2build.h>
 #include FT_GLYPH_H
 
+#include "ass.h"
+
 typedef struct ass_synth_priv_s ass_synth_priv_t;
 
 ass_synth_priv_t *ass_synth_init(double);
@@ -44,10 +46,10 @@ typedef struct bitmap_s {
  * \param bm_g out: pointer to the bitmap of glyph shadow is returned here
  * \param be 1 = produces blurred bitmaps, 0 = normal bitmaps
  */
-int glyph_to_bitmap(ass_synth_priv_t *priv_blur, FT_Glyph glyph,
-                    FT_Glyph outline_glyph, bitmap_t **bm_g,
-                    bitmap_t **bm_o, bitmap_t **bm_s, int be,
-                    double blur_radius, FT_Vector shadow_offset);
+int glyph_to_bitmap(ass_library_t *library, ass_synth_priv_t *priv_blur,
+                    FT_Glyph glyph, FT_Glyph outline_glyph,
+                    bitmap_t **bm_g, bitmap_t **bm_o, bitmap_t **bm_s,
+                    int be, double blur_radius, FT_Vector shadow_offset);
 
 void ass_free_bitmap(bitmap_t *bm);
 
diff --git a/aegisub/libass/ass_cache.c b/aegisub/libass/ass_cache.c
index fc70b0d4f..4c7a3fa53 100644
--- a/aegisub/libass/ass_cache.c
+++ b/aegisub/libass/ass_cache.c
@@ -51,12 +51,14 @@ static void hashmap_item_dtor(void *key, size_t key_size, void *value,
     free(value);
 }
 
-hashmap_t *hashmap_init(size_t key_size, size_t value_size, int nbuckets,
+hashmap_t *hashmap_init(ass_library_t *library, size_t key_size,
+                        size_t value_size, int nbuckets,
                         hashmap_item_dtor_t item_dtor,
                         hashmap_key_compare_t key_compare,
                         hashmap_hash_t hash)
 {
     hashmap_t *map = calloc(1, sizeof(hashmap_t));
+    map->library = library;
     map->nbuckets = nbuckets;
     map->key_size = key_size;
     map->value_size = value_size;
@@ -72,8 +74,9 @@ void hashmap_done(hashmap_t *map)
     int i;
     // print stats
     if (map->count > 0 || map->hit_count + map->miss_count > 0)
-        ass_msg(MSGL_V,
-               "cache statistics: \n  total accesses: %d\n  hits: %d\n  misses: %d\n  object count: %d\n",
+        ass_msg(map->library, MSGL_V,
+               "cache statistics: \n  total accesses: %d\n  hits: %d\n  "
+               "misses: %d\n  object count: %d",
                map->hit_count + map->miss_count, map->hit_count,
                map->miss_count, map->count);
 
@@ -178,10 +181,10 @@ void *ass_font_cache_add(hashmap_t *font_cache, ass_font_t *font)
     return hashmap_insert(font_cache, &(font->desc), font);
 }
 
-hashmap_t *ass_font_cache_init(void)
+hashmap_t *ass_font_cache_init(ass_library_t *library)
 {
     hashmap_t *font_cache;
-    font_cache = hashmap_init(sizeof(ass_font_desc_t),
+    font_cache = hashmap_init(library, sizeof(ass_font_desc_t),
                               sizeof(ass_font_t),
                               1000,
                               font_hash_dtor, font_compare, font_desc_hash);
@@ -196,9 +199,9 @@ void ass_font_cache_done(hashmap_t *font_cache)
 
 // Create hash/compare functions for bitmap and glyph
 #define CREATE_HASH_FUNCTIONS
-#include "ass_cache_template.c"
+#include "ass_cache_template.h"
 #define CREATE_COMPARISON_FUNCTIONS
-#include "ass_cache_template.c"
+#include "ass_cache_template.h"
 
 //---------------------------------
 // bitmap cache
@@ -234,10 +237,11 @@ bitmap_hash_val_t *cache_find_bitmap(hashmap_t *bitmap_cache,
     return hashmap_find(bitmap_cache, key);
 }
 
-hashmap_t *ass_bitmap_cache_init(void)
+hashmap_t *ass_bitmap_cache_init(ass_library_t *library)
 {
     hashmap_t *bitmap_cache;
-    bitmap_cache = hashmap_init(sizeof(bitmap_hash_key_t),
+    bitmap_cache = hashmap_init(library,
+                                sizeof(bitmap_hash_key_t),
                                 sizeof(bitmap_hash_val_t),
                                 0xFFFF + 13,
                                 bitmap_hash_dtor, bitmap_compare,
@@ -252,8 +256,10 @@ void ass_bitmap_cache_done(hashmap_t *bitmap_cache)
 
 hashmap_t *ass_bitmap_cache_reset(hashmap_t *bitmap_cache)
 {
+    ass_library_t *lib = bitmap_cache->library;
+
     ass_bitmap_cache_done(bitmap_cache);
-    return ass_bitmap_cache_init();
+    return ass_bitmap_cache_init(lib);
 }
 
 //---------------------------------
@@ -288,10 +294,10 @@ glyph_hash_val_t *cache_find_glyph(hashmap_t *glyph_cache,
     return hashmap_find(glyph_cache, key);
 }
 
-hashmap_t *ass_glyph_cache_init(void)
+hashmap_t *ass_glyph_cache_init(ass_library_t *library)
 {
     hashmap_t *glyph_cache;
-    glyph_cache = hashmap_init(sizeof(glyph_hash_key_t),
+    glyph_cache = hashmap_init(library, sizeof(glyph_hash_key_t),
                                sizeof(glyph_hash_val_t),
                                0xFFFF + 13,
                                glyph_hash_dtor, glyph_compare, glyph_hash);
@@ -305,8 +311,10 @@ void ass_glyph_cache_done(hashmap_t *glyph_cache)
 
 hashmap_t *ass_glyph_cache_reset(hashmap_t *glyph_cache)
 {
+    ass_library_t *lib = glyph_cache->library;
+
     ass_glyph_cache_done(glyph_cache);
-    return ass_glyph_cache_init();
+    return ass_glyph_cache_init(lib);
 }
 
 
@@ -341,10 +349,10 @@ composite_hash_val_t *cache_find_composite(hashmap_t *composite_cache,
     return hashmap_find(composite_cache, key);
 }
 
-hashmap_t *ass_composite_cache_init(void)
+hashmap_t *ass_composite_cache_init(ass_library_t *library)
 {
     hashmap_t *composite_cache;
-    composite_cache = hashmap_init(sizeof(composite_hash_key_t),
+    composite_cache = hashmap_init(library, sizeof(composite_hash_key_t),
                                    sizeof(composite_hash_val_t),
                                    0xFFFF + 13,
                                    composite_hash_dtor, composite_compare,
@@ -359,6 +367,8 @@ void ass_composite_cache_done(hashmap_t *composite_cache)
 
 hashmap_t *ass_composite_cache_reset(hashmap_t *composite_cache)
 {
+    ass_library_t *lib = composite_cache->library;
+
     ass_composite_cache_done(composite_cache);
-    return ass_composite_cache_init();
+    return ass_composite_cache_init(lib);
 }
diff --git a/aegisub/libass/ass_cache.h b/aegisub/libass/ass_cache.h
index d8de97a18..83c994386 100644
--- a/aegisub/libass/ass_cache.h
+++ b/aegisub/libass/ass_cache.h
@@ -49,9 +49,11 @@ typedef struct hashmap_s {
     int hit_count;
     int miss_count;
     int count;
+    ass_library_t *library;
 } hashmap_t;
 
-hashmap_t *hashmap_init(size_t key_size, size_t value_size, int nbuckets,
+hashmap_t *hashmap_init(ass_library_t *library, size_t key_size,
+                        size_t value_size, int nbuckets,
                         hashmap_item_dtor_t item_dtor,
                         hashmap_key_compare_t key_compare,
                         hashmap_hash_t hash);
@@ -59,14 +61,14 @@ void hashmap_done(hashmap_t *map);
 void *hashmap_insert(hashmap_t *map, void *key, void *value);
 void *hashmap_find(hashmap_t *map, void *key);
 
-hashmap_t *ass_font_cache_init(void);
+hashmap_t *ass_font_cache_init(ass_library_t *library);
 ass_font_t *ass_font_cache_find(hashmap_t *, ass_font_desc_t *desc);
 void *ass_font_cache_add(hashmap_t *, ass_font_t *font);
 void ass_font_cache_done(hashmap_t *);
 
 // Create definitions for bitmap_hash_key and glyph_hash_key
 #define CREATE_STRUCT_DEFINITIONS
-#include "ass_cache_template.c"
+#include "ass_cache_template.h"
 
 typedef struct bitmap_hash_val_s {
     bitmap_t *bm;               // the actual bitmaps
@@ -74,7 +76,7 @@ typedef struct bitmap_hash_val_s {
     bitmap_t *bm_s;
 } bitmap_hash_val_t;
 
-hashmap_t *ass_bitmap_cache_init(void);
+hashmap_t *ass_bitmap_cache_init(ass_library_t *library);
 void *cache_add_bitmap(hashmap_t *, bitmap_hash_key_t *key,
                        bitmap_hash_val_t *val);
 bitmap_hash_val_t *cache_find_bitmap(hashmap_t *bitmap_cache,
@@ -88,7 +90,7 @@ typedef struct composite_hash_val_s {
     unsigned char *b;
 } composite_hash_val_t;
 
-hashmap_t *ass_composite_cache_init(void);
+hashmap_t *ass_composite_cache_init(ass_library_t *library);
 void *cache_add_composite(hashmap_t *, composite_hash_key_t *key,
                           composite_hash_val_t *val);
 composite_hash_val_t *cache_find_composite(hashmap_t *composite_cache,
@@ -105,7 +107,7 @@ typedef struct glyph_hash_val_s {
     int asc, desc;              // ascender/descender of a drawing
 } glyph_hash_val_t;
 
-hashmap_t *ass_glyph_cache_init(void);
+hashmap_t *ass_glyph_cache_init(ass_library_t *library);
 void *cache_add_glyph(hashmap_t *, glyph_hash_key_t *key,
                       glyph_hash_val_t *val);
 glyph_hash_val_t *cache_find_glyph(hashmap_t *glyph_cache,
diff --git a/aegisub/libass/ass_cache_template.c b/aegisub/libass/ass_cache_template.h
similarity index 100%
rename from aegisub/libass/ass_cache_template.c
rename to aegisub/libass/ass_cache_template.h
diff --git a/aegisub/libass/ass_drawing.c b/aegisub/libass/ass_drawing.c
index 09cf2d004..f75bbd9ad 100644
--- a/aegisub/libass/ass_drawing.c
+++ b/aegisub/libass/ass_drawing.c
@@ -138,6 +138,10 @@ static void drawing_finish(ass_drawing_t *drawing)
                                                     drawing->scale_y);
     for (i = 0; i < ol->n_points; i++)
         ol->points[i].y += offset;
+
+    ass_msg(drawing->library, MSGL_V,
+            "Parsed drawing with %d points and %d contours", ol->n_points,
+            ol->n_contours);
 }
 
 /*
@@ -361,6 +365,7 @@ ass_drawing_t *ass_drawing_new(void *fontconfig_priv, ass_font_t *font,
     drawing->size = DRAWING_INITIAL_SIZE;
 
     drawing->ftlibrary = lib;
+    drawing->library = font->library;
     drawing_make_glyph(drawing, fontconfig_priv, font, hint);
 
     drawing->scale_x = 1.;
diff --git a/aegisub/libass/ass_drawing.h b/aegisub/libass/ass_drawing.h
index 323c05dee..dfd68f01b 100644
--- a/aegisub/libass/ass_drawing.h
+++ b/aegisub/libass/ass_drawing.h
@@ -58,6 +58,7 @@ typedef struct ass_drawing_s {
 
     // private
     FT_Library ftlibrary;   // FT library instance, needed for font ops
+    ass_library_t *library;
     int size;           // current buffer size
     ass_drawing_token_t *tokens;    // tokenized drawing
     int max_points;     // current maximum size
diff --git a/aegisub/libass/ass_font.c b/aegisub/libass/ass_font.c
index 0aeeec6c1..34ae292c4 100644
--- a/aegisub/libass/ass_font.c
+++ b/aegisub/libass/ass_font.c
@@ -39,7 +39,7 @@
  * Select Microfost Unicode CharMap, if the font has one.
  * Otherwise, let FreeType decide.
  */
-static void charmap_magic(FT_Face face)
+static void charmap_magic(ass_library_t *library, FT_Face face)
 {
     int i;
     for (i = 0; i < face->num_charmaps; ++i) {
@@ -56,10 +56,11 @@ static void charmap_magic(FT_Face face)
 
     if (!face->charmap) {
         if (face->num_charmaps == 0) {
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_NoCharmaps);
+            ass_msg(library, MSGL_WARN, "Font face with no charmaps");
             return;
         }
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_NoCharmapAutodetected);
+        ass_msg(library, MSGL_WARN,
+                "No charmap autodetected, trying the first one");
         FT_Set_Charmap(face, face->charmaps[0]);
         return;
     }
@@ -125,7 +126,7 @@ static int add_face(void *fc_priv, ass_font_t *font, uint32_t ch)
         return -1;
 
     path =
-        fontconfig_select(fc_priv, font->desc.family,
+        fontconfig_select(font->library, fc_priv, font->desc.family,
                           font->desc.treat_family_as_pattern,
                           font->desc.bold, font->desc.italic, &index, ch);
     if (!path)
@@ -140,21 +141,21 @@ static int add_face(void *fc_priv, ass_font_t *font, uint32_t ch)
                                font->library->fontdata[mem_idx].size, 0,
                                &face);
         if (error) {
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_ErrorOpeningMemoryFont,
-                   path);
+            ass_msg(font->library, MSGL_WARN,
+                    "Error opening memory font: '%s'", path);
             free(path);
             return -1;
         }
     } else {
         error = FT_New_Face(font->ftlibrary, path, index, &face);
         if (error) {
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_ErrorOpeningFont, path,
-                   index);
+            ass_msg(font->library, MSGL_WARN,
+                    "Error opening font: '%s', %d", path, index);
             free(path);
             return -1;
         }
     }
-    charmap_magic(face);
+    charmap_magic(font->library, face);
     buggy_font_workaround(face);
 
     font->faces[font->n_faces++] = face;
@@ -386,17 +387,19 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ass_font_t *font,
 #ifdef CONFIG_FONTCONFIG
     if (index == 0) {
         int face_idx;
-        ass_msg(MSGL_INFO,
-               MSGTR_LIBASS_GlyphNotFoundReselectingFont, ch,
-               font->desc.family, font->desc.bold, font->desc.italic);
+        ass_msg(font->library, MSGL_INFO,
+                "Glyph 0x%X not found, selecting one more "
+                "font for (%s, %d, %d)", ch, font->desc.family,
+                font->desc.bold, font->desc.italic);
         face_idx = add_face(fontconfig_priv, font, ch);
         if (face_idx >= 0) {
             face = font->faces[face_idx];
             index = FT_Get_Char_Index(face, ch);
             if (index == 0) {
-                ass_msg(MSGL_ERR, MSGTR_LIBASS_GlyphNotFound,
-                       ch, font->desc.family, font->desc.bold,
-                       font->desc.italic);
+                ass_msg(font->library, MSGL_ERR,
+                        "Glyph 0x%X not found in font for (%s, %d, %d)",
+                        ch, font->desc.family, font->desc.bold,
+                        font->desc.italic);
             }
         }
     }
@@ -419,7 +422,8 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ass_font_t *font,
 
     error = FT_Load_Glyph(face, index, FT_LOAD_NO_BITMAP | flags);
     if (error) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph);
+        ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
+                index);
         return 0;
     }
 #if (FREETYPE_MAJOR > 2) || \
@@ -433,7 +437,8 @@ FT_Glyph ass_font_get_glyph(void *fontconfig_priv, ass_font_t *font,
 #endif
     error = FT_Get_Glyph(face->glyph, &glyph);
     if (error) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_ErrorLoadingGlyph);
+        ass_msg(font->library, MSGL_WARN, "Error loading glyph, index %d",
+                index);
         return 0;
     }
 
diff --git a/aegisub/libass/ass_fontconfig.c b/aegisub/libass/ass_fontconfig.c
index 2e6377a99..d8d64e229 100644
--- a/aegisub/libass/ass_fontconfig.c
+++ b/aegisub/libass/ass_fontconfig.c
@@ -71,9 +71,10 @@ struct fc_instance_s {
  * \param code: the character that should be present in the font, can be 0
  * \return font file path
 */
-static char *_select_font(fc_instance_t *priv, const char *family,
-                          int treat_family_as_pattern, unsigned bold,
-                          unsigned italic, int *index, uint32_t code)
+static char *_select_font(ass_library_t *library, fc_instance_t *priv,
+                          const char *family, int treat_family_as_pattern,
+                          unsigned bold, unsigned italic, int *index,
+                          uint32_t code)
 {
     FcBool rc;
     FcResult result;
@@ -193,8 +194,9 @@ static char *_select_font(fc_instance_t *priv, const char *family,
     if (!treat_family_as_pattern &&
         !(r_family && strcasecmp((const char *) r_family, family) == 0) &&
         !(r_fullname && strcasecmp((const char *) r_fullname, family) == 0))
-        ass_msg(MSGL_WARN,
-               MSGTR_LIBASS_SelectedFontFamilyIsNotTheRequestedOne,
+        ass_msg(library, MSGL_WARN,
+               "fontconfig: Selected font is not the requested one: "
+               "'%s' != '%s'",
                (const char *) (r_fullname ? r_fullname : r_family), family);
 
     result = FcPatternGetString(rpat, FC_STYLE, 0, &r_style);
@@ -213,9 +215,9 @@ static char *_select_font(fc_instance_t *priv, const char *family,
     if (result != FcResultMatch)
         r_embolden = 0;
 
-    ass_msg(MSGL_V,
-           "[ass] Font info: family '%s', style '%s', fullname '%s',"
-           " slant %d, weight %d%s\n", (const char *) r_family,
+    ass_msg(library, MSGL_V,
+           "Font info: family '%s', style '%s', fullname '%s',"
+           " slant %d, weight %d%s", (const char *) r_family,
            (const char *) r_style, (const char *) r_fullname, r_slant,
            r_weight, r_embolden ? ", embolden" : "");
 
@@ -240,9 +242,10 @@ static char *_select_font(fc_instance_t *priv, const char *family,
  * \param code: the character that should be present in the font, can be 0
  * \return font file path
 */
-char *fontconfig_select(fc_instance_t *priv, const char *family,
-                        int treat_family_as_pattern, unsigned bold,
-                        unsigned italic, int *index, uint32_t code)
+char *fontconfig_select(ass_library_t *library, fc_instance_t *priv,
+                        const char *family, int treat_family_as_pattern,
+                        unsigned bold, unsigned italic, int *index,
+                        uint32_t code)
 {
     char *res = 0;
     if (!priv->config) {
@@ -251,32 +254,36 @@ char *fontconfig_select(fc_instance_t *priv, const char *family,
     }
     if (family && *family)
         res =
-            _select_font(priv, family, treat_family_as_pattern, bold,
-                         italic, index, code);
+            _select_font(library, priv, family, treat_family_as_pattern,
+                         bold, italic, index, code);
     if (!res && priv->family_default) {
         res =
-            _select_font(priv, priv->family_default, 0, bold, italic, index,
-                         code);
+            _select_font(library, priv, priv->family_default, 0, bold,
+                         italic, index, code);
         if (res)
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_UsingDefaultFontFamily,
-                   family, bold, italic, res, *index);
+            ass_msg(library, MSGL_WARN, "fontconfig_select: Using default "
+                    "font family: (%s, %d, %d) -> %s, %d",
+                    family, bold, italic, res, *index);
     }
     if (!res && priv->path_default) {
         res = priv->path_default;
         *index = priv->index_default;
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_UsingDefaultFont,
-               family, bold, italic, res, *index);
+        ass_msg(library, MSGL_WARN, "fontconfig_select: Using default font: "
+                "(%s, %d, %d) -> %s, %d", family, bold, italic,
+                res, *index);
     }
     if (!res) {
-        res = _select_font(priv, "Arial", 0, bold, italic, index, code);
+        res = _select_font(library, priv, "Arial", 0, bold, italic,
+                           index, code);
         if (res)
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_UsingArialFontFamily,
-                   family, bold, italic, res, *index);
+            ass_msg(library, MSGL_WARN, "fontconfig_select: Using 'Arial' "
+                    "font family: (%s, %d, %d) -> %s, %d", family, bold,
+                    italic, res, *index);
     }
     if (res)
-        ass_msg(MSGL_V,
-               "fontconfig_select: (%s, %d, %d) -> %s, %d\n", family, bold,
-               italic, res, *index);
+        ass_msg(library, MSGL_V,
+                "fontconfig_select: (%s, %d, %d) -> %s, %d", family, bold,
+                italic, res, *index);
     return res;
 }
 
@@ -350,11 +357,11 @@ static void process_fontdata(fc_instance_t *priv, ass_library_t *library,
         res = mkdir(fonts_dir);
 #endif
         if (res) {
-            ass_msg(MSGL_WARN,
-                   MSGTR_LIBASS_FailedToCreateDirectory, fonts_dir);
+            ass_msg(library, MSGL_WARN, "Failed to create directory '%s'",
+                    fonts_dir);
         }
     } else if (!S_ISDIR(st.st_mode)) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_NotADirectory, fonts_dir);
+        ass_msg(library, MSGL_WARN, "Not a directory: '%s'", fonts_dir);
     }
 
     fname = validate_fname((char *) name);
@@ -380,7 +387,7 @@ static void process_fontdata(fc_instance_t *priv, ass_library_t *library,
         rc = FT_New_Memory_Face(ftlibrary, (unsigned char *) data,
                                 data_size, face_index, &face);
         if (rc) {
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_ErrorOpeningMemoryFont,
+            ass_msg(library, MSGL_WARN, "Error opening memory font: %s",
                    name);
             return;
         }
@@ -390,24 +397,21 @@ static void process_fontdata(fc_instance_t *priv, ass_library_t *library,
             FcFreeTypeQueryFace(face, (unsigned char *) name, 0,
                                 FcConfigGetBlanks(priv->config));
         if (!pattern) {
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed,
-                   "FcFreeTypeQueryFace");
+            ass_msg(library, MSGL_WARN, "%s failed", "FcFreeTypeQueryFace");
             FT_Done_Face(face);
             return;
         }
 
         fset = FcConfigGetFonts(priv->config, FcSetSystem);     // somehow it failes when asked for FcSetApplication
         if (!fset) {
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed,
-                   "FcConfigGetFonts");
+            ass_msg(library, MSGL_WARN, "%s failed", "FcConfigGetFonts");
             FT_Done_Face(face);
             return;
         }
 
         res = FcFontSetAdd(fset, pattern);
         if (!res) {
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_FunctionCallFailed,
-                   "FcFontSetAdd");
+            ass_msg(library, MSGL_WARN, "%s failed", "FcFontSetAdd");
             FT_Done_Face(face);
             return;
         }
@@ -435,8 +439,8 @@ fc_instance_t *fontconfig_init(ass_library_t *library,
     int i;
 
     if (!fc) {
-        ass_msg(MSGL_WARN,
-               MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed);
+        ass_msg(library, MSGL_WARN,
+               "Fontconfig disabled, only default font will be used.");
         goto exit;
     }
 
@@ -453,8 +457,7 @@ fc_instance_t *fontconfig_init(ass_library_t *library,
     }
 
     if (!rc || !priv->config) {
-        ass_msg(MSGL_FATAL,
-               MSGTR_LIBASS_FcInitLoadConfigAndFontsFailed);
+        ass_msg(library, MSGL_FATAL, "%s failed", "FcInitLoadConfigAndFonts");
         goto exit;
     }
 
@@ -463,10 +466,10 @@ fc_instance_t *fontconfig_init(ass_library_t *library,
 
     if (dir) {
         if (FcDirCacheValid((const FcChar8 *) dir) == FcFalse) {
-            ass_msg(MSGL_INFO, MSGTR_LIBASS_UpdatingFontCache);
+            ass_msg(library, MSGL_INFO, "Updating font cache");
             if (FcGetVersion() >= 20390 && FcGetVersion() < 20400)
-                ass_msg(MSGL_WARN,
-                       MSGTR_LIBASS_BetaVersionsOfFontconfigAreNotSupported);
+                ass_msg(library, MSGL_WARN, "Beta versions of fontconfig"
+                        "are not supported. Update before reporting any bugs");
             // FontConfig >= 2.4.0 updates cache automatically in FcConfigAppFontAddDir()
             if (FcGetVersion() < 20390) {
                 FcFontSet *fcs;
@@ -475,8 +478,7 @@ fc_instance_t *fontconfig_init(ass_library_t *library,
                 fss = FcStrSetCreate();
                 rc = FcStrSetAdd(fss, (const FcChar8 *) dir);
                 if (!rc) {
-                    ass_msg(MSGL_WARN,
-                           MSGTR_LIBASS_FcStrSetAddFailed);
+                    ass_msg(library, MSGL_WARN, "%s failed", "FcStrSetAdd");
                     goto ErrorFontCache;
                 }
 
@@ -484,14 +486,13 @@ fc_instance_t *fontconfig_init(ass_library_t *library,
                                FcConfigGetBlanks(priv->config),
                                (const FcChar8 *) dir, FcFalse);
                 if (!rc) {
-                    ass_msg(MSGL_WARN,
-                           MSGTR_LIBASS_FcDirScanFailed);
+                    ass_msg(library, MSGL_WARN, "%s failed", "FcDirScan");
                     goto ErrorFontCache;
                 }
 
                 rc = FcDirSave(fcs, fss, (const FcChar8 *) dir);
                 if (!rc) {
-                    ass_msg(MSGL_WARN, MSGTR_LIBASS_FcDirSave);
+                    ass_msg(library, MSGL_WARN, "%s failed", "FcDirSave");
                     goto ErrorFontCache;
                 }
               ErrorFontCache:
@@ -501,8 +502,7 @@ fc_instance_t *fontconfig_init(ass_library_t *library,
 
         rc = FcConfigAppFontAddDir(priv->config, (const FcChar8 *) dir);
         if (!rc) {
-            ass_msg(MSGL_WARN,
-                   MSGTR_LIBASS_FcConfigAppFontAddDirFailed);
+            ass_msg(library, MSGL_WARN, "%s failed", "FcConfigAppFontAddDir");
         }
     }
 
@@ -530,8 +530,8 @@ fc_instance_t *fontconfig_init(ass_library_t *library,
 {
     fc_instance_t *priv;
 
-    ass_msg(MSGL_WARN,
-           MSGTR_LIBASS_FontconfigDisabledDefaultFontWillBeUsed);
+    ass_msg(library, MSGL_WARN,
+        "Fontconfig disabled, only default font will be used.");
 
     priv = calloc(1, sizeof(fc_instance_t));
 
diff --git a/aegisub/libass/ass_fontconfig.h b/aegisub/libass/ass_fontconfig.h
index 71b2006b0..53441cce6 100644
--- a/aegisub/libass/ass_fontconfig.h
+++ b/aegisub/libass/ass_fontconfig.h
@@ -35,9 +35,10 @@ typedef struct fc_instance_s fc_instance_t;
 fc_instance_t *fontconfig_init(ass_library_t *library,
                                FT_Library ftlibrary, const char *family,
                                const char *path, int fc, const char *config);
-char *fontconfig_select(fc_instance_t *priv, const char *family,
-                        int treat_family_as_pattern, unsigned bold,
-                        unsigned italic, int *index, uint32_t code);
+char *fontconfig_select(ass_library_t *library, fc_instance_t *priv,
+                        const char *family, int treat_family_as_pattern,
+                        unsigned bold, unsigned italic, int *index,
+                        uint32_t code);
 void fontconfig_done(fc_instance_t *priv);
 
 #endif                          /* LIBASS_FONTCONFIG_H */
diff --git a/aegisub/libass/ass_library.c b/aegisub/libass/ass_library.c
index ce877da7e..f464af7aa 100644
--- a/aegisub/libass/ass_library.c
+++ b/aegisub/libass/ass_library.c
@@ -22,14 +22,27 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdarg.h>
 
 #include "ass.h"
 #include "ass_library.h"
+#include "ass_utils.h"
 
+static void ass_msg_handler(int level, char *fmt, va_list *va, void *data)
+{
+    if (level > MSGL_INFO)
+        return;
+    fprintf(stderr, "[ass] ");
+    vfprintf(stderr, fmt, *va);
+    fprintf(stderr, "\n");
+}
 
 ass_library_t *ass_library_init(void)
 {
-    return calloc(1, sizeof(ass_library_t));
+    ass_library_t* lib = calloc(1, sizeof(ass_library_t));
+    lib->msg_callback = ass_msg_handler;
+
+    return lib;
 }
 
 void ass_library_done(ass_library_t *priv)
@@ -114,3 +127,22 @@ void ass_clear_fonts(ass_library_t *priv)
     priv->fontdata = NULL;
     priv->num_fontdata = 0;
 }
+
+/*
+ * Register a message callback function with libass.  Without setting one,
+ * a default handler is used which prints everything with MSGL_INFO or
+ * higher to the standard output.
+ *
+ * \param msg_cb the callback function
+ * \param data additional data that will be passed to the callback
+ */
+void ass_set_message_cb(ass_library_t *priv,
+                        void (*msg_cb)(int, char *, va_list *, void *),
+                        void *data)
+{
+    if (msg_cb) {
+        priv->msg_callback = msg_cb;
+        priv->msg_callback_data = data;
+    }
+}
+
diff --git a/aegisub/libass/ass_library.h b/aegisub/libass/ass_library.h
index 48ffefe8b..85b0842db 100644
--- a/aegisub/libass/ass_library.h
+++ b/aegisub/libass/ass_library.h
@@ -21,6 +21,8 @@
 #ifndef LIBASS_LIBRARY_H
 #define LIBASS_LIBRARY_H
 
+#include <stdarg.h>
+
 typedef struct ass_fontdata_s {
     char *name;
     char *data;
@@ -34,6 +36,8 @@ struct ass_library_s {
 
     ass_fontdata_t *fontdata;
     int num_fontdata;
+    void (*msg_callback)(int, char *, va_list *, void *);
+    void *msg_callback_data;
 };
 
 #endif                          /* LIBASS_LIBRARY_H */
diff --git a/aegisub/libass/ass_render.c b/aegisub/libass/ass_render.c
index e2eac7b4a..ae65119af 100644
--- a/aegisub/libass/ass_render.c
+++ b/aegisub/libass/ass_render.c
@@ -41,7 +41,7 @@
 #define MAX_GLYPHS_INITIAL 1024
 #define MAX_LINES_INITIAL 64
 #define BLUR_MAX_RADIUS 100.0
-#define MAX_BE 100
+#define MAX_BE 127
 #define SUBPIXEL_MASK 63
 #define SUBPIXEL_ACCURACY 7    // d6 mask for subpixel accuracy adjustment
 
@@ -245,8 +245,8 @@ static void ass_lazy_track_init(ass_renderer_t *render_priv)
     if (track->PlayResX && track->PlayResY)
         return;
     if (!track->PlayResX && !track->PlayResY) {
-        ass_msg(MSGL_WARN,
-               MSGTR_LIBASS_NeitherPlayResXNorPlayResYDefined);
+        ass_msg(render_priv->library, MSGL_WARN,
+               "Neither PlayResX nor PlayResY defined. Assuming 384x288");
         track->PlayResX = 384;
         track->PlayResY = 288;
     } else {
@@ -256,20 +256,20 @@ static void ass_lazy_track_init(ass_renderer_t *render_priv)
             render_priv->width;
         if (!track->PlayResY && track->PlayResX == 1280) {
             track->PlayResY = 1024;
-            ass_msg(MSGL_WARN,
-                   MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
+            ass_msg(render_priv->library, MSGL_WARN,
+                   "PlayResY undefined, setting to %d", track->PlayResY);
         } else if (!track->PlayResY) {
             track->PlayResY = track->PlayResX / orig_aspect + .5;
-            ass_msg(MSGL_WARN,
-                   MSGTR_LIBASS_PlayResYUndefinedSettingY, track->PlayResY);
+            ass_msg(render_priv->library, MSGL_WARN,
+                   "PlayResY undefined, setting to %d", track->PlayResY);
         } else if (!track->PlayResX && track->PlayResY == 1024) {
             track->PlayResX = 1280;
-            ass_msg(MSGL_WARN,
-                   MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
+            ass_msg(render_priv->library, MSGL_WARN,
+                   "PlayResX undefined, setting to %d", track->PlayResX);
         } else if (!track->PlayResX) {
             track->PlayResX = track->PlayResY * orig_aspect + .5;
-            ass_msg(MSGL_WARN,
-                   MSGTR_LIBASS_PlayResXUndefinedSettingX, track->PlayResX);
+            ass_msg(render_priv->library, MSGL_WARN,
+                   "PlayResX undefined, setting to %d", track->PlayResX);
         }
     }
 }
@@ -283,14 +283,14 @@ ass_renderer_t *ass_renderer_init(ass_library_t *library)
 
     error = FT_Init_FreeType(&ft);
     if (error) {
-        ass_msg(MSGL_FATAL, MSGTR_LIBASS_FT_Init_FreeTypeFailed);
+        ass_msg(library, MSGL_FATAL, "%s failed", "FT_Init_FreeType");
         goto ass_init_exit;
     }
 
     FT_Library_Version(ft, &vmajor, &vminor, &vpatch);
-    ass_msg(MSGL_V, "FreeType library version: %d.%d.%d\n",
+    ass_msg(library, MSGL_V, "FreeType library version: %d.%d.%d",
            vmajor, vminor, vpatch);
-    ass_msg(MSGL_V, "FreeType headers version: %d.%d.%d\n",
+    ass_msg(library, MSGL_V, "FreeType headers version: %d.%d.%d",
            FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
 
     priv = calloc(1, sizeof(ass_renderer_t));
@@ -305,10 +305,10 @@ ass_renderer_t *ass_renderer_init(ass_library_t *library)
     priv->ftlibrary = ft;
     // images_root and related stuff is zero-filled in calloc
 
-    priv->cache.font_cache = ass_font_cache_init();
-    priv->cache.bitmap_cache = ass_bitmap_cache_init();
-    priv->cache.composite_cache = ass_composite_cache_init();
-    priv->cache.glyph_cache = ass_glyph_cache_init();
+    priv->cache.font_cache = ass_font_cache_init(library);
+    priv->cache.bitmap_cache = ass_bitmap_cache_init(library);
+    priv->cache.composite_cache = ass_composite_cache_init(library);
+    priv->cache.glyph_cache = ass_glyph_cache_init(library);
 
     priv->text_info.max_glyphs = MAX_GLYPHS_INITIAL;
     priv->text_info.max_lines = MAX_LINES_INITIAL;
@@ -318,9 +318,9 @@ ass_renderer_t *ass_renderer_init(ass_library_t *library)
 
   ass_init_exit:
     if (priv)
-        ass_msg(MSGL_INFO, MSGTR_LIBASS_Init);
+        ass_msg(library, MSGL_INFO, "Init");
     else
-        ass_msg(MSGL_ERR, MSGTR_LIBASS_InitFailed);
+        ass_msg(library, MSGL_ERR, "Init failed");
 
     return priv;
 }
@@ -527,22 +527,22 @@ static ass_image_t **render_glyph(ass_renderer_t *render_priv,
 
     tmp = dst_x - clip_x0;
     if (tmp < 0) {
-        ass_msg(MSGL_DBG2, "clip left\n");
+        ass_msg(render_priv->library, MSGL_DBG2, "clip left");
         b_x0 = -tmp;
     }
     tmp = dst_y - clip_y0;
     if (tmp < 0) {
-        ass_msg(MSGL_DBG2, "clip top\n");
+        ass_msg(render_priv->library, MSGL_DBG2, "clip top");
         b_y0 = -tmp;
     }
     tmp = clip_x1 - dst_x - bm->w;
     if (tmp < 0) {
-        ass_msg(MSGL_DBG2, "clip right\n");
+        ass_msg(render_priv->library, MSGL_DBG2, "clip right");
         b_x1 = bm->w + tmp;
     }
     tmp = clip_y1 - dst_y - bm->h;
     if (tmp < 0) {
-        ass_msg(MSGL_DBG2, "clip bottom\n");
+        ass_msg(render_priv->library, MSGL_DBG2, "clip bottom");
         b_y1 = bm->h + tmp;
     }
 
@@ -939,7 +939,8 @@ static void change_border(ass_renderer_t *render_priv, double border_x,
                                memory, &render_priv->state.stroker);
 #endif
             if (error) {
-                ass_msg(MSGL_V, "failed to get stroker\n");
+                ass_msg(render_priv->library, MSGL_V,
+                        "failed to get stroker");
                 render_priv->state.stroker = 0;
             }
         }
@@ -1172,15 +1173,15 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
             mystrtoll(&p, &t1);
             skip(',');
             mystrtoll(&p, &t2);
-            ass_msg(MSGL_DBG2,
+            ass_msg(render_priv->library, MSGL_DBG2,
                    "movement6: (%f, %f) -> (%f, %f), (%" PRId64 " .. %"
                    PRId64 ")\n", x1, y1, x2, y2, (int64_t) t1,
                    (int64_t) t2);
         } else {
             t1 = 0;
             t2 = render_priv->state.event->Duration;
-            ass_msg(MSGL_DBG2,
-                   "movement: (%f, %f) -> (%f, %f)\n", x1, y1, x2, y2);
+            ass_msg(render_priv->library, MSGL_DBG2,
+                   "movement: (%f, %f) -> (%f, %f)", x1, y1, x2, y2);
         }
         skip(')');
         delta_t = t2 - t1;
@@ -1241,7 +1242,7 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
     } else if (mystrcmp(&p, "alpha")) {
         uint32_t val;
         int i;
-        if (strtocolor(&p, &val)) {
+        if (strtocolor(render_priv->library, &p, &val)) {
             unsigned char a = val >> 24;
             for (i = 0; i < 4; ++i)
                 change_alpha(&render_priv->state.c[i], a, pwr);
@@ -1260,12 +1261,12 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
         int val;
         if (mystrtoi(&p, &val) && val) {
             int v = (val - 1) / 3;      // 0, 1 or 2 for vertical alignment
-            ass_msg(MSGL_DBG2, "an %d\n", val);
+            ass_msg(render_priv->library, MSGL_DBG2, "an %d", val);
             if (v != 0)
                 v = 3 - v;
             val = ((val - 1) % 3) + 1;  // horizontal alignment
             val += v * 4;
-            ass_msg(MSGL_DBG2, "align %d\n", val);
+            ass_msg(render_priv->library, MSGL_DBG2, "align %d", val);
             render_priv->state.alignment = val;
         } else
             render_priv->state.alignment =
@@ -1284,10 +1285,10 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
         skip(',');
         mystrtod(&p, &v2);
         skip(')');
-        ass_msg(MSGL_DBG2, "pos(%f, %f)\n", v1, v2);
+        ass_msg(render_priv->library, MSGL_DBG2, "pos(%f, %f)", v1, v2);
         if (render_priv->state.evt_type == EVENT_POSITIONED) {
-            ass_msg(MSGL_V, "Subtitle has a new \\pos "
-                   "after \\move or \\pos, ignoring\n");
+            ass_msg(render_priv->library, MSGL_V, "Subtitle has a new \\pos "
+                   "after \\move or \\pos, ignoring");
         } else {
             render_priv->state.evt_type = EVENT_POSITIONED;
             render_priv->state.detect_collisions = 0;
@@ -1339,8 +1340,7 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
         skip(',');
         mystrtoi(&p, &v2);
         skip(')');
-        ass_msg(MSGL_DBG2, "org(%d, %d)\n", v1, v2);
-        //                              render_priv->state.evt_type = EVENT_POSITIONED;
+        ass_msg(render_priv->library, MSGL_DBG2, "org(%d, %d)", v1, v2);
         if (!render_priv->state.have_origin) {
             render_priv->state.org_x = v1;
             render_priv->state.org_y = v2;
@@ -1426,9 +1426,9 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
         }
     } else if (mystrcmp(&p, "c")) {
         uint32_t val;
-        if (!strtocolor(&p, &val))
+        if (!strtocolor(render_priv->library, &p, &val))
             val = render_priv->state.style->PrimaryColour;
-        ass_msg(MSGL_DBG2, "color: %X\n", val);
+        ass_msg(render_priv->library, MSGL_DBG2, "color: %X", val);
         change_color(&render_priv->state.c[0], val, pwr);
     } else if ((*p >= '1') && (*p <= '4') && (++p)
                && (mystrcmp(&p, "c") || mystrcmp(&p, "a"))) {
@@ -1437,7 +1437,7 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
         char cmd = *(p - 1);
         uint32_t val;
         assert((n >= '1') && (n <= '4'));
-        if (!strtocolor(&p, &val))
+        if (!strtocolor(render_priv->library, &p, &val))
             switch (n) {
             case '1':
                 val = render_priv->state.style->PrimaryColour;
@@ -1463,10 +1463,11 @@ static char *parse_tag(ass_renderer_t *render_priv, char *p, double pwr)
             change_alpha(render_priv->state.c + cidx, val >> 24, pwr);
             break;
         default:
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_BadCommand, n, cmd);
+            ass_msg(render_priv->library, MSGL_WARN, "Bad command: %c%c",
+                    n, cmd);
             break;
         }
-        ass_msg(MSGL_DBG2, "single c/a at %f: %c%c = %X   \n",
+        ass_msg(render_priv->library, MSGL_DBG2, "single c/a at %f: %c%c = %X",
                pwr, n, cmd, render_priv->state.c[cidx]);
     } else if (mystrcmp(&p, "r")) {
         reset_render_context(render_priv);
@@ -1580,7 +1581,8 @@ static unsigned get_next_char(ass_renderer_t *render_priv, char **str)
                 } else
                     break;
             } else if (*p != '\\')
-                ass_msg(MSGL_V, "Unable to parse: \"%s\" \n", p);
+                ass_msg(render_priv->library, MSGL_V,
+                        "Unable to parse: '%s'", p);
             if (*p == 0)
                 break;
         }
@@ -1626,8 +1628,8 @@ apply_transition_effects(ass_renderer_t *render_priv, ass_event_t *event)
     if (strncmp(event->Effect, "Banner;", 7) == 0) {
         int delay;
         if (cnt < 1) {
-            ass_msg(MSGL_V, "Error parsing effect: %s \n",
-                   event->Effect);
+            ass_msg(render_priv->library, MSGL_V,
+                    "Error parsing effect: '%s'", event->Effect);
             return;
         }
         if (cnt >= 2 && v[1] == 0)      // right-to-left
@@ -1649,8 +1651,8 @@ apply_transition_effects(ass_renderer_t *render_priv, ass_event_t *event)
     } else if (strncmp(event->Effect, "Scroll down;", 12) == 0) {
         render_priv->state.scroll_direction = SCROLL_TB;
     } else {
-        ass_msg(MSGL_V, "Unknown transition effect: %s \n",
-               event->Effect);
+        ass_msg(render_priv->library, MSGL_V,
+                "Unknown transition effect: '%s'", event->Effect);
         return;
     }
     // parse scroll up/down parameters
@@ -1658,8 +1660,8 @@ apply_transition_effects(ass_renderer_t *render_priv, ass_event_t *event)
         int delay;
         int y0, y1;
         if (cnt < 3) {
-            ass_msg(MSGL_V, "Error parsing effect: %s \n",
-                   event->Effect);
+            ass_msg(render_priv->library, MSGL_V,
+                    "Error parsing effect: '%s'", event->Effect);
             return;
         }
         delay = v[2];
@@ -1865,7 +1867,8 @@ static void stroke_outline_glyph(ass_renderer_t *render_priv,
         error = FT_Glyph_StrokeBorder((FT_Glyph *) glyph,
                                       render_priv->state.stroker, 0, 1);
         if (error)
-            ass_msg(MSGL_WARN, MSGTR_LIBASS_FT_Glyph_Stroke_Error, error);
+            ass_msg(render_priv->library, MSGL_WARN,
+                    "FT_Glyph_Stroke error: %d", error);
 
     // "Stroke" with the outline emboldener in two passes.
     // The outlines look uglier, but the emboldening never adds any points
@@ -2037,7 +2040,8 @@ get_bitmap_glyph(ass_renderer_t *render_priv, glyph_info_t *info)
                     -info->hash_key.advance.y);
 
             // render glyph
-            error = glyph_to_bitmap(render_priv->synth_priv,
+            error = glyph_to_bitmap(render_priv->library,
+                                    render_priv->synth_priv,
                                     info->glyph, info->outline_glyph,
                                     &info->bm, &info->bm_o,
                                     &info->bm_s, info->be,
@@ -2075,15 +2079,22 @@ static void measure_text(ass_renderer_t *render_priv)
     int cur_line = 0;
     double max_asc = 0., max_desc = 0.;
     int i;
+    int empty_line = 1;
     text_info->height = 0.;
     for (i = 0; i < text_info->length + 1; ++i) {
         if ((i == text_info->length) || text_info->glyphs[i].linebreak) {
+            if (empty_line && cur_line > 0) {
+                max_asc = text_info->lines[cur_line - 1].asc / 2.0;
+                max_desc = text_info->lines[cur_line - 1].desc / 2.0;
+            }
             text_info->lines[cur_line].asc = max_asc;
             text_info->lines[cur_line].desc = max_desc;
             text_info->height += max_asc + max_desc;
             cur_line++;
             max_asc = max_desc = 0.;
-        }
+            empty_line = 1;
+        } else
+            empty_line = 0;
         if (i < text_info->length) {
             glyph_info_t *cur = text_info->glyphs + i;
             if (d6_to_double(cur->asc) > max_asc)
@@ -2134,8 +2145,8 @@ wrap_lines_smart(ass_renderer_t *render_priv, double max_text_width)
         if (cur->symbol == '\n') {
             break_type = 2;
             break_at = i;
-            ass_msg(MSGL_DBG2, "forced line break at %d\n",
-                   break_at);
+            ass_msg(render_priv->library, MSGL_DBG2,
+                    "forced line break at %d", break_at);
         }
 
         if ((len >= max_text_width)
@@ -2146,8 +2157,9 @@ wrap_lines_smart(ass_renderer_t *render_priv, double max_text_width)
                 break_at = i - 1;
             if (break_at == -1)
                 break_at = 0;
-            ass_msg(MSGL_DBG2, "overfill at %d\n", i);
-            ass_msg(MSGL_DBG2, "line break at %d\n", break_at);
+            ass_msg(render_priv->library, MSGL_DBG2, "overfill at %d", i);
+            ass_msg(render_priv->library, MSGL_DBG2, "line break at %d",
+                    break_at);
         }
 
         if (break_at != -1) {
@@ -2246,8 +2258,8 @@ wrap_lines_smart(ass_renderer_t *render_priv, double max_text_width)
             cur_line++;
             pen_shift_x = d6_to_double(-cur->pos.x);
             pen_shift_y += height + render_priv->settings.line_spacing;
-            ass_msg(MSGL_DBG2,
-                   "shifting from %d to %d by (%f, %f)\n", i,
+            ass_msg(render_priv->library, MSGL_DBG2,
+                   "shifting from %d to %d by (%f, %f)", i,
                    text_info->length - 1, pen_shift_x, pen_shift_y);
         }
         cur->pos.x += double_to_d6(pen_shift_x);
@@ -2311,8 +2323,8 @@ static void process_karaoke_effects(ass_renderer_t *render_priv)
                     dt /= (tm_end - tm_start);
                     x = x_start + (x_end - x_start) * dt;
                 } else {
-                    ass_msg(MSGL_ERR,
-                           MSGTR_LIBASS_UnknownEffectType_InternalError);
+                    ass_msg(render_priv->library, MSGL_ERR,
+                            "Unknown effect type");
                     continue;
                 }
 
@@ -2465,11 +2477,11 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event,
     ass_drawing_t *drawing;
 
     if (event->Style >= render_priv->track->n_styles) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_NoStyleFound);
+        ass_msg(render_priv->library, MSGL_WARN, "No style found");
         return 1;
     }
     if (!event->Text) {
-        ass_msg(MSGL_WARN, MSGTR_LIBASS_EmptyEvent);
+        ass_msg(render_priv->library, MSGL_WARN, "Empty event");
         return 1;
     }
 
@@ -2513,14 +2525,6 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event,
         if (code == 0)
             break;
 
-        // Insert space between two forced breaks to create empty lines
-        // FIXME: should probably be done in wrap_lines_smart,
-        // this is a hack
-        if (previous == '\n' && code == '\n') {
-            code = ' ';
-            p -= 2;
-        }
-
         if (text_info->length >= text_info->max_glyphs) {
             // Raise maximum number of glyphs
             text_info->max_glyphs *= 2;
@@ -2769,8 +2773,8 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event,
         } else {                // subtitle
             double scr_y;
             if (valign != VALIGN_SUB)
-                ass_msg(MSGL_V,
-                       "Invalid valign, supposing 0 (subtitle)\n");
+                ass_msg(render_priv->library, MSGL_V,
+                       "Invalid valign, supposing 0 (subtitle)");
             scr_y =
                 y2scr_sub(render_priv,
                           render_priv->track->PlayResY - MarginV);
@@ -2795,7 +2799,7 @@ ass_render_event(ass_renderer_t *render_priv, ass_event_t *event,
     if (render_priv->state.evt_type == EVENT_POSITIONED) {
         double base_x = 0;
         double base_y = 0;
-        ass_msg(MSGL_DBG2, "positioned event at %f, %f\n",
+        ass_msg(render_priv->library, MSGL_DBG2, "positioned event at %f, %f",
                render_priv->state.pos_x, render_priv->state.pos_y);
         get_base_point(&bbox, alignment, &base_x, &base_y);
         device_x =
@@ -3010,6 +3014,9 @@ static int
 ass_start_frame(ass_renderer_t *render_priv, ass_track_t *track,
                 long long now)
 {
+    if (render_priv->library != track->library)
+        return 1;
+
     ass_settings_t *settings_priv = &render_priv->settings;
 
     if (!render_priv->settings.frame_width
@@ -3176,8 +3183,8 @@ fix_collisions(ass_renderer_t *render_priv, event_images_t *imgs, int cnt)
             s.a = priv->top;
             s.b = priv->top + priv->height;
             if (priv->height != imgs[i].height) {       // no, it's not
-                ass_msg(MSGL_WARN,
-                       MSGTR_LIBASS_EventHeightHasChanged);
+                ass_msg(render_priv->library, MSGL_WARN,
+                        "Warning! Event height has changed");
                 priv->top = 0;
                 priv->height = 0;
             }
diff --git a/aegisub/libass/ass_utils.c b/aegisub/libass/ass_utils.c
index 5f632684c..56ca0ef63 100644
--- a/aegisub/libass/ass_utils.c
+++ b/aegisub/libass/ass_utils.c
@@ -21,15 +21,17 @@
 #include "config.h"
 
 #include <stdlib.h>
+#include <stdio.h>
 #include <inttypes.h>
 #include <ft2build.h>
 #include FT_GLYPH_H
 
+#include "ass_library.h"
+#include "ass.h"
 #include "ass_utils.h"
 
 int mystrtoi(char **p, int *res)
 {
-    // NOTE: base argument is ignored, but not used in libass anyway
     double temp_res;
     char *start = *p;
     temp_res = strtod(*p, p);
@@ -72,7 +74,7 @@ int mystrtod(char **p, double *res)
         return 0;
 }
 
-int strtocolor(char **q, uint32_t *res)
+int strtocolor(ass_library_t *library, char **q, uint32_t *res)
 {
     uint32_t color = 0;
     int result;
@@ -81,7 +83,7 @@ int strtocolor(char **q, uint32_t *res)
     if (*p == '&')
         ++p;
     else
-        ass_msg(MSGL_DBG2, "suspicious color format: \"%s\"\n", p);
+        ass_msg(library, MSGL_DBG2, "suspicious color format: \"%s\"\n", p);
 
     if (*p == 'H' || *p == 'h') {
         ++p;
@@ -120,13 +122,11 @@ char parse_bool(char *str)
     return 0;
 }
 
-void ass_msg(int lvl, char *fmt, ...)
+void ass_msg(ass_library_t *priv, int lvl, char *fmt, ...)
 {
     va_list va;
-    if (lvl > MSGL_INFO)
-        return;
     va_start(va, fmt);
-    vprintf(fmt, va);
+    priv->msg_callback(lvl, fmt, &va, priv->msg_callback_data);
     va_end(va);
 }
 
@@ -160,128 +160,10 @@ unsigned ass_utf8_get_char(char **str)
     return c;
 }
 
-// gaussian blur
-void ass_gauss_blur(unsigned char *buffer,
-          unsigned short *tmp2,
-          int width, int height, int stride, int *m2, int r, int mwidth)
-{
-
-    int x, y;
-
-    unsigned char *s = buffer;
-    unsigned short *t = tmp2 + 1;
-    for (y = 0; y < height; y++) {
-        memset(t - 1, 0, (width + 1) * sizeof(short));
-
-        for (x = 0; x < r; x++) {
-            const int src = s[x];
-            if (src) {
-                register unsigned short *dstp = t + x - r;
-                int mx;
-                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
-                for (mx = r - x; mx < mwidth; mx++) {
-                    dstp[mx] += m3[mx];
-                }
-            }
-        }
-
-        for (; x < width - r; x++) {
-            const int src = s[x];
-            if (src) {
-                register unsigned short *dstp = t + x - r;
-                int mx;
-                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
-                for (mx = 0; mx < mwidth; mx++) {
-                    dstp[mx] += m3[mx];
-                }
-            }
-        }
-
-        for (; x < width; x++) {
-            const int src = s[x];
-            if (src) {
-                register unsigned short *dstp = t + x - r;
-                int mx;
-                const int x2 = r + width - x;
-                unsigned *m3 = (unsigned *) (m2 + src * mwidth);
-                for (mx = 0; mx < x2; mx++) {
-                    dstp[mx] += m3[mx];
-                }
-            }
-        }
-
-        s += stride;
-        t += width + 1;
-    }
-
-    t = tmp2;
-    for (x = 0; x < width; x++) {
-        for (y = 0; y < r; y++) {
-            unsigned short *srcp = t + y * (width + 1) + 1;
-            int src = *srcp;
-            if (src) {
-                register unsigned short *dstp = srcp - 1 + width + 1;
-                const int src2 = (src + 128) >> 8;
-                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
-
-                int mx;
-                *srcp = 128;
-                for (mx = r - 1; mx < mwidth; mx++) {
-                    *dstp += m3[mx];
-                    dstp += width + 1;
-                }
-            }
-        }
-        for (; y < height - r; y++) {
-            unsigned short *srcp = t + y * (width + 1) + 1;
-            int src = *srcp;
-            if (src) {
-                register unsigned short *dstp = srcp - 1 - r * (width + 1);
-                const int src2 = (src + 128) >> 8;
-                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
-
-                int mx;
-                *srcp = 128;
-                for (mx = 0; mx < mwidth; mx++) {
-                    *dstp += m3[mx];
-                    dstp += width + 1;
-                }
-            }
-        }
-        for (; y < height; y++) {
-            unsigned short *srcp = t + y * (width + 1) + 1;
-            int src = *srcp;
-            if (src) {
-                const int y2 = r + height - y;
-                register unsigned short *dstp = srcp - 1 - r * (width + 1);
-                const int src2 = (src + 128) >> 8;
-                unsigned *m3 = (unsigned *) (m2 + src2 * mwidth);
-
-                int mx;
-                *srcp = 128;
-                for (mx = 0; mx < y2; mx++) {
-                    *dstp += m3[mx];
-                    dstp += width + 1;
-                }
-            }
-        }
-        t++;
-    }
-
-    t = tmp2;
-    s = buffer;
-    for (y = 0; y < height; y++) {
-        for (x = 0; x < width; x++) {
-            s[x] = t[x] >> 8;
-        }
-        s += stride;
-        t += width + 1;
-    }
-}
-
 #ifdef CONFIG_ENCA
-void *ass_guess_buffer_cp(unsigned char *buffer, int buflen,
-                      char *preferred_language, char *fallback)
+void *ass_guess_buffer_cp(ass_library_t *library, unsigned char *buffer,
+                          int buflen, char *preferred_language,
+                          char *fallback)
 {
     const char **languages;
     size_t langcnt;
@@ -291,11 +173,10 @@ void *ass_guess_buffer_cp(unsigned char *buffer, int buflen,
     int i;
 
     languages = enca_get_languages(&langcnt);
-    ass_msg(MSGL_V, "ENCA supported languages: ");
+    ass_msg(library, MSGL_V, "ENCA supported languages");
     for (i = 0; i < langcnt; i++) {
-        ass_msg(MSGL_V, "%s ", languages[i]);
+        ass_msg(library, MSGL_V, "lang %s", languages[i]);
     }
-    ass_msg(MSGL_V, "\n");
 
     for (i = 0; i < langcnt; i++) {
         const char *tmp;
@@ -307,7 +188,7 @@ void *ass_guess_buffer_cp(unsigned char *buffer, int buflen,
         tmp = enca_charset_name(encoding.charset, ENCA_NAME_STYLE_ICONV);
         if (tmp && encoding.charset != ENCA_CS_UNKNOWN) {
             detected_sub_cp = strdup(tmp);
-            ass_msg(MSGL_INFO, "ENCA detected charset: %s\n", tmp);
+            ass_msg(library, MSGL_INFO, "ENCA detected charset: %s", tmp);
         }
         enca_analyser_free(analyser);
     }
@@ -316,8 +197,8 @@ void *ass_guess_buffer_cp(unsigned char *buffer, int buflen,
 
     if (!detected_sub_cp) {
         detected_sub_cp = strdup(fallback);
-        ass_msg(MSGL_INFO,
-               "ENCA detection failed: fallback to %s\n", fallback);
+        ass_msg(library, MSGL_INFO,
+               "ENCA detection failed: fallback to %s", fallback);
     }
 
     return detected_sub_cp;
diff --git a/aegisub/libass/ass_utils.h b/aegisub/libass/ass_utils.h
index cb1fae7da..a906ca57a 100644
--- a/aegisub/libass/ass_utils.h
+++ b/aegisub/libass/ass_utils.h
@@ -32,7 +32,7 @@
 #include <enca.h>
 #endif
 
-#include "help_mp.h"
+#include "ass.h"
 
 #define MSGL_FATAL 0
 #define MSGL_ERR 1
@@ -48,15 +48,15 @@ int mystrtoi(char **p, int *res);
 int mystrtoll(char **p, long long *res);
 int mystrtou32(char **p, int base, uint32_t *res);
 int mystrtod(char **p, double *res);
-int strtocolor(char **q, uint32_t *res);
+int strtocolor(ass_library_t *library, char **q, uint32_t *res);
 char parse_bool(char *str);
 unsigned ass_utf8_get_char(char **str);
-void ass_msg(int lvl, char *fmt, ...);
-void ass_gauss_blur(unsigned char *buffer, unsigned short *tmp2,
-                    int width, int height, int stride, int *m2,
-                    int r, int mwidth);
-void *ass_guess_buffer_cp(unsigned char *buffer, int buflen,
-                          char *preferred_language, char *fallback);
+void ass_msg(ass_library_t *priv, int lvl, char *fmt, ...);
+#ifdef CONFIG_ENCA
+void *ass_guess_buffer_cp(ass_library_t *library, unsigned char *buffer,
+                          int buflen, char *preferred_language,
+                          char *fallback);
+#endif
 
 static inline int d6_to_int(int x)
 {
-- 
GitLab