void CTextRenderer::UpdateTextCache_HarfBuzz(array<CHarfbuzzGlyph>* pGlyphChain, const UChar* pTextUTF16, int Start, int Length, bool IsRTL, int FontId) { //Use harfbuzz to shape the text (arabic, tamil, ...) hb_buffer_t* m_pHBBuffer = hb_buffer_create(); hb_buffer_set_unicode_funcs(m_pHBBuffer, hb_icu_get_unicode_funcs()); hb_buffer_set_direction(m_pHBBuffer, (IsRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR)); hb_buffer_set_script(m_pHBBuffer, HB_SCRIPT_ARABIC); hb_buffer_set_language(m_pHBBuffer, hb_language_from_string("ar", 2)); hb_buffer_add_utf16(m_pHBBuffer, pTextUTF16, Length, Start, Length); hb_shape(m_Fonts[FontId]->m_pHBFont, m_pHBBuffer, 0, 0); unsigned int GlyphCount; hb_glyph_info_t* GlyphInfo = hb_buffer_get_glyph_infos(m_pHBBuffer, &GlyphCount); hb_glyph_position_t* GlyphPos = hb_buffer_get_glyph_positions(m_pHBBuffer, &GlyphCount); for(int i=0; i<GlyphCount; i++) { CHarfbuzzGlyph Glyph; Glyph.m_GlyphId = CGlyphId(FontId, GlyphInfo[i].codepoint); Glyph.m_CharPos = GlyphInfo[i].cluster; pGlyphChain->add(Glyph); } hb_buffer_destroy(m_pHBBuffer); }
LayoutEngine::LayoutEngine(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode, le_int32 typoFlags, LEErrorCode &success) : fHbFont(NULL), fHbBuffer(NULL), fTypoFlags(typoFlags) { if (LE_FAILURE(success)) { return; } fHbBuffer = hb_buffer_create (); if (fHbBuffer == hb_buffer_get_empty ()) { success = LE_MEMORY_ALLOCATION_ERROR; return; } hb_buffer_set_unicode_funcs (fHbBuffer, hb_icu_get_unicode_funcs ()); hb_buffer_set_script (fHbBuffer, hb_icu_script_to_script ((UScriptCode) scriptCode)); /* TODO set language */ hb_face_t *face = hb_face_create_for_tables (icu_le_hb_reference_table, (void *) fontInstance, NULL); fHbFont = hb_font_create (face); hb_face_destroy (face); if (fHbFont == hb_font_get_empty ()) { success = LE_MEMORY_ALLOCATION_ERROR; return; } hb_font_set_funcs (fHbFont, icu_le_hb_get_font_funcs (), (void *) fontInstance, NULL); hb_font_set_scale (fHbFont, +from_float (fontInstance->getXPixelsPerEm () * fontInstance->getScaleFactorX ()), -from_float (fontInstance->getYPixelsPerEm () * fontInstance->getScaleFactorY ())); hb_font_set_ppem (fHbFont, fontInstance->getXPixelsPerEm (), fontInstance->getYPixelsPerEm ()); }
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; }
ilG_gui_textlayout *ilG_gui_textlayout_new(ilG_context *ctx, const char *lang, enum ilG_gui_textdir direction, const char *script, il_base *font, const ilA_file *tc, double pt, il_string *source) { il_return_null_on_fail(ctx && lang && script && font && source); struct text_globalctx *gctx = il_base_get(&ctx->base, "il.graphics.gui.text.ctx", NULL, NULL); if (!gctx) { gctx = calloc(1, sizeof(struct text_globalctx)); il_return_null_on_fail(!FT_Init_FreeType(&gctx->library)); il_base_set(&ctx->base, "il.graphics.gui.text.ctx", gctx, sizeof(struct text_globalctx), IL_VOID); } ilG_gui_textlayout *l = il_new(&ilG_gui_textlayout_type); l->context = ctx; l->lang = strdup(lang); l->script = strdup(script); l->font = il_ref(font); l->direction = direction; l->pt = pt; l->source = il_string_ref(source); size_t size; void *data = ilA_contents(tc, font, &size); if (!data) { il_error("Could not open font"); return NULL; } il_return_null_on_fail(!FT_New_Memory_Face(gctx->library, data, size, 0, &l->ft_face)); il_return_null_on_fail(!FT_Set_Char_Size(l->ft_face, 0, pt * 64, 0, 0)); l->hb_ft_font = hb_ft_font_create(l->ft_face, NULL); l->buf = hb_buffer_create(); hb_buffer_set_unicode_funcs(l->buf, hb_icu_get_unicode_funcs()); if (direction == ILG_GUI_DEFAULTDIR) { // TODO: find out how to determine this based on script direction = ILG_GUI_LTR; } hb_buffer_set_direction(l->buf, direction + 3); // Hacky solution hb_buffer_set_script(l->buf, hb_script_from_string(script, -1)); hb_buffer_set_language(l->buf, hb_language_from_string(lang, -1)); hb_buffer_add_utf8(l->buf, source->data, source->length, 0, source->length); hb_shape(l->hb_ft_font, l->buf, NULL, 0); l->glyph_info = hb_buffer_get_glyph_infos(l->buf, &l->glyph_count); l->glyph_pos = hb_buffer_get_glyph_positions(l->buf, &l->glyph_count); return l; }
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; }
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); }
bool sui_layout_init(sui_layout *layout, sui_font *font, const sui_layout_format *fmt, const char *text, size_t length, char **error) { static const int text_dir_table[] = { HB_DIRECTION_LTR, HB_DIRECTION_RTL, HB_DIRECTION_TTB, HB_DIRECTION_BTT }; memset(layout, 0, sizeof(sui_layout)); layout->fmt = *fmt; layout->font = font; FT_Error fterr; if ((fterr = FT_Set_Pixel_Sizes(font->face, 0, fmt->size))) { *error = sui_aprintf("FT_Set_Char_Size: %i\n", fterr); return false; } layout->buffer = hb_buffer_create(); hb_buffer_set_unicode_funcs(layout->buffer, hb_icu_get_unicode_funcs()); hb_buffer_set_direction(layout->buffer, text_dir_table[fmt->dir]); hb_script_t script = hb_script_from_string(fmt->script, -1); if (script == HB_SCRIPT_INVALID || script == HB_SCRIPT_UNKNOWN) { *error = sui_aprintf("Invalid script: %s\n", fmt->script); return false; } hb_buffer_set_script(layout->buffer, script); hb_language_t lang = hb_language_from_string(fmt->lang, -1); if (lang == HB_LANGUAGE_INVALID) { *error = sui_aprintf("Invalid language: %s\n", fmt->lang); return false; } hb_buffer_set_language(layout->buffer, lang); hb_buffer_add_utf8(layout->buffer, text, length, 0, length); hb_shape(font->hb_font, layout->buffer, NULL, 0); layout->infos = hb_buffer_get_glyph_infos(layout->buffer, &layout->count); layout->positions = hb_buffer_get_glyph_positions(layout->buffer, &layout->count); int pen_x = 0, pen_y = 0; int lx = 0, ly = 0, hx = 0, hy = 0; for (unsigned i = 0; i < layout->count; i++) { uint32_t codepoint = layout->infos[i].codepoint; if ((fterr = FT_Load_Glyph(font->face, codepoint, FT_LOAD_DEFAULT))) { *error = sui_aprintf("FT_Load_Glyph: %i\n", fterr); return false; } if (pen_x < lx) { lx = pen_x; } if (pen_y < ly) { ly = pen_y; } int w = font->face->glyph->metrics.width, h = font->face->glyph->metrics.height; if (pen_x + w > hx) { hx = pen_x + w; } if (pen_y + h > hy) { hy = pen_y + h; } pen_x += layout->positions[i].x_advance; pen_y += layout->positions[i].y_advance; } layout->origin = sui_mkpoint(-lx, -ly); layout->size = sui_mkpoint(hx-lx, hy-ly); return true; }
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; }