eressea Posted December 6, 2017 Posted December 6, 2017 (edited) Hi everyone, apart of L2, I also work on many different things and I'd like to share one example I wrote (because it's not so easy finding out how to do it properly). When you need to render some text that requires more complex rendering than Latin - for example Hebrew, Arabic and Devanagari (Hindi) - you'll have to do some text shaping. It's not so difficult for Hebrew and Arabic (if you don't mind sometimes wrong niqqud, you can implement subset of OpenType and use fribidi) but for Devanagari it's almost impossible to implement again (too much work). If you're on Windows platform only, you can use Microsoft's Uniscribe which works perfectly, on OS X/iOS you can use Apple's Coretext which works very well too. But what if you need it to be platform agnostic? Then you can use FreeType and HarfBuzz :) Also, HarfBuzz can use native (Uniscribe/Coretext) shapers on given platform (depends how you compile it) instead of it's own shaper. So the example here, as for fonts, you can download them from Google, if you want to see output without compiling, scroll down. #include <iostream> #include <harfbuzz/hb.h> #include <harfbuzz/hb-ft.h> #include <ft2build.h> #include <freetype.h> // Input texts in UTF-8 (without Byte Order Mask) static const char *texts[] = { "\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d \xd7\xa2\xd7\x95\xd7\x9c\xd7\x9d", "\xd9\x85\xd8\xb1\xd8\xad\xd8\xa8\xd8\xa7 \xd8\xa8\xd8\xa7\xd9\x84\xd8\xb9\xd8\xa7\xd9\x84\xd9\x85", "\xe0\xa4\xa8\xe0\xa4\xae\xe0\xa4\xb8\xe0\xa5\x8d\xe0\xa4\xa4\xe0\xa5\x87 \xe0\xa4\xa6\xe0\xa5\x81\xe0\xa4\xa8\xe0\xa4\xbf\xe0\xa4\xaf\xe0\xa4\xbe" }; // Font sizes in points static const size_t fontSizes[] = { 16, 16, 16 }; // Font filenames static const char *fontFiles[] = { "NotoSansHebrew-Regular.ttf", "NotoSansArabic-Regular.ttf", "NotoSansDevanagari-Regular.ttf" }; int main(int argc, char **argv) { // Freetype library handle FT_Library library; // Initialize FreeType if (FT_Init_FreeType(&library)) { std::cerr << "Can't initialize FreeType" << std::endl; return 1; } // Go through all input texts for (size_t textIndex = 0 ; textIndex < sizeof(texts) / sizeof(texts[0]) ; ++textIndex) { // Current input text const char *text = texts[textIndex]; const size_t fontSize = fontSizes[textIndex]; const char *fontFile = fontFiles[textIndex]; // FreeType font face handle FT_Face face; // Load font if (FT_New_Face(library, fontFile, 0, &face)) { std::cerr << "Can't load font " << fontFile << std::endl; return 1; } // Set character size if (FT_Set_Char_Size(face, fontSize << 6, fontSize << 6, 0, 0)) { std::cerr << "Can't set character size" << std::endl; return 1; } // Set no transform (identity) FT_Set_Transform(face, 0, 0); // Load font into HarfBuzz hb_font_t *hbFont = hb_ft_font_create(face, 0); // Create buffer for our text hb_buffer_t *hbBuffer = hb_buffer_create(); // Add our text to buffer hb_buffer_add_utf8(hbBuffer, text, -1, 0, -1); // Detect direction etc hb_buffer_guess_segment_properties(hbBuffer); // Shape our text hb_shape(hbFont, hbBuffer, 0, 0); // Shaped text info unsigned int len = hb_buffer_get_length(hbBuffer); hb_glyph_info_t *info = hb_buffer_get_glyph_infos(hbBuffer, 0); hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(hbBuffer, 0); // Compute text width and origin (from min and max X and Y drawing coordinate) int originX = 0, originY = 0; int minX = INT_MAX, maxX = INT_MIN, minY = INT_MAX, maxY = INT_MIN; // Go through all glyphs and find minimum and maximum X and Y coordinate for (size_t i = 0 ; i < len ; ++i) { // Load glyph if (FT_Load_Glyph(face, info[i].codepoint, FT_LOAD_RENDER)) { std::cerr << "Can't load glyph " << info[i].codepoint << std::endl; return 1; } // Glyph data FT_GlyphSlot slot = face->glyph; // Get X and Y offset int offsetX = ((pos[i].x_offset + slot->metrics.horiBearingX) >> 6); int offsetY = ((pos[i].y_offset + slot->metrics.horiBearingY) >> 6); // Compute minimum and maximum X and Y for this glyph int glyphMinX = originX + offsetX; int glyphMaxX = originX + slot->bitmap.width + offsetX; int glyphMinY = originY - slot->bitmap.rows + offsetY; int glyphMaxY = originY + offsetY; // Update minimum and maximum X and Y for text if (glyphMinX < minX) minX = glyphMinX; if (glyphMaxX > maxX) maxX = glyphMaxX; if (glyphMinY < minY) minY = glyphMinY; if (glyphMaxY > maxY) maxY = glyphMaxY; // Advance originX += pos[i].x_advance >> 6; } // Text will start on 0 originX = -minX; originY = -minY; // Compute width and height size_t width = maxX - minX + 1; size_t height = maxY - minY + 1; // Allocate buffer for image unsigned char *image = new unsigned char[width * height]; // Clear image bufer memset(image, 0, width * height); // Go through glyphs and draw them for (size_t i = 0 ; i < len ; ++i) { // Load glyph if (FT_Load_Glyph(face, info[i].codepoint, FT_LOAD_RENDER)) { std::cerr << "Can't load glyph " << info[i].codepoint << std::endl; return 1; } // Glyph data FT_GlyphSlot slot = face->glyph; // Pointer to bitmap data unsigned char *ptr = slot->bitmap.buffer; // Get real offset int drawX = originX + ((pos[i].x_offset + slot->metrics.horiBearingX) >> 6); int drawY = originY + ((pos[i].y_offset + slot->metrics.horiBearingY) >> 6); // Copy bitmap for (size_t y = 0 ; y < slot->bitmap.rows ; ++y) { // Copy row for (size_t x = 0 ; x < slot->bitmap.width ; ++x) { if (drawX + x < 0) { std::cerr << "drawX (" << drawX << ") + x (" << x << ") < 0" << std::endl; abort(); } if (drawX + x >= width) { std::cerr << "drawX (" << drawX << ") + x (" << x << ") > width (" << width << ")" << std::endl; abort(); } if (drawY - y < 0) { std::cerr << "drawY (" << drawY << ") - y (" << y << ") < 0" << std::endl; abort(); } if (drawY - y >= height) { std::cerr << "drawY (" << drawY << ") - y (" << y << ") > height (" << height << ")" << std::endl; abort(); } image[(drawY - y) * width + drawX + x] = ptr[x]; } // Advance pointer ptr += slot->bitmap.pitch; } // Advance originX += pos[i].x_advance >> 6; } // Output rendered text for (size_t y = 0 ; y < height ; ++y) { for (size_t x = 0 ; x < width ; ++x) { unsigned char value = image[(height - y - 1) * width + x]; if (value >= 0x80) { std::cout << "XX"; // if it's 128+ } else if (value >= 0x40) { std::cout << ".."; // if it's 64+ } else { std::cout << " "; // if its under 64 } } std::cout << std::endl; } std::cout << std::endl; // Delete image buffer delete [] image; // Destroy buffer for text hb_buffer_destroy(hbBuffer); // Destroy HarfBuzz font hb_font_destroy(hbFont); // Destroy FreeType font FT_Done_Face(face); } // Destroy FreeType FT_Done_FreeType(library); return 0; } XXXX XXXX XXXX XXXX XXXX XXXX XXXXXXXXXXXXXX XXXXXXXXXXXXXXXX XXXX XXXX XX XXXXXXXXXXXXXX XXXX XXXXXXXXXXXXXXXX..XX XXXX XXXX XXXX........XXXX ............XX.. XXXX XXXX ..XX XXXX........XXXX XXXX ............XX.. XX XXXX XXXX XXXX ..XX ..XX XXXX ..XX ..XX XXXX ..XX XXXX ..XX XX.. XX.. XXXX XXXX ..XX XXXX XXXX XX.. XXXX XXXX ..XX XXXX XXXX XX.. ..XX XX.. XXXX ..XX XX.. XXXX XXXX XXXX XXXX ..XX XXXX XX.. XXXXXXXXXX XX.. XXXX ..XX XXXX XXXX XXXX XXXX XXXX ..XX XXXX XXXX XXXXXX XXXX XXXX ..XX XXXX XXXX ..XX XXXX XXXX ..XX XXXX XXXX XXXX ..XXXX XXXX........XXXX ..XX XXXX ....XXXXXXXX.. XXXX........XXXX XXXX ..XX XXXX......XXXXXX XXXXXXXXXXXXXXXX XXXX XXXX XXXXXXXXXXXX XXXXXXXXXXXXXXXX XXXX XXXX XXXXXXXXXXXX.. ..XX XX.. ..XX XX.. XX.. ..XX XXXX ..XX XXXX XXXX ..XX XXXX ..XX XXXX XXXX ..XX XXXX ..XX XXXX XXXX ..XX XXXX XXXXXXXX.. ..XX XXXX .. XXXX ..XXXX.. XX XXXX XXXX....XXXX XX XXXX ..XX XXXX XXXXXXXX.. .. XXXXXX XXXX..XXXX XX XXXX XX.. XX XX XXXX XX.. XXXX .... ......XXXXXX ..XX ..XX..XXXX XXXX XX.. XX XXXX ..XX.. XXXX XX XXXX XX.. XXXX XXXX XX XXXX XX.. XX.. XXXXXX XXXX XX XXXX ..XXXXXX XX XXXX XX.. XXXX XX.. ..XXXX ..XX ..XX XX.. XXXX XXXX..XXXX..XXXX ..XXXX....XXXXXXXXXX....XXXX ..XXXX....XX ..XXXX..XXXXXX......XXXXXX.. ..XXXX..XXXXXX....XX XXXX XXXXXXXXXXXX.. XXXXXXXXXX.. XXXXXXXXXX.. XXXXXXXX.. XXXXXXXX..XXXXXXXXXX.. XXXXXXXXXX XXXXXXXX XX.. ..XX XXXX ..XX.. XXXX ..XX ..XX XXXX.. ..XX .. XX XX.. ..XXXXXX XXXXXXXXXX.. XX.. XXXX.. ..XXXXXX ..XX XX XXXX XX XX.. XXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .. XX.. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.. XXXX XX.. ..XX.. XXXX XXXX ..XX XXXX XXXX XXXX XXXX XXXX XX.. XX.. XX XXXX XXXX ..XX XXXX XX.. ..XX ..XX XXXX XX.. XX.. XX XXXX XXXX XXXXXXXX XXXX XX.. XXXX ..XX XXXX ..XXXXXXXXXXXX.. XX.. XX ..XXXXXX.. ..XXXXXXXXXXXX XXXX.. XXXX ..XXXXXXXXXXXX.. XXXX.. ..XX XXXX XXXX.. XX....XXXXXXXXXXXXXX XXXXXXXXXX XXXX XXXX XX XXXX XXXX.. XX.. XX.. XXXX XXXX ..XX XX.. XXXX.. XX ..XX XX XXXX XX XXXX XXXX ..XX XX.. XXXX ..XXXX XXXX XX.. XX XX XXXX XX.. XXXX XXXX XXXX.. XXXX XX.. XXXXXXXXXXXX XXXX XX.. XX XXXX ..XX.. XXXX XXXXXXXXXX XXXX XX.. ..XX XXXX XX.. XX XXXX ..XX XXXX XX XXXX XX.. ..XX XXXX XXXXXX.. ..XX ..XX XXXX.. XXXX ..XXXXXXXX Edited December 6, 2017 by eressea 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now