std::vector<HarfbuzzPosition> shapeText(const TextRun& text, FTFace &face) { hb_buffer_reset(m_buffer); size_t length = text.text.size(); hb_buffer_add_utf8(m_buffer, text.text.c_str(), length, 0, length); hb_buffer_guess_segment_properties(m_buffer); // harfbuzz shaping std::array<hb_feature_t, 3> features = { HBFeature::KerningOn, HBFeature::LigatureOn, HBFeature::CligOn }; hb_shape(face.m_font, m_buffer, features.data(), features.size()); unsigned int glyphCount; hb_glyph_info_t *glyphInfo = hb_buffer_get_glyph_infos(m_buffer, &glyphCount); hb_glyph_position_t *glyphPos = hb_buffer_get_glyph_positions(m_buffer, &glyphCount); if (glyphCount == 0) { return {}; } int32_t x = 0; int32_t y = 0; std::vector<HarfbuzzPosition> glyphes; for (int glyphIndex = 0; glyphIndex < glyphCount; ++glyphIndex) { glyphes.emplace_back(glyphInfo[glyphIndex].codepoint, Vector2i(x + glyphPos[glyphIndex].x_offset, y + glyphPos[glyphIndex].y_offset)); x += glyphPos[glyphIndex].x_advance; y += glyphPos[glyphIndex].y_advance; }; return glyphes; }
static void release_buffer (hb_buffer_t *buffer, gboolean free_buffer) { if (G_LIKELY (!free_buffer)) { hb_buffer_reset (buffer); G_UNLOCK (cached_buffer); } else hb_buffer_destroy (buffer); }
bool HarfBuzzShaper::shapeHarfBuzzRuns(bool shouldSetDirection) { HarfBuzzScopedPtr<hb_buffer_t> harfBuzzBuffer(hb_buffer_create(), hb_buffer_destroy); hb_buffer_set_unicode_funcs(harfBuzzBuffer.get(), hb_icu_get_unicode_funcs()); for (unsigned i = 0; i < m_harfBuzzRuns.size(); ++i) { unsigned runIndex = m_run.rtl() ? m_harfBuzzRuns.size() - i - 1 : i; HarfBuzzRun* currentRun = m_harfBuzzRuns[runIndex].get(); const SimpleFontData* currentFontData = currentRun->fontData(); if (currentFontData->isSVGFont()) return false; hb_buffer_set_script(harfBuzzBuffer.get(), currentRun->script()); if (shouldSetDirection) hb_buffer_set_direction(harfBuzzBuffer.get(), currentRun->rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); else // Leaving direction to HarfBuzz to guess is *really* bad, but will do for now. hb_buffer_guess_segment_properties(harfBuzzBuffer.get()); // Add a space as pre-context to the buffer. This prevents showing dotted-circle // for combining marks at the beginning of runs. static const uint16_t preContext = ' '; hb_buffer_add_utf16(harfBuzzBuffer.get(), &preContext, 1, 1, 0); if (m_font->isSmallCaps() && u_islower(m_normalizedBuffer[currentRun->startIndex()])) { String upperText = String(m_normalizedBuffer.get() + currentRun->startIndex(), currentRun->numCharacters()); upperText.makeUpper(); currentFontData = m_font->glyphDataForCharacter(upperText[0], false, SmallCapsVariant).fontData; hb_buffer_add_utf16(harfBuzzBuffer.get(), upperText.characters(), currentRun->numCharacters(), 0, currentRun->numCharacters()); } else hb_buffer_add_utf16(harfBuzzBuffer.get(), m_normalizedBuffer.get() + currentRun->startIndex(), currentRun->numCharacters(), 0, currentRun->numCharacters()); FontPlatformData* platformData = const_cast<FontPlatformData*>(¤tFontData->platformData()); HarfBuzzFace* face = platformData->harfBuzzFace(); if (!face) return false; if (m_font->fontDescription().orientation() == Vertical) face->setScriptForVerticalGlyphSubstitution(harfBuzzBuffer.get()); HarfBuzzScopedPtr<hb_font_t> harfBuzzFont(face->createFont(), hb_font_destroy); hb_shape(harfBuzzFont.get(), harfBuzzBuffer.get(), m_features.isEmpty() ? 0 : m_features.data(), m_features.size()); currentRun->applyShapeResult(harfBuzzBuffer.get()); setGlyphPositionsForHarfBuzzRun(currentRun, harfBuzzBuffer.get()); hb_buffer_reset(harfBuzzBuffer.get()); } return true; }
hb_buffer_t *hb_buffer_create(void) { hb_buffer_t *buffer = calloc(1, sizeof(*buffer)); if (!buffer) return hb_buffer_get_empty(); hb_atomic_int32_set(&buffer->ref_cnt, 1); buffer->unicode = hb_unicode_funcs_get_empty(); hb_buffer_reset(buffer); return buffer; }
bool HarfBuzzShaper::shapeHarfBuzzRuns() { HarfBuzzScopedPtr<hb_buffer_t> harfbuzzBuffer(hb_buffer_create(), hb_buffer_destroy); hb_buffer_set_unicode_funcs(harfbuzzBuffer.get(), hb_icu_get_unicode_funcs()); if (m_run.rtl() || m_run.directionalOverride()) hb_buffer_set_direction(harfbuzzBuffer.get(), m_run.rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); for (unsigned i = 0; i < m_harfbuzzRuns.size(); ++i) { unsigned runIndex = m_run.rtl() ? m_harfbuzzRuns.size() - i - 1 : i; HarfBuzzRun* currentRun = m_harfbuzzRuns[runIndex].get(); const SimpleFontData* currentFontData = currentRun->fontData(); if (m_font->isSmallCaps() && u_islower(m_normalizedBuffer[currentRun->startIndex()])) { String upperText = String(m_normalizedBuffer.get() + currentRun->startIndex(), currentRun->numCharacters()); upperText.makeUpper(); currentFontData = m_font->glyphDataForCharacter(upperText[0], false, SmallCapsVariant).fontData; hb_buffer_add_utf16(harfbuzzBuffer.get(), upperText.characters(), currentRun->numCharacters(), 0, currentRun->numCharacters()); } else hb_buffer_add_utf16(harfbuzzBuffer.get(), m_normalizedBuffer.get() + currentRun->startIndex(), currentRun->numCharacters(), 0, currentRun->numCharacters()); FontPlatformData* platformData = const_cast<FontPlatformData*>(¤tFontData->platformData()); HarfBuzzNGFace* face = platformData->harfbuzzFace(); if (!face) return false; HarfBuzzScopedPtr<hb_font_t> harfbuzzFont(face->createFont(), hb_font_destroy); hb_shape(harfbuzzFont.get(), harfbuzzBuffer.get(), m_features.isEmpty() ? 0 : m_features.data(), m_features.size()); currentRun->applyShapeResult(harfbuzzBuffer.get()); setGlyphPositionsForHarfBuzzRun(currentRun, harfbuzzBuffer.get()); hb_buffer_reset(harfbuzzBuffer.get()); if (m_run.rtl() || m_run.directionalOverride()) hb_buffer_set_direction(harfbuzzBuffer.get(), m_run.rtl() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); } return true; }
PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult() { RefPtr<ShapeResult> result = ShapeResult::create(m_font, m_normalizedBufferLength, m_textRun.direction()); HarfBuzzScopedPtr<hb_buffer_t> harfBuzzBuffer(hb_buffer_create(), hb_buffer_destroy); const FontDescription& fontDescription = m_font->getFontDescription(); const String& localeString = fontDescription.locale(); CString locale = localeString.latin1(); const hb_language_t language = hb_language_from_string(locale.data(), locale.length()); bool needsCapsHandling = fontDescription.variantCaps() != FontDescription::CapsNormal; OpenTypeCapsSupport capsSupport; RunSegmenter::RunSegmenterRange segmentRange = { 0, 0, USCRIPT_INVALID_CODE, OrientationIterator::OrientationInvalid, FontFallbackPriority::Invalid }; RunSegmenter runSegmenter( m_normalizedBuffer.get(), m_normalizedBufferLength, m_font->getFontDescription().orientation()); Vector<UChar32> fallbackCharsHint; // TODO: Check whether this treatAsZerowidthspace from the previous script // segmentation plays a role here, does the new scriptRuniterator handle that correctly? while (runSegmenter.consume(&segmentRange)) { RefPtr<FontFallbackIterator> fallbackIterator = m_font->createFontFallbackIterator( segmentRange.fontFallbackPriority); appendToHolesQueue(HolesQueueNextFont, 0, 0); appendToHolesQueue(HolesQueueRange, segmentRange.start, segmentRange.end - segmentRange.start); const SimpleFontData* currentFont = nullptr; RefPtr<UnicodeRangeSet> currentFontRangeSet; bool fontCycleQueued = false; while (m_holesQueue.size()) { HolesQueueItem currentQueueItem = m_holesQueue.takeFirst(); if (currentQueueItem.m_action == HolesQueueNextFont) { // For now, we're building a character list with which we probe // for needed fonts depending on the declared unicode-range of a // segmented CSS font. Alternatively, we can build a fake font // for the shaper and check whether any glyphs were found, or // define a new API on the shaper which will give us coverage // information? if (!collectFallbackHintChars(fallbackCharsHint, fallbackIterator->needsHintList())) { // Give up shaping since we cannot retrieve a font fallback // font without a hintlist. m_holesQueue.clear(); break; } FontDataForRangeSet nextFontDataForRangeSet = fallbackIterator->next(fallbackCharsHint); currentFont = nextFontDataForRangeSet.fontData(); currentFontRangeSet = nextFontDataForRangeSet.ranges(); if (!currentFont) { ASSERT(!m_holesQueue.size()); break; } fontCycleQueued = false; continue; } SmallCapsIterator::SmallCapsBehavior smallCapsBehavior = SmallCapsIterator::SmallCapsSameCase; if (needsCapsHandling) { capsSupport = OpenTypeCapsSupport(currentFont->platformData().harfBuzzFace(), fontDescription.variantCaps(), ICUScriptToHBScript(segmentRange.script)); if (capsSupport.needsRunCaseSplitting()) splitUntilNextCaseChange(currentQueueItem, smallCapsBehavior); } ASSERT(currentQueueItem.m_numCharacters); const SimpleFontData* smallcapsAdjustedFont = needsCapsHandling && capsSupport.needsSyntheticFont(smallCapsBehavior) ? currentFont->smallCapsFontData(fontDescription).get() : currentFont; // Compatibility with SimpleFontData approach of keeping a flag for overriding drawing direction. // TODO: crbug.com/506224 This should go away in favor of storing that information elsewhere, for example in // ShapeResult. const SimpleFontData* directionAndSmallCapsAdjustedFont = fontDataAdjustedForOrientation(smallcapsAdjustedFont, m_font->getFontDescription().orientation(), segmentRange.renderOrientation); CaseMapIntend caseMapIntend = CaseMapIntend::KeepSameCase; if (needsCapsHandling) { caseMapIntend = capsSupport.needsCaseChange(smallCapsBehavior); } CaseMappingHarfBuzzBufferFiller( caseMapIntend, harfBuzzBuffer.get(), m_normalizedBuffer.get(), m_normalizedBufferLength, currentQueueItem.m_startIndex, currentQueueItem.m_numCharacters); CapsFeatureSettingsScopedOverlay capsOverlay(m_features, capsSupport.fontFeatureToUse(smallCapsBehavior)); if (!shapeRange(harfBuzzBuffer.get(), currentQueueItem.m_startIndex, currentQueueItem.m_numCharacters, directionAndSmallCapsAdjustedFont, currentFontRangeSet, segmentRange.script, language)) DLOG(ERROR) << "Shaping range failed."; if (!extractShapeResults(harfBuzzBuffer.get(), result.get(), fontCycleQueued, currentQueueItem, directionAndSmallCapsAdjustedFont, segmentRange.script, !fallbackIterator->hasNext())) DLOG(ERROR) << "Shape result extraction failed."; hb_buffer_reset(harfBuzzBuffer.get()); } } return result.release(); }
void TextLayout::Position (Font *font, size_t size, const char *text, Bytes *bytes) { if (mFont != font) { mFont = font; hb_font_destroy ((hb_font_t*)mHBFont); mHBFont = hb_ft_font_create ((FT_Face)font->face, NULL); } font->SetSize (size); // reset buffer hb_buffer_reset ((hb_buffer_t*)mBuffer); hb_buffer_set_direction ((hb_buffer_t*)mBuffer, (hb_direction_t)mDirection); hb_buffer_set_script ((hb_buffer_t*)mBuffer, (hb_script_t)mScript); hb_buffer_set_language ((hb_buffer_t*)mBuffer, (hb_language_t)mLanguage); // layout the text hb_buffer_add_utf8 ((hb_buffer_t*)mBuffer, text, strlen (text), 0, -1); hb_shape ((hb_font_t*)mHBFont, (hb_buffer_t*)mBuffer, NULL, 0); uint32_t glyph_count; hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos ((hb_buffer_t*)mBuffer, &glyph_count); hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions ((hb_buffer_t*)mBuffer, &glyph_count); //float hres = 100; int posIndex = 0; int glyphSize = sizeof (GlyphPosition); uint32_t dataSize = 4 + (glyph_count * glyphSize); if (bytes->Length () < dataSize) { bytes->Resize (dataSize); } unsigned char* bytesPosition = bytes->Data (); *(bytesPosition) = glyph_count; bytesPosition += 4; hb_glyph_position_t pos; GlyphPosition *data; for (int i = 0; i < glyph_count; i++) { pos = glyph_pos[i]; data = (GlyphPosition*)(bytesPosition); data->index = glyph_info[i].codepoint; data->advanceX = (float)(pos.x_advance / (float)(64)); data->advanceY = (float)(pos.y_advance / (float)64); data->offsetX = (float)(pos.x_offset / (float)(64)); data->offsetY = (float)(pos.y_offset / (float)64); bytesPosition += glyphSize; } }
// ------------------------------------------------------------------- init --- void init( void ) { size_t i, j; int ptSize = 50*64; int device_hdpi = 72; int device_vdpi = 72; atlas = texture_atlas_new( 512, 512, 3 ); /* Init freetype */ FT_Library ft_library; assert(!FT_Init_FreeType(&ft_library)); /* Load our fonts */ FT_Face ft_face[NUM_EXAMPLES]; assert(!FT_New_Face( ft_library, fonts[ENGLISH], 0, &ft_face[ENGLISH]) ); assert(!FT_Set_Char_Size( ft_face[ENGLISH], 0, ptSize, device_hdpi, device_vdpi ) ); // ftfdump( ft_face[ENGLISH] ); // wonderful world of encodings ... force_ucs2_charmap( ft_face[ENGLISH] ); // which we ignore. assert( !FT_New_Face(ft_library, fonts[ARABIC], 0, &ft_face[ARABIC]) ); assert( !FT_Set_Char_Size(ft_face[ARABIC], 0, ptSize, device_hdpi, device_vdpi ) ); // ftfdump( ft_face[ARABIC] ); force_ucs2_charmap( ft_face[ARABIC] ); assert(!FT_New_Face( ft_library, fonts[CHINESE], 0, &ft_face[CHINESE]) ); assert(!FT_Set_Char_Size( ft_face[CHINESE], 0, ptSize, device_hdpi, device_vdpi ) ); // ftfdump( ft_face[CHINESE] ); force_ucs2_charmap( ft_face[CHINESE] ); /* Get our harfbuzz font structs */ hb_font_t *hb_ft_font[NUM_EXAMPLES]; hb_ft_font[ENGLISH] = hb_ft_font_create( ft_face[ENGLISH], NULL ); hb_ft_font[ARABIC] = hb_ft_font_create( ft_face[ARABIC] , NULL ); hb_ft_font[CHINESE] = hb_ft_font_create( ft_face[CHINESE], NULL ); /* Create a buffer for harfbuzz to use */ hb_buffer_t *buf = hb_buffer_create(); for (i=0; i < NUM_EXAMPLES; ++i) { hb_buffer_set_direction( buf, text_directions[i] ); /* or LTR */ hb_buffer_set_script( buf, scripts[i] ); /* see hb-unicode.h */ hb_buffer_set_language( buf, hb_language_from_string(languages[i], strlen(languages[i])) ); /* Layout the text */ hb_buffer_add_utf8( buf, texts[i], strlen(texts[i]), 0, strlen(texts[i]) ); hb_shape( hb_ft_font[i], buf, NULL, 0 ); unsigned int glyph_count; hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count); hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count); FT_GlyphSlot slot; FT_Bitmap ft_bitmap; float size = 24; size_t hres = 64; FT_Error error; FT_Int32 flags = 0; flags |= FT_LOAD_RENDER; flags |= FT_LOAD_TARGET_LCD; FT_Library_SetLcdFilter( ft_library, FT_LCD_FILTER_LIGHT ); FT_Matrix matrix = { (int)((1.0/hres) * 0x10000L), (int)((0.0) * 0x10000L), (int)((0.0) * 0x10000L), (int)((1.0) * 0x10000L) }; /* Set char size */ error = FT_Set_Char_Size( ft_face[i], (int)(ptSize), 0, 72*hres, 72 ); if( error ) { //fprintf( stderr, "FT_Error (line %d, code 0x%02x) : %s\n", // __LINE__, FT_Errors[error].code, FT_Errors[error].message ); FT_Done_Face( ft_face[i] ); break; } /* Set transform matrix */ FT_Set_Transform( ft_face[i], &matrix, NULL ); for (j = 0; j < glyph_count; ++j) { /* Load glyph */ error = FT_Load_Glyph( ft_face[i], glyph_info[j].codepoint, 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_Face( ft_face[i] ); break; } slot = ft_face[i]->glyph; ft_bitmap = slot->bitmap; int ft_bitmap_width = slot->bitmap.width; int ft_bitmap_rows = slot->bitmap.rows; int ft_bitmap_pitch = slot->bitmap.pitch; int ft_glyph_top = slot->bitmap_top; int ft_glyph_left = slot->bitmap_left; int w = ft_bitmap_width/3; // 3 because of LCD/RGB encoding int h = ft_bitmap_rows; ivec4 region = texture_atlas_get_region( atlas, w+1, h+1 ); if ( region.x < 0 ) { fprintf( stderr, "Texture atlas is full (line %d)\n", __LINE__ ); continue; } int x = region.x, y = region.y; texture_atlas_set_region( atlas, region.x, region.y, w, h, ft_bitmap.buffer, ft_bitmap.pitch ); printf("%d: %dx%d %f %f\n", glyph_info[j].codepoint, ft_bitmap_width, ft_bitmap_rows, glyph_pos[j].x_advance/64., glyph_pos[j].y_advance/64.); } /* clean up the buffer, but don't kill it just yet */ hb_buffer_reset(buf); } /* Cleanup */ hb_buffer_destroy( buf ); for( i=0; i < NUM_EXAMPLES; ++i ) hb_font_destroy( hb_ft_font[i] ); FT_Done_FreeType( ft_library ); glClearColor(1,1,1,1); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glGenTextures( 1, &atlas->id ); glBindTexture( GL_TEXTURE_2D, atlas->id ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, atlas->width, atlas->height, 0, GL_RGB, GL_UNSIGNED_BYTE, atlas->data ); typedef struct { float x,y,z, u,v, r,g,b,a, shift, gamma; } vertex_t; vertex_t vertices[4] = { { 0, 0,0, 0,1, 0,0,0,1, 0, 1}, { 0,512,0, 0,0, 0,0,0,1, 0, 1}, {512,512,0, 1,0, 0,0,0,1, 0, 1}, {512, 0,0, 1,1, 0,0,0,1, 0, 1} }; GLuint indices[6] = { 0, 1, 2, 0,2,3 }; buffer = vertex_buffer_new( "vertex:3f," "tex_coord:2f," "color:4f," "ashift:1f," "agamma:1f" ); vertex_buffer_push_back( buffer, vertices, 4, indices, 6 ); shader = shader_load("shaders/text.vert", "shaders/text.frag"); mat4_set_identity( &projection ); mat4_set_identity( &model ); mat4_set_identity( &view ); }
static void test_buffer_properties (fixture_t *fixture, gconstpointer user_data HB_UNUSED) { hb_buffer_t *b = fixture->buffer; hb_unicode_funcs_t *ufuncs; /* test default properties */ g_assert (hb_buffer_get_unicode_funcs (b) == hb_unicode_funcs_get_default ()); g_assert (hb_buffer_get_direction (b) == HB_DIRECTION_INVALID); g_assert (hb_buffer_get_script (b) == HB_SCRIPT_INVALID); g_assert (hb_buffer_get_language (b) == NULL); /* test property changes are retained */ ufuncs = hb_unicode_funcs_create (NULL); hb_buffer_set_unicode_funcs (b, ufuncs); hb_unicode_funcs_destroy (ufuncs); g_assert (hb_buffer_get_unicode_funcs (b) == ufuncs); hb_buffer_set_direction (b, HB_DIRECTION_RTL); g_assert (hb_buffer_get_direction (b) == HB_DIRECTION_RTL); hb_buffer_set_script (b, HB_SCRIPT_ARABIC); g_assert (hb_buffer_get_script (b) == HB_SCRIPT_ARABIC); hb_buffer_set_language (b, hb_language_from_string ("fa", -1)); g_assert (hb_buffer_get_language (b) == hb_language_from_string ("Fa", -1)); hb_buffer_set_flags (b, HB_BUFFER_FLAG_BOT); g_assert (hb_buffer_get_flags (b) == HB_BUFFER_FLAG_BOT); hb_buffer_set_replacement_codepoint (b, (unsigned int) -1); g_assert (hb_buffer_get_replacement_codepoint (b) == (unsigned int) -1); /* test clear_contents clears all these properties: */ hb_buffer_clear_contents (b); g_assert (hb_buffer_get_unicode_funcs (b) == ufuncs); g_assert (hb_buffer_get_direction (b) == HB_DIRECTION_INVALID); g_assert (hb_buffer_get_script (b) == HB_SCRIPT_INVALID); g_assert (hb_buffer_get_language (b) == NULL); /* but not these: */ g_assert (hb_buffer_get_flags (b) != HB_BUFFER_FLAGS_DEFAULT); g_assert (hb_buffer_get_replacement_codepoint (b) != HB_BUFFER_REPLACEMENT_CODEPOINT_DEFAULT); /* test reset clears all properties */ hb_buffer_set_direction (b, HB_DIRECTION_RTL); g_assert (hb_buffer_get_direction (b) == HB_DIRECTION_RTL); hb_buffer_set_script (b, HB_SCRIPT_ARABIC); g_assert (hb_buffer_get_script (b) == HB_SCRIPT_ARABIC); hb_buffer_set_language (b, hb_language_from_string ("fa", -1)); g_assert (hb_buffer_get_language (b) == hb_language_from_string ("Fa", -1)); hb_buffer_set_flags (b, HB_BUFFER_FLAG_BOT); g_assert (hb_buffer_get_flags (b) == HB_BUFFER_FLAG_BOT); hb_buffer_set_replacement_codepoint (b, (unsigned int) -1); g_assert (hb_buffer_get_replacement_codepoint (b) == (unsigned int) -1); hb_buffer_reset (b); g_assert (hb_buffer_get_unicode_funcs (b) == hb_unicode_funcs_get_default ()); g_assert (hb_buffer_get_direction (b) == HB_DIRECTION_INVALID); g_assert (hb_buffer_get_script (b) == HB_SCRIPT_INVALID); g_assert (hb_buffer_get_language (b) == NULL); g_assert (hb_buffer_get_flags (b) == HB_BUFFER_FLAGS_DEFAULT); g_assert (hb_buffer_get_replacement_codepoint (b) == HB_BUFFER_REPLACEMENT_CODEPOINT_DEFAULT); }
// ------------------------------------------------------------------- main --- int main( int argc, char **argv ) { size_t i, j; GLFWwindow* window; glfwSetErrorCallback( error_callback ); if (!glfwInit( )) { exit( EXIT_FAILURE ); } glfwWindowHint( GLFW_VISIBLE, GL_TRUE ); glfwWindowHint( GLFW_RESIZABLE, GL_FALSE ); window = glfwCreateWindow( 1, 1, argv[0], NULL, NULL ); if (!window) { glfwTerminate( ); exit( EXIT_FAILURE ); } glfwMakeContextCurrent( window ); glfwSwapInterval( 1 ); glfwSetFramebufferSizeCallback( window, reshape ); glfwSetWindowRefreshCallback( window, display ); glfwSetKeyCallback( window, keyboard ); #ifndef __APPLE__ glewExperimental = GL_TRUE; GLenum err = glewInit(); if (GLEW_OK != err) { /* Problem: glewInit failed, something is seriously wrong. */ fprintf( stderr, "Error: %s\n", glewGetErrorString(err) ); exit( EXIT_FAILURE ); } fprintf( stderr, "Using GLEW %s\n", glewGetString(GLEW_VERSION) ); #endif texture_atlas_t * atlas = texture_atlas_new( 512, 512, 3 ); texture_font_t *fonts[20]; for ( i=0; i< 20; ++i ) { fonts[i] = texture_font_new_from_file(atlas, 12+i, font_filename), texture_font_load_glyphs(fonts[i], text, direction, language, script ); } typedef struct { float x,y,z, u,v, r,g,b,a, shift, gamma; } vertex_t; vbuffer = vertex_buffer_new( "vertex:3f,tex_coord:2f," "color:4f,ashift:1f,agamma:1f" ); /* Create a buffer for harfbuzz to use */ hb_buffer_t *buffer = hb_buffer_create(); for (i=0; i < 20; ++i) { hb_buffer_set_direction( buffer, direction ); hb_buffer_set_script( buffer, script ); hb_buffer_set_language( buffer, hb_language_from_string(language, strlen(language)) ); hb_buffer_add_utf8( buffer, text, strlen(text), 0, strlen(text) ); hb_shape( fonts[i]->hb_ft_font, buffer, NULL, 0 ); unsigned int glyph_count; hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count); hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); texture_font_load_glyphs( fonts[i], text, direction, language, script ); float gamma = 1.0; float shift = 0.0; float x = 0; float y = 600 - i * (10+i) - 15; float width = 0.0; float hres = fonts[i]->hres; for (j = 0; j < glyph_count; ++j) { int codepoint = glyph_info[j].codepoint; float x_advance = glyph_pos[j].x_advance/(float)(hres*64); float x_offset = glyph_pos[j].x_offset/(float)(hres*64); texture_glyph_t *glyph = texture_font_get_glyph(fonts[i], codepoint); if( i < (glyph_count-1) ) width += x_advance + x_offset; else width += glyph->offset_x + glyph->width; } x = 800 - width - 10 ; for (j = 0; j < glyph_count; ++j) { int codepoint = glyph_info[j].codepoint; // because of vhinting trick we need the extra 64 (hres) float x_advance = glyph_pos[j].x_advance/(float)(hres*64); float x_offset = glyph_pos[j].x_offset/(float)(hres*64); float y_advance = glyph_pos[j].y_advance/(float)(64); float y_offset = glyph_pos[j].y_offset/(float)(64); texture_glyph_t *glyph = texture_font_get_glyph(fonts[i], codepoint); float r = 0.0; float g = 0.0; float b = 0.0; float a = 1.0; float x0 = x + x_offset + glyph->offset_x; float x1 = x0 + glyph->width; float y0 = floor(y + y_offset + glyph->offset_y); float y1 = floor(y0 - glyph->height); float s0 = glyph->s0; float t0 = glyph->t0; float s1 = glyph->s1; float t1 = glyph->t1; vertex_t vertices[4] = { {x0,y0,0, s0,t0, r,g,b,a, shift, gamma}, {x0,y1,0, s0,t1, r,g,b,a, shift, gamma}, {x1,y1,0, s1,t1, r,g,b,a, shift, gamma}, {x1,y0,0, s1,t0, r,g,b,a, shift, gamma} }; GLushort indices[6] = { 0,1,2, 0,2,3 }; vertex_buffer_push_back( vbuffer, vertices, 4, indices, 6 ); x += x_advance; y += y_advance; } /* clean up the buffer, but don't kill it just yet */ hb_buffer_reset(buffer); } glClearColor(1,1,1,1); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glBindTexture( GL_TEXTURE_2D, atlas->id ); texture_atlas_upload( atlas ); vertex_buffer_upload( vbuffer ); shader = shader_load("shaders/text.vert", "shaders/text.frag"); mat4_set_identity( &projection ); mat4_set_identity( &model ); mat4_set_identity( &view ); glfwSetWindowSize( window, 800, 600 ); glfwShowWindow( window ); while(!glfwWindowShouldClose( window )) { display( window ); glfwPollEvents( ); } glfwDestroyWindow( window ); glfwTerminate( ); return 0; }
// ------------------------------------------------------------------- main --- int main( int argc, char **argv ) { size_t i, j; int ptSize = 50*64; int device_hdpi = 72; int device_vdpi = 72; glutInit( &argc, argv ); glutInitWindowSize( 512, 512 ); glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH ); glutCreateWindow( argv[0] ); glutReshapeFunc( reshape ); glutDisplayFunc( display ); glutKeyboardFunc( keyboard ); #ifndef __APPLE__ glewExperimental = GL_TRUE; GLenum err = glewInit(); if (GLEW_OK != err) { /* Problem: glewInit failed, something is seriously wrong. */ fprintf( stderr, "Error: %s\n", glewGetErrorString(err) ); exit( EXIT_FAILURE ); } fprintf( stderr, "Using GLEW %s\n", glewGetString(GLEW_VERSION) ); #endif texture_atlas_t * atlas = texture_atlas_new( 512, 512, 3 ); /* Init freetype */ FT_Library ft_library; assert(!FT_Init_FreeType(&ft_library)); /* Load our fonts */ FT_Face ft_face[NUM_EXAMPLES]; assert(!FT_New_Face( ft_library, fonts[ENGLISH], 0, &ft_face[ENGLISH]) ); assert(!FT_Set_Char_Size( ft_face[ENGLISH], 0, ptSize, device_hdpi, device_vdpi ) ); // ftfdump( ft_face[ENGLISH] ); // wonderful world of encodings ... force_ucs2_charmap( ft_face[ENGLISH] ); // which we ignore. assert( !FT_New_Face(ft_library, fonts[ARABIC], 0, &ft_face[ARABIC]) ); assert( !FT_Set_Char_Size(ft_face[ARABIC], 0, ptSize, device_hdpi, device_vdpi ) ); // ftfdump( ft_face[ARABIC] ); force_ucs2_charmap( ft_face[ARABIC] ); assert(!FT_New_Face( ft_library, fonts[CHINESE], 0, &ft_face[CHINESE]) ); assert(!FT_Set_Char_Size( ft_face[CHINESE], 0, ptSize, device_hdpi, device_vdpi ) ); // ftfdump( ft_face[CHINESE] ); force_ucs2_charmap( ft_face[CHINESE] ); /* Get our harfbuzz font structs */ hb_font_t *hb_ft_font[NUM_EXAMPLES]; hb_ft_font[ENGLISH] = hb_ft_font_create( ft_face[ENGLISH], NULL ); hb_ft_font[ARABIC] = hb_ft_font_create( ft_face[ARABIC] , NULL ); hb_ft_font[CHINESE] = hb_ft_font_create( ft_face[CHINESE], NULL ); /* Create a buffer for harfbuzz to use */ hb_buffer_t *buf = hb_buffer_create(); for (i=0; i < NUM_EXAMPLES; ++i) { hb_buffer_set_direction( buf, text_directions[i] ); /* or LTR */ hb_buffer_set_script( buf, scripts[i] ); /* see hb-unicode.h */ hb_buffer_set_language( buf, hb_language_from_string(languages[i], strlen(languages[i])) ); /* Layout the text */ hb_buffer_add_utf8( buf, texts[i], strlen(texts[i]), 0, strlen(texts[i]) ); hb_shape( hb_ft_font[i], buf, NULL, 0 ); unsigned int glyph_count; hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count); hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count); FT_GlyphSlot slot; FT_Bitmap ft_bitmap; float size = 24; size_t hres = 64; FT_Error error; FT_Int32 flags = 0; flags |= FT_LOAD_RENDER; flags |= FT_LOAD_TARGET_LCD; FT_Library_SetLcdFilter( ft_library, FT_LCD_FILTER_LIGHT ); FT_Matrix matrix = { (int)((1.0/hres) * 0x10000L), (int)((0.0) * 0x10000L), (int)((0.0) * 0x10000L), (int)((1.0) * 0x10000L) }; /* Set char size */ error = FT_Set_Char_Size( ft_face[i], (int)(ptSize), 0, 72*hres, 72 ); if( error ) { //fprintf( stderr, "FT_Error (line %d, code 0x%02x) : %s\n", // __LINE__, FT_Errors[error].code, FT_Errors[error].message ); FT_Done_Face( ft_face[i] ); break; } /* Set transform matrix */ FT_Set_Transform( ft_face[i], &matrix, NULL ); for (j = 0; j < glyph_count; ++j) { /* Load glyph */ error = FT_Load_Glyph( ft_face[i], glyph_info[j].codepoint, 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_Face( ft_face[i] ); break; } slot = ft_face[i]->glyph; ft_bitmap = slot->bitmap; int ft_bitmap_width = slot->bitmap.width; int ft_bitmap_rows = slot->bitmap.rows; int ft_bitmap_pitch = slot->bitmap.pitch; int ft_glyph_top = slot->bitmap_top; int ft_glyph_left = slot->bitmap_left; int w = ft_bitmap_width/3; // 3 because of LCD/RGB encoding int h = ft_bitmap_rows; ivec4 region = texture_atlas_get_region( atlas, w+1, h+1 ); if ( region.x < 0 ) { fprintf( stderr, "Texture atlas is full (line %d)\n", __LINE__ ); continue; } int x = region.x, y = region.y; texture_atlas_set_region( atlas, region.x, region.y, w, h, ft_bitmap.buffer, ft_bitmap.pitch ); printf("%d: %dx%d %f %f\n", glyph_info[j].codepoint, ft_bitmap_width, ft_bitmap_rows, glyph_pos[j].x_advance/64., glyph_pos[j].y_advance/64.); } /* clean up the buffer, but don't kill it just yet */ hb_buffer_reset(buf); } /* Cleanup */ hb_buffer_destroy( buf ); for( i=0; i < NUM_EXAMPLES; ++i ) hb_font_destroy( hb_ft_font[i] ); FT_Done_FreeType( ft_library ); glClearColor(1,1,1,1); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, atlas->id ); texture_atlas_upload( atlas ); typedef struct { float x,y,z, u,v, r,g,b,a, shift, gamma; } vertex_t; vertex_t vertices[4] = { { 0, 0,0, 0,1, 0,0,0,1, 0, 1}, { 0,512,0, 0,0, 0,0,0,1, 0, 1}, {512,512,0, 1,0, 0,0,0,1, 0, 1}, {512, 0,0, 1,1, 0,0,0,1, 0, 1} }; GLuint indices[6] = { 0, 1, 2, 0,2,3 }; buffer = vertex_buffer_new( "vertex:3f," "tex_coord:2f," "color:4f," "ashift:1f," "agamma:1f" ); vertex_buffer_push_back( buffer, vertices, 4, indices, 6 ); shader = shader_load("shaders/text.vert", "shaders/text.frag"); mat4_set_identity( &projection ); mat4_set_identity( &model ); mat4_set_identity( &view ); glutMainLoop( ); return 0; }
PassRefPtr<ShapeResult> HarfBuzzShaper::shapeResult() { RefPtr<ShapeResult> result = ShapeResult::create(m_font, m_normalizedBufferLength, m_textRun.direction()); HarfBuzzScopedPtr<hb_buffer_t> harfBuzzBuffer(hb_buffer_create(), hb_buffer_destroy); const FontDescription& fontDescription = m_font->getFontDescription(); const String& localeString = fontDescription.locale(); CString locale = localeString.latin1(); const hb_language_t language = hb_language_from_string(locale.data(), locale.length()); RunSegmenter::RunSegmenterRange segmentRange = { 0, 0, USCRIPT_INVALID_CODE, OrientationIterator::OrientationInvalid, SmallCapsIterator::SmallCapsSameCase, FontFallbackPriority::Invalid }; RunSegmenter runSegmenter( m_normalizedBuffer.get(), m_normalizedBufferLength, m_font->getFontDescription().orientation(), fontDescription.variant()); Vector<UChar32> fallbackCharsHint; // TODO: Check whether this treatAsZerowidthspace from the previous script // segmentation plays a role here, does the new scriptRuniterator handle that correctly? while (runSegmenter.consume(&segmentRange)) { RefPtr<FontFallbackIterator> fallbackIterator = m_font->createFontFallbackIterator( segmentRange.fontFallbackPriority); appendToHolesQueue(HolesQueueNextFont, 0, 0); appendToHolesQueue(HolesQueueRange, segmentRange.start, segmentRange.end - segmentRange.start); const SimpleFontData* currentFont = nullptr; RefPtr<UnicodeRangeSet> currentFontRangeSet; bool fontCycleQueued = false; while (m_holesQueue.size()) { HolesQueueItem currentQueueItem = m_holesQueue.takeFirst(); if (currentQueueItem.m_action == HolesQueueNextFont) { // For now, we're building a character list with which we probe // for needed fonts depending on the declared unicode-range of a // segmented CSS font. Alternatively, we can build a fake font // for the shaper and check whether any glyphs were found, or // define a new API on the shaper which will give us coverage // information? if (!collectFallbackHintChars(fallbackCharsHint, fallbackIterator->needsHintList())) { // Give up shaping since we cannot retrieve a font fallback // font without a hintlist. m_holesQueue.clear(); break; } FontDataForRangeSet nextFontDataForRangeSet = fallbackIterator->next(fallbackCharsHint); currentFont = nextFontDataForRangeSet.fontData().get(); currentFontRangeSet = nextFontDataForRangeSet.ranges(); if (!currentFont) { ASSERT(!m_holesQueue.size()); break; } fontCycleQueued = false; continue; } // TODO crbug.com/522964: Only use smallCapsFontData when the font does not support true smcp. The spec // says: "To match the surrounding text, a font may provide alternate glyphs for caseless characters when // these features are enabled but when a user agent simulates small capitals, it must not attempt to // simulate alternates for codepoints which are considered caseless." const SimpleFontData* smallcapsAdjustedFont = segmentRange.smallCapsBehavior == SmallCapsIterator::SmallCapsUppercaseNeeded ? currentFont->smallCapsFontData(fontDescription).get() : currentFont; // Compatibility with SimpleFontData approach of keeping a flag for overriding drawing direction. // TODO: crbug.com/506224 This should go away in favor of storing that information elsewhere, for example in // ShapeResult. const SimpleFontData* directionAndSmallCapsAdjustedFont = fontDataAdjustedForOrientation(smallcapsAdjustedFont, m_font->getFontDescription().orientation(), segmentRange.renderOrientation); if (!shapeRange(harfBuzzBuffer.get(), currentQueueItem.m_startIndex, currentQueueItem.m_numCharacters, directionAndSmallCapsAdjustedFont, currentFontRangeSet, segmentRange.script, language)) DLOG(ERROR) << "Shaping range failed."; if (!extractShapeResults(harfBuzzBuffer.get(), result.get(), fontCycleQueued, currentQueueItem, directionAndSmallCapsAdjustedFont, segmentRange.script, !fallbackIterator->hasNext())) DLOG(ERROR) << "Shape result extraction failed."; hb_buffer_reset(harfBuzzBuffer.get()); } } return result.release(); }
bool HarfBuzzShaper::setupHarfBuzzRun() { m_startIndexOfCurrentRun += m_numCharactersOfCurrentRun; // Iterate through the text to take the largest range that stays within // a single font. int endOfRunIndex = m_normalizedBufferLength - m_startIndexOfCurrentRun; SurrogatePairAwareTextIterator iterator(m_normalizedBuffer.get() + m_startIndexOfCurrentRun, 0, endOfRunIndex, endOfRunIndex); UChar32 character; unsigned clusterLength = 0; if (!iterator.consume(character, clusterLength)) return false; m_currentFontData = m_font->glyphDataForCharacter(character, false).fontData; UErrorCode errorCode = U_ZERO_ERROR; UScriptCode currentScript = uscript_getScript(character, &errorCode); if (U_FAILURE(errorCode)) return false; if (currentScript == USCRIPT_INHERITED) currentScript = USCRIPT_COMMON; for (iterator.advance(clusterLength); iterator.consume(character, clusterLength); iterator.advance(clusterLength)) { const SimpleFontData* nextFontData = m_font->glyphDataForCharacter(character, false).fontData; if (nextFontData != m_currentFontData) break; UScriptCode nextScript = uscript_getScript(character, &errorCode); if (U_FAILURE(errorCode)) return false; if (currentScript == nextScript || nextScript == USCRIPT_INHERITED || nextScript == USCRIPT_COMMON) continue; if (currentScript == USCRIPT_COMMON) currentScript = nextScript; else break; } m_numCharactersOfCurrentRun = iterator.currentCharacter(); if (!m_harfbuzzBuffer) { m_harfbuzzBuffer = hb_buffer_create(); hb_buffer_set_unicode_funcs(m_harfbuzzBuffer, hb_icu_get_unicode_funcs()); } else hb_buffer_reset(m_harfbuzzBuffer); hb_buffer_set_script(m_harfbuzzBuffer, hb_icu_script_to_script(currentScript)); // WebKit always sets direction to LTR during width calculation. // We only set direction when direction is explicitly set to RTL so that // preventng wrong width calculation. if (m_run.rtl()) hb_buffer_set_direction(m_harfbuzzBuffer, HB_DIRECTION_RTL); // Determine whether this run needs to be converted to small caps. // nextScriptRun() will always send us a run of the same case, because a // case change while in small-caps mode always results in different // FontData, so we only need to check the first character's case. if (m_font->isSmallCaps() && u_islower(m_normalizedBuffer[m_startIndexOfCurrentRun])) { String upperText = String(m_normalizedBuffer.get() + m_startIndexOfCurrentRun, m_numCharactersOfCurrentRun); upperText.makeUpper(); m_currentFontData = m_font->glyphDataForCharacter(upperText[0], false, SmallCapsVariant).fontData; hb_buffer_add_utf16(m_harfbuzzBuffer, upperText.characters(), m_numCharactersOfCurrentRun, 0, m_numCharactersOfCurrentRun); } else hb_buffer_add_utf16(m_harfbuzzBuffer, m_normalizedBuffer.get() + m_startIndexOfCurrentRun, m_numCharactersOfCurrentRun, 0, m_numCharactersOfCurrentRun); return true; }
int layoutChars(XeTeXLayoutEngine engine, uint16_t chars[], int32_t offset, int32_t count, int32_t max, bool rightToLeft) { bool res; hb_script_t script = HB_SCRIPT_INVALID; hb_direction_t direction = HB_DIRECTION_LTR; hb_segment_properties_t segment_props; hb_shape_plan_t *shape_plan; hb_font_t* hbFont = engine->font->getHbFont(); hb_face_t* hbFace = hb_font_get_face(hbFont); if (engine->font->getLayoutDirVertical()) direction = HB_DIRECTION_TTB; else if (rightToLeft) direction = HB_DIRECTION_RTL; script = hb_ot_tag_to_script (engine->script); if (hbUnicodeFuncs == NULL) hbUnicodeFuncs = _get_unicode_funcs(); hb_buffer_reset(engine->hbBuffer); hb_buffer_set_unicode_funcs(engine->hbBuffer, hbUnicodeFuncs); hb_buffer_add_utf16(engine->hbBuffer, chars, max, offset, count); hb_buffer_set_direction(engine->hbBuffer, direction); hb_buffer_set_script(engine->hbBuffer, script); hb_buffer_set_language(engine->hbBuffer, engine->language); hb_buffer_guess_segment_properties(engine->hbBuffer); hb_buffer_get_segment_properties(engine->hbBuffer, &segment_props); if (engine->ShaperList == NULL) { // HarfBuzz gives graphite2 shaper a priority, so that for hybrid // Graphite/OpenType fonts, Graphite will be used. However, pre-0.9999 // XeTeX preferred OpenType over Graphite, so we are doing the same // here for sake of backward compatibility. engine->ShaperList = (char**) xcalloc(4, sizeof(char*)); engine->ShaperList[0] = (char*) "ot"; engine->ShaperList[1] = (char*) "graphite2"; engine->ShaperList[2] = (char*) "fallback"; engine->ShaperList[3] = NULL; } shape_plan = hb_shape_plan_create_cached(hbFace, &segment_props, engine->features, engine->nFeatures, engine->ShaperList); res = hb_shape_plan_execute(shape_plan, hbFont, engine->hbBuffer, engine->features, engine->nFeatures); if (res) { engine->shaper = strdup(hb_shape_plan_get_shaper(shape_plan)); hb_buffer_set_content_type(engine->hbBuffer, HB_BUFFER_CONTENT_TYPE_GLYPHS); } else { // all selected shapers failed, retrying with default // we don't use _cached here as the cached plain will always fail. hb_shape_plan_destroy(shape_plan); shape_plan = hb_shape_plan_create(hbFace, &segment_props, engine->features, engine->nFeatures, NULL); res = hb_shape_plan_execute(shape_plan, hbFont, engine->hbBuffer, engine->features, engine->nFeatures); if (res) { engine->shaper = strdup(hb_shape_plan_get_shaper(shape_plan)); hb_buffer_set_content_type(engine->hbBuffer, HB_BUFFER_CONTENT_TYPE_GLYPHS); } else { fprintf(stderr, "\nERROR: all shapers failed\n"); exit(3); } } hb_shape_plan_destroy(shape_plan); int glyphCount = hb_buffer_get_length(engine->hbBuffer); #ifdef DEBUG char buf[1024]; unsigned int consumed; printf ("shaper: %s\n", engine->shaper); hb_buffer_serialize_flags_t flags = HB_BUFFER_SERIALIZE_FLAGS_DEFAULT; hb_buffer_serialize_format_t format = HB_BUFFER_SERIALIZE_FORMAT_JSON; hb_buffer_serialize_glyphs (engine->hbBuffer, 0, glyphCount, buf, sizeof(buf), &consumed, hbFont, format, flags); if (consumed) printf ("buffer glyphs: %s\n", buf); #endif return glyphCount; }
// ------------------------------------------------------------------- init --- void init( void ) { size_t i, j; texture_atlas_t * atlas = texture_atlas_new( 512, 512, 3 ); texture_font_t *fonts[20]; for ( i=0; i< 20; ++i ) { fonts[i] = texture_font_new_from_file(atlas, 12+i, font_filename), texture_font_load_glyphs(fonts[i], text, direction, language, script ); } typedef struct { float x,y,z, u,v, r,g,b,a, shift, gamma; } vertex_t; vbuffer = vertex_buffer_new( "vertex:3f,tex_coord:2f," "color:4f,ashift:1f,agamma:1f" ); /* Create a buffer for harfbuzz to use */ hb_buffer_t *buffer = hb_buffer_create(); for (i=0; i < 20; ++i) { hb_buffer_set_direction( buffer, direction ); hb_buffer_set_script( buffer, script ); hb_buffer_set_language( buffer, hb_language_from_string(language, strlen(language)) ); hb_buffer_add_utf8( buffer, text, strlen(text), 0, strlen(text) ); hb_shape( fonts[i]->hb_ft_font, buffer, NULL, 0 ); unsigned int glyph_count; hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buffer, &glyph_count); hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buffer, &glyph_count); texture_font_load_glyphs( fonts[i], text, direction, language, script ); float gamma = 1.0; float shift = 0.0; float x = 0; float y = 600 - i * (10+i) - 15; float width = 0.0; float hres = fonts[i]->hres; for (j = 0; j < glyph_count; ++j) { int codepoint = glyph_info[j].codepoint; float x_advance = glyph_pos[j].x_advance/(float)(hres*64); float x_offset = glyph_pos[j].x_offset/(float)(hres*64); texture_glyph_t *glyph = texture_font_get_glyph(fonts[i], codepoint); if( i < (glyph_count-1) ) width += x_advance + x_offset; else width += glyph->offset_x + glyph->width; } x = 800 - width - 10 ; for (j = 0; j < glyph_count; ++j) { int codepoint = glyph_info[j].codepoint; // because of vhinting trick we need the extra 64 (hres) float x_advance = glyph_pos[j].x_advance/(float)(hres*64); float x_offset = glyph_pos[j].x_offset/(float)(hres*64); float y_advance = glyph_pos[j].y_advance/(float)(64); float y_offset = glyph_pos[j].y_offset/(float)(64); texture_glyph_t *glyph = texture_font_get_glyph(fonts[i], codepoint); float r = 0.0; float g = 0.0; float b = 0.0; float a = 1.0; float x0 = x + x_offset + glyph->offset_x; float x1 = x0 + glyph->width; float y0 = floor(y + y_offset + glyph->offset_y); float y1 = floor(y0 - glyph->height); float s0 = glyph->s0; float t0 = glyph->t0; float s1 = glyph->s1; float t1 = glyph->t1; vertex_t vertices[4] = { {x0,y0,0, s0,t0, r,g,b,a, shift, gamma}, {x0,y1,0, s0,t1, r,g,b,a, shift, gamma}, {x1,y1,0, s1,t1, r,g,b,a, shift, gamma}, {x1,y0,0, s1,t0, r,g,b,a, shift, gamma} }; GLuint indices[6] = { 0,1,2, 0,2,3 }; vertex_buffer_push_back( vbuffer, vertices, 4, indices, 6 ); x += x_advance; y += y_advance; } /* clean up the buffer, but don't kill it just yet */ hb_buffer_reset(buffer); } glClearColor(1,1,1,1); glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, atlas->id ); texture_atlas_upload( atlas ); vertex_buffer_upload( vbuffer ); shader = shader_load("shaders/text.vert", "shaders/text.frag"); mat4_set_identity( &projection ); mat4_set_identity( &model ); mat4_set_identity( &view ); }