summaryrefslogtreecommitdiffstats
path: root/tqtinterface/qt4/include/private/qt4_harfbuzz/src/harfbuzz-shaper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tqtinterface/qt4/include/private/qt4_harfbuzz/src/harfbuzz-shaper.cpp')
-rw-r--r--tqtinterface/qt4/include/private/qt4_harfbuzz/src/harfbuzz-shaper.cpp1338
1 files changed, 1338 insertions, 0 deletions
diff --git a/tqtinterface/qt4/include/private/qt4_harfbuzz/src/harfbuzz-shaper.cpp b/tqtinterface/qt4/include/private/qt4_harfbuzz/src/harfbuzz-shaper.cpp
new file mode 100644
index 0000000..ce4d4ac
--- /dev/null
+++ b/tqtinterface/qt4/include/private/qt4_harfbuzz/src/harfbuzz-shaper.cpp
@@ -0,0 +1,1338 @@
+/*
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * This is part of HarfBuzz, an OpenType Layout engine library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+#include "harfbuzz-shaper.h"
+#include "harfbuzz-shaper-private.h"
+
+#include "harfbuzz-stream-private.h"
+#include <assert.h>
+#include <stdio.h>
+
+#define HB_MIN(a, b) ((a) < (b) ? (a) : (b))
+#define HB_MAX(a, b) ((a) > (b) ? (a) : (b))
+
+// -----------------------------------------------------------------------------------------------------
+//
+// The line break algorithm. See http://www.unicode.org/reports/tr14/tr14-13.html
+//
+// -----------------------------------------------------------------------------------------------------
+
+/* The Unicode algorithm does in our opinion allow line breaks at some
+ places they shouldn't be allowed. The following changes were thus
+ made in comparison to the Unicode reference:
+
+ EX->AL from DB to IB
+ SY->AL from DB to IB
+ SY->PO from DB to IB
+ SY->PR from DB to IB
+ SY->OP from DB to IB
+ AL->PR from DB to IB
+ AL->PO from DB to IB
+ PR->PR from DB to IB
+ PO->PO from DB to IB
+ PR->PO from DB to IB
+ PO->PR from DB to IB
+ HY->PO from DB to IB
+ HY->PR from DB to IB
+ HY->OP from DB to IB
+ NU->EX from PB to IB
+ EX->PO from DB to IB
+*/
+
+// The following line break classes are not treated by the table:
+// AI, BK, CB, CR, LF, NL, SA, SG, SP, XX
+
+enum break_class {
+ // the first 4 values have to agree with the enum in QCharAttributes
+ ProhibitedBreak, // PB in table
+ DirectBreak, // DB in table
+ IndirectBreak, // IB in table
+ CombiningIndirectBreak, // CI in table
+ CombiningProhibitedBreak // CP in table
+};
+#define DB DirectBreak
+#define IB IndirectBreak
+#define CI CombiningIndirectBreak
+#define CP CombiningProhibitedBreak
+#define PB ProhibitedBreak
+
+static const hb_uint8 breakTable[HB_LineBreak_JT+1][HB_LineBreak_JT+1] =
+{
+/* OP CL QU GL NS EX SY IS PR PO NU AL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT */
+/* OP */ { PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, PB, CP, PB, PB, PB, PB, PB, PB },
+/* CL */ { DB, PB, IB, IB, PB, PB, PB, PB, IB, IB, IB, IB, DB, DB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* QU */ { PB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, IB, IB, IB, IB, IB, IB, IB, PB, CI, PB, IB, IB, IB, IB, IB },
+/* GL */ { IB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, IB, IB, IB, IB, IB, IB, IB, PB, CI, PB, IB, IB, IB, IB, IB },
+/* NS */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, DB, DB, DB, DB, DB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* EX */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, IB, DB, IB, DB, DB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* SY */ { IB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, IB, DB, DB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* IS */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, DB, IB, IB, DB, DB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* PR */ { IB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, IB, IB, DB, IB, IB, DB, DB, PB, CI, PB, IB, IB, IB, IB, IB },
+/* PO */ { IB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, IB, DB, DB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* NU */ { IB, PB, IB, IB, IB, IB, PB, PB, IB, IB, IB, IB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* AL */ { IB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, IB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* ID */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, IB, DB, DB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* IN */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, DB, DB, DB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* HY */ { IB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, DB, DB, DB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* BA */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, DB, DB, DB, DB, DB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* BB */ { IB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, IB, IB, IB, IB, IB, IB, IB, PB, CI, PB, IB, IB, IB, IB, IB },
+/* B2 */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, DB, DB, DB, DB, DB, IB, IB, DB, PB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* ZW */ { DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, DB, PB, DB, DB, DB, DB, DB, DB, DB },
+/* CM */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, DB, IB, IB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, DB },
+/* WJ */ { IB, PB, IB, IB, IB, PB, PB, PB, IB, IB, IB, IB, IB, IB, IB, IB, IB, IB, PB, CI, PB, IB, IB, IB, IB, IB },
+/* H2 */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, IB, DB, DB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, IB, IB },
+/* H3 */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, IB, DB, DB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, IB },
+/* JL */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, IB, DB, DB, DB, IB, IB, IB, DB, DB, PB, CI, PB, IB, IB, IB, IB, DB },
+/* JV */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, IB, DB, DB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, IB, IB },
+/* JT */ { DB, PB, IB, IB, IB, PB, PB, PB, DB, IB, DB, DB, DB, IB, IB, IB, DB, DB, PB, CI, PB, DB, DB, DB, DB, IB }
+};
+#undef DB
+#undef IB
+#undef CI
+#undef CP
+#undef PB
+
+static const hb_uint8 graphemeTable[HB_Grapheme_LVT + 1][HB_Grapheme_LVT + 1] =
+{
+// Other, CR, LF, Control,Extend,L, V, T, LV, LVT
+ { true , true , true , true , true , true , true , true , true , true }, // Other,
+ { true , true , true , true , true , true , true , true , true , true }, // CR,
+ { true , false, true , true , true , true , true , true , true , true }, // LF,
+ { true , true , true , true , true , true , true , true , true , true }, // Control,
+ { false, true , true , true , false, false, false, false, false, false }, // Extend,
+ { true , true , true , true , true , false, true , true , true , true }, // L,
+ { true , true , true , true , true , false, false, true , false, true }, // V,
+ { true , true , true , true , true , true , false, false, false, false }, // T,
+ { true , true , true , true , true , false, true , true , true , true }, // LV,
+ { true , true , true , true , true , false, true , true , true , true }, // LVT
+};
+
+static void calcLineBreaks(const HB_UChar16 *uc, hb_uint32 len, HB_CharAttributes *charAttributes)
+{
+ if (!len)
+ return;
+
+ // ##### can this fail if the first char is a surrogate?
+ HB_LineBreakClass cls;
+ HB_GraphemeClass grapheme;
+ HB_GetGraphemeAndLineBreakClass(*uc, &grapheme, &cls);
+ // handle case where input starts with an LF
+ if (cls == HB_LineBreak_LF)
+ cls = HB_LineBreak_BK;
+
+ charAttributes[0].whiteSpace = (cls == HB_LineBreak_SP || cls == HB_LineBreak_BK);
+ charAttributes[0].charStop = true;
+
+ int lcls = cls;
+ for (hb_uint32 i = 1; i < len; ++i) {
+ charAttributes[i].whiteSpace = false;
+ charAttributes[i].charStop = true;
+
+ HB_UChar32 code = uc[i];
+ HB_GraphemeClass ngrapheme;
+ HB_LineBreakClass ncls;
+ HB_GetGraphemeAndLineBreakClass(code, &ngrapheme, &ncls);
+ charAttributes[i].charStop = graphemeTable[ngrapheme][grapheme];
+ // handle surrogates
+ if (ncls == HB_LineBreak_SG) {
+ if (HB_IsHighSurrogate(uc[i]) && i < len - 1 && HB_IsLowSurrogate(uc[i+1])) {
+ continue;
+ } else if (HB_IsLowSurrogate(uc[i]) && HB_IsHighSurrogate(uc[i-1])) {
+ code = HB_SurrogateToUcs4(uc[i-1], uc[i]);
+ HB_GetGraphemeAndLineBreakClass(code, &ngrapheme, &ncls);
+ charAttributes[i].charStop = false;
+ } else {
+ ncls = HB_LineBreak_AL;
+ }
+ }
+
+ // set white space and char stop flag
+ if (ncls >= HB_LineBreak_SP)
+ charAttributes[i].whiteSpace = true;
+
+ HB_LineBreakType lineBreakType = HB_NoBreak;
+ if (cls >= HB_LineBreak_LF) {
+ lineBreakType = HB_ForcedBreak;
+ } else if(cls == HB_LineBreak_CR) {
+ lineBreakType = (ncls == HB_LineBreak_LF) ? HB_NoBreak : HB_ForcedBreak;
+ }
+
+ if (ncls == HB_LineBreak_SP)
+ goto next_no_cls_update;
+ if (ncls >= HB_LineBreak_CR)
+ goto next;
+
+ {
+ int tcls = ncls;
+ // for south east asian chars that require a complex (dictionary analysis), the unicode
+ // standard recommends to treat them as AL. thai_attributes and other attribute methods that
+ // do dictionary analysis can override
+ if (tcls >= HB_LineBreak_SA)
+ tcls = HB_LineBreak_AL;
+ if (cls >= HB_LineBreak_SA)
+ cls = HB_LineBreak_AL;
+
+ int brk = breakTable[cls][tcls];
+ switch (brk) {
+ case DirectBreak:
+ lineBreakType = HB_Break;
+ if (uc[i-1] == 0xad) // soft hyphen
+ lineBreakType = HB_SoftHyphen;
+ break;
+ case IndirectBreak:
+ lineBreakType = (lcls == HB_LineBreak_SP) ? HB_Break : HB_NoBreak;
+ break;
+ case CombiningIndirectBreak:
+ lineBreakType = HB_NoBreak;
+ if (lcls == HB_LineBreak_SP){
+ if (i > 1)
+ charAttributes[i-2].lineBreakType = HB_Break;
+ } else {
+ goto next_no_cls_update;
+ }
+ break;
+ case CombiningProhibitedBreak:
+ lineBreakType = HB_NoBreak;
+ if (lcls != HB_LineBreak_SP)
+ goto next_no_cls_update;
+ case ProhibitedBreak:
+ default:
+ break;
+ }
+ }
+ next:
+ cls = ncls;
+ next_no_cls_update:
+ lcls = ncls;
+ grapheme = ngrapheme;
+ charAttributes[i-1].lineBreakType = lineBreakType;
+ }
+ charAttributes[len-1].lineBreakType = HB_ForcedBreak;
+}
+
+// --------------------------------------------------------------------------------------------------------------------------------------------
+//
+// Basic processing
+//
+// --------------------------------------------------------------------------------------------------------------------------------------------
+
+static inline void positionCluster(HB_ShaperItem *item, int gfrom, int glast)
+{
+ int nmarks = glast - gfrom;
+ assert(nmarks > 0);
+
+ HB_Glyph *glyphs = item->glyphs;
+ HB_GlyphAttributes *attributes = item->attributes;
+
+ HB_GlyphMetrics baseMetrics;
+ item->font->klass->getGlyphMetrics(item->font, glyphs[gfrom], &baseMetrics);
+
+ if (item->item.script == HB_Script_Hebrew
+ && (-baseMetrics.y) > baseMetrics.height)
+ // we need to attach below the baseline, because of the hebrew iud.
+ baseMetrics.height = -baseMetrics.y;
+
+// qDebug("---> positionCluster: cluster from %d to %d", gfrom, glast);
+// qDebug("baseInfo: %f/%f (%f/%f) off=%f/%f", baseInfo.x, baseInfo.y, baseInfo.width, baseInfo.height, baseInfo.xoff, baseInfo.yoff);
+
+ HB_Fixed size = item->font->klass->getFontMetric(item->font, HB_FontAscent) / 10;
+ HB_Fixed offsetBase = HB_FIXED_CONSTANT(1) + (size - HB_FIXED_CONSTANT(4)) / 4;
+ if (size > HB_FIXED_CONSTANT(4))
+ offsetBase += HB_FIXED_CONSTANT(4);
+ else
+ offsetBase += size;
+ //qreal offsetBase = (size - 4) / 4 + qMin<qreal>(size, 4) + 1;
+// qDebug("offset = %f", offsetBase);
+
+ bool rightToLeft = item->item.bidiLevel % 2;
+
+ int i;
+ unsigned char lastCmb = 0;
+ HB_GlyphMetrics attachmentRect;
+ memset(&attachmentRect, 0, sizeof(attachmentRect));
+
+ for(i = 1; i <= nmarks; i++) {
+ HB_Glyph mark = glyphs[gfrom+i];
+ HB_GlyphMetrics markMetrics;
+ item->font->klass->getGlyphMetrics(item->font, mark, &markMetrics);
+ HB_FixedPoint p;
+ p.x = p.y = 0;
+// qDebug("markInfo: %f/%f (%f/%f) off=%f/%f", markInfo.x, markInfo.y, markInfo.width, markInfo.height, markInfo.xoff, markInfo.yoff);
+
+ HB_Fixed offset = offsetBase;
+ unsigned char cmb = attributes[gfrom+i].combiningClass;
+
+ // ### maybe the whole position determination should move down to heuristicSetGlyphAttributes. Would save some
+ // bits in the glyphAttributes structure.
+ if (cmb < 200) {
+ // fixed position classes. We approximate by mapping to one of the others.
+ // currently I added only the ones for arabic, hebrew, lao and thai.
+
+ // for Lao and Thai marks with class 0, see below (heuristicSetGlyphAttributes)
+
+ // add a bit more offset to arabic, a bit hacky
+ if (cmb >= 27 && cmb <= 36 && offset < 3)
+ offset +=1;
+ // below
+ if ((cmb >= 10 && cmb <= 18) ||
+ cmb == 20 || cmb == 22 ||
+ cmb == 29 || cmb == 32)
+ cmb = HB_Combining_Below;
+ // above
+ else if (cmb == 23 || cmb == 27 || cmb == 28 ||
+ cmb == 30 || cmb == 31 || (cmb >= 33 && cmb <= 36))
+ cmb = HB_Combining_Above;
+ //below-right
+ else if (cmb == 9 || cmb == 103 || cmb == 118)
+ cmb = HB_Combining_BelowRight;
+ // above-right
+ else if (cmb == 24 || cmb == 107 || cmb == 122)
+ cmb = HB_Combining_AboveRight;
+ else if (cmb == 25)
+ cmb = HB_Combining_AboveLeft;
+ // fixed:
+ // 19 21
+
+ }
+
+ // combining marks of different class don't interact. Reset the rectangle.
+ if (cmb != lastCmb) {
+ //qDebug("resetting rect");
+ attachmentRect = baseMetrics;
+ }
+
+ switch(cmb) {
+ case HB_Combining_DoubleBelow:
+ // ### wrong in rtl context!
+ case HB_Combining_BelowLeft:
+ p.y += offset;
+ case HB_Combining_BelowLeftAttached:
+ p.x += attachmentRect.x - markMetrics.x;
+ p.y += (attachmentRect.y + attachmentRect.height) - markMetrics.y;
+ break;
+ case HB_Combining_Below:
+ p.y += offset;
+ case HB_Combining_BelowAttached:
+ p.x += attachmentRect.x - markMetrics.x;
+ p.y += (attachmentRect.y + attachmentRect.height) - markMetrics.y;
+
+ p.x += (attachmentRect.width - markMetrics.width) / 2;
+ break;
+ case HB_Combining_BelowRight:
+ p.y += offset;
+ case HB_Combining_BelowRightAttached:
+ p.x += attachmentRect.x + attachmentRect.width - markMetrics.width - markMetrics.x;
+ p.y += attachmentRect.y + attachmentRect.height - markMetrics.y;
+ break;
+ case HB_Combining_Left:
+ p.x -= offset;
+ case HB_Combining_LeftAttached:
+ break;
+ case HB_Combining_Right:
+ p.x += offset;
+ case HB_Combining_RightAttached:
+ break;
+ case HB_Combining_DoubleAbove:
+ // ### wrong in RTL context!
+ case HB_Combining_AboveLeft:
+ p.y -= offset;
+ case HB_Combining_AboveLeftAttached:
+ p.x += attachmentRect.x - markMetrics.x;
+ p.y += attachmentRect.y - markMetrics.y - markMetrics.height;
+ break;
+ case HB_Combining_Above:
+ p.y -= offset;
+ case HB_Combining_AboveAttached:
+ p.x += attachmentRect.x - markMetrics.x;
+ p.y += attachmentRect.y - markMetrics.y - markMetrics.height;
+
+ p.x += (attachmentRect.width - markMetrics.width) / 2;
+ break;
+ case HB_Combining_AboveRight:
+ p.y -= offset;
+ case HB_Combining_AboveRightAttached:
+ p.x += attachmentRect.x + attachmentRect.width - markMetrics.x - markMetrics.width;
+ p.y += attachmentRect.y - markMetrics.y - markMetrics.height;
+ break;
+
+ case HB_Combining_IotaSubscript:
+ default:
+ break;
+ }
+// qDebug("char=%x combiningClass = %d offset=%f/%f", mark, cmb, p.x(), p.y());
+ markMetrics.x += p.x;
+ markMetrics.y += p.y;
+
+ HB_GlyphMetrics unitedAttachmentRect = attachmentRect;
+ unitedAttachmentRect.x = HB_MIN(attachmentRect.x, markMetrics.x);
+ unitedAttachmentRect.y = HB_MIN(attachmentRect.y, markMetrics.y);
+ unitedAttachmentRect.width = HB_MAX(attachmentRect.x + attachmentRect.width, markMetrics.x + markMetrics.width) - unitedAttachmentRect.x;
+ unitedAttachmentRect.height = HB_MAX(attachmentRect.y + attachmentRect.height, markMetrics.y + markMetrics.height) - unitedAttachmentRect.y;
+ attachmentRect = unitedAttachmentRect;
+
+ lastCmb = cmb;
+ if (rightToLeft) {
+ item->offsets[gfrom+i].x = p.x;
+ item->offsets[gfrom+i].y = p.y;
+ } else {
+ item->offsets[gfrom+i].x = p.x - baseMetrics.xOffset;
+ item->offsets[gfrom+i].y = p.y - baseMetrics.yOffset;
+ }
+ item->advances[gfrom+i] = 0;
+ }
+}
+
+void HB_HeuristicPosition(HB_ShaperItem *item)
+{
+ HB_GetGlyphAdvances(item);
+ HB_GlyphAttributes *attributes = item->attributes;
+
+ int cEnd = -1;
+ int i = item->num_glyphs;
+ while (i--) {
+ if (cEnd == -1 && attributes[i].mark) {
+ cEnd = i;
+ } else if (cEnd != -1 && !attributes[i].mark) {
+ positionCluster(item, i, cEnd);
+ cEnd = -1;
+ }
+ }
+}
+
+// set the glyph attributes heuristically. Assumes a 1 to 1 relationship between chars and glyphs
+// and no reordering.
+// also computes logClusters heuristically
+void HB_HeuristicSetGlyphAttributes(HB_ShaperItem *item)
+{
+ const HB_UChar16 *uc = item->string + item->item.pos;
+ hb_uint32 length = item->item.length;
+
+ // ### zeroWidth and justification are missing here!!!!!
+
+ assert(item->num_glyphs <= length);
+
+// qDebug("QScriptEngine::heuristicSetGlyphAttributes, num_glyphs=%d", item->num_glyphs);
+ HB_GlyphAttributes *attributes = item->attributes;
+ unsigned short *logClusters = item->log_clusters;
+
+ hb_uint32 glyph_pos = 0;
+ hb_uint32 i;
+ for (i = 0; i < length; i++) {
+ if (HB_IsHighSurrogate(uc[i]) && i < length - 1
+ && HB_IsLowSurrogate(uc[i + 1])) {
+ logClusters[i] = glyph_pos;
+ logClusters[++i] = glyph_pos;
+ } else {
+ logClusters[i] = glyph_pos;
+ }
+ ++glyph_pos;
+ }
+ assert(glyph_pos == item->num_glyphs);
+
+ // first char in a run is never (treated as) a mark
+ int cStart = 0;
+ const bool symbolFont = item->face->isSymbolFont;
+ attributes[0].mark = false;
+ attributes[0].clusterStart = true;
+ attributes[0].dontPrint = (!symbolFont && uc[0] == 0x00ad) || HB_IsControlChar(uc[0]);
+
+ int pos = 0;
+ HB_CharCategory lastCat;
+ int dummy;
+ HB_GetUnicodeCharProperties(uc[0], &lastCat, &dummy);
+ for (i = 1; i < length; ++i) {
+ if (logClusters[i] == pos)
+ // same glyph
+ continue;
+ ++pos;
+ while (pos < logClusters[i]) {
+ attributes[pos] = attributes[pos-1];
+ ++pos;
+ }
+ // hide soft-hyphens by default
+ if ((!symbolFont && uc[i] == 0x00ad) || HB_IsControlChar(uc[i]))
+ attributes[pos].dontPrint = true;
+ HB_CharCategory cat;
+ int cmb;
+ HB_GetUnicodeCharProperties(uc[i], &cat, &cmb);
+ if (cat != HB_Mark_NonSpacing) {
+ attributes[pos].mark = false;
+ attributes[pos].clusterStart = true;
+ attributes[pos].combiningClass = 0;
+ cStart = logClusters[i];
+ } else {
+ if (cmb == 0) {
+ // Fix 0 combining classes
+ if ((uc[pos] & 0xff00) == 0x0e00) {
+ // thai or lao
+ if (uc[pos] == 0xe31 ||
+ uc[pos] == 0xe34 ||
+ uc[pos] == 0xe35 ||
+ uc[pos] == 0xe36 ||
+ uc[pos] == 0xe37 ||
+ uc[pos] == 0xe47 ||
+ uc[pos] == 0xe4c ||
+ uc[pos] == 0xe4d ||
+ uc[pos] == 0xe4e) {
+ cmb = HB_Combining_AboveRight;
+ } else if (uc[pos] == 0xeb1 ||
+ uc[pos] == 0xeb4 ||
+ uc[pos] == 0xeb5 ||
+ uc[pos] == 0xeb6 ||
+ uc[pos] == 0xeb7 ||
+ uc[pos] == 0xebb ||
+ uc[pos] == 0xecc ||
+ uc[pos] == 0xecd) {
+ cmb = HB_Combining_Above;
+ } else if (uc[pos] == 0xebc) {
+ cmb = HB_Combining_Below;
+ }
+ }
+ }
+
+ attributes[pos].mark = true;
+ attributes[pos].clusterStart = false;
+ attributes[pos].combiningClass = cmb;
+ logClusters[i] = cStart;
+ }
+ // one gets an inter character justification point if the current char is not a non spacing mark.
+ // as then the current char belongs to the last one and one gets a space justification point
+ // after the space char.
+ if (lastCat == HB_Separator_Space)
+ attributes[pos-1].justification = HB_Space;
+ else if (cat != HB_Mark_NonSpacing)
+ attributes[pos-1].justification = HB_Character;
+ else
+ attributes[pos-1].justification = HB_NoJustification;
+
+ lastCat = cat;
+ }
+ pos = logClusters[length-1];
+ if (lastCat == HB_Separator_Space)
+ attributes[pos].justification = HB_Space;
+ else
+ attributes[pos].justification = HB_Character;
+}
+
+#ifndef NO_OPENTYPE
+static const HB_OpenTypeFeature basic_features[] = {
+ { HB_MAKE_TAG('c', 'c', 'm', 'p'), CcmpProperty },
+ { HB_MAKE_TAG('l', 'i', 'g', 'a'), CcmpProperty },
+ { HB_MAKE_TAG('c', 'l', 'i', 'g'), CcmpProperty },
+ {0, 0}
+};
+#endif
+
+HB_Bool HB_ConvertStringToGlyphIndices(HB_ShaperItem *shaper_item)
+{
+ if (shaper_item->glyphIndicesPresent) {
+ shaper_item->num_glyphs = shaper_item->initialGlyphCount;
+ shaper_item->glyphIndicesPresent = false;
+ return true;
+ }
+ return shaper_item->font->klass
+ ->convertStringToGlyphIndices(shaper_item->font,
+ shaper_item->string + shaper_item->item.pos, shaper_item->item.length,
+ shaper_item->glyphs, &shaper_item->num_glyphs,
+ shaper_item->item.bidiLevel % 2);
+}
+
+HB_Bool HB_BasicShape(HB_ShaperItem *shaper_item)
+{
+#ifndef NO_OPENTYPE
+ const int availableGlyphs = shaper_item->num_glyphs;
+#endif
+
+ if (!HB_ConvertStringToGlyphIndices(shaper_item))
+ return false;
+
+ HB_HeuristicSetGlyphAttributes(shaper_item);
+
+#ifndef NO_OPENTYPE
+ if (HB_SelectScript(shaper_item, basic_features)) {
+ HB_OpenTypeShape(shaper_item, /*properties*/0);
+ return HB_OpenTypePosition(shaper_item, availableGlyphs, /*doLogClusters*/true);
+ }
+#endif
+
+ HB_HeuristicPosition(shaper_item);
+ return true;
+}
+
+const HB_ScriptEngine HB_ScriptEngines[] = {
+ // Common
+ { HB_BasicShape, 0},
+ // Greek
+ { HB_GreekShape, 0},
+ // Cyrillic
+ { HB_BasicShape, 0},
+ // Armenian
+ { HB_BasicShape, 0},
+ // Hebrew
+ { HB_HebrewShape, 0 },
+ // Arabic
+ { HB_ArabicShape, 0},
+ // Syriac
+ { HB_ArabicShape, 0},
+ // Thaana
+ { HB_BasicShape, 0 },
+ // Devanagari
+ { HB_IndicShape, HB_IndicAttributes },
+ // Bengali
+ { HB_IndicShape, HB_IndicAttributes },
+ // Gurmukhi
+ { HB_IndicShape, HB_IndicAttributes },
+ // Gujarati
+ { HB_IndicShape, HB_IndicAttributes },
+ // Oriya
+ { HB_IndicShape, HB_IndicAttributes },
+ // Tamil
+ { HB_IndicShape, HB_IndicAttributes },
+ // Telugu
+ { HB_IndicShape, HB_IndicAttributes },
+ // Kannada
+ { HB_IndicShape, HB_IndicAttributes },
+ // Malayalam
+ { HB_IndicShape, HB_IndicAttributes },
+ // Sinhala
+ { HB_IndicShape, HB_IndicAttributes },
+ // Thai
+ { HB_BasicShape, HB_ThaiAttributes },
+ // Lao
+ { HB_BasicShape, 0 },
+ // Tibetan
+ { HB_TibetanShape, HB_TibetanAttributes },
+ // Myanmar
+ { HB_MyanmarShape, HB_MyanmarAttributes },
+ // Georgian
+ { HB_BasicShape, 0 },
+ // Hangul
+ { HB_HangulShape, 0 },
+ // Ogham
+ { HB_BasicShape, 0 },
+ // Runic
+ { HB_BasicShape, 0 },
+ // Khmer
+ { HB_KhmerShape, HB_KhmerAttributes },
+ // N'Ko
+ { HB_ArabicShape, 0}
+};
+
+void HB_GetCharAttributes(const HB_UChar16 *string, hb_uint32 stringLength,
+ const HB_ScriptItem *items, hb_uint32 numItems,
+ HB_CharAttributes *attributes)
+{
+ calcLineBreaks(string, stringLength, attributes);
+
+ for (hb_uint32 i = 0; i < numItems; ++i) {
+ HB_Script script = items[i].script;
+ if (script == HB_Script_Inherited)
+ script = HB_Script_Common;
+ HB_AttributeFunction attributeFunction = HB_ScriptEngines[script].charAttributes;
+ if (!attributeFunction)
+ continue;
+ attributeFunction(script, string, items[i].pos, items[i].length, attributes);
+ }
+}
+
+
+enum BreakRule { NoBreak = 0, Break = 1, Middle = 2 };
+
+static const hb_uint8 wordbreakTable[HB_Word_ExtendNumLet + 1][HB_Word_ExtendNumLet + 1] = {
+// Other Format Katakana ALetter MidLetter MidNum Numeric ExtendNumLet
+ { Break, Break, Break, Break, Break, Break, Break, Break }, // Other
+ { Break, Break, Break, Break, Break, Break, Break, Break }, // Format
+ { Break, Break, NoBreak, Break, Break, Break, Break, NoBreak }, // Katakana
+ { Break, Break, Break, NoBreak, Middle, Break, NoBreak, NoBreak }, // ALetter
+ { Break, Break, Break, Break, Break, Break, Break, Break }, // MidLetter
+ { Break, Break, Break, Break, Break, Break, Break, Break }, // MidNum
+ { Break, Break, Break, NoBreak, Break, Middle, NoBreak, NoBreak }, // Numeric
+ { Break, Break, NoBreak, NoBreak, Break, Break, NoBreak, NoBreak }, // ExtendNumLet
+};
+
+void HB_GetWordBoundaries(const HB_UChar16 *string, hb_uint32 stringLength,
+ const HB_ScriptItem * /*items*/, hb_uint32 /*numItems*/,
+ HB_CharAttributes *attributes)
+{
+ if (stringLength == 0)
+ return;
+ unsigned int brk = HB_GetWordClass(string[0]);
+ attributes[0].wordBoundary = true;
+ for (hb_uint32 i = 1; i < stringLength; ++i) {
+ if (!attributes[i].charStop) {
+ attributes[i].wordBoundary = false;
+ continue;
+ }
+ hb_uint32 nbrk = HB_GetWordClass(string[i]);
+ if (nbrk == HB_Word_Format) {
+ attributes[i].wordBoundary = (HB_GetSentenceClass(string[i-1]) == HB_Sentence_Sep);
+ continue;
+ }
+ BreakRule rule = (BreakRule)wordbreakTable[brk][nbrk];
+ if (rule == Middle) {
+ rule = Break;
+ hb_uint32 lookahead = i + 1;
+ while (lookahead < stringLength) {
+ hb_uint32 testbrk = HB_GetWordClass(string[lookahead]);
+ if (testbrk == HB_Word_Format && HB_GetSentenceClass(string[lookahead]) != HB_Sentence_Sep) {
+ ++lookahead;
+ continue;
+ }
+ if (testbrk == brk) {
+ rule = NoBreak;
+ while (i < lookahead)
+ attributes[i++].wordBoundary = false;
+ nbrk = testbrk;
+ }
+ break;
+ }
+ }
+ attributes[i].wordBoundary = (rule == Break);
+ brk = nbrk;
+ }
+}
+
+
+enum SentenceBreakStates {
+ SB_Initial,
+ SB_Upper,
+ SB_UpATerm,
+ SB_ATerm,
+ SB_ATermC,
+ SB_ACS,
+ SB_STerm,
+ SB_STermC,
+ SB_SCS,
+ SB_BAfter,
+ SB_Break,
+ SB_Look
+};
+
+static const hb_uint8 sentenceBreakTable[HB_Sentence_Close + 1][HB_Sentence_Close + 1] = {
+// Other Sep Format Sp Lower Upper OLetter Numeric ATerm STerm Close
+ { SB_Initial, SB_BAfter , SB_Initial, SB_Initial, SB_Initial, SB_Upper , SB_Initial, SB_Initial, SB_ATerm , SB_STerm , SB_Initial }, // SB_Initial,
+ { SB_Initial, SB_BAfter , SB_Upper , SB_Initial, SB_Initial, SB_Upper , SB_Initial, SB_Initial, SB_UpATerm, SB_STerm , SB_Initial }, // SB_Upper
+
+ { SB_Look , SB_BAfter , SB_UpATerm, SB_ACS , SB_Initial, SB_Upper , SB_Break , SB_Initial, SB_ATerm , SB_STerm , SB_ATermC }, // SB_UpATerm
+ { SB_Look , SB_BAfter , SB_ATerm , SB_ACS , SB_Initial, SB_Break , SB_Break , SB_Initial, SB_ATerm , SB_STerm , SB_ATermC }, // SB_ATerm
+ { SB_Look , SB_BAfter , SB_ATermC , SB_ACS , SB_Initial, SB_Break , SB_Break , SB_Look , SB_ATerm , SB_STerm , SB_ATermC }, // SB_ATermC,
+ { SB_Look , SB_BAfter , SB_ACS , SB_ACS , SB_Initial, SB_Break , SB_Break , SB_Look , SB_ATerm , SB_STerm , SB_Look }, // SB_ACS,
+
+ { SB_Break , SB_BAfter , SB_STerm , SB_SCS , SB_Break , SB_Break , SB_Break , SB_Break , SB_ATerm , SB_STerm , SB_STermC }, // SB_STerm,
+ { SB_Break , SB_BAfter , SB_STermC , SB_SCS , SB_Break , SB_Break , SB_Break , SB_Break , SB_ATerm , SB_STerm , SB_STermC }, // SB_STermC,
+ { SB_Break , SB_BAfter , SB_SCS , SB_SCS , SB_Break , SB_Break , SB_Break , SB_Break , SB_ATerm , SB_STerm , SB_Break }, // SB_SCS,
+ { SB_Break , SB_Break , SB_Break , SB_Break , SB_Break , SB_Break , SB_Break , SB_Break , SB_Break , SB_Break , SB_Break }, // SB_BAfter,
+};
+
+void HB_GetSentenceBoundaries(const HB_UChar16 *string, hb_uint32 stringLength,
+ const HB_ScriptItem * /*items*/, hb_uint32 /*numItems*/,
+ HB_CharAttributes *attributes)
+{
+ if (stringLength == 0)
+ return;
+ hb_uint32 brk = sentenceBreakTable[SB_Initial][HB_GetSentenceClass(string[0])];
+ attributes[0].sentenceBoundary = true;
+ for (hb_uint32 i = 1; i < stringLength; ++i) {
+ if (!attributes[i].charStop) {
+ attributes[i].sentenceBoundary = false;
+ continue;
+ }
+ brk = sentenceBreakTable[brk][HB_GetSentenceClass(string[i])];
+ if (brk == SB_Look) {
+ brk = SB_Break;
+ hb_uint32 lookahead = i + 1;
+ while (lookahead < stringLength) {
+ hb_uint32 sbrk = HB_GetSentenceClass(string[lookahead]);
+ if (sbrk != HB_Sentence_Other && sbrk != HB_Sentence_Numeric && sbrk != HB_Sentence_Close) {
+ break;
+ } else if (sbrk == HB_Sentence_Lower) {
+ brk = SB_Initial;
+ break;
+ }
+ ++lookahead;
+ }
+ if (brk == SB_Initial) {
+ while (i < lookahead)
+ attributes[i++].sentenceBoundary = false;
+ }
+ }
+ if (brk == SB_Break) {
+ attributes[i].sentenceBoundary = true;
+ brk = sentenceBreakTable[SB_Initial][HB_GetSentenceClass(string[i])];
+ } else {
+ attributes[i].sentenceBoundary = false;
+ }
+ }
+}
+
+
+static inline char *tag_to_string(HB_UInt tag)
+{
+ static char string[5];
+ string[0] = (tag >> 24)&0xff;
+ string[1] = (tag >> 16)&0xff;
+ string[2] = (tag >> 8)&0xff;
+ string[3] = tag&0xff;
+ string[4] = 0;
+ return string;
+}
+
+#ifdef OT_DEBUG
+static void dump_string(HB_Buffer buffer)
+{
+ for (uint i = 0; i < buffer->in_length; ++i) {
+ qDebug(" %x: cluster=%d", buffer->in_string[i].gindex, buffer->in_string[i].cluster);
+ }
+}
+#define DEBUG printf
+#else
+#define DEBUG if (1) ; else printf
+#endif
+
+#define DefaultLangSys 0xffff
+#define DefaultScript HB_MAKE_TAG('D', 'F', 'L', 'T')
+
+enum {
+ RequiresGsub = 1,
+ RequiresGpos = 2
+};
+
+struct OTScripts {
+ unsigned int tag;
+ int flags;
+};
+static const OTScripts ot_scripts [] = {
+ // Common
+ { HB_MAKE_TAG('l', 'a', 't', 'n'), 0 },
+ // Greek
+ { HB_MAKE_TAG('g', 'r', 'e', 'k'), 0 },
+ // Cyrillic
+ { HB_MAKE_TAG('c', 'y', 'r', 'l'), 0 },
+ // Armenian
+ { HB_MAKE_TAG('a', 'r', 'm', 'n'), 0 },
+ // Hebrew
+ { HB_MAKE_TAG('h', 'e', 'b', 'r'), 1 },
+ // Arabic
+ { HB_MAKE_TAG('a', 'r', 'a', 'b'), 1 },
+ // Syriac
+ { HB_MAKE_TAG('s', 'y', 'r', 'c'), 1 },
+ // Thaana
+ { HB_MAKE_TAG('t', 'h', 'a', 'a'), 1 },
+ // Devanagari
+ { HB_MAKE_TAG('d', 'e', 'v', 'a'), 1 },
+ // Bengali
+ { HB_MAKE_TAG('b', 'e', 'n', 'g'), 1 },
+ // Gurmukhi
+ { HB_MAKE_TAG('g', 'u', 'r', 'u'), 1 },
+ // Gujarati
+ { HB_MAKE_TAG('g', 'u', 'j', 'r'), 1 },
+ // Oriya
+ { HB_MAKE_TAG('o', 'r', 'y', 'a'), 1 },
+ // Tamil
+ { HB_MAKE_TAG('t', 'a', 'm', 'l'), 1 },
+ // Telugu
+ { HB_MAKE_TAG('t', 'e', 'l', 'u'), 1 },
+ // Kannada
+ { HB_MAKE_TAG('k', 'n', 'd', 'a'), 1 },
+ // Malayalam
+ { HB_MAKE_TAG('m', 'l', 'y', 'm'), 1 },
+ // Sinhala
+ { HB_MAKE_TAG('s', 'i', 'n', 'h'), 1 },
+ // Thai
+ { HB_MAKE_TAG('t', 'h', 'a', 'i'), 1 },
+ // Lao
+ { HB_MAKE_TAG('l', 'a', 'o', ' '), 1 },
+ // Tibetan
+ { HB_MAKE_TAG('t', 'i', 'b', 't'), 1 },
+ // Myanmar
+ { HB_MAKE_TAG('m', 'y', 'm', 'r'), 1 },
+ // Georgian
+ { HB_MAKE_TAG('g', 'e', 'o', 'r'), 0 },
+ // Hangul
+ { HB_MAKE_TAG('h', 'a', 'n', 'g'), 1 },
+ // Ogham
+ { HB_MAKE_TAG('o', 'g', 'a', 'm'), 0 },
+ // Runic
+ { HB_MAKE_TAG('r', 'u', 'n', 'r'), 0 },
+ // Khmer
+ { HB_MAKE_TAG('k', 'h', 'm', 'r'), 1 },
+ // N'Ko
+ { HB_MAKE_TAG('n', 'k', 'o', ' '), 1 }
+};
+enum { NumOTScripts = sizeof(ot_scripts)/sizeof(OTScripts) };
+
+static HB_Bool checkScript(HB_Face face, int script)
+{
+ assert(script < HB_ScriptCount);
+
+ if (!face->gsub && !face->gpos)
+ return false;
+
+ unsigned int tag = ot_scripts[script].tag;
+ int requirements = ot_scripts[script].flags;
+
+ if (requirements & RequiresGsub) {
+ if (!face->gsub)
+ return false;
+
+ HB_UShort script_index;
+ HB_Error error = HB_GSUB_Select_Script(face->gsub, tag, &script_index);
+ if (error) {
+ DEBUG("could not select script %d in GSub table: %d", (int)script, error);
+ error = HB_GSUB_Select_Script(face->gsub, HB_MAKE_TAG('D', 'F', 'L', 'T'), &script_index);
+ if (error)
+ return false;
+ }
+ }
+
+ if (requirements & RequiresGpos) {
+ if (!face->gpos)
+ return false;
+
+ HB_UShort script_index;
+ HB_Error error = HB_GPOS_Select_Script(face->gpos, script, &script_index);
+ if (error) {
+ DEBUG("could not select script in gpos table: %d", error);
+ error = HB_GPOS_Select_Script(face->gpos, HB_MAKE_TAG('D', 'F', 'L', 'T'), &script_index);
+ if (error)
+ return false;
+ }
+
+ }
+ return true;
+}
+
+static HB_Stream getTableStream(void *font, HB_GetFontTableFunc tableFunc, HB_Tag tag)
+{
+ HB_Error error;
+ HB_UInt length = 0;
+ HB_Stream stream = 0;
+
+ if (!font)
+ return 0;
+
+ error = tableFunc(font, tag, 0, &length);
+ if (error)
+ return 0;
+ stream = (HB_Stream)malloc(sizeof(HB_StreamRec));
+ if (!stream)
+ return 0;
+ stream->base = (HB_Byte*)malloc(length);
+ if (!stream->base) {
+ free(stream);
+ return 0;
+ }
+ error = tableFunc(font, tag, stream->base, &length);
+ if (error) {
+ _hb_close_stream(stream);
+ return 0;
+ }
+ stream->size = length;
+ stream->pos = 0;
+ stream->cursor = NULL;
+ return stream;
+}
+
+HB_Face HB_NewFace(void *font, HB_GetFontTableFunc tableFunc)
+{
+ HB_Face face = (HB_Face )malloc(sizeof(HB_FaceRec));
+ if (!face)
+ return 0;
+
+ face->isSymbolFont = false;
+ face->gdef = 0;
+ face->gpos = 0;
+ face->gsub = 0;
+ face->current_script = HB_ScriptCount;
+ face->current_flags = HB_ShaperFlag_Default;
+ face->has_opentype_kerning = false;
+ face->tmpAttributes = 0;
+ face->tmpLogClusters = 0;
+ face->glyphs_substituted = false;
+ face->buffer = 0;
+
+ HB_Error error = HB_Err_Ok;
+ HB_Stream stream;
+ HB_Stream gdefStream;
+
+ gdefStream = getTableStream(font, tableFunc, TTAG_GDEF);
+ error = HB_Err_Not_Covered;
+ if (!gdefStream || (error = HB_Load_GDEF_Table(gdefStream, &face->gdef))) {
+ //DEBUG("error loading gdef table: %d", error);
+ face->gdef = 0;
+ }
+
+ //DEBUG() << "trying to load gsub table";
+ stream = getTableStream(font, tableFunc, TTAG_GSUB);
+ error = HB_Err_Not_Covered;
+ if (!stream || (error = HB_Load_GSUB_Table(stream, &face->gsub, face->gdef, gdefStream))) {
+ face->gsub = 0;
+ if (error != HB_Err_Not_Covered) {
+ //DEBUG("error loading gsub table: %d", error);
+ } else {
+ //DEBUG("face doesn't have a gsub table");
+ }
+ }
+ _hb_close_stream(stream);
+
+ stream = getTableStream(font, tableFunc, TTAG_GPOS);
+ error = HB_Err_Not_Covered;
+ if (!stream || (error = HB_Load_GPOS_Table(stream, &face->gpos, face->gdef, gdefStream))) {
+ face->gpos = 0;
+ DEBUG("error loading gpos table: %d", error);
+ }
+ _hb_close_stream(stream);
+
+ _hb_close_stream(gdefStream);
+
+ for (unsigned int i = 0; i < HB_ScriptCount; ++i)
+ face->supported_scripts[i] = checkScript(face, i);
+
+ if (hb_buffer_new(&face->buffer) != HB_Err_Ok) {
+ HB_FreeFace(face);
+ return 0;
+ }
+
+ return face;
+}
+
+void HB_FreeFace(HB_Face face)
+{
+ if (!face)
+ return;
+ if (face->gpos)
+ HB_Done_GPOS_Table(face->gpos);
+ if (face->gsub)
+ HB_Done_GSUB_Table(face->gsub);
+ if (face->gdef)
+ HB_Done_GDEF_Table(face->gdef);
+ if (face->buffer)
+ hb_buffer_free(face->buffer);
+ if (face->tmpAttributes)
+ free(face->tmpAttributes);
+ if (face->tmpLogClusters)
+ free(face->tmpLogClusters);
+ free(face);
+}
+
+HB_Bool HB_SelectScript(HB_ShaperItem *shaper_item, const HB_OpenTypeFeature *features)
+{
+ HB_Script script = shaper_item->item.script;
+
+ if (!shaper_item->face->supported_scripts[script])
+ return false;
+
+ HB_Face face = shaper_item->face;
+ if (face->current_script == script && face->current_flags == shaper_item->shaperFlags)
+ return true;
+
+ face->current_script = script;
+ face->current_flags = shaper_item->shaperFlags;
+
+ assert(script < HB_ScriptCount);
+ // find script in our list of supported scripts.
+ unsigned int tag = ot_scripts[script].tag;
+
+ if (face->gsub && features) {
+#ifdef OT_DEBUG
+ {
+ HB_FeatureList featurelist = face->gsub->FeatureList;
+ int numfeatures = featurelist.FeatureCount;
+ DEBUG("gsub table has %d features", numfeatures);
+ for (int i = 0; i < numfeatures; i++) {
+ HB_FeatureRecord *r = featurelist.FeatureRecord + i;
+ DEBUG(" feature '%s'", tag_to_string(r->FeatureTag));
+ }
+ }
+#endif
+ HB_GSUB_Clear_Features(face->gsub);
+ HB_UShort script_index;
+ HB_Error error = HB_GSUB_Select_Script(face->gsub, tag, &script_index);
+ if (!error) {
+ DEBUG("script %s has script index %d", tag_to_string(script), script_index);
+ while (features->tag) {
+ HB_UShort feature_index;
+ error = HB_GSUB_Select_Feature(face->gsub, features->tag, script_index, 0xffff, &feature_index);
+ if (!error) {
+ DEBUG(" adding feature %s", tag_to_string(features->tag));
+ HB_GSUB_Add_Feature(face->gsub, feature_index, features->property);
+ }
+ ++features;
+ }
+ }
+ }
+
+ // reset
+ face->has_opentype_kerning = false;
+
+ if (face->gpos) {
+ HB_GPOS_Clear_Features(face->gpos);
+ HB_UShort script_index;
+ HB_Error error = HB_GPOS_Select_Script(face->gpos, tag, &script_index);
+ if (!error) {
+#ifdef OT_DEBUG
+ {
+ HB_FeatureList featurelist = face->gpos->FeatureList;
+ int numfeatures = featurelist.FeatureCount;
+ DEBUG("gpos table has %d features", numfeatures);
+ for(int i = 0; i < numfeatures; i++) {
+ HB_FeatureRecord *r = featurelist.FeatureRecord + i;
+ HB_UShort feature_index;
+ HB_GPOS_Select_Feature(face->gpos, r->FeatureTag, script_index, 0xffff, &feature_index);
+ DEBUG(" feature '%s'", tag_to_string(r->FeatureTag));
+ }
+ }
+#endif
+ HB_UInt *feature_tag_list_buffer;
+ error = HB_GPOS_Query_Features(face->gpos, script_index, 0xffff, &feature_tag_list_buffer);
+ if (!error) {
+ HB_UInt *feature_tag_list = feature_tag_list_buffer;
+ while (*feature_tag_list) {
+ HB_UShort feature_index;
+ if (*feature_tag_list == HB_MAKE_TAG('k', 'e', 'r', 'n')) {
+ if (face->current_flags & HB_ShaperFlag_NoKerning) {
+ ++feature_tag_list;
+ continue;
+ }
+ face->has_opentype_kerning = true;
+ }
+ error = HB_GPOS_Select_Feature(face->gpos, *feature_tag_list, script_index, 0xffff, &feature_index);
+ if (!error)
+ HB_GPOS_Add_Feature(face->gpos, feature_index, PositioningProperties);
+ ++feature_tag_list;
+ }
+ FREE(feature_tag_list_buffer);
+ }
+ }
+ }
+
+ return true;
+}
+
+HB_Bool HB_OpenTypeShape(HB_ShaperItem *item, const hb_uint32 *properties)
+{
+ HB_GlyphAttributes *tmpAttributes;
+ unsigned int *tmpLogClusters;
+
+ HB_Face face = item->face;
+
+ face->length = item->num_glyphs;
+
+ hb_buffer_clear(face->buffer);
+
+ tmpAttributes = (HB_GlyphAttributes *) realloc(face->tmpAttributes, face->length*sizeof(HB_GlyphAttributes));
+ if (!tmpAttributes)
+ return false;
+ face->tmpAttributes = tmpAttributes;
+
+ tmpLogClusters = (unsigned int *) realloc(face->tmpLogClusters, face->length*sizeof(unsigned int));
+ if (!tmpLogClusters)
+ return false;
+ face->tmpLogClusters = tmpLogClusters;
+
+ for (int i = 0; i < face->length; ++i) {
+ hb_buffer_add_glyph(face->buffer, item->glyphs[i], properties ? properties[i] : 0, i);
+ face->tmpAttributes[i] = item->attributes[i];
+ face->tmpLogClusters[i] = item->log_clusters[i];
+ }
+
+#ifdef OT_DEBUG
+ DEBUG("-----------------------------------------");
+// DEBUG("log clusters before shaping:");
+// for (int j = 0; j < length; j++)
+// DEBUG(" log[%d] = %d", j, item->log_clusters[j]);
+ DEBUG("original glyphs: %p", item->glyphs);
+ for (int i = 0; i < length; ++i)
+ DEBUG(" glyph=%4x", hb_buffer->in_string[i].gindex);
+// dump_string(hb_buffer);
+#endif
+
+ face->glyphs_substituted = false;
+ if (face->gsub) {
+ unsigned int error = HB_GSUB_Apply_String(face->gsub, face->buffer);
+ if (error && error != HB_Err_Not_Covered)
+ return false;
+ face->glyphs_substituted = (error != HB_Err_Not_Covered);
+ }
+
+#ifdef OT_DEBUG
+// DEBUG("log clusters before shaping:");
+// for (int j = 0; j < length; j++)
+// DEBUG(" log[%d] = %d", j, item->log_clusters[j]);
+ DEBUG("shaped glyphs:");
+ for (int i = 0; i < length; ++i)
+ DEBUG(" glyph=%4x", hb_buffer->in_string[i].gindex);
+ DEBUG("-----------------------------------------");
+// dump_string(hb_buffer);
+#endif
+
+ return true;
+}
+
+HB_Bool HB_OpenTypePosition(HB_ShaperItem *item, int availableGlyphs, HB_Bool doLogClusters)
+{
+ HB_Face face = item->face;
+
+ bool glyphs_positioned = false;
+ if (face->gpos) {
+ if (face->buffer->positions)
+ memset(face->buffer->positions, 0, face->buffer->in_length*sizeof(HB_PositionRec));
+ // #### check that passing "false,false" is correct
+ glyphs_positioned = HB_GPOS_Apply_String(item->font, face->gpos, face->current_flags, face->buffer, false, false) != HB_Err_Not_Covered;
+ }
+
+ if (!face->glyphs_substituted && !glyphs_positioned) {
+ HB_GetGlyphAdvances(item);
+ return true; // nothing to do for us
+ }
+
+ // make sure we have enough space to write everything back
+ if (availableGlyphs < (int)face->buffer->in_length) {
+ item->num_glyphs = face->buffer->in_length;
+ return false;
+ }
+
+ HB_Glyph *glyphs = item->glyphs;
+ HB_GlyphAttributes *attributes = item->attributes;
+
+ for (unsigned int i = 0; i < face->buffer->in_length; ++i) {
+ glyphs[i] = face->buffer->in_string[i].gindex;
+ attributes[i] = face->tmpAttributes[face->buffer->in_string[i].cluster];
+ if (i && face->buffer->in_string[i].cluster == face->buffer->in_string[i-1].cluster)
+ attributes[i].clusterStart = false;
+ }
+ item->num_glyphs = face->buffer->in_length;
+
+ if (doLogClusters && face->glyphs_substituted) {
+ // we can't do this for indic, as we pass the stuf in syllables and it's easier to do it in the shaper.
+ unsigned short *logClusters = item->log_clusters;
+ int clusterStart = 0;
+ int oldCi = 0;
+ // #### the reconstruction of the logclusters currently does not work if the original string
+ // contains surrogate pairs
+ for (unsigned int i = 0; i < face->buffer->in_length; ++i) {
+ int ci = face->buffer->in_string[i].cluster;
+ // DEBUG(" ci[%d] = %d mark=%d, cmb=%d, cs=%d",
+ // i, ci, glyphAttributes[i].mark, glyphAttributes[i].combiningClass, glyphAttributes[i].clusterStart);
+ if (!attributes[i].mark && attributes[i].clusterStart && ci != oldCi) {
+ for (int j = oldCi; j < ci; j++)
+ logClusters[j] = clusterStart;
+ clusterStart = i;
+ oldCi = ci;
+ }
+ }
+ for (int j = oldCi; j < face->length; j++)
+ logClusters[j] = clusterStart;
+ }
+
+ // calulate the advances for the shaped glyphs
+// DEBUG("unpositioned: ");
+
+ // positioning code:
+ if (glyphs_positioned) {
+ HB_GetGlyphAdvances(item);
+ HB_Position positions = face->buffer->positions;
+ HB_Fixed *advances = item->advances;
+
+// DEBUG("positioned glyphs:");
+ for (unsigned int i = 0; i < face->buffer->in_length; i++) {
+// DEBUG(" %d:\t orig advance: (%d/%d)\tadv=(%d/%d)\tpos=(%d/%d)\tback=%d\tnew_advance=%d", i,
+// glyphs[i].advance.x.toInt(), glyphs[i].advance.y.toInt(),
+// (int)(positions[i].x_advance >> 6), (int)(positions[i].y_advance >> 6),
+// (int)(positions[i].x_pos >> 6), (int)(positions[i].y_pos >> 6),
+// positions[i].back, positions[i].new_advance);
+
+ HB_Fixed adjustment = (item->item.bidiLevel % 2) ? -positions[i].x_advance : positions[i].x_advance;
+
+ if (!(face->current_flags & HB_ShaperFlag_UseDesignMetrics))
+ adjustment = HB_FIXED_ROUND(adjustment);
+
+ if (positions[i].new_advance) {
+ advances[i] = adjustment;
+ } else {
+ advances[i] += adjustment;
+ }
+
+ int back = 0;
+ HB_FixedPoint *offsets = item->offsets;
+ offsets[i].x = positions[i].x_pos;
+ offsets[i].y = positions[i].y_pos;
+ while (positions[i - back].back) {
+ back += positions[i - back].back;
+ offsets[i].x += positions[i - back].x_pos;
+ offsets[i].y += positions[i - back].y_pos;
+ }
+ offsets[i].y = -offsets[i].y;
+
+ if (item->item.bidiLevel % 2) {
+ // ### may need to go back multiple glyphs like in ltr
+ back = positions[i].back;
+ while (back--)
+ offsets[i].x -= advances[i-back];
+ } else {
+ back = 0;
+ while (positions[i - back].back) {
+ back += positions[i - back].back;
+ offsets[i].x -= advances[i-back];
+ }
+ }
+// DEBUG(" ->\tadv=%d\tpos=(%d/%d)",
+// glyphs[i].advance.x.toInt(), glyphs[i].offset.x.toInt(), glyphs[i].offset.y.toInt());
+ }
+ item->kerning_applied = face->has_opentype_kerning;
+ } else {
+ HB_HeuristicPosition(item);
+ }
+
+#ifdef OT_DEBUG
+ if (doLogClusters) {
+ DEBUG("log clusters after shaping:");
+ for (int j = 0; j < length; j++)
+ DEBUG(" log[%d] = %d", j, item->log_clusters[j]);
+ }
+ DEBUG("final glyphs:");
+ for (int i = 0; i < (int)hb_buffer->in_length; ++i)
+ DEBUG(" glyph=%4x char_index=%d mark: %d cmp: %d, clusterStart: %d advance=%d/%d offset=%d/%d",
+ glyphs[i].glyph, hb_buffer->in_string[i].cluster, glyphs[i].attributes.mark,
+ glyphs[i].attributes.combiningClass, glyphs[i].attributes.clusterStart,
+ glyphs[i].advance.x.toInt(), glyphs[i].advance.y.toInt(),
+ glyphs[i].offset.x.toInt(), glyphs[i].offset.y.toInt());
+ DEBUG("-----------------------------------------");
+#endif
+ return true;
+}
+
+HB_Bool HB_ShapeItem(HB_ShaperItem *shaper_item)
+{
+ HB_Bool result = false;
+ if (shaper_item->num_glyphs < shaper_item->item.length) {
+ shaper_item->num_glyphs = shaper_item->item.length;
+ return false;
+ }
+ assert(shaper_item->item.script < HB_ScriptCount);
+ result = HB_ScriptEngines[shaper_item->item.script].shape(shaper_item);
+ shaper_item->glyphIndicesPresent = false;
+ return result;
+}
+