From 19b7afca523f221494bd165680f1aa9ddf3a0e31 Mon Sep 17 00:00:00 2001 From: Konstantin Ritt Date: Sat, 7 Sep 2013 22:55:58 +0300 Subject: Add HarfBuzz-to-Qt bridging code Change-Id: I2f61566fe69d18b80d5831238beb27b34b7be1c8 Reviewed-by: Lars Knoll --- src/gui/text/qfontengine.cpp | 37 +++ src/gui/text/qharfbuzzng.cpp | 730 +++++++++++++++++++++++++++++++++++++++++++ src/gui/text/qharfbuzzng_p.h | 85 +++++ src/gui/text/text.pri | 9 + 4 files changed, 861 insertions(+) create mode 100644 src/gui/text/qharfbuzzng.cpp create mode 100644 src/gui/text/qharfbuzzng_p.h diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 9e6b8d6ffd..d561fcf823 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -48,6 +48,11 @@ #include "qvarlengtharray.h" #include #include + +#ifdef QT_ENABLE_HARFBUZZ_NG +# include "qharfbuzzng_p.h" +# include +#endif #include #include @@ -73,6 +78,10 @@ static inline bool qtransform_equals_no_translate(const QTransform &a, const QTr // Harfbuzz helper functions +#ifdef QT_ENABLE_HARFBUZZ_NG +bool useHarfbuzzNG = qgetenv("QT_HARFBUZZ") != "old"; +#endif + Q_STATIC_ASSERT(sizeof(HB_Glyph) == sizeof(glyph_t)); Q_STATIC_ASSERT(sizeof(HB_Fixed) == sizeof(QFixed)); @@ -253,6 +262,10 @@ QFixed QFontEngine::underlinePosition() const void *QFontEngine::harfbuzzFont() const { +#ifdef QT_ENABLE_HARFBUZZ_NG + if (useHarfbuzzNG) + qFatal("Called QFontEngine::harfbuzzFont() in Harfbuzz-NG mode!"); +#endif if (!font_) { HB_FontRec *hbFont = (HB_FontRec *) malloc(sizeof(HB_FontRec)); Q_CHECK_PTR(hbFont); @@ -277,6 +290,10 @@ void *QFontEngine::harfbuzzFont() const void *QFontEngine::harfbuzzFace() const { +#ifdef QT_ENABLE_HARFBUZZ_NG + if (useHarfbuzzNG) + qFatal("Called QFontEngine::harfbuzzFace() in Harfbuzz-NG mode!"); +#endif if (!face_) { HB_Face hbFace = qHBNewFace(const_cast(this), hb_getSFntTable); Q_CHECK_PTR(hbFace); @@ -292,6 +309,26 @@ void *QFontEngine::harfbuzzFace() const bool QFontEngine::supportsScript(QChar::Script script) const { +#ifdef QT_ENABLE_HARFBUZZ_NG + if (useHarfbuzzNG) { + bool ret = false; + if (hb_face_t *face = hb_qt_face_get_for_engine(const_cast(this))) { + hb_tag_t script_tag_1, script_tag_2; + hb_ot_tags_from_script(hb_qt_script_to_script(script), &script_tag_1, &script_tag_2); + + unsigned int script_index = -1; + ret = hb_ot_layout_table_find_script(face, HB_OT_TAG_GSUB, script_tag_1, &script_index); + if (!ret) { + ret = hb_ot_layout_table_find_script(face, HB_OT_TAG_GSUB, script_tag_2, &script_index); + if (!ret && script_tag_2 != HB_OT_TAG_DEFAULT_SCRIPT) + ret = hb_ot_layout_table_find_script(face, HB_OT_TAG_GSUB, HB_OT_TAG_DEFAULT_SCRIPT, &script_index); + } + + hb_face_destroy(face); + } + return ret; + } +#endif HB_Face hbFace = (HB_Face)harfbuzzFace(); return hbFace->supported_scripts[script_to_hbscript(script)]; } diff --git a/src/gui/text/qharfbuzzng.cpp b/src/gui/text/qharfbuzzng.cpp new file mode 100644 index 0000000000..96a1cff889 --- /dev/null +++ b/src/gui/text/qharfbuzzng.cpp @@ -0,0 +1,730 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Konstantin Ritt +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qharfbuzzng_p.h" + +#include +#include + +#include "qfontengine_p.h" + +QT_BEGIN_NAMESPACE + +// Unicode routines + +static const hb_script_t _qtscript_to_hbscript[] = { + HB_SCRIPT_UNKNOWN, + HB_SCRIPT_INHERITED, + HB_SCRIPT_COMMON, + + HB_SCRIPT_LATIN, + HB_SCRIPT_GREEK, + HB_SCRIPT_CYRILLIC, + HB_SCRIPT_ARMENIAN, + HB_SCRIPT_HEBREW, + HB_SCRIPT_ARABIC, + HB_SCRIPT_SYRIAC, + HB_SCRIPT_THAANA, + HB_SCRIPT_DEVANAGARI, + HB_SCRIPT_BENGALI, + HB_SCRIPT_GURMUKHI, + HB_SCRIPT_GUJARATI, + HB_SCRIPT_ORIYA, + HB_SCRIPT_TAMIL, + HB_SCRIPT_TELUGU, + HB_SCRIPT_KANNADA, + HB_SCRIPT_MALAYALAM, + HB_SCRIPT_SINHALA, + HB_SCRIPT_THAI, + HB_SCRIPT_LAO, + HB_SCRIPT_TIBETAN, + HB_SCRIPT_MYANMAR, + HB_SCRIPT_GEORGIAN, + HB_SCRIPT_HANGUL, + HB_SCRIPT_ETHIOPIC, + HB_SCRIPT_CHEROKEE, + HB_SCRIPT_CANADIAN_ABORIGINAL, + HB_SCRIPT_OGHAM, + HB_SCRIPT_RUNIC, + HB_SCRIPT_KHMER, + HB_SCRIPT_MONGOLIAN, + HB_SCRIPT_HIRAGANA, + HB_SCRIPT_KATAKANA, + HB_SCRIPT_BOPOMOFO, + HB_SCRIPT_HAN, + HB_SCRIPT_YI, + HB_SCRIPT_OLD_ITALIC, + HB_SCRIPT_GOTHIC, + HB_SCRIPT_DESERET, + HB_SCRIPT_TAGALOG, + HB_SCRIPT_HANUNOO, + HB_SCRIPT_BUHID, + HB_SCRIPT_TAGBANWA, + HB_SCRIPT_COPTIC, + HB_SCRIPT_LIMBU, + HB_SCRIPT_TAI_LE, + HB_SCRIPT_LINEAR_B, + HB_SCRIPT_UGARITIC, + HB_SCRIPT_SHAVIAN, + HB_SCRIPT_OSMANYA, + HB_SCRIPT_CYPRIOT, + HB_SCRIPT_BRAILLE, + HB_SCRIPT_BUGINESE, + HB_SCRIPT_NEW_TAI_LUE, + HB_SCRIPT_GLAGOLITIC, + HB_SCRIPT_TIFINAGH, + HB_SCRIPT_SYLOTI_NAGRI, + HB_SCRIPT_OLD_PERSIAN, + HB_SCRIPT_KHAROSHTHI, + HB_SCRIPT_BALINESE, + HB_SCRIPT_CUNEIFORM, + HB_SCRIPT_PHOENICIAN, + HB_SCRIPT_PHAGS_PA, + HB_SCRIPT_NKO, + HB_SCRIPT_SUNDANESE, + HB_SCRIPT_LEPCHA, + HB_SCRIPT_OL_CHIKI, + HB_SCRIPT_VAI, + HB_SCRIPT_SAURASHTRA, + HB_SCRIPT_KAYAH_LI, + HB_SCRIPT_REJANG, + HB_SCRIPT_LYCIAN, + HB_SCRIPT_CARIAN, + HB_SCRIPT_LYDIAN, + HB_SCRIPT_CHAM, + HB_SCRIPT_TAI_THAM, + HB_SCRIPT_TAI_VIET, + HB_SCRIPT_AVESTAN, + HB_SCRIPT_EGYPTIAN_HIEROGLYPHS, + HB_SCRIPT_SAMARITAN, + HB_SCRIPT_LISU, + HB_SCRIPT_BAMUM, + HB_SCRIPT_JAVANESE, + HB_SCRIPT_MEETEI_MAYEK, + HB_SCRIPT_IMPERIAL_ARAMAIC, + HB_SCRIPT_OLD_SOUTH_ARABIAN, + HB_SCRIPT_INSCRIPTIONAL_PARTHIAN, + HB_SCRIPT_INSCRIPTIONAL_PAHLAVI, + HB_SCRIPT_OLD_TURKIC, + HB_SCRIPT_KAITHI, + HB_SCRIPT_BATAK, + HB_SCRIPT_BRAHMI, + HB_SCRIPT_MANDAIC, + HB_SCRIPT_CHAKMA, + HB_SCRIPT_MEROITIC_CURSIVE, + HB_SCRIPT_MEROITIC_HIEROGLYPHS, + HB_SCRIPT_MIAO, + HB_SCRIPT_SHARADA, + HB_SCRIPT_SORA_SOMPENG, + HB_SCRIPT_TAKRI +}; +Q_STATIC_ASSERT(QChar::ScriptCount == sizeof(_qtscript_to_hbscript) / sizeof(_qtscript_to_hbscript[0])); + +hb_script_t hb_qt_script_to_script(QChar::Script script) +{ + return _qtscript_to_hbscript[script]; +} + +QChar::Script hb_qt_script_from_script(hb_script_t script) +{ + uint i = QChar::ScriptCount - 1; + while (i > QChar::Script_Unknown && _qtscript_to_hbscript[i] != script) + --i; + return QChar::Script(i); +} + + +static hb_unicode_combining_class_t +_hb_qt_unicode_combining_class(hb_unicode_funcs_t * /*ufuncs*/, + hb_codepoint_t unicode, + void * /*user_data*/) +{ + return hb_unicode_combining_class_t(QChar::combiningClass(unicode)); +} + +static unsigned int +_hb_qt_unicode_eastasian_width(hb_unicode_funcs_t * /*ufuncs*/, + hb_codepoint_t /*unicode*/, + void * /*user_data*/) +{ + qCritical("hb_qt_unicode_eastasian_width: not implemented!"); + return 1; +} + +static const hb_unicode_general_category_t _qtcategory_to_hbcategory[] = { + HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK, // Mn + HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK, // Mc + HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK, // Me + + HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER, // Nd + HB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER, // Nl + HB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER, // No + + HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR, // Zs + HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR, // Zl + HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR, // Zp + + HB_UNICODE_GENERAL_CATEGORY_CONTROL, // Cc + HB_UNICODE_GENERAL_CATEGORY_FORMAT, // Cf + HB_UNICODE_GENERAL_CATEGORY_SURROGATE, // Cs + HB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE, // Co + HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED, // Cn + + HB_UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER, // Lu + HB_UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER, // Ll + HB_UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER, // Lt + HB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER, // Lm + HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER, // Lo + + HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION, // Pc + HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION, // Pd + HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION, // Ps + HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION, // Pe + HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION, // Pi + HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION, // Pf + HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION, // Po + + HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL, // Sm + HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL, // Sc + HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL, // Sk + HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL // So +}; + +static hb_unicode_general_category_t +_hb_qt_unicode_general_category(hb_unicode_funcs_t * /*ufuncs*/, + hb_codepoint_t unicode, + void * /*user_data*/) +{ + return _qtcategory_to_hbcategory[QChar::category(unicode)]; +} + +static hb_codepoint_t +_hb_qt_unicode_mirroring(hb_unicode_funcs_t * /*ufuncs*/, + hb_codepoint_t unicode, + void * /*user_data*/) +{ + return QChar::mirroredChar(unicode); +} + +static hb_script_t +_hb_qt_unicode_script(hb_unicode_funcs_t * /*ufuncs*/, + hb_codepoint_t unicode, + void * /*user_data*/) +{ + return _qtscript_to_hbscript[QChar::script(unicode)]; +} + +static hb_bool_t +_hb_qt_unicode_compose(hb_unicode_funcs_t * /*ufuncs*/, + hb_codepoint_t a, hb_codepoint_t b, + hb_codepoint_t *ab, + void * /*user_data*/) +{ + // ### optimize + QString s = QString::fromUcs4(&a, 1) + QString::fromUcs4(&b, 1); + QString normalized = s.normalized(QString::NormalizationForm_C); + if (normalized.isEmpty()) + return false; + + QVector ucs4str = normalized.toUcs4(); + if (ucs4str.size() == 1) { + *ab = ucs4str.at(0); + return true; + } + + return false; +} + +static hb_bool_t +_hb_qt_unicode_decompose(hb_unicode_funcs_t * /*ufuncs*/, + hb_codepoint_t ab, + hb_codepoint_t *a, hb_codepoint_t *b, + void * /*user_data*/) +{ + // ### optimize + if (QChar::decompositionTag(ab) != QChar::Canonical) // !NFD + return false; + + QString normalized = QChar::decomposition(ab); + Q_ASSERT(!normalized.isEmpty()); + + const QVector ucs4str = normalized.toUcs4(); + Q_ASSERT(ucs4str.size() <= HB_UNICODE_MAX_DECOMPOSITION_LEN); + + if (ucs4str.size() == 1) { + *a = ucs4str.at(0); + *b = 0; + return *a != ab; + } + + if (ucs4str.size() == 2) { + *a = ucs4str.at(0); + *b = ucs4str.at(1); + + // Here's the ugly part: if ab decomposes to a single character and + // that character decomposes again, we have to detect that and undo + // the second part :-( + QString recomposed = normalized.normalized(QString::NormalizationForm_C); + if (recomposed.isEmpty() || recomposed == normalized) + return false; + + hb_codepoint_t c = recomposed.toUcs4().at(0); + if (c != *a && c != ab) { + *a = c; + *b = 0; + } + return true; + } + + // If decomposed to more than two characters, take the last one, + // and recompose the rest to get the first component + *b = ucs4str.last(); + normalized.chop(1); + QString recomposed = normalized.normalized(QString::NormalizationForm_C); + if (recomposed.isEmpty() || recomposed == normalized) + return false; + + // We expect that recomposed has exactly one character now + *a = recomposed.toUcs4().at(0); + + return true; +} + +static unsigned int +_hb_qt_unicode_decompose_compatibility(hb_unicode_funcs_t * /*ufuncs*/, + hb_codepoint_t u, + hb_codepoint_t *decomposed, + void * /*user_data*/) +{ + if (QChar::decompositionTag(u) == QChar::NoDecomposition) // !NFKD + return 0; + + const QString normalized = QChar::decomposition(u); + + uint outlen = 0; + + // ### replace with QCharIterator + const ushort *p = reinterpret_cast(normalized.unicode()); + const ushort *const e = p + normalized.size(); + for ( ; p != e; ++p) { + uint ucs4 = *p; + if (QChar::isHighSurrogate(ucs4) && p + 1 != e && QChar::isLowSurrogate(p[1])) + ucs4 = QChar::surrogateToUcs4(ucs4, *++p); + Q_ASSERT(outlen < HB_UNICODE_MAX_DECOMPOSITION_LEN); + decomposed[outlen++] = ucs4; + } + + return outlen; +} + + +struct _hb_unicode_funcs_t { + _hb_unicode_funcs_t() + { + funcs = hb_unicode_funcs_create(NULL); + hb_unicode_funcs_set_combining_class_func(funcs, _hb_qt_unicode_combining_class, NULL, NULL); + hb_unicode_funcs_set_eastasian_width_func(funcs, _hb_qt_unicode_eastasian_width, NULL, NULL); + hb_unicode_funcs_set_general_category_func(funcs, _hb_qt_unicode_general_category, NULL, NULL); + hb_unicode_funcs_set_mirroring_func(funcs, _hb_qt_unicode_mirroring, NULL, NULL); + hb_unicode_funcs_set_script_func(funcs, _hb_qt_unicode_script, NULL, NULL); + hb_unicode_funcs_set_compose_func(funcs, _hb_qt_unicode_compose, NULL, NULL); + hb_unicode_funcs_set_decompose_func(funcs, _hb_qt_unicode_decompose, NULL, NULL); + hb_unicode_funcs_set_decompose_compatibility_func(funcs, _hb_qt_unicode_decompose_compatibility, NULL, NULL); + } + ~_hb_unicode_funcs_t() + { + hb_unicode_funcs_destroy(funcs); + } + + hb_unicode_funcs_t *funcs; +}; + +Q_GLOBAL_STATIC(_hb_unicode_funcs_t, qt_ufuncs) + +hb_unicode_funcs_t *hb_qt_get_unicode_funcs() +{ + return qt_ufuncs()->funcs; +} + + +// Font routines + +static hb_bool_t +_hb_qt_font_get_glyph(hb_font_t * /*font*/, void *font_data, + hb_codepoint_t unicode, hb_codepoint_t /*variation_selector*/, + hb_codepoint_t *glyph, + void * /*user_data*/) +{ + QFontEngine *fe = (QFontEngine *)font_data; + Q_ASSERT(fe); + + glyph_t glyphs[2] = { 0, 0 }; + + QGlyphLayout g; + g.numGlyphs = 2; + g.glyphs = glyphs; + + QChar chars[4]; + int numChars = 0; + if (Q_UNLIKELY(QChar::requiresSurrogates(unicode))) { + chars[numChars++] = QChar(QChar::highSurrogate(unicode)); + chars[numChars++] = QChar(QChar::lowSurrogate(unicode)); + } else { + chars[numChars++] = QChar(unicode); + } +#if 0 + if (Q_UNLIKELY(variation_selector != 0)) { + if (Q_UNLIKELY(QChar::requiresSurrogates(variation_selector))) { + chars[numChars++] = QChar(QChar::highSurrogate(variation_selector)); + chars[numChars++] = QChar(QChar::lowSurrogate(variation_selector)); + } else { + chars[numChars++] = QChar(variation_selector); + } + } +#endif + + int numGlyphs = g.numGlyphs; + bool ok = fe->stringToCMap(chars, numChars, &g, &numGlyphs, QFontEngine::GlyphIndicesOnly); + Q_ASSERT(ok); Q_UNUSED(ok) + + *glyph = g.glyphs[0]; + + return true; +} + +static hb_position_t +_hb_qt_font_get_glyph_h_advance(hb_font_t *font, void *font_data, + hb_codepoint_t glyph, + void * /*user_data*/) +{ + QFontEngine *fe = (QFontEngine *)font_data; + Q_ASSERT(fe); + + QFixed advance_x; + QFixed advance_y; + + QGlyphLayout g; + g.numGlyphs = 1; + g.glyphs = &glyph; + g.advances_x = &advance_x; + g.advances_y = &advance_y; + + fe->recalcAdvances(&g, QFontEngine::ShaperFlags(hb_qt_font_get_use_design_metrics(font))); + + return g.advances_x[0].value(); +} + +static hb_position_t +_hb_qt_font_get_glyph_v_advance(hb_font_t * /*font*/, void * /*font_data*/, + hb_codepoint_t /*glyph*/, + void * /*user_data*/) +{ + qCritical("hb_qt_font_get_glyph_v_advance: vertical writing isn't supported!"); + return 0; +} + +static hb_bool_t +_hb_qt_font_get_glyph_h_origin(hb_font_t * /*font*/, void * /*font_data*/, + hb_codepoint_t /*glyph*/, + hb_position_t * /*x*/, hb_position_t * /*y*/, + void * /*user_data*/) +{ + return true; // we always work in the horizontal coordinates +} + +static hb_bool_t +_hb_qt_font_get_glyph_v_origin(hb_font_t * /*font*/, void * /*font_data*/, + hb_codepoint_t /*glyph*/, + hb_position_t * /*x*/, hb_position_t * /*y*/, + void * /*user_data*/) +{ + qCritical("hb_qt_get_glyph_v_origin: vertical writing isn't supported!"); + return false; +} + +static hb_position_t +_hb_qt_font_get_glyph_h_kerning(hb_font_t *font, void *font_data, + hb_codepoint_t first_glyph, hb_codepoint_t second_glyph, + void * /*user_data*/) +{ + QFontEngine *fe = (QFontEngine *)font_data; + Q_ASSERT(fe); + + glyph_t glyphs[2] = { first_glyph, second_glyph }; + QFixed advance_x; + QFixed advance_y; + + QGlyphLayout g; + g.numGlyphs = 2; + g.glyphs = glyphs; + g.advances_x = &advance_x; + g.advances_y = &advance_y; + + fe->doKerning(&g, QFontEngine::ShaperFlags(hb_qt_font_get_use_design_metrics(font))); + + return g.advances_x[0].value(); +} + +static hb_position_t +_hb_qt_font_get_glyph_v_kerning(hb_font_t * /*font*/, void * /*font_data*/, + hb_codepoint_t /*first_glyph*/, hb_codepoint_t /*second_glyph*/, + void * /*user_data*/) +{ + qCritical("hb_qt_get_glyph_v_kerning: vertical writing isn't supported!"); + return 0; +} + +static hb_bool_t +_hb_qt_font_get_glyph_extents(hb_font_t * /*font*/, void *font_data, + hb_codepoint_t glyph, + hb_glyph_extents_t *extents, + void * /*user_data*/) +{ + QFontEngine *fe = (QFontEngine *)font_data; + Q_ASSERT(fe); + + glyph_metrics_t gm = fe->boundingBox(glyph); + + extents->x_bearing = gm.x.value(); + extents->y_bearing = gm.y.value(); + extents->width = gm.width.value(); + extents->height = gm.height.value(); + + return true; +} + +static hb_bool_t +_hb_qt_font_get_glyph_contour_point(hb_font_t * /*font*/, void *font_data, + hb_codepoint_t glyph, + unsigned int point_index, hb_position_t *x, hb_position_t *y, + void * /*user_data*/) +{ + QFontEngine *fe = (QFontEngine *)font_data; + Q_ASSERT(fe); + + QFixed xpos, ypos; + quint32 numPoints = 1; + if (Q_LIKELY(fe->getPointInOutline(glyph, 0, point_index, &xpos, &ypos, &numPoints) == 0)) { + *x = xpos.value(); + *y = ypos.value(); + return true; + } + + *x = *y = 0; + return false; +} + +static hb_bool_t +_hb_qt_font_get_glyph_name(hb_font_t * /*font*/, void * /*font_data*/, + hb_codepoint_t /*glyph*/, + char *name, unsigned int size, + void * /*user_data*/) +{ + qCritical("hb_qt_font_get_glyph_name: not implemented!"); + if (size) + *name = '\0'; + return false; +} + +static hb_bool_t +_hb_qt_font_get_glyph_from_name(hb_font_t * /*font*/, void * /*font_data*/, + const char * /*name*/, int /*len*/, + hb_codepoint_t *glyph, + void * /*user_data*/) +{ + qCritical("hb_qt_font_get_glyph_from_name: not implemented!"); + *glyph = 0; + return false; +} + + +static hb_user_data_key_t _useDesignMetricsKey; + +void hb_qt_font_set_use_design_metrics(hb_font_t *font, uint value) +{ + hb_font_set_user_data(font, &_useDesignMetricsKey, (void *)value, NULL, true); +} + +uint hb_qt_font_get_use_design_metrics(hb_font_t *font) +{ + return uint(hb_font_get_user_data(font, &_useDesignMetricsKey)); +} + + +struct _hb_qt_font_funcs_t { + _hb_qt_font_funcs_t() + { + funcs = hb_font_funcs_create(); + hb_font_funcs_set_glyph_func(funcs, _hb_qt_font_get_glyph, NULL, NULL); + hb_font_funcs_set_glyph_h_advance_func(funcs, _hb_qt_font_get_glyph_h_advance, NULL, NULL); + hb_font_funcs_set_glyph_v_advance_func(funcs, _hb_qt_font_get_glyph_v_advance, NULL, NULL); + hb_font_funcs_set_glyph_h_origin_func(funcs, _hb_qt_font_get_glyph_h_origin, NULL, NULL); + hb_font_funcs_set_glyph_v_origin_func(funcs, _hb_qt_font_get_glyph_v_origin, NULL, NULL); + hb_font_funcs_set_glyph_h_kerning_func(funcs, _hb_qt_font_get_glyph_h_kerning, NULL, NULL); + hb_font_funcs_set_glyph_v_kerning_func(funcs, _hb_qt_font_get_glyph_v_kerning, NULL, NULL); + hb_font_funcs_set_glyph_extents_func(funcs, _hb_qt_font_get_glyph_extents, NULL, NULL); + hb_font_funcs_set_glyph_contour_point_func(funcs, _hb_qt_font_get_glyph_contour_point, NULL, NULL); + hb_font_funcs_set_glyph_name_func(funcs, _hb_qt_font_get_glyph_name, NULL, NULL); + hb_font_funcs_set_glyph_from_name_func(funcs, _hb_qt_font_get_glyph_from_name, NULL, NULL); + } + ~_hb_qt_font_funcs_t() + { + hb_font_funcs_destroy(funcs); + } + + hb_font_funcs_t *funcs; +}; + +Q_GLOBAL_STATIC(_hb_qt_font_funcs_t, qt_ffuncs) + +hb_font_funcs_t *hb_qt_get_font_funcs() +{ + return qt_ffuncs()->funcs; +} + + +static hb_blob_t * +_hb_qt_get_font_table(hb_face_t * /*face*/, hb_tag_t tag, void *user_data) +{ + QFontEngine *fe = (QFontEngine *)user_data; + Q_ASSERT(fe); + + uint length = 0; + if (Q_UNLIKELY(!fe->getSfntTableData(tag, 0, &length) || length == 0)) + return hb_blob_get_empty(); + + char *buffer = (char *)malloc(length); + Q_CHECK_PTR(buffer); + + if (Q_UNLIKELY(!fe->getSfntTableData(tag, reinterpret_cast(buffer), &length))) + length = 0; + + return hb_blob_create(const_cast(buffer), length, + HB_MEMORY_MODE_WRITABLE, + buffer, free); +} + +static inline hb_face_t * +_hb_qt_face_create(QFontEngine *fe) +{ + hb_face_t *face; + + face = hb_face_create_for_tables(_hb_qt_get_font_table, (void *)fe, NULL); + if (Q_UNLIKELY(hb_face_is_immutable(face))) { + hb_face_destroy(face); + return NULL; + } + + hb_face_set_index(face, fe->faceId().index); + hb_face_set_upem(face, fe->emSquareSize().truncate()); + + return face; +} + +static void +_hb_qt_face_release(void *user_data) +{ + if (Q_LIKELY(user_data)) + hb_face_destroy((hb_face_t *)user_data); +} + +hb_face_t *hb_qt_face_get_for_engine(QFontEngine *fe) +{ + if (Q_UNLIKELY(!fe->face_)) { + fe->face_ = _hb_qt_face_create(fe); + if (Q_UNLIKELY(!fe->face_)) + return NULL; + fe->face_destroy_func = _hb_qt_face_release; + } + + return hb_face_reference((hb_face_t *)fe->face_); +} + + +static inline hb_font_t * +_hb_qt_font_create(QFontEngine *fe) +{ + Q_ASSERT(fe); + + hb_face_t *face = hb_qt_face_get_for_engine(fe); + if (Q_UNLIKELY(!face)) + return NULL; + + hb_font_t *font = hb_font_create(face); + + hb_face_destroy(face); // ref-ed in hb_qt_face_get_for_engine() + + if (Q_UNLIKELY(hb_font_is_immutable(font))) { + hb_font_destroy(font); + return NULL; + } + + const int y_ppem = fe->fontDef.pixelSize; + const int x_ppem = (fe->fontDef.pixelSize * fe->fontDef.stretch) / 100; + + hb_font_set_funcs(font, hb_qt_get_font_funcs(), (void *)fe, NULL); + hb_font_set_scale(font, QFixed(x_ppem).value(), -QFixed(y_ppem).value()); + hb_font_set_ppem(font, x_ppem, y_ppem); + + return font; +} + +static void +_hb_qt_font_release(void *user_data) +{ + if (Q_LIKELY(user_data)) + hb_font_destroy((hb_font_t *)user_data); +} + +hb_font_t *hb_qt_font_get_for_engine(QFontEngine *fe) +{ + if (Q_UNLIKELY(!fe->font_)) { + fe->font_ = _hb_qt_font_create(fe); + if (Q_UNLIKELY(!fe->font_)) + return NULL; + fe->font_destroy_func = _hb_qt_font_release; + } + + return hb_font_reference((hb_font_t *)fe->font_); +} + +QT_END_NAMESPACE diff --git a/src/gui/text/qharfbuzzng_p.h b/src/gui/text/qharfbuzzng_p.h new file mode 100644 index 0000000000..10cac69875 --- /dev/null +++ b/src/gui/text/qharfbuzzng_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Konstantin Ritt +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHARFBUZZNG_P_H +#define QHARFBUZZNG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QFontEngine; + +// Unicode + +Q_GUI_EXPORT hb_script_t hb_qt_script_to_script(QChar::Script script); +Q_GUI_EXPORT QChar::Script hb_qt_script_from_script(hb_script_t script); + +Q_GUI_EXPORT hb_unicode_funcs_t *hb_qt_get_unicode_funcs(); + + +// Font + +Q_GUI_EXPORT hb_font_funcs_t *hb_qt_get_font_funcs(); + +Q_GUI_EXPORT hb_face_t *hb_qt_face_get_for_engine(QFontEngine *fe); +Q_GUI_EXPORT hb_font_t *hb_qt_font_get_for_engine(QFontEngine *fe); + +Q_GUI_EXPORT void hb_qt_font_set_use_design_metrics(hb_font_t *font, uint value); +Q_GUI_EXPORT uint hb_qt_font_get_use_design_metrics(hb_font_t *font); + +QT_END_NAMESPACE + +#endif // QHARFBUZZNG_P_H diff --git a/src/gui/text/text.pri b/src/gui/text/text.pri index e9ea2a3f83..59a6f346e2 100644 --- a/src/gui/text/text.pri +++ b/src/gui/text/text.pri @@ -84,3 +84,12 @@ SOURCES += \ HEADERS += \ text/qplatformfontdatabase.h + +contains(QT_CONFIG, harfbuzz)|contains(QT_CONFIG, system-harfbuzz) { + DEFINES += QT_ENABLE_HARFBUZZ_NG + + include($$PWD/../../3rdparty/harfbuzz.pri) + + SOURCES += text/qharfbuzzng.cpp + HEADERS += text/qharfbuzzng_p.h +} -- cgit v1.2.3