summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/harfbuzz/src/harfbuzz-shaper.cpp
diff options
context:
space:
mode:
authorQt by Nokia <qt-info@nokia.com>2011-04-27 12:05:43 +0200
committeraxis <qt-info@nokia.com>2011-04-27 12:05:43 +0200
commit38be0d13830efd2d98281c645c3a60afe05ffece (patch)
tree6ea73f3ec77f7d153333779883e8120f82820abe /src/3rdparty/harfbuzz/src/harfbuzz-shaper.cpp
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'src/3rdparty/harfbuzz/src/harfbuzz-shaper.cpp')
-rw-r--r--src/3rdparty/harfbuzz/src/harfbuzz-shaper.cpp1367
1 files changed, 1367 insertions, 0 deletions
diff --git a/src/3rdparty/harfbuzz/src/harfbuzz-shaper.cpp b/src/3rdparty/harfbuzz/src/harfbuzz-shaper.cpp
new file mode 100644
index 0000000000..c202e1f52b
--- /dev/null
+++ b/src/3rdparty/harfbuzz/src/harfbuzz-shaper.cpp
@@ -0,0 +1,1367 @@
+/*
+ * 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'), LigaProperty },
+ { HB_MAKE_TAG('c', 'l', 'i', 'g'), CligProperty },
+ {0, 0}
+};
+
+static const HB_OpenTypeFeature disabled_features[] = {
+ { HB_MAKE_TAG('c', 'p', 'c', 't'), PositioningProperties },
+ { HB_MAKE_TAG('h', 'a', 'l', 't'), PositioningProperties },
+ // TODO: we need to add certain HB_ShaperFlag for vertical
+ // writing mode to enable these vertical writing features:
+ { HB_MAKE_TAG('v', 'a', 'l', 't'), PositioningProperties },
+ { HB_MAKE_TAG('v', 'h', 'a', 'l'), PositioningProperties },
+ { HB_MAKE_TAG('v', 'k', 'r', 'n'), PositioningProperties },
+ { HB_MAKE_TAG('v', 'p', 'a', 'l'), PositioningProperties },
+ {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;
+
+ HB_Face face = shaper_item->face;
+ if (face->current_script == script && face->current_flags == shaper_item->shaperFlags)
+ return shaper_item->face->supported_scripts[script] ? true : false;
+
+ face->current_script = script;
+ face->current_flags = shaper_item->shaperFlags;
+
+ if (!shaper_item->face->supported_scripts[script])
+ return false;
+
+ 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;
+ bool skip = false;
+ if (*feature_tag_list == HB_MAKE_TAG('k', 'e', 'r', 'n')) {
+ if (face->current_flags & HB_ShaperFlag_NoKerning)
+ skip = true;
+ else
+ face->has_opentype_kerning = true;
+ }
+ features = disabled_features;
+ while (features->tag) {
+ if (*feature_tag_list == features->tag) {
+ skip = true;
+ break;
+ }
+ ++features;
+ }
+ // 'palt' should be turned off by default unless 'kern' is on
+ if (!face->has_opentype_kerning &&
+ *feature_tag_list == HB_MAKE_TAG('p', 'a', 'l', 't'))
+ skip = true;
+
+ if (skip) {
+ ++feature_tag_list;
+ continue;
+ }
+ 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_HeuristicPosition(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;
+}
+