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);
}
示例#2
0
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 ());
}
示例#3
0
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*>(&currentFontData->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;
}
示例#4
0
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;
}
示例#5
0
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*>(&currentFontData->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;
}
示例#6
0
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);
}
示例#7
0
文件: renderer.c 项目: tiffany352/sui
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;
}
示例#9
0
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;
}