/**************************************************************************** ** $Id: qscriptengine.cpp 8 2005-11-16 19:36:46Z dmik $ ** ** ??? ** ** Copyright (C) 2003 Trolltech AS. All rights reserved. ** ** This file is part of the kernel module of the Qt GUI Toolkit. ** ** This file may be distributed under the terms of the Q Public License ** as defined by Trolltech AS of Norway and appearing in the file ** LICENSE.QPL included in the packaging of this file. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition ** licenses may use this file in accordance with the Qt Commercial License ** Agreement provided with the Software. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for ** information about Qt Commercial License Agreements. ** See http://www.trolltech.com/qpl/ for QPL licensing information. ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include "qscriptengine_p.h" #include "qstring.h" #include "qrect.h" #include "qfont.h" #include #include "qtextengine_p.h" #include "qfontengine_p.h" #include #undef None #undef Pre #undef Above #undef Below // -------------------------------------------------------------------------------------------------------------------------------------------- // // Basic processing // // -------------------------------------------------------------------------------------------------------------------------------------------- static inline void positionCluster( QTextEngine *engine, QScriptItem *si, int gfrom, int glast ) { si->hasPositioning = TRUE; int nmarks = glast - gfrom; if ( nmarks <= 0 ) { qWarning( "positionCluster: no marks to position!" ); return; } glyph_t *glyphs = engine->glyphs( si ); qoffset_t *offsets = engine->offsets( si ); GlyphAttributes *glyphAttributes = engine->glyphAttributes( si ); QFontEngine *f = si->fontEngine; glyph_metrics_t baseInfo = f->boundingBox( glyphs[gfrom] ); if ( si->analysis.script == QFont::Hebrew ) { // we need to attach below the baseline, because of the hebrew iud. baseInfo.height= QMAX( baseInfo.height, -baseInfo.y ); } QRect baseRect( baseInfo.x, baseInfo.y, baseInfo.width, baseInfo.height ); // qDebug("---> positionCluster: cluster from %d to %d", gfrom, glast ); // qDebug( "baseInfo: %d/%d (%d/%d) off=%d/%d", baseInfo.x, baseInfo.y, baseInfo.width, baseInfo.height, baseInfo.xoff, baseInfo.yoff ); int size = f->ascent()/10; int offsetBase = (size - 4) / 4 + QMIN( size, 4 ) + 1; // qDebug("offset = %d", offsetBase ); bool rightToLeft = si->analysis.bidiLevel % 2; int i; int lastCmb = -1; QRect attachmentRect; for( i = 1; i <= nmarks; i++ ) { glyph_t mark = glyphs[gfrom+i]; QPoint p; glyph_metrics_t markInfo = f->boundingBox( mark ); QRect markRect( markInfo.x, markInfo.y, markInfo.width, markInfo.height ); int offset = offsetBase; unsigned char cmb = glyphAttributes[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 = QChar::Combining_Below; // above else if ( cmb == 23 || cmb == 27 || cmb == 28 || cmb == 30 || cmb == 31 || (cmb >= 33 && cmb <= 36 ) ) cmb = QChar::Combining_Above; //below-right else if ( cmb == 9 || cmb == 103 || cmb == 118 ) cmb = QChar::Combining_BelowRight; // above-right else if ( cmb == 24 || cmb == 107 || cmb == 122 ) cmb = QChar::Combining_AboveRight; else if ( cmb == 25 ) cmb = QChar::Combining_AboveLeft; // fixed: // 19 21 } // combining marks of different class don't interact. Reset the rectangle. if ( cmb != lastCmb ) { //qDebug( "resetting rect" ); attachmentRect = baseRect; } switch( cmb ) { case QChar::Combining_DoubleBelow: // ### wrong in rtl context! case QChar::Combining_BelowLeft: p += QPoint( 0, offset ); case QChar::Combining_BelowLeftAttached: p += attachmentRect.bottomLeft() - markRect.topLeft(); break; case QChar::Combining_Below: p += QPoint( 0, offset ); case QChar::Combining_BelowAttached: p += attachmentRect.bottomLeft() - markRect.topLeft(); p += QPoint( (attachmentRect.width() - markRect.width())/2 , 0 ); break; case QChar::Combining_BelowRight: p += QPoint( 0, offset ); case QChar::Combining_BelowRightAttached: p += attachmentRect.bottomRight() - markRect.topRight(); break; case QChar::Combining_Left: p += QPoint( -offset, 0 ); case QChar::Combining_LeftAttached: break; case QChar::Combining_Right: p += QPoint( offset, 0 ); case QChar::Combining_RightAttached: break; case QChar::Combining_DoubleAbove: // ### wrong in RTL context! case QChar::Combining_AboveLeft: p += QPoint( 0, -offset ); case QChar::Combining_AboveLeftAttached: p += attachmentRect.topLeft() - markRect.bottomLeft(); break; case QChar::Combining_Above: p += QPoint( 0, -offset ); case QChar::Combining_AboveAttached: p += attachmentRect.topLeft() - markRect.bottomLeft(); p += QPoint( (attachmentRect.width() - markRect.width())/2 , 0 ); break; case QChar::Combining_AboveRight: p += QPoint( 0, -offset ); case QChar::Combining_AboveRightAttached: p += attachmentRect.topRight() - markRect.bottomRight(); break; case QChar::Combining_IotaSubscript: default: break; } // qDebug( "char=%x combiningClass = %d offset=%d/%d", mark, cmb, p.x(), p.y() ); markRect.moveBy( p.x(), p.y() ); attachmentRect |= markRect; lastCmb = cmb; if ( rightToLeft ) { offsets[gfrom+i].x = p.x(); offsets[gfrom+i].y = p.y() - baseInfo.yoff; } else { offsets[gfrom+i].x = p.x() - baseInfo.xoff; offsets[gfrom+i].y = p.y() - baseInfo.yoff; } } } void q_heuristicPosition( QTextEngine *engine, QScriptItem *si ) { GlyphAttributes *glyphAttributes = engine->glyphAttributes( si ); int cEnd = -1; int i = si->num_glyphs; while ( i-- ) { if ( cEnd == -1 && glyphAttributes[i].mark ) { cEnd = i; } else if ( cEnd != -1 && !glyphAttributes[i].mark ) { positionCluster( engine, si, i, cEnd ); cEnd = -1; } } } // set the glyph attributes heuristically. Assumes a 1 to 1 relationship between chars ang glyphs // and no reordering. // also computes logClusters heuristically static void heuristicSetGlyphAttributes( const QString &string, int from, int len, QTextEngine *engine, QScriptItem *si ) { // ### zeroWidth and justification are missing here!!!!! if ( si->num_glyphs != len ) qWarning("QScriptEngine::heuristicSetGlyphAttributes: char length and num glyphs disagree" ); // qDebug("QScriptEngine::heuristicSetGlyphAttributes, num_glyphs=%d", num_glyphs); GlyphAttributes *glyphAttributes = engine->glyphAttributes( si ); unsigned short *logClusters = engine->logClusters( si ); advance_t *advances = engine->advances( si ); // honour the logClusters array if it exists. const QChar *uc = string.unicode() + from; for ( int i = 0; i < si->num_glyphs; i++ ) logClusters[i] = i; int pos = 1; // first char in a run is never (treated as) a mark int cStart = 0; glyphAttributes[0].mark = FALSE; glyphAttributes[0].clusterStart = TRUE; while ( pos < len ) { if ( ::category( uc[pos] ) != QChar::Mark_NonSpacing ) { glyphAttributes[pos].mark = FALSE; glyphAttributes[pos].clusterStart = TRUE; glyphAttributes[pos].combiningClass = 0; cStart = pos; } else { int cmb = combiningClass( uc[pos] ); if ( cmb == 0 ) { // Fix 0 combining classes if ( uc[pos].row() == 0x0e ) { // thai or lao unsigned char col = uc[pos].cell(); if ( col == 0x31 || col == 0x34 || col == 0x35 || col == 0x36 || col == 0x37 || col == 0x47 || col == 0x4c || col == 0x4d || col == 0x4e ) { cmb = QChar::Combining_AboveRight; } else if ( col == 0xb1 || col == 0xb4 || col == 0xb5 || col == 0xb6 || col == 0xb7 || col == 0xbb || col == 0xcc || col == 0xcd ) { cmb = QChar::Combining_Above; } else if ( col == 0xbc ) { cmb = QChar::Combining_Below; } } } glyphAttributes[pos].mark = TRUE; glyphAttributes[pos].clusterStart = FALSE; glyphAttributes[pos].combiningClass = cmb; // qDebug("found a mark at position %d", pos ); logClusters[pos] = cStart; advances[pos] = 0; si->hasPositioning = TRUE; } pos++; } } static void convertToCMap( const QChar *chars, int len, QTextEngine *engine, QScriptItem *si ) { glyph_t *glyphs = engine->glyphs( si ); advance_t *advances = engine->advances( si ); si->num_glyphs = len; engine->ensureSpace( len ); int error = si->fontEngine->stringToCMap( chars, len, glyphs, advances, &si->num_glyphs, (si->analysis.bidiLevel %2) ); if ( error == QFontEngine::OutOfMemory ) { engine->ensureSpace( si->num_glyphs ); si->fontEngine->stringToCMap( chars, len, glyphs, advances, &si->num_glyphs, (si->analysis.bidiLevel %2) ); } } static void basic_shape( int /*script*/, const QString &string, int from, int len, QTextEngine *engine, QScriptItem *si ) { convertToCMap( string.unicode() + from, len, engine, si ); heuristicSetGlyphAttributes( string, from, len, engine, si ); if ( !engine->widthOnly ) { q_heuristicPosition( engine, si ); } } static void basic_attributes( int /*script*/, const QString &text, int from, int len, QCharAttributes *attributes ) { const QChar *uc = text.unicode() + from; attributes += from; QCharAttributes *a = attributes; for ( int i = 0; i < len; i++ ) { QChar::Category cat = ::category( *uc ); a->whiteSpace = (cat == QChar::Separator_Space) && (uc->unicode() != 0xa0); a->softBreak = FALSE; a->charStop = (cat != QChar::Mark_NonSpacing); a->wordStop = FALSE; a->invalid = FALSE; ++uc; ++a; } } // -------------------------------------------------------------------------------------------------------------------------------------------- // // Middle eastern languages // // -------------------------------------------------------------------------------------------------------------------------------------------- /* Arabic shaping obeys a number of rules according to the joining classes (see Unicode book, section on arabic). Each unicode char has a joining class (right, dual (left&right), center (joincausing) or transparent). transparent joining is not encoded in QChar::joining(), but applies to all combining marks and format marks. Right join-causing: dual + center Left join-causing: dual + right + center Rules are as follows (for a string already in visual order, as we have it here): R1 Transparent characters do not affect joining behaviour. R2 A right joining character, that has a right join-causing char on the right will get form XRight (R3 A left joining character, that has a left join-causing char on the left will get form XLeft) Note: the above rule is meaningless, as there are no pure left joining characters defined in Unicode R4 A dual joining character, that has a left join-causing char on the left and a right join-causing char on the right will get form XMedial R5 A dual joining character, that has a right join causing char on the right, and no left join causing char on the left will get form XRight R6 A dual joining character, that has a left join causing char on the left, and no right join causing char on the right will get form XLeft R7 Otherwise the character will get form XIsolated Additionally we have to do the minimal ligature support for lam-alef ligatures: L1 Transparent characters do not affect ligature behaviour. L2 Any sequence of Alef(XRight) + Lam(XMedial) will form the ligature Alef.Lam(XLeft) L3 Any sequence of Alef(XRight) + Lam(XLeft) will form the ligature Alef.Lam(XIsolated) The two functions defined in this class do shaping in visual and logical order. For logical order just replace right with previous and left with next in the above rules ;-) */ /* Two small helper functions for arabic shaping. They get the next shape causing character on either side of the char in question. Implements rule R1. leftChar() returns true if the char to the left is a left join-causing char rightChar() returns true if the char to the right is a right join-causing char */ enum Shape { XIsolated, XFinal, XInitial, XMedial }; /* Two small helper functions for arabic shaping. They get the next shape causing character on either side of the char in question. Implements rule R1. leftChar() returns true if the char to the left is a left join-causing char rightChar() returns true if the char to the right is a right join-causing char */ static inline const QChar *prevChar( const QString &str, int pos ) { //qDebug("leftChar: pos=%d", pos); pos--; const QChar *ch = str.unicode() + pos; while( pos > -1 ) { if( ::category( *ch ) != QChar::Mark_NonSpacing ) return ch; pos--; ch--; } return &QChar::replacement; } static inline const QChar *nextChar( const QString &str, int pos) { pos++; int len = str.length(); const QChar *ch = str.unicode() + pos; while( pos < len ) { //qDebug("rightChar: %d isLetter=%d, joining=%d", pos, ch.isLetter(), ch.joining()); if( ::category( *ch ) != QChar::Mark_NonSpacing ) return ch; // assume it's a transparent char, this might not be 100% correct pos++; ch++; } return &QChar::replacement; } /* and the same thing for logical ordering :) */ static inline bool prevLogicalCharJoins( const QString &str, int pos) { return ( joining( *nextChar( str, pos ) ) != QChar::OtherJoining ); } static inline bool nextLogicalCharJoins( const QString &str, int pos) { QChar::Joining join = joining( *prevChar( str, pos ) ); return ( join == QChar::Dual || join == QChar::Center ); } static inline Shape glyphVariantLogical( const QString &str, int pos) { QChar::Joining joining = ::joining( str.unicode()[pos] ); //qDebug("checking %x, joining=%d", str[pos].unicode(), joining); switch ( joining ) { case QChar::OtherJoining: case QChar::Center: // these don't change shape return XIsolated; case QChar::Right: // only rule R2 applies return ( nextLogicalCharJoins( str, pos ) ) ? XFinal : XIsolated; case QChar::Dual: bool right = nextLogicalCharJoins( str, pos ); bool left = prevLogicalCharJoins( str, pos ); //qDebug("dual: right=%d, left=%d", right, left); return ( right ) ? ( left ? XMedial : XFinal ) : ( left ? XInitial : XIsolated ); } return XIsolated; } // The unicode to unicode shaping codec. // does only presentation forms B at the moment, but that should be enough for // simple display static const ushort arabicUnicodeMapping[256][2] = { // base of shaped forms, and number-1 of them ( 0 for non shaping, // 1 for right binding and 3 for dual binding // These are just the glyphs available in Unicode, // some characters are in R class, but have no glyphs in Unicode. { 0x0600, 0 }, // 0x0600 { 0x0601, 0 }, // 0x0601 { 0x0602, 0 }, // 0x0602 { 0x0603, 0 }, // 0x0603 { 0x0604, 0 }, // 0x0604 { 0x0605, 0 }, // 0x0605 { 0x0606, 0 }, // 0x0606 { 0x0607, 0 }, // 0x0607 { 0x0608, 0 }, // 0x0608 { 0x0609, 0 }, // 0x0609 { 0x060A, 0 }, // 0x060A { 0x060B, 0 }, // 0x060B { 0x060C, 0 }, // 0x060C { 0x060D, 0 }, // 0x060D { 0x060E, 0 }, // 0x060E { 0x060F, 0 }, // 0x060F { 0x0610, 0 }, // 0x0610 { 0x0611, 0 }, // 0x0611 { 0x0612, 0 }, // 0x0612 { 0x0613, 0 }, // 0x0613 { 0x0614, 0 }, // 0x0614 { 0x0615, 0 }, // 0x0615 { 0x0616, 0 }, // 0x0616 { 0x0617, 0 }, // 0x0617 { 0x0618, 0 }, // 0x0618 { 0x0619, 0 }, // 0x0619 { 0x061A, 0 }, // 0x061A { 0x061B, 0 }, // 0x061B { 0x061C, 0 }, // 0x061C { 0x061D, 0 }, // 0x061D { 0x061E, 0 }, // 0x061E { 0x061F, 0 }, // 0x061F { 0x0620, 0 }, // 0x0620 { 0xFE80, 0 }, // 0x0621 HAMZA { 0xFE81, 1 }, // 0x0622 R ALEF WITH MADDA ABOVE { 0xFE83, 1 }, // 0x0623 R ALEF WITH HAMZA ABOVE { 0xFE85, 1 }, // 0x0624 R WAW WITH HAMZA ABOVE { 0xFE87, 1 }, // 0x0625 R ALEF WITH HAMZA BELOW { 0xFE89, 3 }, // 0x0626 D YEH WITH HAMZA ABOVE { 0xFE8D, 1 }, // 0x0627 R ALEF { 0xFE8F, 3 }, // 0x0628 D BEH { 0xFE93, 1 }, // 0x0629 R TEH MARBUTA { 0xFE95, 3 }, // 0x062A D TEH { 0xFE99, 3 }, // 0x062B D THEH { 0xFE9D, 3 }, // 0x062C D JEEM { 0xFEA1, 3 }, // 0x062D D HAH { 0xFEA5, 3 }, // 0x062E D KHAH { 0xFEA9, 1 }, // 0x062F R DAL { 0xFEAB, 1 }, // 0x0630 R THAL { 0xFEAD, 1 }, // 0x0631 R REH { 0xFEAF, 1 }, // 0x0632 R ZAIN { 0xFEB1, 3 }, // 0x0633 D SEEN { 0xFEB5, 3 }, // 0x0634 D SHEEN { 0xFEB9, 3 }, // 0x0635 D SAD { 0xFEBD, 3 }, // 0x0636 D DAD { 0xFEC1, 3 }, // 0x0637 D TAH { 0xFEC5, 3 }, // 0x0638 D ZAH { 0xFEC9, 3 }, // 0x0639 D AIN { 0xFECD, 3 }, // 0x063A D GHAIN { 0x063B, 0 }, // 0x063B { 0x063C, 0 }, // 0x063C { 0x063D, 0 }, // 0x063D { 0x063E, 0 }, // 0x063E { 0x063F, 0 }, // 0x063F { 0x0640, 0 }, // 0x0640 C TATWEEL // ### Join Causing, only one glyph { 0xFED1, 3 }, // 0x0641 D FEH { 0xFED5, 3 }, // 0x0642 D QAF { 0xFED9, 3 }, // 0x0643 D KAF { 0xFEDD, 3 }, // 0x0644 D LAM { 0xFEE1, 3 }, // 0x0645 D MEEM { 0xFEE5, 3 }, // 0x0646 D NOON { 0xFEE9, 3 }, // 0x0647 D HEH { 0xFEED, 1 }, // 0x0648 R WAW { 0x0649, 0 }, // 0x0649 ALEF MAKSURA // ### Dual, glyphs not consecutive, handle in code. { 0xFEF1, 3 }, // 0x064A D YEH { 0x064B, 0 }, // 0x064B { 0x064C, 0 }, // 0x064C { 0x064D, 0 }, // 0x064D { 0x064E, 0 }, // 0x064E { 0x064F, 0 }, // 0x064F { 0x0650, 0 }, // 0x0650 { 0x0651, 0 }, // 0x0651 { 0x0652, 0 }, // 0x0652 { 0x0653, 0 }, // 0x0653 { 0x0654, 0 }, // 0x0654 { 0x0655, 0 }, // 0x0655 { 0x0656, 0 }, // 0x0656 { 0x0657, 0 }, // 0x0657 { 0x0658, 0 }, // 0x0658 { 0x0659, 0 }, // 0x0659 { 0x065A, 0 }, // 0x065A { 0x065B, 0 }, // 0x065B { 0x065C, 0 }, // 0x065C { 0x065D, 0 }, // 0x065D { 0x065E, 0 }, // 0x065E { 0x065F, 0 }, // 0x065F { 0x0660, 0 }, // 0x0660 { 0x0661, 0 }, // 0x0661 { 0x0662, 0 }, // 0x0662 { 0x0663, 0 }, // 0x0663 { 0x0664, 0 }, // 0x0664 { 0x0665, 0 }, // 0x0665 { 0x0666, 0 }, // 0x0666 { 0x0667, 0 }, // 0x0667 { 0x0668, 0 }, // 0x0668 { 0x0669, 0 }, // 0x0669 { 0x066A, 0 }, // 0x066A { 0x066B, 0 }, // 0x066B { 0x066C, 0 }, // 0x066C { 0x066D, 0 }, // 0x066D { 0x066E, 0 }, // 0x066E { 0x066F, 0 }, // 0x066F { 0x0670, 0 }, // 0x0670 { 0xFB50, 1 }, // 0x0671 R ALEF WASLA { 0x0672, 0 }, // 0x0672 { 0x0673, 0 }, // 0x0673 { 0x0674, 0 }, // 0x0674 { 0x0675, 0 }, // 0x0675 { 0x0676, 0 }, // 0x0676 { 0x0677, 0 }, // 0x0677 { 0x0678, 0 }, // 0x0678 { 0xFB66, 3 }, // 0x0679 D TTEH { 0xFB5E, 3 }, // 0x067A D TTEHEH { 0xFB52, 3 }, // 0x067B D BEEH { 0x067C, 0 }, // 0x067C { 0x067D, 0 }, // 0x067D { 0xFB56, 3 }, // 0x067E D PEH { 0xFB62, 3 }, // 0x067F D TEHEH { 0xFB5A, 3 }, // 0x0680 D BEHEH { 0x0681, 0 }, // 0x0681 { 0x0682, 0 }, // 0x0682 { 0xFB76, 3 }, // 0x0683 D NYEH { 0xFB72, 3 }, // 0x0684 D DYEH { 0x0685, 0 }, // 0x0685 { 0xFB7A, 3 }, // 0x0686 D TCHEH { 0xFB7E, 3 }, // 0x0687 D TCHEHEH { 0xFB88, 1 }, // 0x0688 R DDAL { 0x0689, 0 }, // 0x0689 { 0x068A, 0 }, // 0x068A { 0x068B, 0 }, // 0x068B { 0xFB84, 1 }, // 0x068C R DAHAL { 0xFB82, 1 }, // 0x068D R DDAHAL { 0xFB86, 1 }, // 0x068E R DUL { 0x068F, 0 }, // 0x068F { 0x0690, 0 }, // 0x0690 { 0xFB8C, 1 }, // 0x0691 R RREH { 0x0692, 0 }, // 0x0692 { 0x0693, 0 }, // 0x0693 { 0x0694, 0 }, // 0x0694 { 0x0695, 0 }, // 0x0695 { 0x0696, 0 }, // 0x0696 { 0x0697, 0 }, // 0x0697 { 0xFB8A, 1 }, // 0x0698 R JEH { 0x0699, 0 }, // 0x0699 { 0x069A, 0 }, // 0x069A { 0x069B, 0 }, // 0x069B { 0x069C, 0 }, // 0x069C { 0x069D, 0 }, // 0x069D { 0x069E, 0 }, // 0x069E { 0x069F, 0 }, // 0x069F { 0x06A0, 0 }, // 0x06A0 { 0x06A1, 0 }, // 0x06A1 { 0x06A2, 0 }, // 0x06A2 { 0x06A3, 0 }, // 0x06A3 { 0xFB6A, 3 }, // 0x06A4 D VEH { 0x06A5, 0 }, // 0x06A5 { 0xFB6E, 3 }, // 0x06A6 D PEHEH { 0x06A7, 0 }, // 0x06A7 { 0x06A8, 0 }, // 0x06A8 { 0xFB8E, 3 }, // 0x06A9 D KEHEH { 0x06AA, 0 }, // 0x06AA { 0x06AB, 0 }, // 0x06AB { 0x06AC, 0 }, // 0x06AC { 0xFBD3, 3 }, // 0x06AD D NG { 0x06AE, 0 }, // 0x06AE { 0xFB92, 3 }, // 0x06AF D GAF { 0x06B0, 0 }, // 0x06B0 { 0xFB9A, 3 }, // 0x06B1 D NGOEH { 0x06B2, 0 }, // 0x06B2 { 0xFB96, 3 }, // 0x06B3 D GUEH { 0x06B4, 0 }, // 0x06B4 { 0x06B5, 0 }, // 0x06B5 { 0x06B6, 0 }, // 0x06B6 { 0x06B7, 0 }, // 0x06B7 { 0x06B8, 0 }, // 0x06B8 { 0x06B9, 0 }, // 0x06B9 { 0x06BA, 0 }, // 0x06BA { 0xFBA0, 3 }, // 0x06BB D RNOON { 0x06BC, 0 }, // 0x06BC { 0x06BD, 0 }, // 0x06BD { 0xFBAA, 3 }, // 0x06BE D HEH DOACHASHMEE { 0x06BF, 0 }, // 0x06BF { 0xFBA4, 1 }, // 0x06C0 R HEH WITH YEH ABOVE { 0xFBA6, 3 }, // 0x06C1 D HEH GOAL { 0x06C2, 0 }, // 0x06C2 { 0x06C3, 0 }, // 0x06C3 { 0x06C4, 0 }, // 0x06C4 { 0xFBE0, 1 }, // 0x06C5 R KIRGHIZ OE { 0xFBD9, 1 }, // 0x06C6 R OE { 0xFBD7, 1 }, // 0x06C7 R U { 0xFBDB, 1 }, // 0x06C8 R YU { 0xFBE2, 1 }, // 0x06C9 R KIRGHIZ YU { 0x06CA, 0 }, // 0x06CA { 0xFBDE, 1 }, // 0x06CB R VE { 0xFBFC, 3 }, // 0x06CC D FARSI YEH { 0x06CD, 0 }, // 0x06CD { 0x06CE, 0 }, // 0x06CE { 0x06CF, 0 }, // 0x06CF { 0xFBE4, 3 }, // 0x06D0 D E { 0x06D1, 0 }, // 0x06D1 { 0xFBAE, 1 }, // 0x06D2 R YEH BARREE { 0xFBB0, 1 }, // 0x06D3 R YEH BARREE WITH HAMZA ABOVE { 0x06D4, 0 }, // 0x06D4 { 0x06D5, 0 }, // 0x06D5 { 0x06D6, 0 }, // 0x06D6 { 0x06D7, 0 }, // 0x06D7 { 0x06D8, 0 }, // 0x06D8 { 0x06D9, 0 }, // 0x06D9 { 0x06DA, 0 }, // 0x06DA { 0x06DB, 0 }, // 0x06DB { 0x06DC, 0 }, // 0x06DC { 0x06DD, 0 }, // 0x06DD { 0x06DE, 0 }, // 0x06DE { 0x06DF, 0 }, // 0x06DF { 0x06E0, 0 }, // 0x06E0 { 0x06E1, 0 }, // 0x06E1 { 0x06E2, 0 }, // 0x06E2 { 0x06E3, 0 }, // 0x06E3 { 0x06E4, 0 }, // 0x06E4 { 0x06E5, 0 }, // 0x06E5 { 0x06E6, 0 }, // 0x06E6 { 0x06E7, 0 }, // 0x06E7 { 0x06E8, 0 }, // 0x06E8 { 0x06E9, 0 }, // 0x06E9 { 0x06EA, 0 }, // 0x06EA { 0x06EB, 0 }, // 0x06EB { 0x06EC, 0 }, // 0x06EC { 0x06ED, 0 }, // 0x06ED { 0x06EE, 0 }, // 0x06EE { 0x06EF, 0 }, // 0x06EF { 0x06F0, 0 }, // 0x06F0 { 0x06F1, 0 }, // 0x06F1 { 0x06F2, 0 }, // 0x06F2 { 0x06F3, 0 }, // 0x06F3 { 0x06F4, 0 }, // 0x06F4 { 0x06F5, 0 }, // 0x06F5 { 0x06F6, 0 }, // 0x06F6 { 0x06F7, 0 }, // 0x06F7 { 0x06F8, 0 }, // 0x06F8 { 0x06F9, 0 }, // 0x06F9 { 0x06FA, 0 }, // 0x06FA { 0x06FB, 0 }, // 0x06FB { 0x06FC, 0 }, // 0x06FC { 0x06FD, 0 }, // 0x06FD { 0x06FE, 0 }, // 0x06FE { 0x06FF, 0 } // 0x06FF }; // the arabicUnicodeMapping does not work for U+0649 ALEF MAKSURA, this table does static const ushort alefMaksura[4] = {0xFEEF, 0xFEF0, 0xFBE8, 0xFBE9}; // this is a bit tricky. Alef always binds to the right, so the second parameter descibing the shape // of the lam can be either initial of medial. So initial maps to the isolated form of the ligature, // medial to the final form static const ushort arabicUnicodeLamAlefMapping[6][4] = { { 0xfffd, 0xfffd, 0xfef5, 0xfef6 }, // 0x622 R Alef with Madda above { 0xfffd, 0xfffd, 0xfef7, 0xfef8 }, // 0x623 R Alef with Hamza above { 0xfffd, 0xfffd, 0xfffd, 0xfffd }, // 0x624 // Just to fill the table ;-) { 0xfffd, 0xfffd, 0xfef9, 0xfefa }, // 0x625 R Alef with Hamza below { 0xfffd, 0xfffd, 0xfffd, 0xfffd }, // 0x626 // Just to fill the table ;-) { 0xfffd, 0xfffd, 0xfefb, 0xfefc } // 0x627 R Alef }; static inline int getShape( uchar cell, int shape ) { #ifdef Q_OS_MAC // the arabicUnicodeMapping does not work for U+0649 ALEF MAKSURA, handle this here uint ch = ( cell != 0x49 ) ? (shape ? arabicUnicodeMapping[cell][0] + shape : 0x600+cell) : alefMaksura[shape] ; #else // the arabicUnicodeMapping does not work for U+0649 ALEF MAKSURA, handle this here uint ch = ( cell != 0x49 ) ? arabicUnicodeMapping[cell][0] + shape : alefMaksura[shape] ; #endif return ch; } static void shapedString(const QString& uc, int from, int len, QChar *shapeBuffer, int *shapedLength, bool reverse, GlyphAttributes *attrs, unsigned short *logClusters ) { if( len < 0 ) { len = uc.length() - from; } else if( len == 0 ) { *shapedLength = 0; return; } const QChar *ch = uc.unicode() + from; QChar *data = shapeBuffer; int clusterStart = 0; for ( int i = 0; i < len; i++ ) { uchar r = ch->row(); int gpos = data - shapeBuffer; if ( r != 0x06 ) { if ( r == 0x20 ) { uchar c = ch->cell(); if (c == 0x0c || c == 0x0d) // remove ZWJ and ZWNJ goto skip; } if ( reverse ) *data = mirroredChar( *ch ); else *data = *ch; } else { uchar c = ch->cell(); int pos = i + from; int shape = glyphVariantLogical( uc, pos ); // qDebug("mapping U+%x to shape %d glyph=0x%x", ch->unicode(), shape, getShape(c, shape)); // take care of lam-alef ligatures (lam right of alef) ushort map; switch ( c ) { case 0x44: { // lam const QChar *pch = nextChar( uc, pos ); if ( pch->row() == 0x06 ) { switch ( pch->cell() ) { case 0x22: case 0x23: case 0x25: case 0x27: // qDebug(" lam of lam-alef ligature"); map = arabicUnicodeLamAlefMapping[pch->cell() - 0x22][shape]; goto next; default: break; } } break; } case 0x22: // alef with madda case 0x23: // alef with hamza above case 0x25: // alef with hamza below case 0x27: // alef if ( prevChar( uc, pos )->unicode() == 0x0644 ) { // have a lam alef ligature //qDebug(" alef of lam-alef ligature"); goto skip; } default: break; } map = getShape( c, shape ); next: *data = map; } // #### Qt 4.0: Fixme attrs[gpos].zeroWidth = false; if ( ::category( *ch ) == QChar::Mark_NonSpacing ) { attrs[gpos].mark = TRUE; // qDebug("glyph %d (char %d) is mark!", gpos, i ); } else { attrs[gpos].mark = FALSE; clusterStart = data - shapeBuffer; } attrs[gpos].clusterStart = !attrs[gpos].mark; attrs[gpos].combiningClass = combiningClass( *ch ); data++; skip: ch++; logClusters[i] = clusterStart; } *shapedLength = data - shapeBuffer; } #if defined( Q_WS_X11) && !defined( QT_NO_XFTFREETYPE ) static void arabicSyriacOpenTypeShape( int script, QOpenType *openType, const QString &string, int from, int len, QTextEngine *engine, QScriptItem *si ) { convertToCMap( string.unicode() + from, len, engine, si ); heuristicSetGlyphAttributes( string, from, len, engine, si ); unsigned char gv[256]; unsigned char *glyphVariant = gv; bool ap[256]; bool *apply = ap; if ( si->num_glyphs > 255 ) { glyphVariant = (unsigned char *)malloc(si->num_glyphs*sizeof(unsigned char)); apply = (bool *)malloc(si->num_glyphs*sizeof(bool)); } for ( int i = 0; i < si->num_glyphs; i++ ) glyphVariant[i] = glyphVariantLogical( string, from + i ); glyph_t *glyphs = engine->glyphs(si); GlyphAttributes *attrs = engine->glyphAttributes(si); unsigned short *logClusters = engine->logClusters(si); unsigned short *uc = (unsigned short *)string.unicode() + from; // Hack to remove ZWJ and ZWNJ from rendered output. int j = 0; for ( int i = 0; i < si->num_glyphs; i++ ) { if (uc[i] == 0x200c || uc[i] == 0x200d) continue; glyphs[j] = glyphs[i]; attrs[j] = attrs[i]; glyphVariant[j] = glyphVariant[i]; logClusters[i] = logClusters[j]; ++j; } si->num_glyphs = j; openType->init(glyphs, attrs, si->num_glyphs, logClusters, si->num_glyphs); // call features in the order defined by http://www.microsoft.com/typography/otfntdev/arabicot/shaping.htm openType->applyGSUBFeature(FT_MAKE_TAG( 'c', 'c', 'm', 'p' )); if (script == QFont::Arabic) { const struct { int tag; int shape; } features[] = { { FT_MAKE_TAG( 'i', 's', 'o', 'l' ), XIsolated }, { FT_MAKE_TAG( 'f', 'i', 'n', 'a' ), XFinal }, { FT_MAKE_TAG( 'm', 'e', 'd', 'i' ), XMedial }, { FT_MAKE_TAG( 'i', 'n', 'i', 't' ), XInitial } }; for (int j = 0; j < 4; ++j) { for ( int i = 0; i < si->num_glyphs; i++ ) apply[i] = (glyphVariant[i] == features[j].shape); openType->applyGSUBFeature(features[j].tag, apply); } } else { const struct { int tag; int shape; } features[] = { { FT_MAKE_TAG( 'i', 's', 'o', 'l' ), XIsolated }, { FT_MAKE_TAG( 'f', 'i', 'n', 'a' ), XFinal }, { FT_MAKE_TAG( 'f', 'i', 'n', '2' ), XFinal }, { FT_MAKE_TAG( 'f', 'i', 'n', '3' ), XFinal }, { FT_MAKE_TAG( 'm', 'e', 'd', 'i' ), XMedial }, { FT_MAKE_TAG( 'm', 'e', 'd', '2' ), XMedial }, { FT_MAKE_TAG( 'i', 'n', 'i', 't' ), XInitial } }; for (int j = 0; j < 7; ++j) { for ( int i = 0; i < si->num_glyphs; i++ ) apply[i] = (glyphVariant[i] == features[j].shape); openType->applyGSUBFeature(features[j].tag, apply); } } const int commonFeatures[] = { // these features get applied to all glyphs and both scripts FT_MAKE_TAG( 'r', 'l', 'i', 'g' ), FT_MAKE_TAG( 'c', 'a', 'l', 't' ), FT_MAKE_TAG( 'l', 'i', 'g', 'a' ), FT_MAKE_TAG( 'd', 'l', 'i', 'g' ) }; for (int j = 0; j < 4; ++j) openType->applyGSUBFeature(commonFeatures[j]); if (script == QFont::Arabic) { const int features[] = { FT_MAKE_TAG( 'c', 's', 'w', 'h' ), // mset is used in old Win95 fonts that don't have a 'mark' positioning table. FT_MAKE_TAG( 'm', 's', 'e', 't' ) }; for (int j = 0; j < 2; ++j) openType->applyGSUBFeature(features[j]); } openType->applyGPOSFeatures(); si->num_glyphs = 0; openType->appendTo(engine, si); if (glyphVariant != gv) { free(glyphVariant); free(apply); } } #endif static void arabic_attributes( int /*script*/, const QString &text, int from, int len, QCharAttributes *attributes ) { const QChar *uc = text.unicode() + from; attributes += from; for ( int i = 0; i < len; i++ ) { QChar::Category cat = ::category( *uc ); attributes->whiteSpace = (cat == QChar::Separator_Space) && (uc->unicode() != 0xa0); attributes->softBreak = FALSE; attributes->charStop = (cat != QChar::Mark_NonSpacing); attributes->wordStop = FALSE; attributes->invalid = FALSE; ++uc; ++attributes; } } // #### stil missing: identify invalid character combinations static void arabic_shape( int /*script*/, const QString &string, int from, int len, QTextEngine *engine, QScriptItem *si ) { #if defined( Q_WS_X11) && !defined( QT_NO_XFTFREETYPE ) QOpenType *openType = si->fontEngine->openType(); if ( openType && openType->supportsScript( QFont::Arabic ) ) { arabicSyriacOpenTypeShape( QFont::Arabic, openType, string, from, len, engine, si ); return; } #endif const QString &text = string; GlyphAttributes *glyphAttributes = engine->glyphAttributes( si ); unsigned short *logClusters = engine->logClusters( si ); QChar *shapedChars = (QChar *)::malloc( len * sizeof( QChar ) ); int slen; shapedString( text, from, len, shapedChars, &slen, (si->analysis.bidiLevel%2), glyphAttributes, logClusters ); convertToCMap( shapedChars, slen, engine, si ); advance_t *advances = engine->advances(si); for (int i = 0; i < slen; ++i) if (glyphAttributes[i].mark) advances[i] = 0; ::free( shapedChars ); q_heuristicPosition( engine, si ); } #ifdef Q_WS_X11 # include "qscriptengine_x11.cpp" #elif defined( Q_WS_WIN ) # include "qscriptengine_win.cpp" #elif defined( Q_WS_PM ) # include "qscriptengine_pm.cpp" #elif defined( Q_WS_MAC ) # include "qscriptengine_mac.cpp" #elif defined( Q_WS_QWS ) # include "qscriptengine_qws.cpp" #endif