diff --git a/src/cairo-ft-font.c b/src/cairo-ft-font.c index 1ad7145..8887fc0 100644 --- a/src/cairo-ft-font.c +++ b/src/cairo-ft-font.c @@ -180,6 +180,29 @@ typedef struct _cairo_ft_unscaled_font_map { int num_open_faces; } cairo_ft_unscaled_font_map_t; +static cairo_bool_t FT_CAN_LCD_FILTER = FALSE; + +static cairo_bool_t +_ft_can_lcd_filter (void) +{ +#if HAVE_FT_LIBRARY_SETLCDFILTER + FT_Library library; + FT_Error fterror; + + FT_Init_FreeType(&library); + fterror = FT_Library_SetLcdFilter(library, FT_LCD_FILTER_DEFAULT); + + if (fterror != FT_Err_Unimplemented_Feature) + return TRUE; + else + return FALSE; + + FT_Done_FreeType(library); +#else + return FALSE; +#endif +} + static cairo_ft_unscaled_font_map_t *cairo_ft_unscaled_font_map = NULL; static void @@ -1042,6 +1065,24 @@ _fill_xrender_bitmap(FT_Bitmap *target, } +/* Empirically-derived subpixel filtering values thanks to Keith + * Packard and libXft. */ +static const int filters[3][3] = { + /* red */ +#if 0 + { 65538*4/7,65538*2/7,65538*1/7 }, + /* green */ + { 65536*1/4, 65536*2/4, 65537*1/4 }, + /* blue */ + { 65538*1/7,65538*2/7,65538*4/7 }, +#endif + { 65538*9/13,65538*3/13,65538*1/13 }, + /* green */ + { 65538*1/6, 65538*4/6, 65538*1/6 }, + /* blue */ + { 65538*1/13,65538*3/13,65538*9/13 }, +}; + /* Fills in val->image with an image surface created from @bitmap */ static cairo_status_t @@ -1053,7 +1094,7 @@ _get_bitmap_surface (FT_Bitmap *bitmap, int width, height, stride; unsigned char *data; int format = CAIRO_FORMAT_A8; - cairo_image_surface_t *image; + cairo_bool_t subpixel = FALSE; width = bitmap->width; height = bitmap->rows; @@ -1110,7 +1151,7 @@ _get_bitmap_surface (FT_Bitmap *bitmap, case FT_PIXEL_MODE_LCD: case FT_PIXEL_MODE_LCD_V: case FT_PIXEL_MODE_GRAY: - if (font_options->antialias != CAIRO_ANTIALIAS_SUBPIXEL) { + if (font_options->antialias != CAIRO_ANTIALIAS_SUBPIXEL) { stride = bitmap->pitch; if (own_buffer) { data = bitmap->buffer; @@ -1121,17 +1162,114 @@ _get_bitmap_surface (FT_Bitmap *bitmap, memcpy (data, bitmap->buffer, stride * height); } - format = CAIRO_FORMAT_A8; + format = CAIRO_FORMAT_A8; } else { - /* if we get there, the data from the source bitmap - * really comes from _fill_xrender_bitmap, and is - * made of 32-bit ARGB or ABGR values */ - assert (own_buffer != 0); - assert (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY); + FT_CAN_LCD_FILTER = _ft_can_lcd_filter(); + if (FT_CAN_LCD_FILTER) { + /* if we get there, the data from the source bitmap + * really comes from _fill_xrender_bitmap, and is + * made of 32-bit ARGB or ABGR values */ + assert (own_buffer != 0); + assert (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY); - data = bitmap->buffer; - stride = bitmap->pitch; - format = CAIRO_FORMAT_ARGB32; + data = bitmap->buffer; + stride = bitmap->pitch; + format = CAIRO_FORMAT_ARGB32; + } else { + int x, y; + unsigned char *in_line, *out_line, *in; + unsigned int *out; + unsigned int red, green, blue; + int rf, gf, bf; + int s; + int o, os; + unsigned char *data_rgba; + unsigned int width_rgba, stride_rgba; + int vmul = 1; + int hmul = 1; + + switch (font_options->subpixel_order) { + case CAIRO_SUBPIXEL_ORDER_DEFAULT: + case CAIRO_SUBPIXEL_ORDER_RGB: + case CAIRO_SUBPIXEL_ORDER_BGR: + default: + width /= 3; + hmul = 3; + break; + case CAIRO_SUBPIXEL_ORDER_VRGB: + case CAIRO_SUBPIXEL_ORDER_VBGR: + vmul = 3; + height /= 3; + break; + } + /* + * Filter the glyph to soften the color fringes + */ + width_rgba = width; + stride = bitmap->pitch; + stride_rgba = (width_rgba * 4 + 3) & ~3; + data_rgba = calloc (stride_rgba, height); + if (data_rgba == NULL) { + if (own_buffer) + free (bitmap->buffer); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + os = 1; + switch (font_options->subpixel_order) { + case CAIRO_SUBPIXEL_ORDER_VRGB: + os = stride; + case CAIRO_SUBPIXEL_ORDER_DEFAULT: + case CAIRO_SUBPIXEL_ORDER_RGB: + default: + rf = 0; + gf = 1; + bf = 2; + break; + case CAIRO_SUBPIXEL_ORDER_VBGR: + os = stride; + case CAIRO_SUBPIXEL_ORDER_BGR: + bf = 0; + gf = 1; + rf = 2; + break; + } + in_line = bitmap->buffer; + out_line = data_rgba; + for (y = 0; y < height; y++) + { + in = in_line; + out = (unsigned int *) out_line; + in_line += stride * vmul; + out_line += stride_rgba; + for (x = 0; x < width * hmul; x += hmul) + { + red = green = blue = 0; + o = 0; + for (s = 0; s < 3; s++) + { + red += filters[rf][s]*in[x+o]; + green += filters[gf][s]*in[x+o]; + blue += filters[bf][s]*in[x+o]; + o += os; + } + red = red / 65536; + green = green / 65536; + blue = blue / 65536; + *out++ = (green << 24) | (red << 16) | (green << 8) | blue; + } + } + + /* Images here are stored in native format. The + * backend must convert to its own format as needed + */ + if (own_buffer) + free (bitmap->buffer); + data = data_rgba; + stride = stride_rgba; + format = CAIRO_FORMAT_ARGB32; + } + subpixel = TRUE; } break; case FT_PIXEL_MODE_GRAY2: @@ -1143,20 +1281,19 @@ _get_bitmap_surface (FT_Bitmap *bitmap, return _cairo_error (CAIRO_STATUS_NO_MEMORY); } - /* XXX */ - *surface = image = (cairo_image_surface_t *) + *surface = (cairo_image_surface_t *) cairo_image_surface_create_for_data (data, format, width, height, stride); - if (image->base.status) { + if ((*surface)->base.status) { free (data); return (*surface)->base.status; } - if (font_options->antialias == CAIRO_ANTIALIAS_SUBPIXEL) - pixman_image_set_component_alpha (image->pixman_image, TRUE); + if (subpixel) + pixman_image_set_component_alpha ((*surface)->pixman_image, TRUE); - _cairo_image_surface_assume_ownership_of_data (image); + _cairo_image_surface_assume_ownership_of_data ((*surface)); return CAIRO_STATUS_SUCCESS; } @@ -1188,6 +1325,7 @@ _render_glyph_outline (FT_Face face, FT_Bitmap bitmap; FT_BBox cbox; unsigned int width, height; + double device_offset_left, device_offset_top; cairo_status_t status; FT_Error fterror; FT_Library library = glyphslot->library; @@ -1199,18 +1337,6 @@ _render_glyph_outline (FT_Face face, break; case CAIRO_ANTIALIAS_SUBPIXEL: - switch (font_options->subpixel_order) { - case CAIRO_SUBPIXEL_ORDER_DEFAULT: - case CAIRO_SUBPIXEL_ORDER_RGB: - case CAIRO_SUBPIXEL_ORDER_BGR: - render_mode = FT_RENDER_MODE_LCD; - break; - - case CAIRO_SUBPIXEL_ORDER_VRGB: - case CAIRO_SUBPIXEL_ORDER_VBGR: - render_mode = FT_RENDER_MODE_LCD_V; - break; - } switch (font_options->lcd_filter) { case CAIRO_LCD_FILTER_NONE: @@ -1228,6 +1354,27 @@ _render_glyph_outline (FT_Face face, break; } +#if HAVE_FT_LIBRARY_SETLCDFILTER + fterror = FT_Library_SetLcdFilter (library, lcd_filter); + if(fterror != FT_Err_Unimplemented_Feature) + FT_CAN_LCD_FILTER = TRUE; +#endif + + if (FT_CAN_LCD_FILTER) { + switch (font_options->subpixel_order) { + case CAIRO_SUBPIXEL_ORDER_DEFAULT: + case CAIRO_SUBPIXEL_ORDER_RGB: + case CAIRO_SUBPIXEL_ORDER_BGR: + render_mode = FT_RENDER_MODE_LCD; + break; + + case CAIRO_SUBPIXEL_ORDER_VRGB: + case CAIRO_SUBPIXEL_ORDER_VBGR: + render_mode = FT_RENDER_MODE_LCD_V; + break; + } + } + break; case CAIRO_ANTIALIAS_DEFAULT: @@ -1268,75 +1415,126 @@ _render_glyph_outline (FT_Face face, cairo_image_surface_create_for_data (NULL, format, 0, 0, 0); if ((*surface)->base.status) return (*surface)->base.status; - } else { - int bitmap_size; + } else { - switch (render_mode) { - case FT_RENDER_MODE_LCD: - if (font_options->subpixel_order == CAIRO_SUBPIXEL_ORDER_BGR) { - rgba = FC_RGBA_BGR; - } else { - rgba = FC_RGBA_RGB; - } - break; - case FT_RENDER_MODE_LCD_V: - if (font_options->subpixel_order == CAIRO_SUBPIXEL_ORDER_VBGR) { - rgba = FC_RGBA_VBGR; - } else { - rgba = FC_RGBA_VRGB; - } - break; - case FT_RENDER_MODE_MONO: - case FT_RENDER_MODE_LIGHT: - case FT_RENDER_MODE_NORMAL: - case FT_RENDER_MODE_MAX: - default: - break; - } + if (FT_CAN_LCD_FILTER) { -#if HAVE_FT_LIBRARY_SETLCDFILTER - FT_Library_SetLcdFilter (library, lcd_filter); -#endif + int bitmap_size; - fterror = FT_Render_Glyph (face->glyph, render_mode); + switch (render_mode) { + case FT_RENDER_MODE_LCD: + if (font_options->subpixel_order == CAIRO_SUBPIXEL_ORDER_BGR) { + rgba = FC_RGBA_BGR; + } else { + rgba = FC_RGBA_RGB; + } + break; + case FT_RENDER_MODE_LCD_V: + if (font_options->subpixel_order == CAIRO_SUBPIXEL_ORDER_VBGR) { + rgba = FC_RGBA_VBGR; + } else { + rgba = FC_RGBA_VRGB; + } + break; + case FT_RENDER_MODE_MONO: + case FT_RENDER_MODE_LIGHT: + case FT_RENDER_MODE_NORMAL: + case FT_RENDER_MODE_MAX: + default: + break; + } + + fterror = FT_Render_Glyph (face->glyph, render_mode); #if HAVE_FT_LIBRARY_SETLCDFILTER - FT_Library_SetLcdFilter (library, FT_LCD_FILTER_NONE); + FT_Library_SetLcdFilter (library, FT_LCD_FILTER_NONE); #endif - if (fterror != 0) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); + if (fterror != 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); - bitmap_size = _compute_xrender_bitmap_size (&bitmap, + bitmap_size = _compute_xrender_bitmap_size (&bitmap, face->glyph, render_mode); - if (bitmap_size < 0) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); + if (bitmap_size < 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); - bitmap.buffer = calloc (1, bitmap_size); - if (bitmap.buffer == NULL) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); + bitmap.buffer = calloc (1, bitmap_size); + if (bitmap.buffer == NULL) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); - _fill_xrender_bitmap (&bitmap, face->glyph, render_mode, + _fill_xrender_bitmap (&bitmap, face->glyph, render_mode, (rgba == FC_RGBA_BGR || rgba == FC_RGBA_VBGR)); + device_offset_left = (double)-glyphslot->bitmap_left; + device_offset_top = (double)+glyphslot->bitmap_top; + + } else { + + FT_Matrix matrix; + int hmul = 1; + int vmul = 1; + unsigned int stride; + + stride = (width * hmul + 3) & ~3; + + matrix.xx = matrix.yy = 0x10000L; + matrix.xy = matrix.yx = 0; + + switch (font_options->subpixel_order) { + case CAIRO_SUBPIXEL_ORDER_RGB: + case CAIRO_SUBPIXEL_ORDER_BGR: + case CAIRO_SUBPIXEL_ORDER_DEFAULT: + default: + matrix.xx *= 3; + hmul = 3; + break; + case CAIRO_SUBPIXEL_ORDER_VRGB: + case CAIRO_SUBPIXEL_ORDER_VBGR: + matrix.yy *= 3; + vmul = 3; + break; + } + FT_Outline_Transform (outline, &matrix); + + bitmap.pixel_mode = FT_PIXEL_MODE_GRAY; + bitmap.num_grays = 256; + stride = (width * hmul + 3) & -4; + + bitmap.pitch = stride; + bitmap.width = width * hmul; + bitmap.rows = height * vmul; + bitmap.buffer = calloc (stride, bitmap.rows); + if (bitmap.buffer == NULL) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + FT_Outline_Translate (outline, -cbox.xMin*hmul, -cbox.yMin*vmul); + + if (FT_Outline_Get_Bitmap (glyphslot->library, outline, &bitmap) != 0) { + free (bitmap.buffer); + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + device_offset_left = floor (-(double) cbox.xMin / 64.0); + device_offset_top = floor (+(double) cbox.yMax / 64.0); + } + /* Note: * _get_bitmap_surface will free bitmap.buffer if there is an error */ status = _get_bitmap_surface (&bitmap, TRUE, font_options, surface); if (status) return status; - - /* Note: the font's coordinate system is upside down from ours, so the - * Y coordinate of the control box needs to be negated. Moreover, device - * offsets are position of glyph origin relative to top left while xMin - * and yMax are offsets of top left relative to origin. Another negation. - */ - cairo_surface_set_device_offset (&(*surface)->base, - (double)-glyphslot->bitmap_left, - (double)+glyphslot->bitmap_top); } + /* Note: the font's coordinate system is upside down from ours, so the + * Y coordinate of the control box needs to be negated. Moreover, device + * offsets are position of glyph origin relative to top left while xMin + * and yMax are offsets of top left relative to origin. Another negation. + */ + cairo_surface_set_device_offset (&(*surface)->base, + device_offset_left, + device_offset_top); return CAIRO_STATUS_SUCCESS; } @@ -1729,7 +1927,7 @@ _cairo_ft_options_merge (cairo_ft_options_t *options, load_flags |= FT_LOAD_NO_HINTING; break; case CAIRO_HINT_STYLE_SLIGHT: - load_target = FT_LOAD_TARGET_LIGHT; + load_target |= FT_LOAD_TARGET_LIGHT; break; case CAIRO_HINT_STYLE_MEDIUM: break; @@ -1740,11 +1938,11 @@ _cairo_ft_options_merge (cairo_ft_options_t *options, case CAIRO_SUBPIXEL_ORDER_DEFAULT: case CAIRO_SUBPIXEL_ORDER_RGB: case CAIRO_SUBPIXEL_ORDER_BGR: - load_target = FT_LOAD_TARGET_LCD; + load_target |= FT_LOAD_TARGET_LCD; break; case CAIRO_SUBPIXEL_ORDER_VRGB: case CAIRO_SUBPIXEL_ORDER_VBGR: - load_target = FT_LOAD_TARGET_LCD_V; + load_target |= FT_LOAD_TARGET_LCD_V; break; } }