/*
* 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"
extern "C" {
#include "harfbuzz-debug.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!!!!!
// BEGIN android-changed
// We apply the same fix for Chrome to Android.
// Chrome team will talk with upsteam about it.
assert(length <= item->num_glyphs);
// END android-changed
// 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;
}
// BEGIN android-removed
// We apply the same fix for Chrome to Android.
// Chrome team will talk with upsteam about it
//
// assert(glyph_pos == item->num_glyphs);
//
// END android-removed
// 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 HBDebug
#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 == HB_Err_Not_Covered) {
error = HB_GSUB_Select_Script(face->gsub, DefaultScript, &script_index);
}
if (!error) {
DEBUG("script %s has script index %d", tag_to_string(tag), 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 == HB_Err_Not_Covered) {
error = HB_GPOS_Select_Script(face->gpos, DefaultScript, &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 = 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());
DEBUG("Glyph offset[%d] x:%d, y: %d)", i, offsets[i].x, offsets[i].y);
}
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;
}