| /*====================================================================* |
| - Copyright (C) 2008 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| /* |
| * freetype.c |
| * static l_int32 ftUtfToUniChar() |
| * static PIX *ftDrawBitmap() |
| * FT_LIBRARY *ftInitLibrary() |
| * void ftShutdownLibrary() |
| * PIX *pixWriteTTFText() |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <math.h> |
| #include "allheaders.h" |
| |
| #include <ft2build.h> |
| #include FT_FREETYPE_H |
| #include FT_GLYPH_H |
| |
| #undef MAX |
| #define MAX(a, b) (((a)>(b))?(a):(b)) |
| |
| #define ROUNDUPDOWN(val, updown) (!updown) ? (val < 0 ? ((val - 63) >> 6) : val >> 6) : (val > 0 ? ((val + 63) >> 6) : val >> 6) |
| |
| struct ft_library_st { |
| FT_Library library; |
| }; |
| |
| static l_int32 |
| ftUtfToUniChar(char *str, |
| l_int32 *chPtr) |
| { |
| l_int32 byte; |
| |
| /* HTML4.0 entities in decimal form, e.g. Å {{{ */ |
| byte = *((unsigned char *) str); |
| if (byte == '&') { |
| l_int32 i, n = 0; |
| |
| byte = *((unsigned char *) (str+1)); |
| if (byte == '#') { |
| for (i = 2; i < 8; i++) { |
| byte = *((unsigned char *) (str+i)); |
| if (byte >= '0' && byte <= '9') { |
| n = (n * 10) + (byte - '0'); |
| } else { |
| break; |
| } |
| } |
| if (byte == ';') { |
| *chPtr = (l_int32) n; |
| return ++i; |
| } |
| } |
| } |
| /* }}} */ |
| |
| /* Unroll 1 to 3 byte UTF-8 sequences */ |
| |
| byte = *((unsigned char *) str); |
| if (byte < 0xC0) { |
| /* |
| * Handles properly formed UTF-8 characters between 0x01 and 0x7F. |
| * Also treats \0 and naked trail bytes 0x80 to 0xBF as valid |
| * characters representing themselves. |
| */ |
| |
| *chPtr = (l_int32) byte; |
| return 1; |
| } else if (byte < 0xE0) { |
| if ((str[1] & 0xC0) == 0x80) { |
| /* Two-byte-character lead-byte followed by a trail-byte. */ |
| |
| *chPtr = (l_int32) (((byte & 0x1F) << 6) | (str[1] & 0x3F)); |
| return 2; |
| } |
| /* |
| * A two-byte-character lead-byte not followed by trail-byte |
| * represents itself. |
| */ |
| |
| *chPtr = (l_int32) byte; |
| return 1; |
| } else if (byte < 0xF0) { |
| if (((str[1] & 0xC0) == 0x80) && ((str[2] & 0xC0) == 0x80)) { |
| /* Three-byte-character lead byte followed by two trail bytes. */ |
| |
| *chPtr = (l_int32) (((byte & 0x0F) << 12) | ((str[1] & 0x3F) << 6) | (str[2] & 0x3F)); |
| return 3; |
| } |
| /* A three-byte-character lead-byte not followed by two trail-bytes represents itself. */ |
| |
| *chPtr = (l_int32) byte; |
| return 1; |
| } |
| |
| *chPtr = (l_int32)byte; |
| return 1; |
| } |
| /* }}} */ |
| |
| static PIX * |
| ftDrawBitmap(l_uint32 *datad, |
| l_uint32 color, |
| FT_Bitmap bitmap, |
| l_int32 pen_x, |
| l_int32 pen_y, |
| l_int32 width, |
| l_int32 height) |
| { |
| l_uint32 *ppixel = NULL, pixel; |
| l_int32 x, y, row, col, pc, pcr, i; |
| l_uint8 tmp; |
| |
| PROCNAME("ftDrawBitmap"); |
| |
| for (row = 0; row < bitmap.rows; row++) { |
| pc = row * bitmap.pitch; |
| pcr = pc; |
| y = pen_y + row; |
| |
| /* clip if out of bounds */ |
| if (y >= height || y < 0) { |
| continue; |
| } |
| |
| for (col = 0; col < bitmap.width; col++, pc++) { |
| int level; |
| |
| if (bitmap.pixel_mode == ft_pixel_mode_grays) { |
| level = (bitmap.buffer[pc] * 127/ (bitmap.num_grays - 1)); |
| } else if (bitmap.pixel_mode == ft_pixel_mode_mono) { |
| level = ((bitmap.buffer[(col>>3)+pcr]) & (1<<(~col&0x07))) ? 127 : 0; |
| } else { |
| return (PIX *)ERROR_PTR("unsupported ft_pixel mode", procName, NULL); |
| } |
| |
| if (color >= 0) { |
| level = level * (127 - GET_DATA_BYTE(&color, L_ALPHA_CHANNEL)) / 127; |
| } |
| |
| level = 127 - level; |
| x = pen_x + col; |
| |
| /* clip if out of bounds */ |
| if (x >= width || x < 0) { |
| continue; |
| } |
| |
| ppixel = datad + y*width + x; |
| |
| /* mix 2 colors using level as alpha */ |
| if (level != 127) { |
| l_uint8 new, old; |
| |
| pixel = *ppixel; |
| for (i = 0; i < 3; i++) { |
| new = GET_DATA_BYTE(&color, i); |
| old = GET_DATA_BYTE(&pixel, i); |
| tmp = (double)old * ((double)level/127) + (double)new * ((double)(127 - level)/127); |
| SET_DATA_BYTE(ppixel, i, tmp); |
| } |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| |
| FT_LIBRARY * |
| ftInitLibrary(void) |
| { |
| FT_Error err; |
| FT_LIBRARY *lib_ptr; |
| |
| lib_ptr = CALLOC(1, sizeof(FT_LIBRARY)); |
| |
| err = FT_Init_FreeType(&lib_ptr->library); |
| if (err) { |
| FREE(lib_ptr); |
| return NULL; |
| } |
| return lib_ptr; |
| } |
| |
| |
| void |
| ftShutdownLibrary(FT_LIBRARY *lib_ptr) |
| { |
| if (lib_ptr) { |
| FT_Done_FreeType(lib_ptr->library); |
| FREE(lib_ptr); |
| } |
| } |
| |
| |
| PIX * |
| pixWriteTTFText(FT_LIBRARY *lib_ptr, |
| PIX *pixs, |
| l_float32 size, |
| l_float32 angle, |
| l_int32 x, |
| l_int32 y, |
| l_int32 letter_space, |
| l_uint32 color, |
| l_uint8 *fontfile, |
| l_uint8 *text, |
| l_int32 text_len, |
| l_int32 *brect) |
| { |
| PIX *pixd, *pixt = NULL; |
| FT_Error err; |
| FT_Face face; |
| FT_Glyph image; |
| FT_BitmapGlyph bitmap; |
| FT_CharMap charmap; |
| FT_Matrix matrix; |
| FT_Vector pen, penf; |
| FT_UInt glyph_index, previous; |
| FT_BBox char_bbox, bbox; |
| l_uint32 *datad, letter_space_x, letter_space_y; |
| l_int32 i, found, len, ch, x1 = 0, y1 = 0, width, height; |
| l_uint16 platform, encoding; |
| char *next; |
| l_float32 cos_a, sin_a; |
| |
| PROCNAME("pixWriteTTFText"); |
| |
| if (pixGetDepth(pixs) != 32) { |
| pixt = pixConvertTo32(pixs); |
| if (!pixt) { |
| return (PIX *)ERROR_PTR("failed to convert pixs to 32bpp image", procName, NULL); |
| } |
| |
| pixd = pixCopy(NULL, pixt); |
| } else { |
| pixd = pixCopy(NULL, pixs); |
| } |
| |
| datad = pixGetData(pixd); |
| |
| if (!pixd) { |
| if (pixt) { |
| pixDestroy(&pixt); |
| pixDestroy(&pixd); |
| } |
| return (PIX *)ERROR_PTR("pixd not made", procName, NULL); |
| } |
| |
| width = pixGetWidth(pixd); |
| height = pixGetHeight(pixd); |
| |
| err = FT_New_Face (lib_ptr->library, (char *)fontfile, 0, &face); |
| if (err) { |
| if (pixt) { |
| pixDestroy(&pixt); |
| pixDestroy(&pixd); |
| } |
| return (PIX *)ERROR_PTR("failed to load font file", procName, NULL); |
| } |
| |
| err = FT_Set_Char_Size(face, 0, (FT_F26Dot6) (size * 64), LEPTONICA_FT_RESOLUTION, LEPTONICA_FT_RESOLUTION); |
| if (err) { |
| if (pixt) { |
| pixDestroy(&pixt); |
| pixDestroy(&pixd); |
| } |
| FT_Done_Face(face); |
| return (PIX *)ERROR_PTR("failed to set font size", procName, NULL); |
| } |
| |
| found = 0; |
| |
| for (i = 0; i < face->num_charmaps; i++) { |
| charmap = face->charmaps[i]; |
| platform = charmap->platform_id; |
| encoding = charmap->encoding_id; |
| |
| if ((platform == 3 && encoding == 1) /* Windows Unicode */ |
| || (platform == 3 && encoding == 0) /* Windows Symbol */ |
| || (platform == 2 && encoding == 1) /* ISO Unicode */ |
| || (platform == 0)) /* Apple Unicode */ |
| { |
| found = 1; |
| break; |
| } |
| } |
| |
| if (!found) { |
| if (pixt) { |
| pixDestroy(&pixt); |
| pixDestroy(&pixd); |
| } |
| FT_Done_Face(face); |
| return (PIX *)ERROR_PTR("could not find Unicode charmap", procName, NULL); |
| } |
| |
| /* degrees to radians */ |
| angle = angle * (M_PI/180); |
| sin_a = sin(angle); |
| cos_a = cos(angle); |
| |
| matrix.xx = (FT_Fixed) (cos_a * (1 << 16)); |
| matrix.yx = (FT_Fixed) (sin_a * (1 << 16)); |
| matrix.xy = -matrix.yx; |
| matrix.yy = matrix.xx; |
| |
| FT_Set_Transform(face, &matrix, NULL); |
| |
| penf.x = penf.y = 0; /* running position of non-rotated string */ |
| pen.x = pen.y = 0; /* running position of rotated string */ |
| |
| previous = 0; |
| |
| next = (char *)text; |
| i = 0; |
| while (*next) { |
| if (i == 0) { /* use char spacing for 1+ characters */ |
| letter_space_x = 0; |
| letter_space_y = 0; |
| } else { |
| letter_space_x = cos_a * letter_space * i; |
| letter_space_y = -sin_a * letter_space * i; |
| } |
| |
| len = ftUtfToUniChar(next, &ch); |
| // ch |= 0xf000; |
| next += len; |
| |
| glyph_index = FT_Get_Char_Index(face, ch); |
| |
| err = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); |
| if (err) { |
| if (pixt) { |
| pixDestroy(&pixt); |
| pixDestroy(&pixd); |
| } |
| FT_Done_Face(face); |
| return (PIX *)ERROR_PTR("could not load glyph into the slot", procName, NULL); |
| } |
| |
| err = FT_Get_Glyph(face->glyph, &image); |
| if (err) { |
| if (pixt) { |
| pixDestroy(&pixt); |
| pixDestroy(&pixd); |
| } |
| FT_Done_Face(face); |
| return (PIX *)ERROR_PTR("could not extract glyph from a slot", procName, NULL); |
| } |
| |
| if (brect) { |
| FT_Glyph_Get_CBox(image, ft_glyph_bbox_gridfit, &char_bbox); |
| |
| char_bbox.xMin += penf.x; |
| char_bbox.yMin += penf.y; |
| char_bbox.xMax += penf.x; |
| char_bbox.yMax += penf.y; |
| |
| if (i == 0) { |
| bbox.xMin = char_bbox.xMin; |
| bbox.yMin = char_bbox.yMin; |
| bbox.xMax = char_bbox.xMax; |
| bbox.yMax = char_bbox.yMax; |
| } else { |
| if (bbox.xMin > char_bbox.xMin) { |
| bbox.xMin = char_bbox.xMin; |
| } |
| if (bbox.yMin > char_bbox.yMin) { |
| bbox.yMin = char_bbox.yMin; |
| } |
| if (bbox.xMax < char_bbox.xMax) { |
| bbox.xMax = char_bbox.xMax; |
| } |
| if (bbox.yMax < char_bbox.yMax) { |
| bbox.yMax = char_bbox.yMax; |
| } |
| } |
| } |
| |
| if (image->format != ft_glyph_format_bitmap && FT_Glyph_To_Bitmap(&image, ft_render_mode_normal, 0, 1)) { |
| if (pixt) { |
| pixDestroy(&pixt); |
| pixDestroy(&pixd); |
| } |
| FT_Done_Face(face); |
| return (PIX *)ERROR_PTR("could not convert glyph to bitmap", procName, NULL); |
| } |
| |
| /* now, draw to our target surface */ |
| bitmap = (FT_BitmapGlyph) image; |
| |
| ftDrawBitmap(datad, color, bitmap->bitmap, letter_space_x + x + x1 + ((pen.x + 31) >> 6) + bitmap->left, letter_space_y + y - y1 + ((pen.y + 31) >> 6) - bitmap->top, width, height); |
| |
| /* record current glyph index for kerning */ |
| previous = glyph_index; |
| |
| /* increment pen position */ |
| pen.x += image->advance.x >> 10; |
| pen.y -= image->advance.y >> 10; |
| |
| penf.x += face->glyph->metrics.horiAdvance; |
| |
| FT_Done_Glyph(image); |
| i++; |
| } |
| |
| if (brect) { |
| double d1 = sin (angle + 0.78539816339744830962); |
| double d2 = sin (angle - 0.78539816339744830962); |
| |
| /* rotate bounding rectangle */ |
| brect[0] = (int) (bbox.xMin * cos_a - bbox.yMin * sin_a); |
| brect[1] = (int) (bbox.xMin * sin_a + bbox.yMin * cos_a); |
| brect[2] = (int) (bbox.xMax * cos_a - bbox.yMin * sin_a); |
| brect[3] = (int) (bbox.xMax * sin_a + bbox.yMin * cos_a); |
| brect[4] = (int) (bbox.xMax * cos_a - bbox.yMax * sin_a); |
| brect[5] = (int) (bbox.xMax * sin_a + bbox.yMax * cos_a); |
| brect[6] = (int) (bbox.xMin * cos_a - bbox.yMax * sin_a); |
| brect[7] = (int) (bbox.xMin * sin_a + bbox.yMax * cos_a); |
| |
| /* scale, round and offset brect */ |
| brect[0] = x + ROUNDUPDOWN(brect[0], d2 > 0); |
| brect[1] = y - ROUNDUPDOWN(brect[1], d1 < 0); |
| brect[2] = x + ROUNDUPDOWN(brect[2], d1 > 0); |
| brect[3] = y - ROUNDUPDOWN(brect[3], d2 > 0); |
| brect[4] = x + ROUNDUPDOWN(brect[4], d2 < 0); |
| brect[5] = y - ROUNDUPDOWN(brect[5], d1 > 0); |
| brect[6] = x + ROUNDUPDOWN(brect[6], d1 < 0); |
| brect[7] = y - ROUNDUPDOWN(brect[7], d2 < 0); |
| } |
| |
| if (pixt) { |
| pixDestroy(&pixt); |
| } |
| return pixd; |
| } |
| |