/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2023
 *					All rights reserved
 *
 *  This file is part of GPAC / Scene Rendering sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <gpac/internal/compositor_dev.h>

#ifndef GPAC_DISABLE_FONTS

#include <gpac/modules/font.h>
#include "visual_manager.h"
#include "nodes_stacks.h"
#include "texturing.h"


struct _gf_ft_mgr
{
	GF_FontReader *reader;

	GF_Font *font, *default_font;
	GF_Path *line_path;

	u32 *id_buffer;
	u32 id_buffer_size;

	Bool wait_font_load;
};

GF_EXPORT
GF_FontManager *gf_font_manager_new()
{
	char *def_font = "SERIF";
	GF_FontManager *font_mgr;
	GF_FontReader *ifce;
	Bool wait_for_fonts = gf_opts_get_bool("core", "wait-fonts");

	ifce = (GF_FontReader *) gf_module_load(GF_FONT_READER_INTERFACE, NULL);
	if (ifce) ifce->init_font_engine(ifce);


	GF_SAFEALLOC(font_mgr, GF_FontManager);
	if (!font_mgr) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate font manager\n"));
		return NULL;
	}
	font_mgr->reader = ifce;
	font_mgr->id_buffer_size = 20;
	font_mgr->id_buffer = gf_malloc(sizeof(u32)*font_mgr->id_buffer_size);
	gf_font_manager_set_font(font_mgr, &def_font, 1, 0);
	font_mgr->default_font = font_mgr->font;

	font_mgr->line_path= gf_path_new();
	gf_path_add_move_to(font_mgr->line_path, -FIX_ONE/2, FIX_ONE/2);
	gf_path_add_line_to(font_mgr->line_path, FIX_ONE/2, FIX_ONE/2);
	gf_path_add_line_to(font_mgr->line_path, FIX_ONE/2, -FIX_ONE/2);
	gf_path_add_line_to(font_mgr->line_path, -FIX_ONE/2, -FIX_ONE/2);
	gf_path_close(font_mgr->line_path);


	font_mgr->wait_font_load = wait_for_fonts;

	return font_mgr;
}

void gf_font_predestroy(GF_Font *font)
{
	if (font->spans) {
		while (gf_list_count(font->spans)) {
			GF_TextSpan *ts = gf_list_get(font->spans, 0);
			gf_list_rem(font->spans, 0);
#ifndef GPAC_DISABLE_COMPOSITOR
			gf_node_dirty_set(ts->user, 0, 0);
#endif
			ts->user=NULL;
		}
		gf_list_del(font->spans);
		font->spans = NULL;
	}
}

void gf_font_del(GF_Font *font)
{
	gf_font_predestroy(font);
	if (!font->get_glyphs) {
		GF_Glyph *glyph;
		glyph = font->glyph;
		while (glyph) {
			GF_Glyph *next = glyph->next;
			gf_path_del(glyph->path);
			gf_free(glyph);
			glyph = next;
		}
	}
	gf_free(font->name);
	gf_free(font);
}


void gf_font_manager_del(GF_FontManager *fm)
{
	GF_Font *font;
	if (fm->reader) {
		fm->reader->shutdown_font_engine(fm->reader);
		gf_modules_close_interface((GF_BaseInterface *)fm->reader);
	}

	font = fm->font;
	while (font) {
		GF_Font *next = font->next;
		gf_font_del(font);
		font = next;
	}
	gf_free(fm->id_buffer);
	gf_path_del(fm->line_path);
	gf_free(fm);
}

GF_Err gf_font_manager_register_font(GF_FontManager *fm, GF_Font *font)
{
	if (fm->font) {
		GF_Font *a_font = fm->font;
		while (a_font->next) a_font = a_font->next;
		a_font->next = font;
	} else {
		fm->font = font;
	}
	font->ft_mgr = fm;
	if (!font->spans) font->spans = gf_list_new();
	return GF_OK;
}

GF_Err gf_font_manager_unregister_font(GF_FontManager *fm, GF_Font *font)
{
	GF_Font *prev_font, *a_font;

	prev_font = NULL;
	a_font = fm->font;
	while (a_font) {
		if (a_font==font) break;
		prev_font = a_font;
		a_font = a_font->next;
	}
	if (prev_font) {
		prev_font->next = font->next;
	} else {
		fm->font = font->next;
	}
	gf_font_predestroy(font);
	return GF_OK;
}


GF_EXPORT
GF_Font *gf_font_manager_set_font_ex(GF_FontManager *fm, char **alt_fonts, u32 nb_fonts, u32 styles, Bool check_only)
{
	u32 i;
	GF_Err e;
	Bool has_italic = (styles & GF_FONT_ITALIC) ? 1 : 0;
	Bool has_smallcaps = (styles & GF_FONT_SMALLCAPS) ? 1 : 0;
	GF_Font *the_font = NULL;

	for (i=0; i<nb_fonts; i++) {
		char *font_name;
		const char *opt;
		Bool is_def_font=GF_FALSE;
		u32 weight_diff = 0xFFFFFFFF;
		GF_Font *best_font = NULL;
		GF_Font *font = fm->font;
		font_name = alt_fonts[i];
		if (!font_name) font_name = "SANS";

		if (!stricmp(font_name, "SERIF")) {
			opt = gf_opts_get_key("FontCache", "FontSerif");
			if (opt) font_name = (char*)opt;
			is_def_font = GF_TRUE;
		}
		else if (!stricmp(font_name, "SANS") || !stricmp(font_name, "sans-serif")) {
			opt = gf_opts_get_key("FontCache", "FontSans");
			if (opt) font_name = (char*)opt;
			is_def_font = GF_TRUE;
		}
		else if (!stricmp(font_name, "TYPEWRITER") || !stricmp(font_name, "monospace")) {
			opt = gf_opts_get_key("FontCache", "FontFixed");
			if (opt) font_name = (char*)opt;
			is_def_font = GF_TRUE;
		}

		while (font && font_name) {
			if (fm->wait_font_load && font->not_loaded && !check_only && !stricmp(font->name, font_name)) {
				GF_Font *a_font = NULL;
				if (font->get_alias) a_font = font->get_alias(font->udta);
				if (!a_font || a_font->not_loaded)
					return font;
			}
			if ((check_only || !font->not_loaded) && font->name
				//def font may have a style concatenated if only fonts with styles other than none/italic/bold are present
				&& (!stricmp(font->name, font_name) || (is_def_font && strncmp(font->name, font_name, strlen(font_name))))
			) {
				s32 fw;
				s32 w;
				u32 diff;
				Bool ft_has_smallcaps;
				Bool ft_has_weight;

				if (check_only) return font;

				ft_has_weight = (font->styles & GF_FONT_WEIGHT_MASK) ? 1 : 0;
				if (font->styles == styles) {
					the_font = font;
					break;
				}
				/*check we have the same font variant*/
				ft_has_smallcaps = (font->styles & GF_FONT_SMALLCAPS) ? 1 : 0;
				if (ft_has_smallcaps != has_smallcaps) {
					font = font->next;
					continue;
				}
				/*check if we have an oblique/italic match*/
				if (has_italic) {
					if (! (font->styles & (GF_FONT_OBLIQUE|GF_FONT_ITALIC))) {
						font = font->next;
						continue;
					}
					/*if italic force it*/
					if (font->styles & GF_FONT_ITALIC) best_font = font;
					/*if oblic use it if no italic found*/
					else if (!best_font) best_font  = font;
					else {
						font = font->next;
						continue;
					}
				}

				/*compute min weight diff*/
				fw = font->styles>>10;
				w = styles>>10;
				diff = ABS(fw - w);
				if (ft_has_weight) {
					if (diff<weight_diff) {
						weight_diff = diff;
						best_font = font;
					}
				}
				/*no weight means "all"*/
				else {
					if ((font->styles & GF_FONT_STYLE_MASK) == (styles & GF_FONT_STYLE_MASK) ) {
						weight_diff = diff;
						best_font = font;
						font = font->next;
						continue;
					}
				}
			}
			font = font->next;
		}
		if (the_font) break;
		if (fm->reader) {
			e = fm->reader->set_font(fm->reader, font_name, styles);
			if (!e) {
				GF_SAFEALLOC(the_font, GF_Font);
				if (!the_font) {
					return NULL;
				}
				fm->reader->get_font_info(fm->reader, &the_font->name, &the_font->em_size, &the_font->ascent, &the_font->descent, &the_font->underline, &the_font->line_spacing, &the_font->max_advance_h, &the_font->max_advance_v);
				the_font->styles = styles;
				if (!the_font->name) the_font->name = gf_strdup(font_name);

				if (fm->font) {
					font = fm->font;
					while (font->next) font = font->next;
					font->next = the_font;
				} else {
					fm->font = the_font;
				}
				the_font->ft_mgr = fm;
				return the_font;
			}
		}
		if (best_font) {
			the_font = best_font;
			break;
		}
	}

	/*check for font alias*/
	if (the_font && the_font->get_alias) {
		return the_font->get_alias(the_font->udta);

	}

	if (!the_font) {
		if (check_only) return NULL;
		the_font = fm->default_font;
	}
	/*embeded font*/
	if (fm->reader && the_font && !the_font->get_glyphs)
		fm->reader->set_font(fm->reader, the_font->name, the_font->styles);

	return the_font;
}
GF_Font *gf_font_manager_set_font(GF_FontManager *fm, char **alt_fonts, u32 nb_fonts, u32 styles)
{
	return gf_font_manager_set_font_ex(fm, alt_fonts, nb_fonts, styles, 0);
}

static GF_Glyph *gf_font_get_glyph(GF_FontManager *fm, GF_Font *font, u32 name)
{
	GF_Glyph *glyph = font->glyph;
	while (glyph) {
		if (glyph->ID==name) return glyph;
		glyph = glyph->next;
	}

	if (name==GF_CARET_CHAR) {
		GF_SAFEALLOC(glyph, GF_Glyph);
		if (!glyph) return NULL;
		glyph->height = font->ascent;
		glyph->horiz_advance = 0;
		glyph->width = 0;
		glyph->ID = GF_CARET_CHAR;
		glyph->path = gf_path_new();
		gf_path_add_move_to(glyph->path, 0, INT2FIX(font->descent));
		gf_path_add_line_to(glyph->path, 0, INT2FIX(font->ascent));
		gf_path_add_line_to(glyph->path, 1, INT2FIX(font->ascent));
		gf_path_add_line_to(glyph->path, 1, INT2FIX(font->descent));
		gf_path_close(glyph->path);
		glyph->utf_name=0;
	} else if (name==(u32) '\n') {
		GF_SAFEALLOC(glyph, GF_Glyph);
		if (!glyph) return NULL;
		glyph->height = font->ascent;
		glyph->horiz_advance = 0;
		glyph->width = 0;
		glyph->ID = name;
		glyph->utf_name=name;
	} else if (name==(u32) '\t') {
		return NULL;
	} else {
		/*load glyph*/
		if (font->load_glyph) {
			glyph = font->load_glyph(font->udta, name);
		} else {
			if (!fm->reader) return NULL;
			//		fm->reader->set_font(fm->reader, font->name, font->styles);
			glyph = fm->reader->load_glyph(fm->reader, name);
		}
	}
	if (!glyph) return NULL;

	if (!font->glyph) font->glyph = glyph;
	else {
		GF_Glyph *a_glyph = font->glyph;
		while (a_glyph->next) a_glyph = a_glyph->next;
		a_glyph->next = glyph;
	}
	/*space character - this may need adjustment for other empty glyphs*/
	if (glyph->path && !glyph->path->n_points) {
		glyph->path->bbox.x = 0;
		glyph->path->bbox.width = INT2FIX(font->max_advance_h);
		glyph->path->bbox.y = INT2FIX(font->ascent);
		glyph->path->bbox.height = INT2FIX(font->ascent - font->descent);
	}
	return glyph;
}

GF_EXPORT
GF_TextSpan *gf_font_manager_create_span(GF_FontManager *fm, GF_Font *font, char *text, Fixed font_size, Bool needs_x_offset, Bool needs_y_offset, Bool needs_rotate, const char *xml_lang, Bool fliped_text, u32 styles, GF_Node *user)
{
	GF_Err e;
	Bool is_rtl;
	u32 len, i;
	GF_TextSpan *span;

	if (!strlen(text)) return NULL;

	len = fm->id_buffer_size;
	if (font->get_glyphs)
		e = font->get_glyphs(font->udta, text, fm->id_buffer, &len, xml_lang, &is_rtl);
	else
		e = fm->reader->get_glyphs(fm->reader, text, fm->id_buffer, &len, xml_lang, &is_rtl);

	if (e==GF_BUFFER_TOO_SMALL) {
		fm->id_buffer_size = len;
		fm->id_buffer = gf_realloc(fm->id_buffer, sizeof(u32) * len);
		if (!fm->id_buffer) return NULL;

		if (font->get_glyphs)
			e = font->get_glyphs(font->udta, text, fm->id_buffer, &len, xml_lang, &is_rtl);
		else
			e = fm->reader->get_glyphs(fm->reader, text, fm->id_buffer, &len, xml_lang, &is_rtl);
	}
	if (e) return NULL;

	GF_SAFEALLOC(span, GF_TextSpan);
	if (!span) return NULL;
	span->font = font;
	span->font_size = font_size;
	if (font->em_size)
		span->font_scale = font_size / font->em_size;
	span->x_scale = span->y_scale = FIX_ONE;
//	span->lang = xml_lang;
	if (fliped_text) span->flags |= GF_TEXT_SPAN_FLIP;
	if (styles & GF_FONT_UNDERLINED) span->flags |= GF_TEXT_SPAN_UNDERLINE;
	if (styles & GF_FONT_STRIKEOUT) span->flags |= GF_TEXT_SPAN_STRIKEOUT;
	span->nb_glyphs = len;
	span->glyphs = gf_malloc(sizeof(void *)*len);
	if (needs_x_offset) {
		span->dx = gf_malloc(sizeof(Fixed)*len);
		memset(span->dx, 0, sizeof(Fixed)*len);
	}
	if (needs_y_offset) {
		span->dy = gf_malloc(sizeof(Fixed)*len);
		memset(span->dy, 0, sizeof(Fixed)*len);
	}
	if (needs_rotate) {
		span->rot = gf_malloc(sizeof(Fixed)*len);
		memset(span->rot, 0, sizeof(Fixed)*len);
	}

	for (i=0; i<len; i++) {
		span->glyphs[i] = gf_font_get_glyph(fm, font, fm->id_buffer[i]);
	}
	span->user = user;
	if (span->font->spans)
		gf_list_add(font->spans, span);
	if (is_rtl) span->flags |= GF_TEXT_SPAN_RIGHT_TO_LEFT;
	return span;
}


#ifndef GPAC_DISABLE_COMPOSITOR

typedef struct _span_internal
{
	/*zoom when texture was computed*/
	Fixed last_zoom;
	/*texture handler for bitmap text*/
	GF_TextureHandler *txh;
	/*texture path (rectangle)*/
	GF_Path *path;

#ifndef GPAC_DISABLE_3D
	/*span mesh (built out of the # glyphs)*/
	GF_Mesh *mesh;
	/*span texture mesh (rectangle)*/
	GF_Mesh *tx_mesh;
	/*span outline*/
	GF_Mesh *outline;
#endif
} GF_SpanExtensions;

#endif


void gf_font_manager_delete_span(GF_FontManager *fm, GF_TextSpan *span)
{
	if (span->user && span->font->spans) gf_list_del_item(span->font->spans, span);

	gf_free(span->glyphs);
	if (span->dx) gf_free(span->dx);
	if (span->dy) gf_free(span->dy);
	if (span->rot) gf_free(span->rot);

#ifndef GPAC_DISABLE_COMPOSITOR
	if (span->ext) {
		if (span->ext->path) gf_path_del(span->ext->path);
#ifndef GPAC_DISABLE_3D
		if (span->ext->mesh) mesh_free(span->ext->mesh);
		if (span->ext->tx_mesh) mesh_free(span->ext->tx_mesh);
		if (span->ext->outline) mesh_free(span->ext->outline);
#endif
		if (span->ext->txh) {
			gf_sc_texture_destroy(span->ext->txh);
			if (span->ext->txh->data) gf_free(span->ext->txh->data);
			gf_free(span->ext->txh);
		}
		gf_free(span->ext);
	}
#endif

	gf_free(span);
}

GF_EXPORT
void gf_font_manager_refresh_span_bounds(GF_TextSpan *span)
{
	u32 i;
	Fixed descent, ascent, bline;
	Fixed min_x, min_y, max_y;

	if (!span->nb_glyphs) {
		span->bounds.width = span->bounds.height = 0;
		return;
	}
	descent = 0;
	if (span->font->descent<0) descent = -span->font_scale * span->font->descent;
	ascent = span->font->ascent * span->font_scale;

	/*if fliped text (SVG), the min_y is at the ascent side*/
	if (span->flags & GF_TEXT_SPAN_FLIP) {
		Fixed tmp = ascent;
		ascent = descent;
		descent = tmp;
	}

	bline = span->font->baseline * span->font_scale;

	min_x = span->dx ? FIX_MAX : span->off_x;
	min_y = span->dy ? FIX_MAX : span->off_y - descent;
	max_y = span->dy ? -FIX_MAX : span->off_y + ascent;

	/*adjust start_x for first glyph*/
	if (span->glyphs[0] && span->glyphs[0]->path) {
		min_x += gf_mulfix(span->glyphs[0]->path->bbox.x, span->font_scale);
	}
	span->bounds = gf_rect_center(0, 0);

	for (i=0; i<span->nb_glyphs; i++) {
		Fixed g_width;
		GF_Rect rc;

		/*compute glyph size*/
		if (!span->glyphs[i]) g_width = span->font->max_advance_h * span->font_scale;
		/*if last glyph of the span, increase by width only*/
//		else if (i+1==span->nb_glyphs) g_width = span->glyphs[i]->width * span->font_scale;
		/*otherwise increase by the horizontal advance*/
		else g_width = span->glyphs[i]->horiz_advance * span->font_scale;

		if (span->dy) {
			if (span->dy[i] - descent < min_y) min_y = span->dy[i] - descent;
			if (span->dy[i] + ascent > max_y) max_y = span->dy[i] + ascent;
		}
		else if (span->glyphs[i]) {
			Fixed size = span->glyphs[i]->height * span->font_scale;
			if (size > max_y-min_y) max_y = size + min_y;
		}

		if (span->dx) {
			rc.x = span->dx[i];
		} else {
			rc.x = min_x;
			if (span->flags & GF_TEXT_SPAN_HORIZONTAL)
				min_x += g_width;
		}
		rc.y = span->dy ? span->dy[i] + ascent : max_y;
		rc.width = g_width;
		rc.height = max_y - min_y;

		if (span->rot) {
			GF_Matrix2D tr;
			gf_mx2d_init(tr);
			gf_mx2d_add_rotation(&tr, rc.x, rc.y - ascent - bline, span->rot[i]);
			gf_mx2d_apply_rect(&tr, &rc);
		}
		gf_rect_union(&span->bounds, &rc);
	}
}




GF_Path *gf_font_span_create_path(GF_TextSpan *span)
{
	u32 i;
	GF_Matrix2D mat;
	Fixed dx, dy;
	GF_Path *path = gf_path_new();

	gf_mx2d_init(mat);
	mat.m[0] = gf_mulfix(span->font_scale, span->x_scale);
	mat.m[4] = gf_mulfix(span->font_scale, span->y_scale);
	if (span->flags & GF_TEXT_SPAN_FLIP) gf_mx2d_add_scale(&mat, FIX_ONE, -FIX_ONE);

	dx = gf_divfix(span->off_x, mat.m[0]);
	dy = gf_divfix(span->off_y, mat.m[4]);

	for (i=0; i<span->nb_glyphs; i++) {
		if (!span->glyphs[i]) {
			if (span->flags & GF_TEXT_SPAN_HORIZONTAL) {
				dx += INT2FIX(span->font->max_advance_h);
			} else {
				dy -= INT2FIX(span->font->max_advance_v);
			}
		} else {
			if (span->dx) dx = gf_divfix(span->dx[i], mat.m[0]);
			if (span->dy) dy = gf_divfix(span->dy[i], mat.m[4]);


			if (span->glyphs[i]->utf_name != ' ') {
				GF_Matrix2D mx;
				gf_mx2d_init(mx);
				if (span->rot) {
					gf_mx2d_add_rotation(&mx, 0, 0, -span->rot[i]);
				}
				if (span->glyphs[i]->ID==GF_CARET_CHAR) {
					gf_mx2d_add_scale(&mx, mat.m[0], FIX_ONE);
				}
				gf_mx2d_add_translation(&mx, dx, dy);
				gf_path_add_subpath(path, span->glyphs[i]->path, &mx);
			}

			if (span->flags & GF_TEXT_SPAN_HORIZONTAL) {
				dx += INT2FIX(span->glyphs[i]->horiz_advance);
			} else {
				dy -= INT2FIX(span->glyphs[i]->vert_advance);
			}
		}
	}

	for (i=0; i<path->n_points; i++) {
		gf_mx2d_apply_point(&mat, &path->points[i]);
	}
	return path;
}

#ifndef GPAC_DISABLE_COMPOSITOR

static void span_alloc_extensions(GF_TextSpan *span)
{
	if (span->ext) return;
	GF_SAFEALLOC(span->ext, GF_SpanExtensions);
}

#ifndef GPAC_DISABLE_3D
static void span_build_mesh(GF_TextSpan *span)
{
	span_alloc_extensions(span);
	span->ext->tx_mesh = new_mesh();
	mesh_set_vertex(span->ext->tx_mesh, span->bounds.x, span->bounds.y-span->bounds.height, 0, 0, 0, FIX_ONE, 0, FIX_ONE);
	mesh_set_vertex(span->ext->tx_mesh, span->bounds.x+span->bounds.width, span->bounds.y-span->bounds.height, 0, 0, 0, FIX_ONE, FIX_ONE, FIX_ONE);
	mesh_set_vertex(span->ext->tx_mesh, span->bounds.x+span->bounds.width, span->bounds.y, 0, 0, 0, FIX_ONE, FIX_ONE, 0);
	mesh_set_vertex(span->ext->tx_mesh, span->bounds.x, span->bounds.y, 0, 0, 0, FIX_ONE, 0, 0);
	mesh_set_triangle(span->ext->tx_mesh, 0, 1, 2);
	mesh_set_triangle(span->ext->tx_mesh, 0, 2, 3);
	span->ext->tx_mesh->flags |= MESH_IS_2D;
	span->ext->tx_mesh->mesh_type = MESH_TRIANGLES;
	mesh_update_bounds(span->ext->tx_mesh);
}
#endif

/*don't build too large textures*/
#define MAX_TX_SIZE		512
/*and don't build too small ones otherwise result is as crap as non-textured*/
#define MIN_TX_SIZE		32

static Bool span_setup_texture(GF_Compositor *compositor, GF_TextSpan *span, Bool for_3d, GF_TraverseState *tr_state)
{
	GF_Path *span_path;
	GF_Rect bounds;
	Fixed cx, cy, sx, sy, max, min;
	u32 tw, th;
	GF_Matrix2D mx;
	GF_EVGStencil *stencil, *brush;
	GF_EVGSurface *surface;
	u32 width, height;
	Fixed scale;
	span_alloc_extensions(span);

	/*something failed*/
	if (span->ext->txh && !span->ext->txh->data) return 0;

	if (span->ext->txh && span->ext->txh->data) {
		if (span->ext->last_zoom == compositor->zoom) {

#ifndef GPAC_DISABLE_3D
			if (for_3d && !span->ext->tx_mesh) span_build_mesh(span);
#endif
			return 1;
		}
	}
	span->ext->last_zoom = compositor->zoom;

	bounds = span->bounds;

	/*check not too big, but also not really small (meter metrics)*/
	max = INT2FIX(MAX_TX_SIZE);
	min = INT2FIX(MIN_TX_SIZE);
	scale = compositor->zoom;
	if (!tr_state->pixel_metrics) {
		scale = gf_mulfix(scale, tr_state->min_hsize);
	}
	if ((gf_mulfix(scale, bounds.width)>max) || (gf_mulfix(scale, bounds.height)>max)) {
		scale = MIN(gf_divfix(max, bounds.width), gf_divfix(max, bounds.height));
	}
	else if ((gf_mulfix(scale, bounds.width)<min) || (gf_mulfix(scale, bounds.height)<min)) {
		scale = MAX(gf_divfix(min, bounds.width), gf_divfix(min, bounds.height));
	}
	if (scale<FIX_ONE) scale = FIX_ONE;

	/*get closest pow2 sizes*/
	tw = FIX2INT( gf_ceil(gf_mulfix(scale, bounds.width)) );
	width = MIN_TX_SIZE;
	while (width <tw) {
		if (width >=MAX_TX_SIZE) break;
		width *=2;
	}
	th = FIX2INT( gf_ceil(gf_mulfix(scale, bounds.height)) );
	height = MIN_TX_SIZE;
	while (height<th) {
		height*=2;
		if (height>=MAX_TX_SIZE) break;
	}
	/*and get scaling*/
	sx = gf_divfix( INT2FIX(width), bounds.width);
	sy = gf_divfix( INT2FIX(height), bounds.height);

	if (span->ext->txh && (width == span->ext->txh->width) && (height==span->ext->txh->height)) return 1;

	if (span->ext->path) gf_path_del(span->ext->path);
	span->ext->path = NULL;
#ifndef GPAC_DISABLE_3D
	if (span->ext->tx_mesh) mesh_free(span->ext->tx_mesh);
	span->ext->tx_mesh = NULL;
#endif

	if (span->ext->txh) {
		gf_sc_texture_destroy(span->ext->txh);
		if (span->ext->txh->data) gf_free(span->ext->txh->data);
		gf_free(span->ext->txh);
	}
	GF_SAFEALLOC(span->ext->txh, GF_TextureHandler);
	if (!span->ext->txh) return 0;
	gf_sc_texture_setup(span->ext->txh, compositor, NULL);
	gf_sc_texture_allocate(span->ext->txh);
	stencil = gf_sc_texture_get_stencil(span->ext->txh);
	if (!stencil) stencil = gf_evg_stencil_new(GF_STENCIL_TEXTURE);

	/*FIXME - make it work with alphagrey...*/
	span->ext->txh->width = width;
	span->ext->txh->height = height;
	span->ext->txh->stride = 4*width;
	span->ext->txh->pixelformat = GF_PIXEL_RGBA;
	span->ext->txh->transparent = 1;
	span->ext->txh->flags |= GF_SR_TEXTURE_NO_GL_FLIP;

	surface = gf_evg_surface_new(1);
	if (!surface) {
		gf_sc_texture_release(span->ext->txh);
		return 0;
	}
	span->ext->txh->data = (char *) gf_malloc(sizeof(char)*span->ext->txh->stride*span->ext->txh->height);
	memset(span->ext->txh->data, 0, sizeof(char)*span->ext->txh->stride*span->ext->txh->height);

	gf_evg_stencil_set_texture(stencil, span->ext->txh->data, span->ext->txh->width, span->ext->txh->height, span->ext->txh->stride, span->ext->txh->pixelformat);
	gf_evg_surface_attach_to_texture(surface, stencil);

	brush = gf_evg_stencil_new(GF_STENCIL_SOLID);
	gf_evg_stencil_set_brush_color(brush, for_3d ? 0xFFFFFFFF : tr_state->ctx->aspect.fill_color);

	cx = bounds.x + bounds.width/2;
	cy = bounds.y - bounds.height/2;

	gf_mx2d_init(mx);
	gf_mx2d_add_translation(&mx, -cx, -cy);
	gf_mx2d_add_scale(&mx, sx, sy);
//	gf_mx2d_add_scale(&mx, 99*FIX_ONE/100, 99*FIX_ONE/100);

	gf_evg_surface_set_matrix(surface, &mx);
	gf_evg_surface_set_raster_level(surface, GF_RASTER_HIGH_QUALITY);
	span_path = gf_font_span_create_path(span);
	gf_evg_surface_set_path(surface, span_path);

	gf_evg_surface_fill(surface, brush);
	gf_evg_stencil_delete(brush);
	gf_evg_surface_delete(surface);
	gf_path_del(span_path);

	if (span->font->baseline) {
		Fixed dy = gf_mulfix(span->font->baseline, span->font_scale);
		bounds.y += dy;
		span->bounds.y += dy;
	}
	span->ext->path = gf_path_new();
	gf_path_add_move_to(span->ext->path, bounds.x, bounds.y-bounds.height);
	gf_path_add_line_to(span->ext->path, bounds.x+bounds.width, bounds.y-bounds.height);
	gf_path_add_line_to(span->ext->path, bounds.x+bounds.width, bounds.y);
	gf_path_add_line_to(span->ext->path, bounds.x, bounds.y);
	gf_path_close(span->ext->path);

	gf_sc_texture_set_stencil(span->ext->txh, stencil);
	gf_sc_texture_set_data(span->ext->txh);

#ifndef GPAC_DISABLE_3D
	gf_sc_texture_set_blend_mode(span->ext->txh, TX_BLEND);
	if (for_3d) span_build_mesh(span);
#endif
	return 1;
}


#ifndef GPAC_DISABLE_3D

static void span_fill_3d(GF_TextSpan *span, GF_TraverseState *tr_state)
{
	span_alloc_extensions(span);
	if (!span->ext->mesh) {
		GF_Path *path = gf_font_span_create_path(span);
		span->ext->mesh = new_mesh();
		mesh_from_path(span->ext->mesh, path);
		gf_path_del(path);
	}

	visual_3d_mesh_paint(tr_state, span->ext->mesh);
}

static void span_strike_3d(GF_TextSpan *span, GF_TraverseState *tr_state, DrawAspect2D *asp, Bool vect_outline)
{
	span_alloc_extensions(span);
	if (!span->ext->outline) {
		GF_Path *path = gf_font_span_create_path(span);
		span->ext->outline = new_mesh();
#ifdef GPAC_HAS_GLU
		if (vect_outline) {
			GF_Path *outline = gf_path_get_outline(path, asp->pen_props);
			gf_mesh_tesselate_path(span->ext->outline, outline, asp->line_texture ? 2 : 1);
			gf_path_del(outline);
		} else {
			mesh_get_outline(span->ext->outline, path);
		}
#else
		vect_outline = 0;
		mesh_get_outline(span->ext->outline, path);
#endif
		gf_path_del(path);
	}
	if (vect_outline) {
		visual_3d_mesh_paint(tr_state, span->ext->outline);
	} else {
		visual_3d_mesh_strike(tr_state, span->ext->outline, asp->pen_props.width, asp->line_scale, asp->pen_props.dash);
	}
}


void gf_font_spans_draw_3d(GF_List *spans, GF_TraverseState *tr_state, DrawAspect2D *asp, u32 text_hl, Bool force_texturing)
{
	u32 i;
	SFColorRGBA hl_color;
	GF_TextSpan *span;
	Bool fill_2d, vect_outline, can_texture_text;
	GF_Compositor *compositor = (GF_Compositor*)tr_state->visual->compositor;

	vect_outline = !compositor->linegl;

	visual_3d_set_state(tr_state->visual, V3D_STATE_BLEND, 0);

	fill_2d = 0;
	if (!asp) {
		if (!visual_3d_setup_appearance(tr_state)) return;
	} else {
		fill_2d = (asp->fill_color) ? 1 : 0;
	}
	memset(&hl_color, 0, sizeof(SFColorRGBA));

	if (text_hl && (fill_2d || !asp) ) {
		/*reverse video: highlighting uses the text color, and text color is inverted (except alpha channel)
		the ideal impl would be to use the background color for the text, but since the text may be
		displayed over anything non uniform this would require clipping the highlight rect with the text
		which is too onerous (and not supported anyway) */
		if (text_hl == 0x00FFFFFF) {
			if (!asp) {
#ifndef GPAC_DISABLE_VRML
				if (tr_state->appear) {
					SFColor c, rc;
					c = ((M_Material *) ((M_Appearance *)  tr_state->appear)->material)->diffuseColor;
					hl_color = gf_sg_sfcolor_to_rgba(c);
					hl_color.alpha = ((M_Material *) ((M_Appearance *)  tr_state->appear)->material)->transparency;
					/*invert diffuse color and resetup*/
					rc.red = FIX_ONE - c.red;
					rc.green = FIX_ONE - c.green;
					rc.blue = FIX_ONE - c.blue;
					((M_Material *) ((M_Appearance *)  tr_state->appear)->material)->diffuseColor = rc;
					visual_3d_setup_appearance(tr_state);
					((M_Material *) ((M_Appearance *)  tr_state->appear)->material)->diffuseColor = c;
				} else
#endif /*GPAC_DISABLE_VRML*/
				{
					hl_color.red = hl_color.green = hl_color.blue = 0;
					hl_color.alpha = FIX_ONE;
				}
			} else {
				hl_color.alpha = FIX_ONE;
				hl_color.red = INT2FIX( GF_COL_R(asp->fill_color) ) / 255;
				hl_color.green = INT2FIX( GF_COL_G(asp->fill_color) ) / 255;
				hl_color.blue = INT2FIX( GF_COL_B(asp->fill_color) ) / 255;
				if (GF_COL_A(asp->fill_color) ) {
					u8 r = GF_COL_R(asp->fill_color);
					u8 g = GF_COL_G(asp->fill_color);
					u8 b = GF_COL_B(asp->fill_color);
					asp->fill_color = GF_COL_ARGB(GF_COL_A(asp->fill_color), 255-r, 255-g, 255-b);
				}
			}
		} else {
			hl_color.red = INT2FIX(GF_COL_R(text_hl)) / 255;
			hl_color.green = INT2FIX(GF_COL_G(text_hl)) / 255;
			hl_color.blue = INT2FIX(GF_COL_B(text_hl)) / 255;
			hl_color.alpha = INT2FIX(GF_COL_A(text_hl)) / 255;
		}
		if (asp && !asp->fill_color) text_hl = 0;
	}

	/*setup texture*/
	visual_3d_setup_texture(tr_state, FIX_ONE);
	can_texture_text = 0;
	if (fill_2d || !asp) {
		/*check if we can use text texturing*/
		if (force_texturing || (compositor->textxt != GF_TEXTURE_TEXT_NEVER) ) {
			if (fill_2d && asp->pen_props.width) {
				can_texture_text = 0;
			} else {
				can_texture_text = tr_state->mesh_num_textures ? 0 : 1;
			}
		}
	}

	visual_3d_enable_antialias(tr_state->visual, compositor->aa);
	if (fill_2d || !asp || tr_state->mesh_num_textures) {
		if (fill_2d && asp) visual_3d_set_material_2d_argb(tr_state->visual, asp->fill_color);

		i = tr_state->text_split_idx ? tr_state->text_split_idx-1 : 0;
		while ((span = (GF_TextSpan *)gf_list_enum(spans, &i))) {
			if (text_hl) {
				visual_3d_fill_rect(tr_state->visual, span->bounds, hl_color);

				if (fill_2d) {
					if (asp)
						visual_3d_set_material_2d_argb(tr_state->visual, asp->fill_color);
				} else
					visual_3d_setup_appearance(tr_state);
			}

			if (can_texture_text && span_setup_texture(tr_state->visual->compositor, span, 1, tr_state)) {
				tr_state->mesh_num_textures = gf_sc_texture_enable(span->ext->txh, NULL);
				if (tr_state->mesh_num_textures) {
					Bool has_mat_2d = tr_state->visual->compositor->visual->has_material_2d;
					visual_3d_mesh_paint(tr_state, span->ext->tx_mesh);
					gf_sc_texture_disable(span->ext->txh);
					tr_state->mesh_num_textures = 0;
					tr_state->visual->has_material_2d = has_mat_2d;
				}
			} else {
				span_fill_3d(span, tr_state);
			}

			if (tr_state->text_split_idx) break;
		}
		tr_state->visual->has_material_2d = GF_FALSE;

		/*reset texturing in case of line texture*/
		if (!asp) visual_3d_disable_texture(tr_state);
	}
	visual_3d_set_state(tr_state->visual, V3D_STATE_BLEND, 0);

	if (asp && asp->pen_props.width) {
		if (!asp->line_scale) {
			drawable_compute_line_scale(tr_state, asp);
		}
		asp->pen_props.width = gf_divfix(asp->pen_props.width, asp->line_scale);
		visual_3d_set_2d_strike(tr_state, asp);

		if (tr_state->text_split_idx) {
			span = (GF_TextSpan *)gf_list_get(spans, tr_state->text_split_idx-1);
			span_strike_3d(span, tr_state, asp, vect_outline);
		} else {
			i=0;
			while ((span = (GF_TextSpan *)gf_list_enum(spans, &i))) {
				span_strike_3d(span, tr_state, asp, vect_outline);
			}
		}
	}
}


#endif

static void gf_font_span_draw_2d(GF_TraverseState *tr_state, GF_TextSpan *span, DrawableContext *ctx, GF_Rect *bounds)
{
	u32 flags, i;
	Bool flip_text;
	Fixed dx, dy, sx, sy, lscale, bline;
	Bool needs_texture = (ctx->aspect.fill_texture || ctx->aspect.line_texture) ? 1 : 0;
	GF_Matrix2D mx, tx;

	gf_mx2d_copy(mx, ctx->transform);

	flags = ctx->flags;
	dx = span->off_x;
	dy = span->off_y;
	sx = gf_mulfix(span->font_scale, span->x_scale);
	sy = gf_mulfix(span->font_scale, span->y_scale);

	flip_text = (ctx->flags & CTX_FLIPED_COORDS) ? tr_state->visual->center_coords : !tr_state->visual->center_coords;

	bline = span->font->baseline*span->font_scale;
	lscale = ctx->aspect.line_scale;
	ctx->aspect.line_scale = gf_divfix(ctx->aspect.line_scale, span->font_scale);

	for (i=0; i<span->nb_glyphs; i++) {
		if (!span->glyphs[i]) {
			if (span->flags & GF_TEXT_SPAN_HORIZONTAL) {
				dx += sx * span->font->max_advance_h;
			} else {
				dy -= sy * span->font->max_advance_v;
			}
			continue;
		}
		if (span->glyphs[i]->ID==GF_CARET_CHAR) {
			if (tr_state->visual->compositor->show_caret) {
				ctx->transform.m[0] = FIX_ONE;
			} else {
				continue;
			}
		} else {
			ctx->transform.m[0] = sx;
		}
		ctx->transform.m[4] = flip_text  ? -sy : sy;
		ctx->transform.m[1] = ctx->transform.m[3] = 0;
		ctx->transform.m[2] = span->dx ? span->dx[i] : dx;
		ctx->transform.m[5] = span->dy ? span->dy[i] : dy;
		ctx->transform.m[5] += bline;
		if (span->rot) {
			gf_mx2d_add_rotation(&ctx->transform, ctx->transform.m[2], ctx->transform.m[5], span->rot[i]);
		}

		gf_mx2d_add_matrix(&ctx->transform, &mx);

		if (needs_texture) {
			tx.m[0] = sx;
			tx.m[4] = sy;
			tx.m[1] = tx.m[3] = 0;
			tx.m[2] = span->dx ? span->dx[i] : dx;
			tx.m[5] = span->dy ? span->dy[i] : dy;
			tx.m[5] += bline;
			if (span->rot) {
				gf_mx2d_add_rotation(&tx, tx.m[2], tx.m[5], span->rot[i]);
			}
			gf_mx2d_inverse(&tx);

			visual_2d_texture_path_extended(tr_state->visual, span->glyphs[i]->path, ctx->aspect.fill_texture, ctx, bounds ? bounds : &span->bounds, &tx, tr_state);
			visual_2d_draw_path_extended(tr_state->visual, span->glyphs[i]->path, ctx, NULL, NULL, tr_state, bounds ? bounds : &span->bounds, &tx, GF_FALSE);
		} else {
			visual_2d_draw_path(tr_state->visual, span->glyphs[i]->path, ctx, NULL, NULL, tr_state);
		}
		ctx->flags = flags;

		if (span->flags & GF_TEXT_SPAN_HORIZONTAL) {
			dx += sx * span->glyphs[i]->horiz_advance;
		} else {
			dy -= sy * span->glyphs[i]->vert_advance;
		}
	}
	gf_mx2d_copy(ctx->transform, mx);
	ctx->aspect.line_scale = lscale;
}

void gf_font_underline_span(GF_TraverseState *tr_state, GF_TextSpan *span, DrawableContext *ctx, Bool is_strikeout)
{
	GF_Matrix2D mx, m;
	u32 col;
	Fixed sx, width, diff;
	if (span->dx || span->dy) return;

	gf_mx2d_copy(mx, ctx->transform);
	sx = gf_mulfix(span->font_scale, span->x_scale);

	if (span->flags & GF_TEXT_SPAN_FLIP)
		diff = sx * (span->font->descent - span->font->underline);
	else
		diff = sx * (- span->font->ascent + span->font->underline);

	if (is_strikeout)
		diff = 2*diff/3;

	gf_mx2d_init(m);
	gf_mx2d_add_scale(&m, span->bounds.width, FIX_ONE);
	gf_mx2d_add_translation(&m, span->bounds.x + span->bounds.width / 2, span->bounds.y+diff);
	gf_mx2d_pre_multiply(&ctx->transform, &m);

	col = ctx->aspect.fill_color;
	width = ctx->aspect.pen_props.width;
	ctx->aspect.pen_props.width = 0;
	ctx->flags &= ~CTX_PATH_FILLED;
	if (span->anchor) ctx->aspect.fill_color = 0xFF0000FF;

	visual_2d_draw_path(tr_state->visual, span->font->ft_mgr->line_path, ctx, NULL, NULL, tr_state);
	ctx->aspect.fill_color = col;

	gf_mx2d_copy(ctx->transform, mx);
	ctx->aspect.pen_props.width = width;
}

#if 0
static u32 col_reverse_video(u32 col)
{
	u32 a, r, g, b;
	a = GF_COL_A(col);
	r = GF_COL_R(col);
	g = GF_COL_G(col);
	b = GF_COL_B(col);
	return GF_COL_ARGB(a, 255-r, 255-g, 255-b);
}
#endif

static GF_Rect font_get_sel_rect(GF_TraverseState *tr_state)
{
	GF_Vec s, e;
	GF_Rect rc;
	GF_Compositor *compositor = tr_state->visual->compositor;

	e.x = compositor->end_sel.x;
	e.y = compositor->end_sel.y;
	e.z = 0;
	gf_mx_apply_vec(&compositor->hit_local_to_world, &e);

	s.x = compositor->start_sel.x;
	s.y = compositor->start_sel.y;
	s.z = 0;
	gf_mx_apply_vec(&compositor->hit_local_to_world, &s);

	if (s.x > e.x) {
		rc.x = e.x;
		rc.width = s.x - e.x;
	} else {
		rc.x = s.x;
		rc.width = e.x - s.x;
	}
	if (s.y > e.y) {
		rc.y = s.y;
		rc.height = s.y - e.y;
	} else {
		rc.y = e.y;
		rc.height = e.y - s.y;
	}
	if (!rc.height) rc.height = FIX_ONE;
	return rc;
}

static void gf_font_spans_select(GF_TextSpan *span, GF_TraverseState *tr_state, DrawableContext *ctx, Bool has_more_spans, Bool first_span, GF_Rect *rc)
{
	GF_Matrix2D mx;
	u32 flags, i, color;
	Bool has_selection = 0;
	Fixed dx, dy, sx, sy, width, ascent, descent;
	GF_Compositor *compositor = tr_state->visual->compositor;

	if (first_span) rc->width = 0;

	if (!(span->flags & GF_TEXT_SPAN_SELECTED) ) return;


	dx = gf_mulfix(span->off_x, span->x_scale);
	dy = gf_mulfix(span->off_y, span->y_scale);
	sx = gf_mulfix(span->font_scale, span->x_scale);
	sy = gf_mulfix(span->font_scale, span->y_scale);
	ascent = span->font->ascent*sy;
	descent = span->font->descent*sy;

	width = 0;
	flags = 0;
	if (ctx) {
		width = ctx->aspect.pen_props.width;
		ctx->aspect.pen_props.width = 0;
		flags = ctx->flags;
		gf_mx2d_copy(mx, ctx->transform);
	}

	/*compute sel rectangle*/
	if (!rc->width) {
		*rc = font_get_sel_rect(tr_state);
	}

	color = compositor->text_sel_color;

	for (i=0; i<span->nb_glyphs; i++) {
		GF_Rect g_rc;
		Bool end_of_line = 0;
		Fixed advance;
		if (!span->glyphs[i]) continue;
		advance = sx * span->glyphs[i]->horiz_advance;
		if (span->dx) dx = span->dx[i];
		if (span->dy) dy = span->dy[i];
		if (dx + advance/2 < rc->x) {
			dx += advance;
			continue;
		}

		g_rc.height = ascent-descent;

		if (dx + advance/2 > rc->x + rc->width) {
			u32 j;
			Bool has_several_lines = 0;
			if (!span->dy) break;
			if (!has_selection) continue;

			for (j=i+1; j<span->nb_glyphs; j++) {
				if (span->dy[j] > span->dy[i]) {
					if (span->flags & GF_TEXT_SPAN_FLIP)
						g_rc.y = span->dy[j]-descent;
					else
						g_rc.y = span->dy[j]+ascent;
					g_rc.x = span->bounds.x;
					g_rc.width = span->bounds.width;
					if (gf_rect_overlaps(g_rc, *rc)) {
						has_several_lines =1;
						break;
					}
				}
			}
			if (has_more_spans && dy<rc->y) has_several_lines =1;

			if (!has_several_lines) break;
			end_of_line = 1;
			/*move selection rect to include start of line - FIXME this depends on ltr/rtl*/
			rc->width = rc->width+rc->x - span->bounds.x;
			rc->x = span->bounds.x;
		}

		if (span->flags & GF_TEXT_SPAN_FLIP)
			g_rc.y = dy-descent;
		else
			g_rc.y = dy+ascent;
		g_rc.x = dx;
		g_rc.width = advance;


		if (!end_of_line && !gf_rect_overlaps(g_rc, *rc))
			continue;
		/*
				if (dy < rc.y-rc.height) {
					if (!span->dx) break;
					continue;
				}
		*/
		has_selection = 1;
		if (ctx) {
			g_rc.width+=2;
			if (span->rot) {
				gf_mx2d_init(ctx->transform);
				gf_mx2d_add_rotation(&ctx->transform, g_rc.x , g_rc.y, span->rot[i]);
				gf_mx2d_add_matrix(&ctx->transform, &mx);
			}
			visual_2d_fill_rect(tr_state->visual, ctx, &g_rc, color, 0, tr_state);
			ctx->flags = flags;
			compositor->store_text_state = GF_SC_TSEL_ACTIVE;
		} else {
			if (!compositor->sel_buffer_alloc || compositor->sel_buffer_len == compositor->sel_buffer_alloc) {
				if (!compositor->sel_buffer_alloc) compositor->sel_buffer_alloc ++;
				compositor->sel_buffer_alloc = 2*compositor->sel_buffer_alloc;
				compositor->sel_buffer = gf_realloc(compositor->sel_buffer, sizeof(u16)*compositor->sel_buffer_alloc);
			}
			compositor->sel_buffer[compositor->sel_buffer_len] = (u16) span->glyphs[i]->utf_name;
			compositor->sel_buffer_len++;
		}

		dx += advance;
	}
	if (ctx) {
		gf_mx2d_copy(ctx->transform, mx);
		ctx->aspect.pen_props.width = width;
	}
	if (has_selection && has_more_spans && dy<rc->y) {
		rc->width = rc->width+rc->x - span->bounds.x;
		rc->x = span->bounds.x;
	}
}

void gf_font_spans_get_selection(GF_Node *node, GF_List *spans, GF_TraverseState *tr_state)
{
	u32 i, count;
	GF_Rect rc;
	count = gf_list_count(spans);
	for (i=0; i<count; i++) {
		GF_TextSpan *span = (GF_TextSpan *)gf_list_get(spans, i);
		gf_font_spans_select(span, tr_state, NULL, (i+1<count) ? 1 : 0, (i==0) ? 1 : 0, &rc);
	}
}


void gf_font_spans_draw_2d(GF_List *spans, GF_TraverseState *tr_state, u32 hl_color, Bool force_texture_text, GF_Rect *bounds)
{
	Bool use_texture_text, is_rv;
	GF_Compositor *compositor = tr_state->visual->compositor;
	u32 i, count;
	GF_TextSpan *span;
	DrawableContext *ctx = tr_state->ctx;
	GF_Rect rc;

	use_texture_text = 0;
	if (force_texture_text || (compositor->textxt==GF_TEXTURE_TEXT_ALWAYS) ) {
		use_texture_text = !ctx->aspect.fill_texture && !ctx->aspect.pen_props.width;
	}

	is_rv = 0;
	if (hl_color) {
		/*reverse video: highlighting uses the text color, and text color is inverted (except alpha channel)
		the ideal impl would be to use the background color for the text, but since the text may be
		displayed over anything non uniform this would require clipping the highlight rect with the text
		which is too onerous (and not supported anyway) */
		if (hl_color==0x00FFFFFF) {
			u32 a, r, g, b;
			hl_color = tr_state->ctx->aspect.fill_color;

			a = GF_COL_A(tr_state->ctx->aspect.fill_color);
			if (a) {
				r = GF_COL_R(tr_state->ctx->aspect.fill_color);
				g = GF_COL_G(tr_state->ctx->aspect.fill_color);
				b = GF_COL_B(tr_state->ctx->aspect.fill_color);
				tr_state->ctx->aspect.fill_color = GF_COL_ARGB(a, 255-r, 255-g, 255-b);
			}
			is_rv = 1;
		}
		if (GF_COL_A(hl_color) == 0) hl_color = 0;
	}

	memset(&rc, 0, sizeof(GF_Rect));
	count = gf_list_count(spans);
	i=ctx->sub_path_index ? ctx->sub_path_index-1 : 0;
	for(; i<count; i++) {
		span = (GF_TextSpan *)gf_list_get(spans, i);
		if (compositor->text_selection)
			gf_font_spans_select(span, tr_state, ctx, (i+1<count) ? 1 : 0, (i==0)? GF_TRUE : GF_FALSE, &rc);
		else if (hl_color)
			visual_2d_fill_rect(tr_state->visual, ctx, &span->bounds, hl_color, 0, tr_state);

		if (use_texture_text && span_setup_texture(compositor, span, 0, tr_state)) {
			visual_2d_texture_path_text(tr_state->visual, ctx, span->ext->path, &span->bounds, span->ext->txh, tr_state);
		} else {
			gf_font_span_draw_2d(tr_state, span, ctx, bounds);
		}
		if (span->anchor || (span->flags & GF_TEXT_SPAN_UNDERLINE) ) gf_font_underline_span(tr_state, span, ctx, GF_FALSE);
		if (span->flags & GF_TEXT_SPAN_STRIKEOUT) gf_font_underline_span(tr_state, span, ctx, GF_TRUE);
		if (ctx->sub_path_index) break;
	}
	if (is_rv) tr_state->ctx->aspect.fill_color = hl_color;
}

void gf_font_spans_pick(GF_Node *node, GF_List *spans, GF_TraverseState *tr_state, GF_Rect *node_bounds, Bool use_dom_events, Drawable *drawable)
{
	u32 i, count, j, glyph_idx;
	Fixed dx, dy;
#ifndef GPAC_DISABLE_3D
	GF_Matrix inv_mx;
#endif
	GF_TextSpan *span;
	DrawAspect2D asp;
	GF_Matrix2D inv_2d;
	Fixed x, y;
	GF_Compositor *compositor = tr_state->visual->compositor;

	if (compositor->text_selection) {
		if (compositor->text_selection != tr_state->text_parent) return;
		if (compositor->store_text_state==GF_SC_TSEL_FROZEN) return;
	}

	/*TODO: pick the real glyph and not just the bounds of the text span*/
	count = gf_list_count(spans);

#ifndef GPAC_DISABLE_3D
	if (tr_state->visual->type_3d) {
		GF_Ray r;
		SFVec3f local_pt;

		r = tr_state->ray;
		gf_mx_copy(inv_mx, tr_state->model_matrix);
		gf_mx_inverse(&inv_mx);
		gf_mx_apply_ray(&inv_mx, &r);

		if (!compositor_get_2d_plane_intersection(&r, &local_pt)) return;

		x = local_pt.x;
		y = local_pt.y;
	} else
#endif
	{
		gf_mx2d_copy(inv_2d, tr_state->transform);
		gf_mx2d_inverse(&inv_2d);
		x = tr_state->ray.orig.x;
		y = tr_state->ray.orig.y;
		gf_mx2d_apply_coords(&inv_2d, &x, &y);
	}

	if (use_dom_events) {
		memset(&asp, 0, sizeof(DrawAspect2D));
		drawable_get_aspect_2d_svg(node, &asp, tr_state);
	}

	span = NULL;
	for (i=0; i<count; i++) {
		Fixed loc_x, loc_y;
		span = (GF_TextSpan*)gf_list_get(spans, i);

		if ((x>=span->bounds.x)
		        && (y<=span->bounds.y)
		        && (x<=span->bounds.x+span->bounds.width)
		        && (y>=span->bounds.y-span->bounds.height)) {
		} else {
			continue;
		}

		glyph_idx = 0;
		dx = span->off_x;
		dy = span->off_y;
		for (j=0; j<span->nb_glyphs; j++) {
			GF_Rect rc;
			if (!span->glyphs[j]) {
				if (span->flags & GF_TEXT_SPAN_HORIZONTAL) {
					dx += span->font_scale * span->font->max_advance_h;
				} else {
					dy -= span->font_scale * span->font->max_advance_v;
				}
				continue;
			}
			else if (span->glyphs[j]->ID==GF_CARET_CHAR) {
				continue;
			}
			glyph_idx++;

			loc_x = x - (span->dx ? span->dx[j] : dx);
			loc_y = y - (span->dy ? span->dy[j] : dy);
			loc_x = gf_divfix(loc_x, span->font_scale);
			loc_y = gf_divfix(loc_y, span->font_scale) + span->font->baseline;
			if (span->flags & GF_TEXT_SPAN_FLIP) loc_y = - loc_y;

			gf_path_get_bounds(span->glyphs[j]->path, &rc);
			rc.height = INT2FIX(span->font->ascent + span->font->descent);

			if (use_dom_events && !compositor->sel_buffer) {
				if (svg_drawable_is_over(drawable, loc_x, loc_y, &asp, tr_state, &rc))
					goto picked;
			} else {
				if ( (loc_x >= rc.x) && (loc_y <= rc.y) && (loc_x <= rc.x + rc.width) && (loc_y >= rc.y - rc.height) ) {
					goto picked;
				}
//				if (gf_path_point_over(span->glyphs[j]->path, loc_x, loc_y)) goto picked;
			}

			if (span->flags & GF_TEXT_SPAN_HORIZONTAL) {
				dx += span->font_scale * span->glyphs[j]->horiz_advance;
			} else {
				dy -= span->font_scale * span->glyphs[j]->vert_advance;
			}
		}
	}
	return;

picked:
	compositor->hit_local_point.x = x;
	compositor->hit_local_point.y = y;
	compositor->hit_local_point.z = 0;

#ifndef GPAC_DISABLE_3D
	if (tr_state->visual->type_3d) {
		gf_mx_copy(compositor->hit_world_to_local, tr_state->model_matrix);
		gf_mx_copy(compositor->hit_local_to_world, inv_mx);
	} else
#endif
	{
		gf_mx_from_mx2d(&compositor->hit_world_to_local, &tr_state->transform);
		gf_mx_from_mx2d(&compositor->hit_local_to_world, &inv_2d);
	}

	compositor->hit_node = node;
	if (span->anchor) compositor->hit_node = span->anchor;

	compositor->end_sel.x = compositor->hit_world_point.x;
	compositor->end_sel.y = compositor->hit_world_point.y;
	if (compositor->text_selection) {
		gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME);
		if (tr_state->visual->offscreen) gf_node_dirty_set(tr_state->visual->offscreen, GF_SG_CHILD_DIRTY, 0);
		span->flags |= GF_TEXT_SPAN_SELECTED;
	} else {
		compositor->start_sel = compositor->end_sel;
	}
	compositor->picked_span_idx = i;
	compositor->picked_glyph_idx = glyph_idx;

	compositor->hit_text = tr_state->text_parent;
	compositor->hit_use_dom_events = use_dom_events;
	compositor->hit_normal.x = compositor->hit_normal.y = 0;
	compositor->hit_normal.z = FIX_ONE;
	compositor->hit_texcoords.x = gf_divfix(x, node_bounds->width) + FIX_ONE/2;
	compositor->hit_texcoords.y = gf_divfix(y, node_bounds->height) + FIX_ONE/2;

#ifndef GPAC_DISABLE_VRML
	if (compositor_is_composite_texture(tr_state->appear)) {
		compositor->hit_appear = tr_state->appear;
	} else
#endif /*GPAC_DISABLE_VRML*/
	{
		compositor->hit_appear = NULL;
	}

	gf_list_reset(tr_state->visual->compositor->sensors);
	count = gf_list_count(tr_state->vrml_sensors);
	for (i=0; i<count; i++) {
		gf_list_add(tr_state->visual->compositor->sensors, gf_list_get(tr_state->vrml_sensors, i));
	}
}

#endif // GPAC_DISABLE_COMPOSITOR

#else
GF_TextSpan *gf_font_manager_create_span(GF_FontManager *fm, GF_Font *font, char *text, Fixed font_size, Bool needs_x_offset, Bool needs_y_offset, Bool needs_rotate, const char *xml_lang, Bool fliped_text, u32 styles, GF_Node *user)
{
	return NULL;
}
void gf_font_manager_delete_span(GF_FontManager *fm, GF_TextSpan *span)
{
}
void gf_font_manager_refresh_span_bounds(GF_TextSpan *span)
{
}
GF_Err gf_font_manager_register_font(GF_FontManager *fm, GF_Font *font)
{
	return GF_NOT_SUPPORTED;
}
GF_Err gf_font_manager_unregister_font(GF_FontManager *fm, GF_Font *font)
{
	return GF_NOT_SUPPORTED;
}
GF_Font *gf_font_manager_set_font(GF_FontManager *fm, char **alt_fonts, u32 nb_fonts, u32 styles)
{
	return NULL;
}
GF_Font *gf_font_manager_set_font_ex(GF_FontManager *fm, char **alt_fonts, u32 nb_fonts, u32 styles, Bool check_only)
{
	return NULL;
}
GF_Path *gf_font_span_create_path(GF_TextSpan *span)
{
	return NULL;
}
#ifndef GPAC_DISABLE_COMPOSITOR
#include "visual_manager.h"

void gf_font_spans_draw_2d(GF_List *spans, GF_TraverseState *tr_state, u32 hl_color, Bool force_texture_text, GF_Rect *bounds)
{
}
void gf_font_spans_draw_3d(GF_List *spans, GF_TraverseState *tr_state, DrawAspect2D *asp, u32 text_hl, Bool force_texturing)
{
}
void gf_font_spans_get_selection(GF_Node *node, GF_List *spans, GF_TraverseState *tr_state)
{
}
void gf_font_spans_pick(GF_Node *node, GF_List *spans, GF_TraverseState *tr_state, GF_Rect *node_bounds, Bool use_dom_events, Drawable *drawable)
{
}
#endif

#endif // GPAC_DISABLE_FONTS
