FT_GlyphSlot_Embolden( FT_GlyphSlot slot ) { FT_Library library = slot->library; FT_Face face = FT_SLOT_FACE( slot ); FT_Error error; FT_Pos xstr, ystr; if ( slot->format != FT_GLYPH_FORMAT_OUTLINE && slot->format != FT_GLYPH_FORMAT_BITMAP ) return; /* some reasonable strength */ xstr = FT_MulFix( face->units_per_EM, face->size->metrics.y_scale ) / 35; ystr = xstr; if ( slot->format == FT_GLYPH_FORMAT_OUTLINE ) { error = FT_Outline_Embolden( &slot->outline, xstr ); /* ignore error */ /* this is more than enough for most glyphs; if you need accurate */ /* values, you have to call FT_Outline_Get_CBox */ xstr = xstr * 2; ystr = xstr; } else if ( slot->format == FT_GLYPH_FORMAT_BITMAP ) { xstr = FT_PIX_FLOOR( xstr ); if ( xstr == 0 ) xstr = 1 << 6; ystr = FT_PIX_FLOOR( ystr ); error = FT_GlyphSlot_Own_Bitmap( slot ); if ( error ) return; error = FT_Bitmap_Embolden( library, &slot->bitmap, xstr, ystr ); if ( error ) return; } if ( slot->advance.x ) slot->advance.x += xstr; if ( slot->advance.y ) slot->advance.y += ystr; slot->metrics.width += xstr; slot->metrics.height += ystr; slot->metrics.horiBearingY += ystr; slot->metrics.horiAdvance += xstr; slot->metrics.vertBearingX -= xstr / 2; slot->metrics.vertBearingY += ystr; slot->metrics.vertAdvance += ystr; if ( slot->format == FT_GLYPH_FORMAT_BITMAP ) slot->bitmap_top += ystr >> 6; }
// 让一个字体槽加粗,并且填充其他的大小属性 void New_GlyphSlot_Embolden( FT_GlyphSlot slot, const FT_Pos str_x, const FT_Pos str_y) { FT_Library library; FT_Face face; FT_Error error; FT_BBox newBox, oldBox; FT_Pos xstr = (FT_Pos)str_x; FT_Pos ystr = (FT_Pos)str_y; CC_ASSERT(slot != NULL); library = slot->library; face = slot->face; if ( slot->format != FT_GLYPH_FORMAT_OUTLINE && slot->format != FT_GLYPH_FORMAT_BITMAP ) return; if ( slot->format == FT_GLYPH_FORMAT_OUTLINE ) { FT_Outline_Get_CBox(&slot->outline , &oldBox); error = New_FT_Outline_Embolden( &slot->outline, xstr , ystr); if ( error ) return; FT_Outline_Get_CBox(&slot->outline , &newBox); xstr = (newBox.xMax - newBox.xMin) - (oldBox.xMax - oldBox.xMin); ystr = (newBox.yMax - newBox.yMin) - (oldBox.yMax - oldBox.yMin); } else if ( slot->format == FT_GLYPH_FORMAT_BITMAP ) { xstr = FT_PIX_FLOOR( xstr ); if ( xstr == 0 ) xstr = 1 << 6; ystr = FT_PIX_FLOOR( ystr ); error = FT_Bitmap_Embolden( library, &slot->bitmap, xstr, ystr ); if ( error ) return; } if ( slot->advance.x ) slot->advance.x += xstr; if ( slot->advance.y ) slot->advance.y += ystr; slot->metrics.width += xstr; slot->metrics.height += ystr; slot->metrics.horiBearingY += ystr; slot->metrics.horiAdvance += xstr; slot->metrics.vertBearingX -= xstr / 2; slot->metrics.vertBearingY += ystr; slot->metrics.vertAdvance += ystr; if ( slot->format == FT_GLYPH_FORMAT_BITMAP ) slot->bitmap_top += ystr >> 6; }
bool ExtractGlyph(unsigned int characterSize, char32_t character, UInt32 style, FontGlyph* dst) override { #ifdef NAZARA_DEBUG if (!dst) { NazaraError("Glyph destination cannot be null"); return false; } #endif SetCharacterSize(characterSize); if (FT_Load_Char(m_face, character, FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL) != 0) { NazaraError("Failed to load character"); return false; } FT_GlyphSlot& glyph = m_face->glyph; const FT_Pos boldStrength = 2 << 6; bool embolden = (style & TextStyle_Bold); dst->advance = (embolden) ? boldStrength >> 6 : 0; if (embolden && glyph->format == FT_GLYPH_FORMAT_OUTLINE) { // http://www.freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_Outline_Embolden FT_Outline_Embolden(&glyph->outline, boldStrength); embolden = false; } // http://www.freetype.org/freetype2/docs/reference/ft2-glyph_management.html#FT_Glyph_To_Bitmap // Conversion du glyphe vers le format bitmap // Cette fonction ne fait rien dans le cas où le glyphe est déjà un bitmap if (FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL) != 0) { NazaraError("Failed to convert glyph to bitmap"); return false; } // Dans le cas où nous voulons des caractères gras mais que nous n'avons pas pu agir plus tôt // nous demandons à FreeType d'agir directement sur le bitmap généré if (embolden) { // http://www.freetype.org/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden // "If you want to embolden the bitmap owned by a FT_GlyphSlot_Rec, you should call FT_GlyphSlot_Own_Bitmap on the slot first" FT_GlyphSlot_Own_Bitmap(glyph); FT_Bitmap_Embolden(s_library, &glyph->bitmap, boldStrength, boldStrength); } dst->advance += glyph->metrics.horiAdvance >> 6; dst->aabb.x = glyph->metrics.horiBearingX >> 6; dst->aabb.y = -(glyph->metrics.horiBearingY >> 6); // Inversion du repère dst->aabb.width = glyph->metrics.width >> 6; dst->aabb.height = glyph->metrics.height >> 6; unsigned int width = glyph->bitmap.width; unsigned int height = glyph->bitmap.rows; if (width > 0 && height > 0) { dst->image.Create(ImageType_2D, PixelFormatType_A8, width, height); UInt8* pixels = dst->image.GetPixels(); const UInt8* data = glyph->bitmap.buffer; // Selon la documentation FreeType, le glyphe peut être encodé en format A8 (huit bits d'alpha par pixel) // ou au format A1 (un bit d'alpha par pixel). // Cependant dans un cas comme dans l'autre, il nous faut gérer le pitch (les données peuvent ne pas être contigues) // ainsi que le padding dans le cas du format A1 (Chaque ligne prends un nombre fixe d'octets) if (glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { // Format A1 for (unsigned int y = 0; y < height; ++y) { for (unsigned int x = 0; x < width; ++x) *pixels++ = (data[x/8] & ((1 << (7 - x%8)) ? 255 : 0)); data += glyph->bitmap.pitch; } } else { // Format A8 if (glyph->bitmap.pitch == static_cast<int>(width*sizeof(UInt8))) // Pouvons-nous copier directement ? dst->image.Update(glyph->bitmap.buffer); else { for (unsigned int y = 0; y < height; ++y) { std::memcpy(pixels, data, width*sizeof(UInt8)); data += glyph->bitmap.pitch; pixels += width*sizeof(UInt8); } } } } else dst->image.Destroy(); // On s'assure que l'image ne contient alors rien return true; }
static FT_Error Render_Embolden( int num_indices, int first_index ) { int start_x, start_y, step_y, x, y; int i; FT_Size size; FT_Face face; FT_GlyphSlot slot; FT_Pos xstr, ystr; error = FTDemo_Get_Size( handle, &size ); if ( error ) { /* probably a non-existent bitmap font size */ return error; } INIT_SIZE( size, start_x, start_y, step_y, x, y ); face = size->face; slot = face->glyph; ystr = status.ptsize * status.res / 72; xstr = status.xbold_factor * ystr; ystr = status.ybold_factor * ystr; for ( i = first_index; i < num_indices; i++ ) { int gindex; if ( handle->encoding == FT_ENCODING_NONE ) gindex = i; else gindex = FTDemo_Get_Index( handle, i ); error = FT_Load_Glyph( face, gindex, handle->load_flags ); if ( !error ) { /* this is essentially the code of function */ /* `FT_GlyphSlot_Embolden' */ if ( slot->format == FT_GLYPH_FORMAT_OUTLINE ) { error = FT_Outline_EmboldenXY( &slot->outline, xstr, ystr ); /* ignore error */ } else if ( slot->format == FT_GLYPH_FORMAT_BITMAP ) { /* round to full pixels */ xstr &= ~63; ystr &= ~63; error = FT_GlyphSlot_Own_Bitmap( slot ); if ( error ) goto Next; error = FT_Bitmap_Embolden( slot->library, &slot->bitmap, xstr, ystr ); if ( error ) goto Next; } else goto Next; if ( slot->advance.x ) slot->advance.x += xstr; if ( slot->advance.y ) slot->advance.y += ystr; slot->metrics.width += xstr; slot->metrics.height += ystr; slot->metrics.horiAdvance += xstr; slot->metrics.vertAdvance += ystr; if ( slot->format == FT_GLYPH_FORMAT_BITMAP ) slot->bitmap_top += ystr >> 6; error = FTDemo_Draw_Slot( handle, display, slot, &x, &y ); if ( error ) goto Next; else if ( X_TOO_LONG( x, size, display ) ) { x = start_x; y += step_y; if ( Y_TOO_LONG( y, size, display ) ) break; } } else Next: status.Fail++; }
Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) const { // The glyph to return Glyph glyph; // First, transform our ugly void* to a FT_Face FT_Face face = static_cast<FT_Face>(m_face); if (!face) return glyph; // Set the character size if (!setCurrentSize(characterSize)) return glyph; // Load the glyph corresponding to the code point if (FT_Load_Char(face, codePoint, FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT) != 0) return glyph; // Retrieve the glyph FT_Glyph glyphDesc; if (FT_Get_Glyph(face->glyph, &glyphDesc) != 0) return glyph; // Apply bold if necessary -- first technique using outline (highest quality) FT_Pos weight = 1 << 6; bool outline = (glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE); if (bold && outline) { FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc; FT_Outline_Embolden(&outlineGlyph->outline, weight); } // Convert the glyph to a bitmap (i.e. rasterize it) FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1); FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>(glyphDesc)->bitmap; // Apply bold if necessary -- fallback technique using bitmap (lower quality) if (bold && !outline) { FT_Bitmap_Embolden(static_cast<FT_Library>(m_library), &bitmap, weight, weight); } // Compute the glyph's advance offset glyph.advance = static_cast<float>(face->glyph->metrics.horiAdvance) / static_cast<float>(1 << 6); if (bold) glyph.advance += static_cast<float>(weight) / static_cast<float>(1 << 6); int width = bitmap.width; int height = bitmap.rows; if ((width > 0) && (height > 0)) { // Leave a small padding around characters, so that filtering doesn't // pollute them with pixels from neighbors const unsigned int padding = 1; // Get the glyphs page corresponding to the character size Page& page = m_pages[characterSize]; // Find a good position for the new glyph into the texture glyph.textureRect = findGlyphRect(page, width + 2 * padding, height + 2 * padding); // Make sure the texture data is positioned in the center // of the allocated texture rectangle glyph.textureRect.left += padding; glyph.textureRect.top += padding; glyph.textureRect.width -= 2 * padding; glyph.textureRect.height -= 2 * padding; // Compute the glyph's bounding box glyph.bounds.left = static_cast<float>(face->glyph->metrics.horiBearingX) / static_cast<float>(1 << 6); glyph.bounds.top = -static_cast<float>(face->glyph->metrics.horiBearingY) / static_cast<float>(1 << 6); glyph.bounds.width = static_cast<float>(face->glyph->metrics.width) / static_cast<float>(1 << 6); glyph.bounds.height = static_cast<float>(face->glyph->metrics.height) / static_cast<float>(1 << 6); // Extract the glyph's pixels from the bitmap m_pixelBuffer.resize(width * height * 4, 255); const Uint8* pixels = bitmap.buffer; if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { // Pixels are 1 bit monochrome values for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { // The color channels remain white, just fill the alpha channel std::size_t index = (x + y * width) * 4 + 3; m_pixelBuffer[index] = ((pixels[x / 8]) & (1 << (7 - (x % 8)))) ? 255 : 0; } pixels += bitmap.pitch; } } else { // Pixels are 8 bits gray levels for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { // The color channels remain white, just fill the alpha channel std::size_t index = (x + y * width) * 4 + 3; m_pixelBuffer[index] = pixels[x]; } pixels += bitmap.pitch; } } // Write the pixels to the texture unsigned int x = glyph.textureRect.left; unsigned int y = glyph.textureRect.top; unsigned int w = glyph.textureRect.width; unsigned int h = glyph.textureRect.height; page.texture.update(&m_pixelBuffer[0], w, h, x, y); } // Delete the FT glyph FT_Done_Glyph(glyphDesc); // Force an OpenGL flush, so that the font's texture will appear updated // in all contexts immediately (solves problems in multi-threaded apps) glCheck(glFlush()); // Done :) return glyph; }
void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGlyph& glyph) { const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); switch ( face->glyph->format ) { case FT_GLYPH_FORMAT_OUTLINE: { FT_Outline* outline = &face->glyph->outline; FT_BBox bbox; FT_Bitmap target; if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) { emboldenOutline(face, outline); } int dx = 0, dy = 0; if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { dx = SkFixedToFDot6(glyph.getSubXFixed()); dy = SkFixedToFDot6(glyph.getSubYFixed()); // negate dy since freetype-y-goes-up and skia-y-goes-down dy = -dy; } FT_Outline_Get_CBox(outline, &bbox); /* what we really want to do for subpixel is offset(dx, dy) compute_bounds offset(bbox & !63) but that is two calls to offset, so we do the following, which achieves the same thing with only one offset call. */ FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63), dy - ((bbox.yMin + dy) & ~63)); if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } } else { target.width = glyph.fWidth; target.rows = glyph.fHeight; target.pitch = glyph.rowBytes(); target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage); target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); } } break; case FT_GLYPH_FORMAT_BITMAP: { if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) { FT_GlyphSlot_Own_Bitmap(face->glyph); FT_Bitmap_Embolden(face->glyph->library, &face->glyph->bitmap, kBitmapEmboldenStrength, 0); } SkASSERT_CONTINUE(glyph.fWidth == face->glyph->bitmap.width); SkASSERT_CONTINUE(glyph.fHeight == face->glyph->bitmap.rows); SkASSERT_CONTINUE(glyph.fTop == -face->glyph->bitmap_top); SkASSERT_CONTINUE(glyph.fLeft == face->glyph->bitmap_left); const uint8_t* src = (const uint8_t*)face->glyph->bitmap.buffer; uint8_t* dst = (uint8_t*)glyph.fImage; if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY || (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && glyph.fMaskFormat == SkMask::kBW_Format)) { unsigned srcRowBytes = face->glyph->bitmap.pitch; unsigned dstRowBytes = glyph.rowBytes(); unsigned minRowBytes = SkMin32(srcRowBytes, dstRowBytes); unsigned extraRowBytes = dstRowBytes - minRowBytes; for (int y = face->glyph->bitmap.rows - 1; y >= 0; --y) { memcpy(dst, src, minRowBytes); memset(dst + minRowBytes, 0, extraRowBytes); src += srcRowBytes; dst += dstRowBytes; } } else if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && glyph.fMaskFormat == SkMask::kA8_Format) { for (int y = 0; y < face->glyph->bitmap.rows; ++y) { uint8_t byte = 0; int bits = 0; const uint8_t* src_row = src; uint8_t* dst_row = dst; for (int x = 0; x < face->glyph->bitmap.width; ++x) { if (!bits) { byte = *src_row++; bits = 8; } *dst_row++ = byte & 0x80 ? 0xff : 0; bits--; byte <<= 1; } src += face->glyph->bitmap.pitch; dst += glyph.rowBytes(); } } else if (SkMask::kLCD16_Format == glyph.fMaskFormat) { if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } } else { SkDEBUGFAIL("unknown glyph bitmap transform needed"); } } break; default: SkDEBUGFAIL("unknown glyph format"); memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); return; } // We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, // it is optional #if defined(SK_GAMMA_APPLY_TO_A8) if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) { uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; unsigned rowBytes = glyph.rowBytes(); for (int y = glyph.fHeight - 1; y >= 0; --y) { for (int x = glyph.fWidth - 1; x >= 0; --x) { dst[x] = fPreBlend.fG[dst[x]]; } dst += rowBytes; } } #endif }
static BOOL ft_get_char(void *p_priv, u16 char_code, rsc_fstyle_t *p_fstyle, rsc_char_info_t *p_info) { RET_CODE ret = SUCCESS; ft_cache_t *p_vf_cache = NULL; u32 i = 0, ckey = 0; ft_priv_t *p_vf = (ft_priv_t *)p_priv; FT_Stroker p_stroker = {0}; FT_Raster_Params params = {0}; FT_Glyph p_glyph = {0}; spans_t spans = {0}; FT_BBox bbox_fg = {0}, bbox_bg = {0}; u16 w_bg = 0, w_fg = 0, h_bg = 0, h_fg = 0; FT_Matrix matrix = {0}; rsc_font_t hdr_font = {{0}}; u8 *p_font = NULL; MT_ASSERT(p_vf != NULL); if(ft_hit_cache(p_priv, char_code, p_fstyle, p_info)) { p_info->ckey = p_fstyle->color ? 0 : 1; p_info->bpp = p_vf->bpp; return TRUE; } MT_ASSERT(p_fstyle->width <= p_vf->max_width); MT_ASSERT(p_fstyle->height <= p_vf->max_height); if(!rsc_get_font(p_vf->rsc_handle, p_fstyle->font_id, &hdr_font, &p_font)) { return FALSE; } MT_ASSERT(hdr_font.type == FONT_VECTOR); if(!ft_check_char(p_priv, char_code, p_font, hdr_font.head.org_size, p_fstyle)) { return FALSE; } p_vf_cache = ft_insert_cache(p_priv, char_code, p_fstyle); ckey = p_fstyle->color ? 0 : 1; MT_ASSERT(p_vf_cache != NULL); if((p_fstyle->attr & VFONT_ITALIC) != 0) { matrix.xx = 0x10000L; matrix.xy = 0x1000L; matrix.yx = 0; matrix.yy = 0x10000L; FT_Set_Transform(p_vf->p_face, &matrix, 0); } else { FT_Set_Transform(p_vf->p_face, NULL, 0); } ret = FT_Set_Pixel_Sizes(p_vf->p_face, p_vf_cache->char_width, p_vf_cache->char_height); MT_ASSERT(ret == SUCCESS); if(((p_fstyle->attr & VFONT_STROK) == 0) || (p_vf->is_alpha_spt == FALSE)) { if(p_vf->is_alpha_spt) { ret = FT_Load_Char(p_vf->p_face, char_code, FT_LOAD_RENDER); p_info->is_alpha_spt = TRUE; } else { ret = FT_Load_Char(p_vf->p_face, char_code, FT_LOAD_RENDER | FT_LOAD_MONOCHROME); p_info->is_alpha_spt = FALSE; } if((p_fstyle->attr & VFONT_BOLD) != 0) { FT_Bitmap_Embolden(p_vf->p_library, &p_vf->p_face->glyph->bitmap, 64, 64); } MT_ASSERT(ret == SUCCESS); p_info->height = p_vf->p_face->glyph->bitmap.rows; p_info->width = p_vf->p_face->glyph->bitmap.width; p_info->xoffset = p_vf->p_face->glyph->bitmap_left; p_info->yoffset = ((p_vf->p_face->size->metrics.ascender - p_vf->p_face->glyph->metrics.horiBearingY) >> 6); p_info->pitch = p_vf->max_width * p_vf->bpp >> 3; p_info->alpha_pitch = p_vf->max_width; p_info->x_step = ((p_vf->p_face->glyph->advance.x) >> 6); p_info->step_width = p_vf->p_face->glyph->metrics.horiAdvance >> 6; //p_info->step_height = p_vf->p_face->size->metrics.y_ppem; p_info->step_height = p_vf->p_face->size->metrics.height >> 6; p_vf_cache->pitch = p_info->pitch; p_vf_cache->alpha_pitch = p_info->alpha_pitch; p_vf_cache->xoff = p_info->xoffset; p_vf_cache->yoff = p_info->yoffset; p_vf_cache->step_width = p_info->step_width; p_vf_cache->step_height = p_info->step_height; p_vf_cache->x_step = p_info->x_step; p_vf_cache->width = p_info->width; p_vf_cache->height = p_info->height; p_vf_cache->color = p_fstyle->color; if(p_vf->is_alpha_spt) { for(i = 0; i < p_vf->p_face->glyph->bitmap.rows; i++) { memcpy((u8 *)p_vf_cache->p_alpha + i * p_vf->max_width, p_vf->p_face->glyph->bitmap.buffer + i * p_vf->p_face->glyph->bitmap.pitch, p_vf->p_face->glyph->bitmap.pitch); } for(i = 0; i < (p_vf->max_height * p_vf->max_width); i++) { if(p_vf->bpp == 32) { *((u32 *)(p_vf_cache->p_char) + i) = p_fstyle->color; } else if(p_vf->bpp == 16) { *((u16 *)(p_vf_cache->p_char) + i) = p_fstyle->color; } } } else { if(p_vf->bpp == 32) { mono_to_u32buf(p_vf->p_face->glyph->bitmap.buffer, p_vf->p_face->glyph->bitmap.width, p_vf->p_face->glyph->bitmap.rows, p_vf->p_face->glyph->bitmap.pitch, p_vf_cache->p_char, p_vf->max_width * 4, p_fstyle->color, ckey); } else if(p_vf->bpp == 16) { mono_to_u16buf(p_vf->p_face->glyph->bitmap.buffer, p_vf->p_face->glyph->bitmap.width, p_vf->p_face->glyph->bitmap.rows, p_vf->p_face->glyph->bitmap.pitch, p_vf_cache->p_char, p_vf->max_width * 2, p_fstyle->color, ckey); } } p_info->p_alpha = p_vf_cache->p_alpha; p_info->p_char = p_vf_cache->p_char; p_info->p_strok_alpha = NULL; p_info->p_strok_char = NULL; } else if((p_fstyle->attr & VFONT_STROK) != 0)
// // 2015-04-18 note by Paolo // // The original code from xcsoar was not spacing characters correctly in LK. // I have no idea if the problem exists in xcsoar, but we had to fix it here. // After quite some time, and frustration reading the confusing docs of FreeType, // I came to this solution which is much more accurate. // We use subpixels assuming we are always grid-aligned, which is true for our case. // Kerning does work, but we must always check that we are not going below the // available space, to avoid overlapping previous glyph. This is necessary since // the bitmap operations are copying, not merging, bitmaps. I have no idea how // Windows is doing this, apparently microsoft does not merge glyphs too. // Hinting is required to keep vertical alignement, which may hide a bug. // I am not sure if all of this (long and complicated) work is correct or it is instead // a workaround for a more complex problem existing elsewhere. // However it does work for us, against all odds. // Update april 21st: merging instead of copying makes the kerning process working fine. // void Font::Render(const TCHAR *text, const PixelSize size, void *_buffer) const { assert(text != nullptr); #ifndef _UNICODE assert(ValidateUTF8(text)); #endif uint8_t *buffer = (uint8_t *)_buffer; std::fill_n(buffer, BufferSize(size), 0); const FT_Face face = this->face; const FT_GlyphSlot glyph = face->glyph; #ifdef USE_KERNING bool use_kerning = FT_HAS_KERNING(face); unsigned prev_index = 0; #endif int x = 0; #ifdef FIX_HINTING FT_Pos prev_rsb_delta=0; #endif #ifndef ENABLE_OPENGL const Poco::ScopedLock<Poco::Mutex> protect(freetype_mutex); #endif while (true) { const auto n = NextChar(text); if (n.first == 0) break; const unsigned ch = n.first; text = n.second; FT_UInt i = FT_Get_Char_Index(face, ch); if (i == 0) continue; FT_Error error = FT_Load_Glyph(face, i, load_flags); if (error) continue; const FT_Glyph_Metrics metrics = glyph->metrics; #ifdef USE_KERNING if (use_kerning && x) { if (prev_index != 0) { FT_Vector delta; FT_Get_Kerning(face, prev_index, i, ft_kerning_default, &delta); #ifdef LIGHT_KERNING if (-delta.x <= metrics.horiBearingX) x += delta.x ; else x -= (metrics.horiBearingX + 64); #else x += delta.x; #endif } } prev_index = i; #endif #ifdef FIX_HINTING if (prev_rsb_delta - glyph->lsb_delta >= 32 ) x -= 64;// >> 6; else if ( prev_rsb_delta - glyph->lsb_delta < -32 ) x += 64;// >> 6; prev_rsb_delta = glyph->rsb_delta; #endif error = FT_Render_Glyph(glyph, render_mode); if (error) continue; /* * 32, 0 = Microsoft GDI weight=600 (64=32) */ if (demibold) FT_Bitmap_Embolden(ft_library,&glyph->bitmap, 32,0); RenderGlyph((uint8_t *)buffer, size.cx, size.cy, #ifdef USE_KERNING glyph, (x >> 6)+glyph->bitmap_left , ascent_height - FT_FLOOR(metrics.horiBearingY)); x += glyph->advance.x; // equivalent to metrics.horiAdvance #else glyph, (x + metrics.horiBearingX ) >> 6 , ascent_height - FT_FLOOR(metrics.horiBearingY)); x += (metrics.width > metrics.horiAdvance ? metrics.width : metrics.horiAdvance); #endif } }
static say_glyph *say_font_load_glyph(say_font *font, say_font_page *page, uint32_t codepoint, uint8_t bold, size_t size) { uint32_t bold_codepoint = ((bold ? 1 : 0) << 31) | codepoint; say_glyph *glyph = malloc(sizeof(say_glyph)); say_table_set(page->glyphs, bold_codepoint, glyph); glyph->offset = 0; glyph->bounds = say_make_rect(2, 0, 2, 2); glyph->sub_rect = say_make_rect(2, 0, 2, 2); if (!(font->face && say_font_set_size(font, size))) return glyph; if (FT_Load_Char(font->face, codepoint, FT_LOAD_TARGET_NORMAL) != 0) return glyph; FT_Glyph ft_glyph; if (FT_Get_Glyph(font->face->glyph, &ft_glyph) != 0) return glyph; FT_Pos weight = 1 << 6; uint8_t outline = ft_glyph->format == FT_GLYPH_FORMAT_OUTLINE; if (bold && outline) { FT_OutlineGlyph outline_glyph = (FT_OutlineGlyph)ft_glyph; FT_Outline_Embolden(&outline_glyph->outline, weight); } FT_Glyph_To_Bitmap(&ft_glyph, FT_RENDER_MODE_NORMAL, 0, 1); FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)ft_glyph; FT_Bitmap *bitmap = &bitmap_glyph->bitmap; if (bold && !outline) { FT_Bitmap_Embolden(font->library, bitmap, weight, weight); } glyph->offset = ft_glyph->advance.x >> 16; if (bold) glyph->offset += weight >> 6; int width = bitmap->width; int height = bitmap->rows; if (width > 0 && height > 0) { static const int padding = 1; glyph->sub_rect = say_page_find_rect(page, width + (2 * padding), height + (2 * padding)); glyph->bounds.x = +bitmap_glyph->left - padding; glyph->bounds.y = -bitmap_glyph->top - padding; glyph->bounds.w = width + (2 * padding); glyph->bounds.h = height + (2 * padding); say_rect actual_rect = glyph->sub_rect; actual_rect.x += padding; actual_rect.y += padding; actual_rect.w -= 2 * padding; actual_rect.h -= 2 * padding; uint8_t *pixels = bitmap->buffer; if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) { for (int y = actual_rect.y; y < actual_rect.y + actual_rect.h; y++) { for (int x = actual_rect.x; x < actual_rect.x + actual_rect.w; x++) { int pixel_x = x - actual_rect.x; uint8_t alpha = ((pixels[pixel_x / 8]) & (1 << (7 - (pixel_x % 8)))) ? 255 : 0; say_image_set(page->image, x, y, say_make_color(255, 255, 255, alpha)); } pixels += bitmap->pitch; } } else { for (int y = actual_rect.y; y < actual_rect.y + actual_rect.h; y++) { for (int x = actual_rect.x; x < actual_rect.x + actual_rect.w; x++) { int pixel_x = x - actual_rect.x; say_image_set(page->image, x, y, say_make_color(255, 255, 255, pixels[pixel_x])); } pixels += bitmap->pitch; } } } FT_Done_Glyph(ft_glyph); return glyph; }
Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, float outlineThickness) const { // The glyph to return Glyph glyph; // First, transform our ugly void* to a FT_Face FT_Face face = static_cast<FT_Face>(m_face); if (!face) return glyph; // Set the character size if (!setCurrentSize(characterSize)) return glyph; // Load the glyph corresponding to the code point FT_Int32 flags = FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT; if (outlineThickness != 0) flags |= FT_LOAD_NO_BITMAP; if (FT_Load_Char(face, codePoint, flags) != 0) return glyph; // Retrieve the glyph FT_Glyph glyphDesc; if (FT_Get_Glyph(face->glyph, &glyphDesc) != 0) return glyph; // Apply bold and outline (there is no fallback for outline) if necessary -- first technique using outline (highest quality) FT_Pos weight = 1 << 6; bool outline = (glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE); if (outline) { if (bold) { FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc; FT_Outline_Embolden(&outlineGlyph->outline, weight); } if (outlineThickness != 0) { FT_Stroker stroker = static_cast<FT_Stroker>(m_stroker); FT_Stroker_Set(stroker, static_cast<FT_Fixed>(outlineThickness * static_cast<float>(1 << 6)), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); FT_Glyph_Stroke(&glyphDesc, stroker, true); } } // Convert the glyph to a bitmap (i.e. rasterize it) FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1); FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>(glyphDesc)->bitmap; // Apply bold if necessary -- fallback technique using bitmap (lower quality) if (!outline) { if (bold) FT_Bitmap_Embolden(static_cast<FT_Library>(m_library), &bitmap, weight, weight); if (outlineThickness != 0) err() << "Failed to outline glyph (no fallback available)" << std::endl; } // Compute the glyph's advance offset glyph.advance = static_cast<float>(face->glyph->metrics.horiAdvance) / static_cast<float>(1 << 6); if (bold) glyph.advance += static_cast<float>(weight) / static_cast<float>(1 << 6); int width = bitmap.width; int height = bitmap.rows; if ((width > 0) && (height > 0)) { // Leave a small padding around characters, so that filtering doesn't // pollute them with pixels from neighbors const unsigned int padding = 1; width += 2 * padding; height += 2 * padding; // Get the glyphs page corresponding to the character size Page& page = m_pages[characterSize]; // Find a good position for the new glyph into the texture glyph.textureRect = findGlyphRect(page, width, height); // Make sure the texture data is positioned in the center // of the allocated texture rectangle glyph.textureRect.left += padding; glyph.textureRect.top += padding; glyph.textureRect.width -= 2 * padding; glyph.textureRect.height -= 2 * padding; // Compute the glyph's bounding box glyph.bounds.left = static_cast<float>(face->glyph->metrics.horiBearingX) / static_cast<float>(1 << 6); glyph.bounds.top = -static_cast<float>(face->glyph->metrics.horiBearingY) / static_cast<float>(1 << 6); glyph.bounds.width = static_cast<float>(face->glyph->metrics.width) / static_cast<float>(1 << 6) + outlineThickness * 2; glyph.bounds.height = static_cast<float>(face->glyph->metrics.height) / static_cast<float>(1 << 6) + outlineThickness * 2; // Resize the pixel buffer to the new size and fill it with transparent white pixels m_pixelBuffer.resize(width * height * 4); Uint8* current = &m_pixelBuffer[0]; Uint8* end = current + width * height * 4; while (current != end) { (*current++) = 255; (*current++) = 255; (*current++) = 255; (*current++) = 0; } // Extract the glyph's pixels from the bitmap const Uint8* pixels = bitmap.buffer; if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { // Pixels are 1 bit monochrome values for (unsigned int y = padding; y < height - padding; ++y) { for (unsigned int x = padding; x < width - padding; ++x) { // The color channels remain white, just fill the alpha channel std::size_t index = x + y * width; m_pixelBuffer[index * 4 + 3] = ((pixels[(x - padding) / 8]) & (1 << (7 - ((x - padding) % 8)))) ? 255 : 0; } pixels += bitmap.pitch; } } else { // Pixels are 8 bits gray levels for (unsigned int y = padding; y < height - padding; ++y) { for (unsigned int x = padding; x < width - padding; ++x) { // The color channels remain white, just fill the alpha channel std::size_t index = x + y * width; m_pixelBuffer[index * 4 + 3] = pixels[x - padding]; } pixels += bitmap.pitch; } } // Write the pixels to the texture unsigned int x = glyph.textureRect.left - padding; unsigned int y = glyph.textureRect.top - padding; unsigned int w = glyph.textureRect.width + 2 * padding; unsigned int h = glyph.textureRect.height + 2 * padding; page.texture.update(&m_pixelBuffer[0], w, h, x, y); } // Delete the FT glyph FT_Done_Glyph(glyphDesc); // Done :) return glyph; }
void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGlyph& glyph) { const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); switch ( face->glyph->format ) { case FT_GLYPH_FORMAT_OUTLINE: { FT_Outline* outline = &face->glyph->outline; FT_BBox bbox; FT_Bitmap target; if (fRec.fFlags & SkScalerContext::kEmbolden_Flag && !(face->style_flags & FT_STYLE_FLAG_BOLD)) { emboldenOutline(face, outline); } int dx = 0, dy = 0; if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { dx = SkFixedToFDot6(glyph.getSubXFixed()); dy = SkFixedToFDot6(glyph.getSubYFixed()); // negate dy since freetype-y-goes-up and skia-y-goes-down dy = -dy; } FT_Outline_Get_CBox(outline, &bbox); /* what we really want to do for subpixel is offset(dx, dy) compute_bounds offset(bbox & !63) but that is two calls to offset, so we do the following, which achieves the same thing with only one offset call. */ FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63), dy - ((bbox.yMin + dy) & ~63)); if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); SkMask mask; glyph.toMask(&mask); if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(face->glyph->bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(face->glyph->bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } } else { target.width = glyph.fWidth; target.rows = glyph.fHeight; target.pitch = glyph.rowBytes(); target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage); target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); } } break; case FT_GLYPH_FORMAT_BITMAP: { FT_Pixel_Mode pixel_mode = static_cast<FT_Pixel_Mode>(face->glyph->bitmap.pixel_mode); SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat); // Assume that the other formats do not exist. SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode || FT_PIXEL_MODE_GRAY == pixel_mode || FT_PIXEL_MODE_BGRA == pixel_mode); // These are the only formats this ScalerContext should request. SkASSERT(SkMask::kBW_Format == maskFormat || SkMask::kA8_Format == maskFormat || SkMask::kARGB32_Format == maskFormat || SkMask::kLCD16_Format == maskFormat); if (fRec.fFlags & SkScalerContext::kEmbolden_Flag && !(face->style_flags & FT_STYLE_FLAG_BOLD)) { FT_GlyphSlot_Own_Bitmap(face->glyph); FT_Bitmap_Embolden(face->glyph->library, &face->glyph->bitmap, kBitmapEmboldenStrength, 0); } // If no scaling needed, directly copy glyph bitmap. if (glyph.fWidth == face->glyph->bitmap.width && glyph.fHeight == face->glyph->bitmap.rows && glyph.fTop == -face->glyph->bitmap_top && glyph.fLeft == face->glyph->bitmap_left) { SkMask dstMask; glyph.toMask(&dstMask); copyFTBitmap(face->glyph->bitmap, dstMask); break; } // Otherwise, scale the bitmap. // Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB) SkBitmap unscaledBitmap; unscaledBitmap.setConfig(SkBitmapConfig_for_FTPixelMode(pixel_mode), face->glyph->bitmap.width, face->glyph->bitmap.rows); unscaledBitmap.allocPixels(); SkMask unscaledBitmapAlias; unscaledBitmapAlias.fImage = reinterpret_cast<uint8_t*>(unscaledBitmap.getPixels()); unscaledBitmapAlias.fBounds.set(0, 0, unscaledBitmap.width(), unscaledBitmap.height()); unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes(); unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkBitmapConfig(unscaledBitmap.config()); copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias); // Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD. // BW requires an A8 target for resizing, which can then be down sampled. // LCD should use a 4x A8 target, which will then be down sampled. // For simplicity, LCD uses A8 and is replicated. int bitmapRowBytes = 0; if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) { bitmapRowBytes = glyph.rowBytes(); } SkBitmap dstBitmap; dstBitmap.setConfig(SkBitmapConfig_for_SkMaskFormat(maskFormat), glyph.fWidth, glyph.fHeight, bitmapRowBytes); if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) { dstBitmap.allocPixels(); } else { dstBitmap.setPixels(glyph.fImage); } // Scale unscaledBitmap into dstBitmap. SkCanvas canvas(dstBitmap); canvas.clear(SK_ColorTRANSPARENT); canvas.scale(SkIntToScalar(glyph.fWidth) / SkIntToScalar(face->glyph->bitmap.width), SkIntToScalar(glyph.fHeight) / SkIntToScalar(face->glyph->bitmap.rows)); SkPaint paint; paint.setFilterLevel(SkPaint::kLow_FilterLevel); canvas.drawBitmap(unscaledBitmap, 0, 0, &paint); // If the destination is BW or LCD, convert from A8. if (SkMask::kBW_Format == maskFormat) { // Copy the A8 dstBitmap into the A1 glyph.fImage. SkMask dstMask; glyph.toMask(&dstMask); packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes()); } else if (SkMask::kLCD16_Format == maskFormat) { // Copy the A8 dstBitmap into the LCD16 glyph.fImage. uint8_t* src = dstBitmap.getAddr8(0, 0); uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage); for (int y = dstBitmap.height(); y --> 0;) { for (int x = 0; x < dstBitmap.width(); ++x) { dst[x] = grayToRGB16(src[x]); } dst = (uint16_t*)((char*)dst + glyph.rowBytes()); src += dstBitmap.rowBytes(); } } } break; default: SkDEBUGFAIL("unknown glyph format"); memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); return; } // We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, // it is optional #if defined(SK_GAMMA_APPLY_TO_A8) if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) { uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; unsigned rowBytes = glyph.rowBytes(); for (int y = glyph.fHeight - 1; y >= 0; --y) { for (int x = glyph.fWidth - 1; x >= 0; --x) { dst[x] = fPreBlend.fG[dst[x]]; } dst += rowBytes; } } #endif }
int _PGFT_LoadGlyph(FontGlyph *glyph, PGFT_char character, const FontRenderMode *mode, void *internal) { static FT_Vector delta = {0, 0}; FT_Render_Mode rmode = (mode->render_flags & FT_RFLAG_ANTIALIAS ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO); FT_Vector strong_delta = {0, 0}; FT_Glyph image = 0; FT_Glyph_Metrics *ft_metrics; TextContext *context = (TextContext *)internal; FT_UInt32 load_flags; FT_UInt gindex; FT_Fixed rotation_angle = mode->rotation_angle; /* FT_Matrix transform; */ FT_Vector h_bearing_rotated; FT_Vector v_bearing_rotated; FT_Vector h_advance_rotated; FT_Vector v_advance_rotated; FT_Error error = 0; /* * Calculate the corresponding glyph index for the char */ gindex = FTC_CMapCache_Lookup(context->charmap, context->id, -1, (FT_UInt32)character); glyph->glyph_index = gindex; /* * Get loading information */ load_flags = get_load_flags(mode); /* * Load the glyph into the glyph slot */ if (FT_Load_Glyph(context->font, glyph->glyph_index, (FT_Int)load_flags) || FT_Get_Glyph(context->font->glyph, &image)) goto cleanup; /* * Perform any outline transformations */ if (mode->style & FT_STYLE_STRONG) { FT_UShort x_ppem = context->font->size->metrics.x_ppem; FT_Fixed bold_str; FT_BBox before; FT_BBox after; bold_str = FX16_CEIL_TO_FX6(mode->strength * x_ppem); FT_Outline_Get_CBox(&((FT_OutlineGlyph)image)->outline, &before); if (FT_Outline_Embolden(&((FT_OutlineGlyph)image)->outline, bold_str)) goto cleanup; FT_Outline_Get_CBox(&((FT_OutlineGlyph)image)->outline, &after); strong_delta.x += ((after.xMax - after.xMin) - (before.xMax - before.xMin)); strong_delta.y += ((after.yMax - after.yMin) - (before.yMax - before.yMin)); } if (context->do_transform) { if (FT_Glyph_Transform(image, &context->transform, &delta)) { goto cleanup; } } /* * Finished with outline transformations, now replace with a bitmap */ error = FT_Glyph_To_Bitmap(&image, rmode, 0, 1); if (error) { goto cleanup; } if (mode->style & FT_STYLE_WIDE) { FT_Bitmap *bitmap = &((FT_BitmapGlyph)image)->bitmap; int w = bitmap->width; FT_UShort x_ppem = context->font->size->metrics.x_ppem; FT_Pos x_strength; x_strength = FX16_CEIL_TO_FX6(mode->strength * x_ppem); /* FT_Bitmap_Embolden returns an error for a zero width bitmap */ if (w > 0) { error = FT_Bitmap_Embolden(context->lib, bitmap, x_strength, (FT_Pos)0); if (error) { goto cleanup; } strong_delta.x += INT_TO_FX6(bitmap->width - w); } else { strong_delta.x += x_strength; } } /* Fill the glyph */ ft_metrics = &context->font->glyph->metrics; h_advance_rotated.x = ft_metrics->horiAdvance + strong_delta.x; h_advance_rotated.y = 0; v_advance_rotated.x = 0; v_advance_rotated.y = ft_metrics->vertAdvance + strong_delta.y; if (rotation_angle != 0) { FT_Angle counter_rotation = INT_TO_FX6(360) - rotation_angle; FT_Vector_Rotate(&h_advance_rotated, rotation_angle); FT_Vector_Rotate(&v_advance_rotated, counter_rotation); } glyph->image = (FT_BitmapGlyph)image; glyph->width = INT_TO_FX6(glyph->image->bitmap.width); glyph->height = INT_TO_FX6(glyph->image->bitmap.rows); h_bearing_rotated.x = INT_TO_FX6(glyph->image->left); h_bearing_rotated.y = INT_TO_FX6(glyph->image->top); fill_metrics(&glyph->h_metrics, ft_metrics->horiBearingX, ft_metrics->horiBearingY, &h_bearing_rotated, &h_advance_rotated); if (rotation_angle == 0) { v_bearing_rotated.x = ft_metrics->vertBearingX - strong_delta.x / 2; v_bearing_rotated.y = ft_metrics->vertBearingY; } else { /* * Adjust the vertical metrics. */ FT_Vector v_origin; v_origin.x = (glyph->h_metrics.bearing_x - ft_metrics->vertBearingX + strong_delta.x / 2); v_origin.y = (glyph->h_metrics.bearing_y + ft_metrics->vertBearingY); FT_Vector_Rotate(&v_origin, rotation_angle); v_bearing_rotated.x = glyph->h_metrics.bearing_rotated.x - v_origin.x; v_bearing_rotated.y = v_origin.y - glyph->h_metrics.bearing_rotated.y; } fill_metrics(&glyph->v_metrics, ft_metrics->vertBearingX, ft_metrics->vertBearingY, &v_bearing_rotated, &v_advance_rotated); return 0; /* * Cleanup on error */ cleanup: if (image) { FT_Done_Glyph(image); } return -1; }