1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtextengine_p.h"
44 #include <private/qfontengine_coretext_p.h>
45 #include <private/qfontengine_mac_p.h>
49 // set the glyph attributes heuristically. Assumes a 1 to 1 relationship between chars and glyphs
51 // also computes logClusters heuristically
52 static void heuristicSetGlyphAttributes(const QChar *uc, int length, QGlyphLayout *glyphs, unsigned short *logClusters, int num_glyphs)
54 // ### zeroWidth and justification are missing here!!!!!
58 // qDebug("QScriptEngine::heuristicSetGlyphAttributes, num_glyphs=%d", item->num_glyphs);
60 const bool symbolFont = false; // ####
61 glyphs->attributes[0].mark = false;
62 glyphs->attributes[0].clusterStart = true;
63 glyphs->attributes[0].dontPrint = (!symbolFont && uc[0].unicode() == 0x00ad) || qIsControlChar(uc[0].unicode());
66 int lastCat = QChar::category(uc[0].unicode());
67 for (int i = 1; i < length; ++i) {
68 if (logClusters[i] == pos)
72 while (pos < logClusters[i]) {
75 // hide soft-hyphens by default
76 if ((!symbolFont && uc[i].unicode() == 0x00ad) || qIsControlChar(uc[i].unicode()))
77 glyphs->attributes[pos].dontPrint = true;
78 const QUnicodeTables::Properties *prop = QUnicodeTables::properties(uc[i].unicode());
79 int cat = prop->category;
81 // one gets an inter character justification point if the current char is not a non spacing mark.
82 // as then the current char belongs to the last one and one gets a space justification point
83 // after the space char.
84 if (lastCat == QChar::Separator_Space)
85 glyphs->attributes[pos-1].justification = HB_Space;
86 else if (cat != QChar::Mark_NonSpacing)
87 glyphs->attributes[pos-1].justification = HB_Character;
89 glyphs->attributes[pos-1].justification = HB_NoJustification;
93 pos = logClusters[length-1];
94 if (lastCat == QChar::Separator_Space)
95 glyphs->attributes[pos].justification = HB_Space;
97 glyphs->attributes[pos].justification = HB_Character;
100 struct QArabicProperties {
102 unsigned char justification;
104 Q_DECLARE_TYPEINFO(QArabicProperties, Q_PRIMITIVE_TYPE);
111 // intermediate state
116 // these groups correspond to the groups defined in the Unicode standard.
117 // Some of these groups are equal with regards to both joining and line breaking behaviour,
118 // and thus have the same enum value
120 // I'm not sure the mapping of syriac to arabic enums is correct with regards to justification, but as
121 // I couldn't find any better document I'll hope for the best.
159 YehWithTail = HamzaOnHehGoal,
160 YehBarre = HamzaOnHehGoal,
189 Sadhe = HamzaOnHehGoal,
192 // Compiler bug? Otherwise ArabicGroupsEnd would be equal to Dal + 1.
193 Dummy = HamzaOnHehGoal,
197 static const unsigned char arabic_group[0x150] = {
198 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
199 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
200 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
201 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
203 Transparent, Transparent, Transparent, Transparent,
204 Transparent, Transparent, ArabicNone, ArabicNone,
205 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
206 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
208 ArabicNone, ArabicNone, Alef, Alef,
209 Waw, Alef, Yeh, Alef,
210 Beh, TehMarbuta, Beh, Beh,
215 Tah, Ain, Ain, ArabicNone,
216 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
219 Kashida, Feh, Qaf, Kaf,
220 Lam, Meem, Noon, Heh,
221 Waw, Yeh, Yeh, Transparent,
222 Transparent, Transparent, Transparent, Transparent,
224 Transparent, Transparent, Transparent, Transparent,
225 Transparent, Transparent, Transparent, Transparent,
226 Transparent, ArabicNone, ArabicNone, ArabicNone,
227 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
229 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
230 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
231 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
232 ArabicNone, ArabicNone, Beh, Qaf,
234 Transparent, Alef, Alef, Alef,
235 ArabicNone, Alef, Waw, Waw,
247 Reh, Reh, Seen, Seen,
252 Qaf, Gaf, SwashKaf, Gaf,
257 Lam, Noon, Noon, Noon,
258 Noon, Noon, KnottedHeh, Hah,
261 TehMarbuta, HehGoal, HamzaOnHehGoal, HamzaOnHehGoal,
264 Yeh, YehWithTail, Yeh, Waw,
266 Yeh, Yeh, YehBarre, YehBarre,
267 ArabicNone, TehMarbuta, Transparent, Transparent,
268 Transparent, Transparent, Transparent, Transparent,
269 Transparent, ArabicNone, ArabicNone, Transparent,
271 Transparent, Transparent, Transparent, Transparent,
272 Transparent, ArabicNone, ArabicNone, Transparent,
273 Transparent, ArabicNone, Transparent, Transparent,
274 Transparent, Transparent, Dal, Reh,
276 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
277 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
278 ArabicNone, ArabicNone, Seen, Sad,
279 Ain, ArabicNone, ArabicNone, KnottedHeh,
282 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
283 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
284 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
285 ArabicNone, ArabicNone, ArabicNone, ArabicNone,
287 Alaph, Transparent, Beth, Gamal,
288 Gamal, Dalath, Dalath, He,
289 SyriacWaw, Zain, Heth, Teth,
290 Teth, Yudh, YudhHe, Kaph,
292 Lamadh, Mim, Nun, Semakh,
293 FinalSemakh, SyriacE, Pe, ReversedPe,
294 Sadhe, Qaph, Dalath, Shin,
295 Taw, Beth, Gamal, Dalath,
297 Transparent, Transparent, Transparent, Transparent,
298 Transparent, Transparent, Transparent, Transparent,
299 Transparent, Transparent, Transparent, Transparent,
300 Transparent, Transparent, Transparent, Transparent,
302 Transparent, Transparent, Transparent, Transparent,
303 Transparent, Transparent, Transparent, Transparent,
304 Transparent, Transparent, Transparent, ArabicNone,
305 ArabicNone, Zain, Kaph, Fe,
308 static inline ArabicGroup arabicGroup(unsigned short uc)
310 if (uc >= 0x0600 && uc < 0x750)
311 return (ArabicGroup) arabic_group[uc-0x600];
312 else if (uc == 0x200d)
314 else if (QChar::category(uc) == QChar::Separator_Space)
322 Arabic shaping obeys a number of rules according to the joining classes (see Unicode book, section on
325 Each unicode char has a joining class (right, dual (left&right), center (joincausing) or transparent).
326 transparent joining is not encoded in QChar::joining(), but applies to all combining marks and format marks.
328 Right join-causing: dual + center
329 Left join-causing: dual + right + center
331 Rules are as follows (for a string already in visual order, as we have it here):
333 R1 Transparent characters do not affect joining behaviour.
334 R2 A right joining character, that has a right join-causing char on the right will get form XRight
335 (R3 A left joining character, that has a left join-causing char on the left will get form XLeft)
336 Note: the above rule is meaningless, as there are no pure left joining characters defined in Unicode
337 R4 A dual joining character, that has a left join-causing char on the left and a right join-causing char on
338 the right will get form XMedial
339 R5 A dual joining character, that has a right join causing char on the right, and no left join causing char on the left
341 R6 A dual joining character, that has a left join causing char on the left, and no right join causing char on the right
343 R7 Otherwise the character will get form XIsolated
345 Additionally we have to do the minimal ligature support for lam-alef ligatures:
347 L1 Transparent characters do not affect ligature behaviour.
348 L2 Any sequence of Alef(XRight) + Lam(XMedial) will form the ligature Alef.Lam(XLeft)
349 L3 Any sequence of Alef(XRight) + Lam(XLeft) will form the ligature Alef.Lam(XIsolated)
351 The state table below handles rules R1-R7.
362 static const Joining joining_for_group[ArabicGroupsEnd] = {
365 JNone, // ArabicSpace
367 JTransparent, // Transparent
384 JRight // HamzaOnHehGoal
393 static const JoiningPair joining_table[5][4] =
394 // None, Causing, Dual, Right
396 { { XIsolated, XIsolated }, { XIsolated, XCausing }, { XIsolated, XInitial }, { XIsolated, XIsolated } }, // XIsolated
397 { { XFinal, XIsolated }, { XFinal, XCausing }, { XFinal, XInitial }, { XFinal, XIsolated } }, // XFinal
398 { { XIsolated, XIsolated }, { XInitial, XCausing }, { XInitial, XMedial }, { XInitial, XFinal } }, // XInitial
399 { { XFinal, XIsolated }, { XMedial, XCausing }, { XMedial, XMedial }, { XMedial, XFinal } }, // XMedial
400 { { XIsolated, XIsolated }, { XIsolated, XCausing }, { XIsolated, XMedial }, { XIsolated, XFinal } }, // XCausing
405 According to http://www.microsoft.com/middleeast/Arabicdev/IE6/KBase.asp
407 1. Find the priority of the connecting opportunities in each word
408 2. Add expansion at the highest priority connection opportunity
409 3. If more than one connection opportunity have the same highest value,
410 use the opportunity closest to the end of the word.
412 Following is a chart that provides the priority for connection
413 opportunities and where expansion occurs. The character group names
414 are those in table 6.6 of the UNICODE 2.0 book.
417 PrioritY Glyph Condition Kashida Location
419 Arabic_Kashida User inserted Kashida The user entered a Kashida in a position. After the user
420 (Shift+j or Shift+[E with hat]) Thus, it is the highest priority to insert an inserted kashida
423 Arabic_Seen Seen, Sad Connecting to the next character. After the character.
424 (Initial or medial form).
426 Arabic_HaaDal Teh Marbutah, Haa, Dal Connecting to previous character. Before the final form
429 Arabic_Alef Alef, Tah, Lam, Connecting to previous character. Before the final form
430 Kaf and Gaf of these characters.
432 Arabic_BaRa Reh, Yeh Connected to medial Beh Before preceding medial Baa
434 Arabic_Waw Waw, Ain, Qaf, Feh Connecting to previous character. Before the final form of
437 Arabic_Normal Other connecting Connecting to previous character. Before the final form
438 characters of these characters.
442 This seems to imply that we have at most one kashida point per arabic word.
446 void qt_getArabicProperties(const unsigned short *chars, int len, QArabicProperties *properties)
448 // qDebug("arabicSyriacOpenTypeShape: properties:");
450 int lastGroup = ArabicNone;
452 ArabicGroup group = arabicGroup(chars[0]);
453 Joining j = joining_for_group[group];
454 QArabicShape shape = joining_table[XIsolated][j].form2;
455 properties[0].justification = HB_NoJustification;
457 for (int i = 1; i < len; ++i) {
458 // #### fix handling for spaces and punktuation
459 properties[i].justification = HB_NoJustification;
461 group = arabicGroup(chars[i]);
462 j = joining_for_group[group];
464 if (j == JTransparent) {
465 properties[i].shape = XIsolated;
469 properties[lastPos].shape = joining_table[shape][j].form1;
470 shape = joining_table[shape][j].form2;
474 if (properties[lastPos].shape == XInitial || properties[lastPos].shape == XMedial)
475 properties[i-1].justification = HB_Arabic_Seen;
478 if (properties[lastPos].shape == XFinal)
479 properties[lastPos-1].justification = HB_Arabic_HaaDal;
482 if (properties[lastPos].shape == XFinal)
483 properties[lastPos-1].justification = HB_Arabic_Alef;
486 if (properties[lastPos].shape == XFinal)
487 properties[lastPos-1].justification = HB_Arabic_Waw;
490 if (properties[lastPos].shape == XFinal)
491 properties[lastPos-1].justification = HB_Arabic_Normal;
500 lastGroup = ArabicNone;
505 // ### Center should probably be treated as transparent when it comes to justification.
509 properties[i].justification = HB_Arabic_Space;
512 properties[i].justification = HB_Arabic_Kashida;
530 if (properties[lastPos].shape == XMedial && arabicGroup(chars[lastPos]) == Beh)
531 properties[lastPos-1].justification = HB_Arabic_BaRa;
544 case ArabicGroupsEnd:
550 properties[lastPos].shape = joining_table[shape][JNone].form1;
553 // for (int i = 0; i < len; ++i)
554 // qDebug("arabic properties(%d): uc=%x shape=%d, justification=%d", i, chars[i], properties[i].shape, properties[i].justification);
557 void QTextEngine::shapeTextMac(int item) const
559 QScriptItem &si = layoutData->items[item];
561 si.glyph_data_offset = layoutData->used;
563 QFontEngine *font = fontEngine(si, &si.ascent, &si.descent, &si.leading);
564 if (font->type() != QFontEngine::Multi) {
565 shapeTextWithHarfbuzz(item);
569 #ifndef QT_MAC_USE_COCOA
570 QFontEngineMacMulti *fe = static_cast<QFontEngineMacMulti *>(font);
572 QCoreTextFontEngineMulti *fe = static_cast<QCoreTextFontEngineMulti *>(font);
574 QTextEngine::ShaperFlags flags;
575 if (si.analysis.bidiLevel % 2)
576 flags |= RightToLeft;
577 if (option.useDesignMetrics())
578 flags |= DesignMetrics;
580 attributes(); // pre-initialize char attributes
582 const int len = length(item);
583 int num_glyphs = length(item);
584 const QChar *str = layoutData->string.unicode() + si.position;
585 ushort upperCased[256];
586 if (si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase
587 || si.analysis.flags == QScriptAnalysis::Lowercase) {
588 ushort *uc = upperCased;
590 uc = new ushort[len];
591 for (int i = 0; i < len; ++i) {
592 if(si.analysis.flags == QScriptAnalysis::Lowercase)
593 uc[i] = str[i].toLower().unicode();
595 uc[i] = str[i].toUpper().unicode();
597 str = reinterpret_cast<const QChar *>(uc);
600 ensureSpace(num_glyphs);
601 num_glyphs = layoutData->glyphLayout.numGlyphs - layoutData->used;
603 QGlyphLayout g = availableGlyphs(&si);
604 g.numGlyphs = num_glyphs;
605 unsigned short *log_clusters = logClusters(&si);
607 bool stringToCMapFailed = false;
608 if (!fe->stringToCMap(str, len, &g, &num_glyphs, flags, log_clusters, attributes(), &si)) {
609 ensureSpace(num_glyphs);
610 g = availableGlyphs(&si);
611 stringToCMapFailed = !fe->stringToCMap(str, len, &g, &num_glyphs, flags, log_clusters,
615 if (!stringToCMapFailed) {
616 heuristicSetGlyphAttributes(str, len, &g, log_clusters, num_glyphs);
618 si.num_glyphs = num_glyphs;
620 layoutData->used += si.num_glyphs;
622 QGlyphLayout g = shapedGlyphs(&si);
624 if (si.analysis.script == QUnicodeTables::Arabic) {
625 QVarLengthArray<QArabicProperties> props(len + 2);
626 QArabicProperties *properties = props.data();
634 if (f + l < layoutData->string.length()) {
637 qt_getArabicProperties((const unsigned short *)(layoutData->string.unicode()+f), l, props.data());
639 unsigned short *log_clusters = logClusters(&si);
641 for (int i = 0; i < len; ++i) {
642 int gpos = log_clusters[i];
643 g.attributes[gpos].justification = properties[i].justification;
648 const ushort *uc = reinterpret_cast<const ushort *>(str);
650 if ((si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase
651 || si.analysis.flags == QScriptAnalysis::Lowercase)