fz_pixmap * fz_renderftstrokedglyph(fz_font *font, int gid, fz_matrix trm, fz_matrix ctm, fz_strokestate *state) { FT_Face face = font->ftface; float expansion = fz_matrixexpansion(ctm); int linewidth = state->linewidth * expansion * 64 / 2; FT_Matrix m; FT_Vector v; FT_Error fterr; FT_Stroker stroker; FT_Glyph glyph; FT_BitmapGlyph bitmap; fz_pixmap *pix; int y; m.xx = trm.a * 64; /* should be 65536 */ m.yx = trm.b * 64; m.xy = trm.c * 64; m.yy = trm.d * 64; v.x = trm.e * 64; v.y = trm.f * 64; fterr = FT_Set_Char_Size(face, 65536, 65536, 72, 72); /* should be 64, 64 */ if (fterr) { fz_warn("FT_Set_Char_Size: %s", ft_errorstring(fterr)); return nil; } FT_Set_Transform(face, &m, &v); fterr = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING); if (fterr) { fz_warn("FT_Load_Glyph(gid %d): %s", gid, ft_errorstring(fterr)); return nil; } fterr = FT_Stroker_New(fz_ftlib, &stroker); if (fterr) { fz_warn("FT_Stroker_New: %s", ft_errorstring(fterr)); return nil; } FT_Stroker_Set(stroker, linewidth, state->linecap, state->linejoin, state->miterlimit * 65536); fterr = FT_Get_Glyph(face->glyph, &glyph); if (fterr) { fz_warn("FT_Get_Glyph: %s", ft_errorstring(fterr)); FT_Stroker_Done(stroker); return nil; } fterr = FT_Glyph_Stroke(&glyph, stroker, 1); if (fterr) { fz_warn("FT_Glyph_Stroke: %s", ft_errorstring(fterr)); FT_Done_Glyph(glyph); FT_Stroker_Done(stroker); return nil; } fterr = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); if (fterr) { fz_warn("FT_Glyph_To_Bitmap: %s", ft_errorstring(fterr)); FT_Done_Glyph(glyph); FT_Stroker_Done(stroker); return nil; } bitmap = (FT_BitmapGlyph)glyph; pix = fz_newpixmap(NULL, bitmap->left, bitmap->top - bitmap->bitmap.rows, bitmap->bitmap.width, bitmap->bitmap.rows); for (y = 0; y < pix->h; y++) { memcpy(pix->samples + y * pix->w, bitmap->bitmap.buffer + (pix->h - y - 1) * bitmap->bitmap.pitch, pix->w); } FT_Done_Glyph(glyph); FT_Stroker_Done(stroker); return pix; }
// ----------------------------------------------- texture_font_load_glyphs --- size_t texture_font_load_glyphs( texture_font_t * self, const wchar_t * charcodes ) { assert( self ); assert( charcodes ); size_t i, x, y, width, height, depth, w, h; FT_Library library; FT_Error error; FT_Face face; FT_Glyph ft_glyph = {0}; // clang - no init val FT_GlyphSlot slot; FT_Bitmap ft_bitmap; FT_UInt glyph_index; texture_glyph_t *glyph; ivec4 region; size_t missed = 0; width = self->atlas->width; height = self->atlas->height; depth = self->atlas->depth; if( !texture_font_load_face( &library, self->filename, self->size, &face ) ) { return wcslen(charcodes); } /* Load each glyph */ for( i=0; i<wcslen(charcodes); ++i ) { glyph_index = FT_Get_Char_Index( face, charcodes[i] ); // WARNING: We use texture-atlas depth to guess if user wants // LCD subpixel rendering FT_Int32 flags = 0; if( self->outline_type > 0 ) { flags |= FT_LOAD_NO_BITMAP; } else { flags |= FT_LOAD_RENDER; } if( !self->hinting ) { flags |= FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT; } else { flags |= FT_LOAD_FORCE_AUTOHINT; } if( depth == 3 ) { FT_Library_SetLcdFilter( library, FT_LCD_FILTER_LIGHT ); flags |= FT_LOAD_TARGET_LCD; if( self->filtering ) { FT_Library_SetLcdFilterWeights( library, self->lcd_weights ); } } error = FT_Load_Glyph( face, glyph_index, flags ); if( error ) { fprintf( stderr, "FT_Error (line %d, code 0x%02x) : %s\n", __LINE__, FT_Errors[error].code, FT_Errors[error].message ); FT_Done_FreeType( library ); return wcslen(charcodes)-i; } int ft_bitmap_width = 0; int ft_bitmap_rows = 0; // SD not used //int ft_bitmap_pitch = 0; int ft_glyph_top = 0; int ft_glyph_left = 0; if( self->outline_type == 0 ) { slot = face->glyph; ft_bitmap = slot->bitmap; ft_bitmap_width = slot->bitmap.width; ft_bitmap_rows = slot->bitmap.rows; //ft_bitmap_pitch = slot->bitmap.pitch; ft_glyph_top = slot->bitmap_top; ft_glyph_left = slot->bitmap_left; } else { FT_Stroker stroker; error = FT_Stroker_New( library, &stroker ); if( error ) { fprintf(stderr, "FT_Error (0x%02x) : %s\n", FT_Errors[error].code, FT_Errors[error].message); return 0; } FT_Stroker_Set( stroker, (int)(self->outline_thickness *64), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); error = FT_Get_Glyph( face->glyph, &ft_glyph); if( error ) { fprintf(stderr, "FT_Error (0x%02x) : %s\n", FT_Errors[error].code, FT_Errors[error].message); return 0; } if( self->outline_type == 1 ) { error = FT_Glyph_Stroke( &ft_glyph, stroker, 1 ); } else if ( self->outline_type == 2 ) { error = FT_Glyph_StrokeBorder( &ft_glyph, stroker, 0, 1 ); } else if ( self->outline_type == 3 ) { error = FT_Glyph_StrokeBorder( &ft_glyph, stroker, 1, 1 ); } if( error ) { fprintf(stderr, "FT_Error (0x%02x) : %s\n", FT_Errors[error].code, FT_Errors[error].message); return 0; } if( depth == 1) { error = FT_Glyph_To_Bitmap( &ft_glyph, FT_RENDER_MODE_NORMAL, 0, 1); if( error ) { fprintf(stderr, "FT_Error (0x%02x) : %s\n", FT_Errors[error].code, FT_Errors[error].message); return 0; } } else { error = FT_Glyph_To_Bitmap( &ft_glyph, FT_RENDER_MODE_LCD, 0, 1); if( error ) { fprintf(stderr, "FT_Error (0x%02x) : %s\n", FT_Errors[error].code, FT_Errors[error].message); return 0; } } FT_BitmapGlyph ft_bitmap_glyph = (FT_BitmapGlyph) ft_glyph; ft_bitmap = ft_bitmap_glyph->bitmap; ft_bitmap_width = ft_bitmap.width; ft_bitmap_rows = ft_bitmap.rows; //ft_bitmap_pitch = ft_bitmap.pitch; ft_glyph_top = ft_bitmap_glyph->top; ft_glyph_left = ft_bitmap_glyph->left; FT_Stroker_Done(stroker); } // We want each glyph to be separated by at least one black pixel // (for example for shader used in demo-subpixel.c) w = ft_bitmap_width/depth + 1; h = ft_bitmap_rows + 1; region = texture_atlas_get_region( self->atlas, w, h ); if ( region.XYZW.x < 0 ) { missed++; fprintf( stderr, "Texture atlas is full (line %d)\n", __LINE__ ); continue; } w = w - 1; h = h - 1; x = region.XYZW.x; y = region.XYZW.y; texture_atlas_set_region( self->atlas, x, y, w, h, ft_bitmap.buffer, ft_bitmap.pitch ); glyph = texture_glyph_new( ); glyph->charcode = charcodes[i]; glyph->width = w; glyph->height = h; glyph->outline_type = self->outline_type; glyph->outline_thickness = self->outline_thickness; glyph->offset_x = ft_glyph_left; glyph->offset_y = ft_glyph_top; glyph->s0 = x/(float)width; glyph->t0 = y/(float)height; glyph->s1 = (x + glyph->width)/(float)width; glyph->t1 = (y + glyph->height)/(float)height; // Discard hinting to get advance FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER | FT_LOAD_NO_HINTING); slot = face->glyph; glyph->advance_x = slot->advance.x/64.0; glyph->advance_y = slot->advance.y/64.0; vector_push_back( self->glyphs, &glyph ); } if( self->outline_type > 0 ) { FT_Done_Glyph( ft_glyph ); } FT_Done_Face( face ); FT_Done_FreeType( library ); texture_atlas_upload( self->atlas ); texture_font_generate_kerning( self ); return missed; }
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 agg_text_renderer<T>::render(glyph_positions const& pos) { prepare_glyphs(pos); FT_Error error; FT_Vector start; FT_Vector start_halo; int height = pixmap_.height(); pixel_position const& base_point = pos.get_base_point(); start.x = static_cast<FT_Pos>(base_point.x * (1 << 6)); start.y = static_cast<FT_Pos>((height - base_point.y) * (1 << 6)); start_halo = start; start.x += transform_.tx * 64; start.y += transform_.ty * 64; start_halo.x += halo_transform_.tx * 64; start_halo.y += halo_transform_.ty * 64; FT_Matrix halo_matrix; halo_matrix.xx = halo_transform_.sx * 0x10000L; halo_matrix.xy = halo_transform_.shx * 0x10000L; halo_matrix.yy = halo_transform_.sy * 0x10000L; halo_matrix.yx = halo_transform_.shy * 0x10000L; FT_Matrix matrix; matrix.xx = transform_.sx * 0x10000L; matrix.xy = transform_.shx * 0x10000L; matrix.yy = transform_.sy * 0x10000L; matrix.yx = transform_.shy * 0x10000L; // default formatting double halo_radius = 0; color black(0,0,0); unsigned fill = black.rgba(); unsigned halo_fill = black.rgba(); double text_opacity = 1.0; double halo_opacity = 1.0; for (auto const& glyph : glyphs_) { halo_fill = glyph.properties.halo_fill.rgba(); halo_opacity = glyph.properties.halo_opacity; halo_radius = glyph.properties.halo_radius * scale_factor_; // make sure we've got reasonable values. if (halo_radius <= 0.0 || halo_radius > 1024.0) continue; FT_Glyph g; error = FT_Glyph_Copy(glyph.image, &g); if (!error) { FT_Glyph_Transform(g, &halo_matrix, &start_halo); if (rasterizer_ == HALO_RASTERIZER_FULL) { stroker_->init(halo_radius); FT_Glyph_Stroke(&g, stroker_->get(), 1); error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1); if (!error) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g); composite_bitmap(pixmap_, &bit->bitmap, halo_fill, bit->left, height - bit->top, halo_opacity, halo_comp_op_); } } else { error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1); if (!error) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g); render_halo(&bit->bitmap, halo_fill, bit->left, height - bit->top, halo_radius, halo_opacity, halo_comp_op_); } } } FT_Done_Glyph(g); } // render actual text for (auto & glyph : glyphs_) { fill = glyph.properties.fill.rgba(); text_opacity = glyph.properties.text_opacity; FT_Glyph_Transform(glyph.image, &matrix, &start); error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL, 0, 1); if (!error) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image); composite_bitmap(pixmap_, &bit->bitmap, fill, bit->left, height - bit->top, text_opacity, comp_op_); } FT_Done_Glyph(glyph.image); } }
void agg_text_renderer<T>::render(glyph_positions const& pos) { glyphs_.clear(); prepare_glyphs(pos); FT_Error error; FT_Vector start; int height = pixmap_.height(); pixel_position const& base_point = pos.get_base_point(); start.x = static_cast<FT_Pos>(base_point.x * (1 << 6)); start.y = static_cast<FT_Pos>((height - base_point.y) * (1 << 6)); //render halo double halo_radius = 0; char_properties_ptr format; for (auto const& glyph : glyphs_) { if (glyph.properties) { format = glyph.properties; // Settings have changed. halo_radius = glyph.properties->halo_radius * scale_factor_; } // make sure we've got reasonable values. if (halo_radius <= 0.0 || halo_radius > 1024.0) continue; FT_Glyph g; error = FT_Glyph_Copy(glyph.image, &g); if (!error) { FT_Glyph_Transform(g,0,&start); if (rasterizer_ == HALO_RASTERIZER_FULL) { stroker_->init(halo_radius); FT_Glyph_Stroke(&g, stroker_->get(), 1); error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1); if (!error) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g); composite_bitmap(pixmap_, &bit->bitmap, format->halo_fill.rgba(), bit->left, height - bit->top, format->text_opacity, comp_op_); } } else { error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1); if (!error) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g); render_halo(&bit->bitmap, format->halo_fill.rgba(), bit->left, height - bit->top, halo_radius, format->text_opacity, comp_op_); } } } FT_Done_Glyph(g); } // render actual text for (auto & glyph : glyphs_) { if (glyph.properties) { format = glyph.properties; } FT_Glyph_Transform(glyph.image, 0, &start); error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL,0,1); if (!error) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image); composite_bitmap(pixmap_, &bit->bitmap, format->fill.rgba(), bit->left, height - bit->top, format->text_opacity, comp_op_); } } }
void agg_text_renderer<T>::render(glyph_positions const& pos) { prepare_glyphs(pos); FT_Error error; FT_Vector start; FT_Vector start_halo; int height = pixmap_.height(); pixel_position const& base_point = pos.get_base_point(); start.x = static_cast<FT_Pos>(base_point.x * (1 << 6)); start.y = static_cast<FT_Pos>((height - base_point.y) * (1 << 6)); start_halo = start; start.x += transform_.tx * 64; start.y += transform_.ty * 64; start_halo.x += halo_transform_.tx * 64; start_halo.y += halo_transform_.ty * 64; FT_Matrix halo_matrix; halo_matrix.xx = halo_transform_.sx * 0x10000L; halo_matrix.xy = halo_transform_.shx * 0x10000L; halo_matrix.yy = halo_transform_.sy * 0x10000L; halo_matrix.yx = halo_transform_.shy * 0x10000L; FT_Matrix matrix; matrix.xx = transform_.sx * 0x10000L; matrix.xy = transform_.shx * 0x10000L; matrix.yy = transform_.sy * 0x10000L; matrix.yx = transform_.shy * 0x10000L; // default formatting double halo_radius = 0; color black(0,0,0); unsigned fill = black.rgba(); unsigned halo_fill = black.rgba(); double text_opacity = 1.0; double halo_opacity = 1.0; for (auto const& glyph : glyphs_) { halo_fill = glyph.properties.halo_fill.rgba(); halo_opacity = glyph.properties.halo_opacity; halo_radius = glyph.properties.halo_radius * scale_factor_; // make sure we've got reasonable values. if (halo_radius <= 0.0 || halo_radius > 1024.0) continue; FT_Glyph g; error = FT_Glyph_Copy(glyph.image, &g); if (!error) { FT_Glyph_Transform(g, &halo_matrix, &start_halo); if (rasterizer_ == HALO_RASTERIZER_FULL) { stroker_->init(halo_radius); FT_Glyph_Stroke(&g, stroker_->get(), 1); error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1); if (!error) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g); if (bit->bitmap.pixel_mode != FT_PIXEL_MODE_BGRA) { composite_bitmap(pixmap_, &bit->bitmap, halo_fill, bit->left, height - bit->top, halo_opacity, halo_comp_op_); } } } else { error = FT_Glyph_To_Bitmap(&g, FT_RENDER_MODE_NORMAL, 0, 1); if (error) { continue; } FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(g); if (bit->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { pixel_position render_pos(base_point); image_rgba8 glyph_image(render_glyph_image(glyph, bit->bitmap, transform_, render_pos)); const constexpr std::size_t pixel_size = sizeof(image_rgba8::pixel_type); render_halo<pixel_size>(glyph_image.bytes(), glyph_image.width(), glyph_image.height(), halo_fill, render_pos.x, render_pos.y, halo_radius, halo_opacity, halo_comp_op_); } else { render_halo<1>(bit->bitmap.buffer, bit->bitmap.width, bit->bitmap.rows, halo_fill, bit->left, height - bit->top, halo_radius, halo_opacity, halo_comp_op_); } } } FT_Done_Glyph(g); } // render actual text for (auto & glyph : glyphs_) { fill = glyph.properties.fill.rgba(); text_opacity = glyph.properties.text_opacity; FT_Glyph_Transform(glyph.image, &matrix, &start); error = 0; if ( glyph.image->format != FT_GLYPH_FORMAT_BITMAP ) { error = FT_Glyph_To_Bitmap(&glyph.image ,FT_RENDER_MODE_NORMAL, 0, 1); } if (error == 0) { FT_BitmapGlyph bit = reinterpret_cast<FT_BitmapGlyph>(glyph.image); int pixel_mode = bit->bitmap.pixel_mode; if (pixel_mode == FT_PIXEL_MODE_BGRA) { int x = base_point.x + glyph.pos.x; int y = base_point.y - glyph.pos.y; agg::trans_affine transform( glyph_transform(transform_, bit->bitmap.rows, x, y, -glyph.rot.angle(), glyph.bbox)); composite_color_glyph(pixmap_, bit->bitmap, transform, text_opacity, comp_op_); } else { composite_bitmap(pixmap_, &bit->bitmap, fill, bit->left, height - bit->top, text_opacity, comp_op_); } } FT_Done_Glyph(glyph.image); } }