// TODO(drott): crbug.com/623940 Fix lack of context sensitive case mapping here.
void CaseMappingHarfBuzzBufferFiller::fillSlowCase(
    CaseMapIntend caseMapIntend,
    AtomicString locale,
    const UChar* buffer,
    unsigned bufferLength,
    unsigned startIndex,
    unsigned numCharacters)
{
    // Record pre-context.
    hb_buffer_add_utf16(m_harfBuzzBuffer, toUint16(buffer), bufferLength, startIndex, 0);

    for (unsigned charIndex = startIndex; charIndex < startIndex + numCharacters;) {
        unsigned newCharIndex = charIndex;
        U16_FWD_1(buffer, newCharIndex, numCharacters);
        String charByChar(&buffer[charIndex], newCharIndex - charIndex);
        String caseMappedChar;
        if (caseMapIntend == CaseMapIntend::UpperCase)
            caseMappedChar = charByChar.upper(locale);
        else
            caseMappedChar = charByChar.lower(locale);

        for (unsigned j = 0; j < caseMappedChar.length();) {
            UChar32 codepoint = 0;
            U16_NEXT(caseMappedChar.characters16(), j, caseMappedChar.length(), codepoint);
            // Add all characters of the case mapping result at the same cluster position.
            hb_buffer_add(m_harfBuzzBuffer, codepoint, charIndex);
        }
        charIndex = newCharIndex;
    }

    // Record post-context
    hb_buffer_add_utf16(m_harfBuzzBuffer, toUint16(buffer), bufferLength, startIndex + numCharacters, 0);
}
Example #2
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;
}
Example #3
0
static void
fixture_init (fixture_t *fixture, gconstpointer user_data)
{
  hb_buffer_t *b;
  unsigned int i;

  b = fixture->buffer = hb_buffer_create ();

  switch (GPOINTER_TO_INT (user_data))
  {
    case BUFFER_EMPTY:
      break;

    case BUFFER_ONE_BY_ONE:
      for (i = 1; i < G_N_ELEMENTS (utf32) - 1; i++)
	hb_buffer_add (b, utf32[i], i);
      break;

    case BUFFER_UTF32:
      hb_buffer_add_utf32 (b, utf32, G_N_ELEMENTS (utf32), 1, G_N_ELEMENTS (utf32) - 2);
      break;

    case BUFFER_UTF16:
      hb_buffer_add_utf16 (b, utf16, G_N_ELEMENTS (utf16), 1, G_N_ELEMENTS (utf16) - 2);
      break;

    case BUFFER_UTF8:
      hb_buffer_add_utf8  (b, utf8,  G_N_ELEMENTS (utf8),  1, G_N_ELEMENTS (utf8)  - 2);
      break;

    default:
      g_assert_not_reached ();
  }
}
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);
}
static inline void addToHarfBuzzBufferInternal(hb_buffer_t* buffer,
    const FontDescription& fontDescription, const UChar* normalizedBuffer,
    unsigned normalizedBufferLength, unsigned startIndex, unsigned numCharacters)
{
    // TODO: Revisit whether we can always fill the hb_buffer_t with the
    // full run text, but only specify startIndex and numCharacters for the part
    // to be shaped. Then simplify/change the complicated index computations in
    // extractShapeResults().
    if (fontDescription.variant() == FontVariantSmallCaps) {
        String upperText = String(normalizedBuffer, normalizedBufferLength)
            .upper();
        // TextRun is 16 bit, therefore upperText is 16 bit, even after we call
        // makeUpper().
        ASSERT(!upperText.is8Bit());
        hb_buffer_add_utf16(buffer, toUint16(upperText.characters16()),
            normalizedBufferLength, startIndex, numCharacters);
    } else {
        hb_buffer_add_utf16(buffer, toUint16(normalizedBuffer),
            normalizedBufferLength, startIndex, numCharacters);
    }
}
Example #6
0
int main(int, char **)
{
    hb_buffer_t *buffer = hb_buffer_create();

    const uint16_t string[] = { 'A', 'b', 'c' };
    hb_buffer_add_utf16(buffer, string, 3, 0, 3);
    hb_buffer_guess_segment_properties(buffer);
    hb_buffer_set_flags(buffer, hb_buffer_flags_t(HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES));

    hb_buffer_destroy(buffer);

    return 0;
}
Example #7
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;
}
CaseMappingHarfBuzzBufferFiller::CaseMappingHarfBuzzBufferFiller(
    CaseMapIntend caseMapIntend,
    AtomicString locale,
    hb_buffer_t* harfBuzzBuffer,
    const UChar* buffer,
    unsigned bufferLength,
    unsigned startIndex,
    unsigned numCharacters)
    : m_harfBuzzBuffer(harfBuzzBuffer)
{

    if (caseMapIntend == CaseMapIntend::KeepSameCase) {
        hb_buffer_add_utf16(m_harfBuzzBuffer,
                            toUint16(buffer),
                            bufferLength,
                            startIndex,
                            numCharacters);
    } else {
        String caseMappedText;
        if (caseMapIntend == CaseMapIntend::UpperCase) {
            caseMappedText = String(buffer, bufferLength).upper(locale);
        } else {
            caseMappedText = String(buffer, bufferLength).lower(locale);
        }

        if (caseMappedText.length() != bufferLength) {
            fillSlowCase(caseMapIntend, locale, buffer, bufferLength, startIndex, numCharacters);
            return;
        }

        ASSERT(caseMappedText.length() == bufferLength);
        ASSERT(!caseMappedText.is8Bit());
        hb_buffer_add_utf16(m_harfBuzzBuffer, toUint16(caseMappedText.characters16()),
                            bufferLength, startIndex, numCharacters);
    }
}
Example #9
0
// Input: characters, font?
// Output: glyphs, positions, char indices
// Returns: number of glyphs
le_int32 LayoutEngine::layoutChars(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft,
				   float x, float y, LEErrorCode &success)
{
    if (LE_FAILURE(success)) {
        return 0;
    }

    if (chars == NULL || offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) {
        success = LE_ILLEGAL_ARGUMENT_ERROR;
        return 0;
    }

    this->x = x;
    this->y = y;

    hb_buffer_set_length (fHbBuffer, 0);
    hb_buffer_set_direction (fHbBuffer, rightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
    hb_buffer_add_utf16 (fHbBuffer, chars, max, offset, count);

    hb_shape (fHbFont, fHbBuffer, NULL, 0);

    return hb_buffer_get_length (fHbBuffer);
}
Example #10
0
static void shape_text(text_line & line,
                       text_itemizer & itemizer,
                       std::map<unsigned,double> & width_map,
                       face_manager_freetype & font_manager,
                       double scale_factor)
{
    unsigned start = line.first_char();
    unsigned end = line.last_char();
    std::size_t length = end - start;
    if (!length) return;

    std::list<text_item> const& list = itemizer.itemize(start, end);

    line.reserve(length);

    auto hb_buffer_deleter = [](hb_buffer_t * buffer) { hb_buffer_destroy(buffer);};
    const std::unique_ptr<hb_buffer_t, decltype(hb_buffer_deleter)> buffer(hb_buffer_create(),hb_buffer_deleter);
    hb_buffer_pre_allocate(buffer.get(), safe_cast<int>(length));
    mapnik::value_unicode_string const& text = itemizer.text();

    for (auto const& text_item : list)
    {
        face_set_ptr face_set = font_manager.get_face_set(text_item.format_->face_name, text_item.format_->fontset);
        double size = text_item.format_->text_size * scale_factor;
        face_set->set_unscaled_character_sizes();
        std::size_t num_faces = face_set->size();
        std::size_t pos = 0;
        font_feature_settings const& ff_settings = text_item.format_->ff_settings;
        int ff_count = safe_cast<int>(ff_settings.count());
        for (auto const& face : *face_set)
        {
            ++pos;
            hb_buffer_clear_contents(buffer.get());
            hb_buffer_add_utf16(buffer.get(), uchar_to_utf16(text.getBuffer()), text.length(), text_item.start, static_cast<int>(text_item.end - text_item.start));
            hb_buffer_set_direction(buffer.get(), (text_item.dir == UBIDI_RTL)?HB_DIRECTION_RTL:HB_DIRECTION_LTR);
            hb_buffer_set_script(buffer.get(), _icu_script_to_script(text_item.script));
            hb_font_t *font(hb_ft_font_create(face->get_face(), nullptr));
            // https://github.com/mapnik/test-data-visual/pull/25
            #if HB_VERSION_MAJOR > 0
             #if HB_VERSION_ATLEAST(1, 0 , 5)
            hb_ft_font_set_load_flags(font,FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING);
             #endif
            #endif
            hb_shape(font, buffer.get(), ff_settings.get_features(), ff_count);
            hb_font_destroy(font);

            unsigned num_glyphs = hb_buffer_get_length(buffer.get());

            hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(buffer.get(), nullptr);
            hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer.get(), nullptr);

            bool font_has_all_glyphs = true;
            // Check if all glyphs are valid.
            for (unsigned i=0; i<num_glyphs; ++i)
            {
                if (!glyphs[i].codepoint)
                {
                    font_has_all_glyphs = false;
                    break;
                }
            }
            if (!font_has_all_glyphs && (pos < num_faces))
            {
                //Try next font in fontset
                continue;
            }

            double max_glyph_height = 0;
            for (unsigned i=0; i<num_glyphs; ++i)
            {
                auto const& gpos = positions[i];
                auto const& glyph = glyphs[i];
                unsigned char_index = glyph.cluster;
                glyph_info g(glyph.codepoint,char_index,text_item.format_);
                if (face->glyph_dimensions(g))
                {
                    g.face = face;
                    g.scale_multiplier = size / face->get_face()->units_per_EM;
                    //Overwrite default advance with better value provided by HarfBuzz
                    g.unscaled_advance = gpos.x_advance;
                    g.offset.set(gpos.x_offset * g.scale_multiplier, gpos.y_offset * g.scale_multiplier);
                    double tmp_height = g.height();
                    if (tmp_height > max_glyph_height) max_glyph_height = tmp_height;
                    width_map[char_index] += g.advance();
                    line.add_glyph(std::move(g), scale_factor);
                }
            }
            line.update_max_char_height(max_glyph_height);
            break; //When we reach this point the current font had all glyphs.
        }
    }
}
Example #11
0
static int ShapeParagraphHarfBuzz( filter_t *p_filter,
                                   paragraph_t **p_old_paragraph )
{
    paragraph_t *p_paragraph = *p_old_paragraph;
    paragraph_t *p_new_paragraph = 0;
    filter_sys_t *p_sys = p_filter->p_sys;
    int i_total_glyphs = 0;
    int i_ret = VLC_EGENERIC;

    if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
    {
        msg_Err( p_filter, "ShapeParagraphHarfBuzz() invalid parameters. "
                 "Paragraph size: %d. Runs count %d",
                 p_paragraph->i_size, p_paragraph->i_runs_count );
        return VLC_EGENERIC;
    }

    for( int i = 0; i < p_paragraph->i_runs_count; ++i )
    {
        run_desc_t *p_run = p_paragraph->p_runs + i;
        text_style_t *p_style = p_run->p_style;

        /*
         * When using HarfBuzz, this is where font faces are loaded.
         * In the other two paths (shaping with FriBidi or no
         * shaping at all), faces are loaded in LoadGlyphs()
         */
        FT_Face p_face = 0;
        if( !p_run->p_face )
        {
            p_face = LoadFace( p_filter, p_style );
            if( !p_face )
            {
                p_face = p_sys->p_face;
                p_style = &p_sys->style;
                p_run->p_style = p_style;
            }
            p_run->p_face = p_face;
        }
        else
            p_face = p_run->p_face;

        p_run->p_hb_font = hb_ft_font_create( p_face, 0 );
        if( !p_run->p_hb_font )
        {
            msg_Err( p_filter,
                     "ShapeParagraphHarfBuzz(): hb_ft_font_create() error" );
            goto error;
        }

        p_run->p_buffer = hb_buffer_create();
        if( !p_run->p_buffer )
        {
            msg_Err( p_filter,
                     "ShapeParagraphHarfBuzz(): hb_buffer_create() error" );
            goto error;
        }

        hb_buffer_set_direction( p_run->p_buffer, p_run->direction );
        hb_buffer_set_script( p_run->p_buffer, p_run->script );
#ifdef __OS2__
        hb_buffer_add_utf16( p_run->p_buffer,
                             p_paragraph->p_code_points + p_run->i_start_offset,
                             p_run->i_end_offset - p_run->i_start_offset, 0,
                             p_run->i_end_offset - p_run->i_start_offset );
#else
        hb_buffer_add_utf32( p_run->p_buffer,
                             p_paragraph->p_code_points + p_run->i_start_offset,
                             p_run->i_end_offset - p_run->i_start_offset, 0,
                             p_run->i_end_offset - p_run->i_start_offset );
#endif
        hb_shape( p_run->p_hb_font, p_run->p_buffer, 0, 0 );
        p_run->p_glyph_infos =
            hb_buffer_get_glyph_infos( p_run->p_buffer, &p_run->i_glyph_count );
        p_run->p_glyph_positions =
            hb_buffer_get_glyph_positions( p_run->p_buffer, &p_run->i_glyph_count );

        if( p_run->i_glyph_count <= 0 )
        {
            msg_Err( p_filter,
                     "ShapeParagraphHarfBuzz() invalid glyph count in shaped run" );
            goto error;
        }

        i_total_glyphs += p_run->i_glyph_count;
    }

    p_new_paragraph = NewParagraph( p_filter, i_total_glyphs, 0, 0, 0,
                                    p_paragraph->i_runs_size );
    if( !p_new_paragraph )
    {
        i_ret = VLC_ENOMEM;
        goto error;
    }
    p_new_paragraph->paragraph_type = p_paragraph->paragraph_type;

    int i_index = 0;
    for( int i = 0; i < p_paragraph->i_runs_count; ++i )
    {
        run_desc_t *p_run = p_paragraph->p_runs + i;
        hb_glyph_info_t *p_infos = p_run->p_glyph_infos;
        hb_glyph_position_t *p_positions = p_run->p_glyph_positions;
        for( unsigned int j = 0; j < p_run->i_glyph_count; ++j )
        {
            /*
             * HarfBuzz reverses the order of glyphs in RTL runs. We reverse
             * it again here to keep the glyphs in their logical order.
             * For line breaking of paragraphs to work correctly, visual
             * reordering should be done after line breaking has taken
             * place.
             */
            int i_run_index = p_run->direction == HB_DIRECTION_LTR ?
                    j : p_run->i_glyph_count - 1 - j;
            int i_source_index =
                    p_infos[ i_run_index ].cluster + p_run->i_start_offset;

            p_new_paragraph->p_code_points[ i_index ] = 0;
            p_new_paragraph->pi_glyph_indices[ i_index ] =
                p_infos[ i_run_index ].codepoint;
            p_new_paragraph->p_scripts[ i_index ] =
                p_paragraph->p_scripts[ i_source_index ];
            p_new_paragraph->p_types[ i_index ] =
                p_paragraph->p_types[ i_source_index ];
            p_new_paragraph->p_levels[ i_index ] =
                p_paragraph->p_levels[ i_source_index ];
            p_new_paragraph->pp_styles[ i_index ] =
                p_paragraph->pp_styles[ i_source_index ];
            p_new_paragraph->pi_karaoke_bar[ i_index ] =
                p_paragraph->pi_karaoke_bar[ i_source_index ];
            p_new_paragraph->p_glyph_bitmaps[ i_index ].i_x_offset =
                p_positions[ i_run_index ].x_offset;
            p_new_paragraph->p_glyph_bitmaps[ i_index ].i_y_offset =
                p_positions[ i_run_index ].y_offset;
            p_new_paragraph->p_glyph_bitmaps[ i_index ].i_x_advance =
                p_positions[ i_run_index ].x_advance;
            p_new_paragraph->p_glyph_bitmaps[ i_index ].i_y_advance =
                p_positions[ i_run_index ].y_advance;

            ++i_index;
        }
        if( AddRun( p_filter, p_new_paragraph, i_index - p_run->i_glyph_count,
                    i_index, p_run->p_face ) )
            goto error;
    }

    for( int i = 0; i < p_paragraph->i_runs_count; ++i )
    {
        hb_font_destroy( p_paragraph->p_runs[ i ].p_hb_font );
        hb_buffer_destroy( p_paragraph->p_runs[ i ].p_buffer );
    }
    FreeParagraph( *p_old_paragraph );
    *p_old_paragraph = p_new_paragraph;

    return VLC_SUCCESS;

error:
    for( int i = 0; i < p_paragraph->i_runs_count; ++i )
    {
        if( p_paragraph->p_runs[ i ].p_hb_font )
            hb_font_destroy( p_paragraph->p_runs[ i ].p_hb_font );
        if( p_paragraph->p_runs[ i ].p_buffer )
            hb_buffer_destroy( p_paragraph->p_runs[ i ].p_buffer );
    }

    if( p_new_paragraph )
        FreeParagraph( p_new_paragraph );

    return i_ret;
}
Example #12
0
JNIEXPORT jboolean JNICALL Java_sun_font_SunLayoutEngine_shape
    (JNIEnv *env, jclass cls,
     jobject font2D,
     jobject fontStrike,
     jfloat ptSize,
     jfloatArray matrix,
     jlong pFace,
     jlong pNativeFont,
     jboolean aat,
     jcharArray text,
     jobject gvdata,
     jint script,
     jint offset,
     jint limit,
     jint baseIndex,
     jobject startPt,
     jint flags,
     jint slot) {

     hb_buffer_t *buffer;
     hb_face_t* hbface;
     hb_font_t* hbfont;
     jchar  *chars;
     jsize len;
     int glyphCount;
     hb_glyph_info_t *glyphInfo;
     hb_glyph_position_t *glyphPos;
     hb_direction_t direction = HB_DIRECTION_LTR;
     hb_feature_t *features = NULL;
     int featureCount = 0;
     char* kern = (flags & TYPO_KERN) ? "kern" : "-kern";
     char* liga = (flags & TYPO_LIGA) ? "liga" : "-liga";
     jboolean ret;
     unsigned int buflen;

     JDKFontInfo *jdkFontInfo =
         createJDKFontInfo(env, font2D, fontStrike, ptSize, 
                           pNativeFont, matrix, aat);
     if (!jdkFontInfo) {
        return JNI_FALSE;
     }
     jdkFontInfo->env = env; // this is valid only for the life of this JNI call.
     jdkFontInfo->font2D = font2D;
     jdkFontInfo->fontStrike = fontStrike;

     hbface = (hb_face_t*) jlong_to_ptr(pFace);
     hbfont = hb_jdk_font_create(hbface, jdkFontInfo, NULL);

     buffer = hb_buffer_create();
     hb_buffer_set_script(buffer, getHBScriptCode(script));
     hb_buffer_set_language(buffer,
                            hb_ot_tag_to_language(HB_OT_TAG_DEFAULT_LANGUAGE));
     if ((flags & TYPO_RTL) != 0) {
         direction = HB_DIRECTION_RTL;
     }
     hb_buffer_set_direction(buffer, direction);
     hb_buffer_set_cluster_level(buffer,
                                 HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);

     chars = (*env)->GetCharArrayElements(env, text, NULL);
     if ((*env)->ExceptionCheck(env)) {
         hb_buffer_destroy(buffer);
         hb_font_destroy(hbfont);
         free((void*)jdkFontInfo);
         return JNI_FALSE;
     }
     len = (*env)->GetArrayLength(env, text);

     hb_buffer_add_utf16(buffer, chars, len, offset, limit-offset);

     features = calloc(2, sizeof(hb_feature_t));
     if (features) {
         hb_feature_from_string(kern, -1, &features[featureCount++]);
         hb_feature_from_string(liga, -1, &features[featureCount++]);
     }

     hb_shape_full(hbfont, buffer, features, featureCount, 0);
     glyphCount = hb_buffer_get_length(buffer);
     glyphInfo = hb_buffer_get_glyph_infos(buffer, 0);
     glyphPos = hb_buffer_get_glyph_positions(buffer, &buflen);

     ret = storeGVData(env, gvdata, slot, baseIndex, offset, startPt,
                       limit - offset, glyphCount, glyphInfo, glyphPos,
                       jdkFontInfo->devScale);

     hb_buffer_destroy (buffer);
     hb_font_destroy(hbfont);
     free((void*)jdkFontInfo);
     if (features != NULL) free(features);
     (*env)->ReleaseCharArrayElements(env, text, chars, JNI_ABORT);
     return ret;
}
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;
}
Example #14
0
void drawIndicText(JNIEnv *env, jobject thiz, jstring unicodeText, jint xStart,
				   jint yBaseLine, jint charHeight, jobject lock, jstring fontPath, jint language) {
	FT_Library ft_library;
	FT_Face ft_face;

	hb_font_t *font;
	hb_buffer_t *buffer;
	int glyph_count;
	hb_glyph_info_t *glyph_info;
	hb_glyph_position_t *glyph_pos;
	hb_bool_t fail;

	FT_UInt glyph_index;
	FT_GlyphSlot slot;
	FT_Error error;


	jboolean iscopy;
	const jchar *text;
	int num_chars, i;

	int pen_x;
	int glyphPosX, glyphPosY;

	const char *fontFilePath = (*env)->GetStringUTFChars(env, fontPath, NULL);


	text = (*env)->GetStringChars(env, unicodeText, &iscopy);
	num_chars = (*env)->GetStringLength(env, unicodeText);

	/* initialize library */
	error = FT_Init_FreeType(&ft_library);
	if (error) {
		__android_log_print(6, "drawIndicText",
							"Error initializing FreeType library\n");
		return;
	}
	// __android_log_print(2, "drawIndicText",
	//	    "Successfully initialized FreeType library\n");

	error = FT_New_Face(ft_library, fontFilePath, 0, &ft_face); /* create face object */
	if (error == FT_Err_Unknown_File_Format) {
		__android_log_print(6, "drawIndicText",
							"The font file could be opened and read, but it appears that its font format is unsupported %s ",
							fontFilePath);
		return;
	} else if (error) {
		__android_log_print(6, "drawIndicText",
							"The font file could not be opened or read, or it might be broken");
		return;
	}
	// __android_log_print(2, "drawIndicText",
	//	    "Successfully created font-face object\n");

	font = hb_ft_font_create(ft_face, NULL);

	error = FT_Set_Pixel_Sizes(ft_face, 0, charHeight); /* set character size */

	slot = ft_face->glyph;
	pen_x = xStart;

	/* Create a buffer for harfbuzz to use */
	buffer = hb_buffer_create();

	hb_buffer_set_script(buffer, scripts[language]);

	/* Layout the text */
	hb_buffer_add_utf16(buffer, text, num_chars, 0, num_chars);
	// __android_log_print(2, "drawIndicText", "Before HarfBuzz shape()\n");

	hb_shape(font, buffer, NULL, 0);
	// __android_log_print(2, "drawIndicText", "After HarfBuzz shape()\n");

	glyph_count = hb_buffer_get_length(buffer);
	glyph_info = hb_buffer_get_glyph_infos(buffer, 0);
	glyph_pos = hb_buffer_get_glyph_positions(buffer, 0);

	for (i = 0; i < glyph_count; i++) {
		glyph_index = glyph_info[i].codepoint;

		// __android_log_print(2, "drawIndicText", "Glyph%d = %x", i, glyph_index);

		error = FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_DEFAULT);
		if (error) {
			/* ignore errors */
			continue;
		}

		/* convert to an anti-aliased bitmap */
		error = FT_Render_Glyph(ft_face->glyph, FT_RENDER_MODE_NORMAL);
		if (error) {
			continue;
		}

		/* now, draw to our target surface (convert position) */
		draw_bitmap(&slot->bitmap, pen_x + slot->bitmap_left,
					yBaseLine - slot->bitmap_top, env, thiz, lock);

		/* increment pen position */
		pen_x += slot->advance.x >> 6;
	}

	hb_buffer_destroy(buffer);

	(*env)->ReleaseStringChars(env, unicodeText, text);
	(*env)->ReleaseStringUTFChars(env, fontPath, fontFilePath);
	hb_font_destroy(font);
	FT_Done_Face(ft_face);
	FT_Done_FreeType(ft_library);

	return;
}
void Java_org_iisc_mile_indictext_android_EditIndicText_drawIndicText(
		JNIEnv* env, jobject thiz, jstring unicodeText, jint xStart,
		jint yBaseLine, jint charHeight, jobject lock) {
	FT_Library ft_library;
	FT_Face ft_face;

	hb_font_t *font;
	hb_buffer_t *buffer;
	int glyph_count;
	hb_glyph_info_t *glyph_info;
	hb_glyph_position_t *glyph_pos;
	hb_bool_t fail;

	FT_UInt glyph_index;
	FT_GlyphSlot slot;
	FT_Error error;

	char* fontFilePath;
	jboolean iscopy;
	const jchar *text;
	int num_chars, i;

	int pen_x;
	int glyphPosX, glyphPosY;

	fontFilePath =
			"/sdcard/Android/data/org.iisc.mile.indictext.android/Lohit-Kannada.ttf";
	text = (*env)->GetStringChars(env, unicodeText, &iscopy);
	num_chars = (*env)->GetStringLength(env, unicodeText);

	error = FT_Init_FreeType(&ft_library); /* initialize library */
	if (error) {
		__android_log_print(6, "drawIndicText",
				"Error initializing FreeType library\n");
		return;
	}
	__android_log_print(2, "drawIndicText",
			"Successfully initialized FreeType library\n");

	error = FT_New_Face(ft_library, fontFilePath, 0, &ft_face); /* create face object */
	if (error == FT_Err_Unknown_File_Format) {
		__android_log_print(6, "drawIndicText",
				"The font file could be opened and read, but it appears that its font format is unsupported");
		return;
	} else if (error) {
		__android_log_print(6, "drawIndicText",
				"The font file could not be opened or read, or it might be broken");
		return;
	}
	__android_log_print(2, "drawIndicText",
			"Successfully created font-face object\n");

	font = hb_ft_font_create(ft_face, NULL);

	error = FT_Set_Pixel_Sizes(ft_face, 0, charHeight); /* set character size */
	/* error handling omitted */
	__android_log_print(2, "drawIndicText",
			"Successfully set character size to %d\n", charHeight);

	__android_log_print(2, "drawIndicText", "Text being rendered = %s\n", text);
	slot = ft_face->glyph;
	pen_x = xStart;

	/* Create a buffer for harfbuzz to use */
	buffer = hb_buffer_create();

	//hb_buffer_set_unicode_funcs(buffer, hb_icu_get_unicode_funcs());
	//alternatively you can use hb_buffer_set_unicode_funcs(buffer, hb_glib_get_unicode_funcs());

	//hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); /* or LTR */
	hb_buffer_set_script(buffer, HB_SCRIPT_KANNADA); /* see hb-unicode.h */
	//hb_buffer_set_language(buffer, hb_language_from_string("ka"));

	/* Layout the text */
	hb_buffer_add_utf16(buffer, text, num_chars, 0, num_chars);
	__android_log_print(2, "drawIndicText", "Before HarfBuzz shape()\n");
	hb_shape(font, buffer, NULL, 0);
	__android_log_print(2, "drawIndicText", "After HarfBuzz shape()\n");

	glyph_count = hb_buffer_get_length(buffer);
	glyph_info = hb_buffer_get_glyph_infos(buffer, 0);
	glyph_pos = hb_buffer_get_glyph_positions(buffer, 0);

	for (i = 0; i < glyph_count; i++) {
		glyph_index = glyph_info[i].codepoint;
		__android_log_print(2, "drawIndicText", "Glyph%d = %x", i, glyph_index);
		error = FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_DEFAULT);
		if (error) {
			continue; /* ignore errors */
		}
		/* convert to an anti-aliased bitmap */
		error = FT_Render_Glyph(ft_face->glyph, FT_RENDER_MODE_NORMAL);
		if (error) {
			continue;
		}

		/* now, draw to our target surface (convert position) */
		draw_bitmap(&slot->bitmap, pen_x + slot->bitmap_left,
				yBaseLine - slot->bitmap_top, env, thiz, lock);
		//glyphPosX = pen_x + glyph_pos[i].x_offset;
		//glyphPosY = yBaseLine - glyph_pos[i].y_offset;
		//draw_bitmap(&slot->bitmap, glyphPosX, glyphPosY, env, thiz, lock);

		/* increment pen position */
		pen_x += slot->advance.x >> 6;
		//pen_x += glyph_pos[i].x_advance / 64;
		__android_log_print(2, "drawIndicText",
				"\tx_offset = %d\ty_offset = %d\tx_advance = %d\ty_advance = %d\n",
				glyph_pos[i].x_offset / 64, glyph_pos[i].y_offset / 64,
				glyph_pos[i].x_advance / 64, glyph_pos[i].y_advance / 64);
		__android_log_print(2, "drawIndicText",
				"\tbitmap_left = %d\tbitmap_top = %d\tadvance.x = %d\tadvance.y = %d\n",
				slot->bitmap_left, slot->bitmap_top, slot->advance.x >> 6,
				slot->advance.y >> 6);
	}

	hb_buffer_destroy(buffer);

	(*env)->ReleaseStringChars(env, unicodeText, text);
	hb_font_destroy(font);
	FT_Done_Face(ft_face);
	FT_Done_FreeType(ft_library);

	return;
}
void AppendStringToBuffer<true, 2>(const FString& InString, const int32 InStartIndex, const int32 InLength, hb_buffer_t* InHarfBuzzTextBuffer)
{
	// A unicode encoding with a TCHAR size of 2 bytes is assumed to be UTF-16
	hb_buffer_add_utf16(InHarfBuzzTextBuffer, reinterpret_cast<const uint16_t*>(InString.GetCharArray().GetData()), InString.Len(), InStartIndex, InLength);
}
Example #17
0
int main (int argc, char** argv)
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " <num-iter>" << std::endl;
        return EXIT_FAILURE;
    }

    const unsigned NUM_ITER = atoi(argv[1]);

    // open first face in the font
    FT_Library ft_library = 0;
    FT_Error error = FT_Init_FreeType(&ft_library);
    if (error) throw std::runtime_error("Failed to initialize FreeType2 library");

    FT_Face ft_face[NUM_EXAMPLES];
    FT_New_Face(ft_library, "fonts/DejaVuSerif.ttf", 0, &ft_face[ENGLISH]);
    FT_New_Face(ft_library, "fonts/amiri-0.104/amiri-regular.ttf", 0, &ft_face[ARABIC]);
    FT_New_Face(ft_library, "fonts/fireflysung-1.3.0/fireflysung.ttf", 0, &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);

    {
        std::cerr << "Starting ICU shaping:" << std::endl;
        progress_timer timer1(std::clog,"ICU shaping done");
        UErrorCode err = U_ZERO_ERROR;
        for (unsigned i = 0; i < NUM_ITER; ++i)
        {
            for (unsigned j = 0; j < NUM_EXAMPLES; ++j)
            {
                UnicodeString text = UnicodeString::fromUTF8(texts[j]);
                int32_t length = text.length();
                UnicodeString reordered;
                UnicodeString shaped;
                UBiDi *bidi = ubidi_openSized(length, 0, &err);
                ubidi_setPara(bidi, text.getBuffer(), length, UBIDI_DEFAULT_LTR, 0, &err);
                ubidi_writeReordered(bidi, reordered.getBuffer(length),
                                     length, UBIDI_DO_MIRRORING, &err);
                ubidi_close(bidi);
                reordered.releaseBuffer(length);
                u_shapeArabic(reordered.getBuffer(), length,
                              shaped.getBuffer(length), length,
                              U_SHAPE_LETTERS_SHAPE | U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
                              U_SHAPE_TEXT_DIRECTION_VISUAL_LTR, &err);
                shaped.releaseBuffer(length);
                if (U_SUCCESS(err))
                {
                    U_NAMESPACE_QUALIFIER StringCharacterIterator iter(shaped);
                    for (iter.setToStart(); iter.hasNext();)
                    {
                        UChar ch = iter.nextPostInc();
                        int32_t glyph_index = FT_Get_Char_Index(ft_face[j], ch);
                        if (i == 0)
                        {
                            std::cerr << glyph_index <<  ":";
                        }
                    }
                    if (i == 0) std::cerr << std::endl;
                }
            }
        }
    }

    {
        const char **shaper_list = hb_shape_list_shapers();
        for ( ;*shaper_list; shaper_list++)
        {
            std::cerr << *shaper_list << std::endl;
        }

        std::cerr << "Starting Harfbuzz shaping" << std::endl;
        progress_timer timer2(std::clog,"Harfbuzz shaping done");
        const char* const shapers[]  = { /*"ot",*/"fallback" };
        hb_buffer_t *buffer(hb_buffer_create());

        for (unsigned i = 0; i < NUM_ITER; ++i)
        {
            for (unsigned j = 0; j < NUM_EXAMPLES; ++j)
            {
                UnicodeString text = UnicodeString::fromUTF8(texts[j]);
                int32_t length = text.length();
                hb_buffer_clear_contents(buffer);
                //hb_buffer_set_unicode_funcs(buffer.get(), hb_icu_get_unicode_funcs());
                hb_buffer_pre_allocate(buffer, length);
                hb_buffer_add_utf16(buffer, text.getBuffer(), text.length(), 0, length);
                hb_buffer_set_direction(buffer, text_directions[j]);
                hb_buffer_set_script(buffer, scripts[j]);
                hb_buffer_set_language(buffer,hb_language_from_string(languages[j], std::strlen(languages[j])));
                //hb_shape(hb_ft_font[j], buffer.get(), 0, 0);
                hb_shape_full(hb_ft_font[j], buffer, 0, 0, shapers);
                unsigned num_glyphs = hb_buffer_get_length(buffer);
                hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(buffer, NULL);
                //hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(buffer.get(), NULL);
                for (unsigned k=0; k<num_glyphs; ++k)
                {
                    int32_t glyph_index = glyphs[k].codepoint;
                    if (i == 0)
                    {
                        std::cerr  <<  glyph_index << ":";
                    }
                }
                if (i == 0) std::cerr << std::endl;
            }
        }
        hb_buffer_destroy(buffer);
    }

    // cleanup
    for (int j=0; j < NUM_EXAMPLES; ++j)
    {
        hb_font_destroy(hb_ft_font[j]);
    }
    FT_Done_FreeType(ft_library);

    return EXIT_SUCCESS;
}
Example #18
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;
}