Subversion Repositories Scribus

Rev

Rev 22017 | Rev 22367 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
22017 jghali 1
#include "textshaper.h"
2
 
3
#include <harfbuzz/hb.h>
4
#include <harfbuzz/hb-ft.h>
5
#include <harfbuzz/hb-icu.h>
6
#include <unicode/ubidi.h>
7
 
8
#include "scrptrun.h"
9
 
10
#include "glyphcluster.h"
11
#include "pageitem.h"
12
#include "scribusdoc.h"
13
#include "storytext.h"
14
#include "styles/paragraphstyle.h"
15
#include "util.h"
16
 
17
 
18
TextShaper::TextShaper(ITextContext* context, ITextSource &story, int firstChar, bool singlePar)
19
        : m_context(context),
20
        m_contextNeeded(false),
21
        m_story(story),
22
        m_firstChar(firstChar),
23
        m_singlePar(singlePar)
24
{ }
25
 
26
TextShaper::TextShaper(ITextSource &story, int firstChar)
27
        : m_context(NULL),
28
        m_contextNeeded(false),
29
        m_story(story),
30
        m_firstChar(firstChar),
31
        m_singlePar(false)
32
{
33
        for (int i = m_firstChar; i < m_story.length(); ++i)
34
        {
35
                QChar ch = m_story.text(i);
36
                if (ch == SpecialChars::PARSEP || ch == SpecialChars::LINEBREAK)
37
                        continue;
38
                QString str(ch);
39
                m_textMap.insert(i, i);
40
                m_text.append(str);
41
        }
42
}
43
 
44
QList<TextShaper::TextRun> TextShaper::itemizeBiDi()
45
{
46
        QList<TextRun> textRuns;
47
        UBiDi *obj = ubidi_open();
48
        UErrorCode err = U_ZERO_ERROR;
49
 
50
        UBiDiLevel parLevel = UBIDI_LTR;
51
        ParagraphStyle style = m_story.paragraphStyle(m_firstChar);
52
        if (style.direction() == ParagraphStyle::RTL)
53
                parLevel = UBIDI_RTL;
54
 
55
        ubidi_setPara(obj, (const UChar*) m_text.utf16(), m_text.length(), parLevel, NULL, &err);
56
        if (U_SUCCESS(err))
57
        {
58
                int32_t count = ubidi_countRuns(obj, &err);
59
                if (U_SUCCESS(err))
60
                {
61
                        textRuns.reserve(count);
62
                        for (int32_t i = 0; i < count; i++)
63
                        {
64
                                int32_t start, length;
65
                                UBiDiDirection dir = ubidi_getVisualRun(obj, i, &start, &length);
66
                                textRuns.append(TextRun(start, length, dir));
67
                        }
68
                }
69
        }
70
 
71
        ubidi_close(obj);
72
        return textRuns;
73
}
74
 
75
QList<TextShaper::TextRun> TextShaper::itemizeScripts(const QList<TextRun> &runs)
76
{
77
        QList<TextRun> newRuns;
78
        ScriptRun scriptrun((const UChar*) m_text.utf16(), m_text.length());
79
 
80
        foreach (TextRun run, runs)
81
        {
82
                int start = run.start;
83
                QList<TextRun> subRuns;
84
 
85
                while (scriptrun.next())
86
                {
87
                        if (scriptrun.getScriptStart() <= start && scriptrun.getScriptEnd() > start)
88
                                break;
89
                }
90
 
91
                while (start < run.start + run.len)
92
                {
93
                        int end = qMin(scriptrun.getScriptEnd(), run.start + run.len);
94
                        UScriptCode script = scriptrun.getScriptCode();
95
                        if (run.dir == UBIDI_RTL)
96
                                subRuns.prepend(TextRun(start, end - start, run.dir, script));
97
                        else
98
                                subRuns.append(TextRun(start, end - start, run.dir, script));
99
 
100
                        start = end;
101
                        scriptrun.next();
102
                }
103
 
104
                scriptrun.reset();
105
                newRuns.append(subRuns);
106
        }
107
 
108
        return newRuns;
109
}
110
 
111
QList<TextShaper::FeaturesRun> TextShaper::itemizeFeatures(const TextRun &run)
112
{
113
        QList<FeaturesRun> newRuns;
114
        QList<FeaturesRun> subfeature;
115
        int start = run.start;
116
 
117
        while (start < run.start + run.len)
118
        {
119
                int end = start;
120
                QStringList startFeatures = m_story.charStyle(m_textMap.value(start)).fontFeatures().split(",");
121
                while (end < run.start + run.len)
122
                {
123
                        QStringList endFeatures = m_story.charStyle(m_textMap.value(end)).fontFeatures().split(",");
124
                        if (startFeatures != endFeatures)
125
                                break;
126
                        end++;
127
                }
128
                subfeature.append(FeaturesRun(start, end - start, startFeatures));
129
                start = end;
130
                startFeatures.clear();
131
        }
132
        newRuns.append(subfeature);
133
        return newRuns;
134
}
135
 
136
QList<TextShaper::TextRun> TextShaper::itemizeStyles(const QList<TextRun> &runs)
137
{
138
        QList<TextRun> newRuns;
139
 
140
        foreach (TextRun run, runs) {
141
                int start = run.start;
142
                QList<TextRun> subRuns;
143
 
144
                while (start < run.start + run.len)
145
                {
146
                        int end = start;
147
                        const CharStyle &startStyle = m_story.charStyle(m_textMap.value(start));
148
                        while (end < run.start + run.len)
149
                        {
150
                                const CharStyle &endStyle = m_story.charStyle(m_textMap.value(end));
151
                                if (!startStyle.equivForShaping(endStyle))
152
                                        break;
153
                                end++;
154
                        }
155
                        if (run.dir == UBIDI_RTL)
156
                                subRuns.prepend(TextRun(start, end - start, run.dir, run.script));
157
                        else
158
                                subRuns.append(TextRun(start, end - start, run.dir, run.script));
159
                        start = end;
160
                }
161
 
162
                newRuns.append(subRuns);
163
        }
164
 
165
        return newRuns;
166
}
167
 
168
void TextShaper::buildText(int fromPos, int toPos, QVector<int>& smallCaps)
169
{
170
        m_text = "";
171
 
172
        if (toPos > m_story.length() || toPos < 0)
173
                toPos = m_story.length();
174
 
175
        for (int i = fromPos; i < toPos; ++i)
176
        {
177
                QString str = m_story.text(i,1);
178
 
179
                if (m_singlePar)
180
                {
181
                        QChar ch = str[0];
182
                        if (ch == SpecialChars::PARSEP || ch == SpecialChars::LINEBREAK)
183
                                continue;
184
                }
185
 
186
                if (m_story.hasExpansionPoint(i))
187
                {
188
                        m_contextNeeded = true;
189
                        if (m_context != NULL)
190
                        {
191
                                str = m_context->expand(m_story.expansionPoint(i));
192
                                if (str.isEmpty())
193
                                        str = SpecialChars::ZWNBSPACE;
194
                        }
195
                        else
196
                        {
197
                                str = SpecialChars::OBJECT;
198
                        }
199
                }
200
 
201
                str.replace(SpecialChars::SHYPHEN, SpecialChars::ZWNJ);
202
 
203
                const CharStyle &style = m_story.charStyle(i);
204
                int effects = style.effects() & ScStyle_UserStyles;
205
                bool hasSmallCap = false;
206
                if ((effects & ScStyle_AllCaps) || (effects & ScStyle_SmallCaps))
207
                {
208
                        QLocale locale(style.language());
209
                        QString upper = locale.toUpper(str);
210
                        if (upper != str)
211
                        {
212
                                if (effects & ScStyle_SmallCaps)
213
                                        hasSmallCap = true;
214
                                str = upper;
215
                        }
216
                }
217
 
218
                for (int j = 0; j < str.length(); j++)
219
                {
220
                        m_textMap.insert(m_text.length() + j, i);
221
                        if (hasSmallCap)
222
                                smallCaps.append(m_text.length() + j);
223
                }
224
 
225
                m_text.append(str);
226
        }
227
}
228
 
229
 
230
ShapedText TextShaper::shape(int fromPos, int toPos)
231
{
232
        m_contextNeeded = false;
233
 
234
        ShapedText result(ShapedText(&m_story, fromPos, toPos, m_context));
235
 
236
 
237
        QVector<int> smallCaps;
238
 
239
        buildText(fromPos, toPos, smallCaps);
240
 
241
        QList<TextRun> bidiRuns = itemizeBiDi();
242
        QList<TextRun> scriptRuns = itemizeScripts(bidiRuns);
243
        QList<TextRun> textRuns = itemizeStyles(scriptRuns);
244
 
245
        QVector<int32_t> lineBreaks;
246
        BreakIterator* lineIt = StoryText::getLineIterator();
247
        // FIXME-HOST: add some fallback code if the iterator failed
248
        if (lineIt)
249
        {
250
                lineIt->setText((const UChar*) m_text.utf16());
251
                for (int32_t pos = lineIt->first(); pos != BreakIterator::DONE; pos = lineIt->next())
252
                        lineBreaks.append(pos);
253
        }
254
 
255
        QVector<int32_t> justificationTracking;
256
 
257
        // Insert implicit spaces in justification between characters
258
        // in scripts that do not use spaces to seperate words
259
        foreach (const TextRun& run, scriptRuns) {
260
                switch (run.script) {
261
                // clustered scripts from https://drafts.csswg.org/css-text-3/#script-groups
262
                case USCRIPT_KHMER:
263
                case USCRIPT_LAO:
264
                case USCRIPT_MYANMAR:
265
                case USCRIPT_NEW_TAI_LUE:
266
                case USCRIPT_TAI_LE:
267
                case USCRIPT_TAI_VIET:
268
                case USCRIPT_THAI:
269
                {
270
                        BreakIterator* charIt = StoryText::getGraphemeIterator();
271
                        if (charIt)
272
                        {
273
                                const QString text = m_text.mid(run.start, run.len);
274
                                charIt->setText((const UChar*) text.utf16());
275
                                int32_t pos = charIt->first();
276
                                while (pos != BreakIterator::DONE && pos < text.length())
277
                                {
278
                                        UErrorCode status = U_ZERO_ERROR;
279
                                        UScriptCode sc = uscript_getScript(text.at(pos).unicode(), &status);
280
                                        // do not insert implicit space before punctuation
281
                                        // or other non-script specific characters
282
                                        if (sc != USCRIPT_COMMON)
283
                                                justificationTracking.append(run.start + pos - 1);
284
                                        pos = charIt->next();
285
                                }
286
                        }
287
                        break;
288
                }
289
                default:
290
                        break;
291
                }
292
        }
293
 
294
        foreach (const TextRun& textRun, textRuns) {
295
                const CharStyle &style = m_story.charStyle(m_textMap.value(textRun.start));
296
 
297
                const ScFace &scFace = style.font();
298
                hb_font_t *hbFont = reinterpret_cast<hb_font_t*>(scFace.hbFont());
299
                if (hbFont == NULL)
300
                        continue;
301
 
302
                hb_font_set_scale(hbFont, style.fontSize(), style.fontSize());
303
                FT_Face ftFace = hb_ft_font_get_face(hbFont);
304
                if (ftFace)
305
                        FT_Set_Char_Size(ftFace, style.fontSize(), 0, 72, 0);
306
 
307
                hb_direction_t hbDirection = (textRun.dir == UBIDI_LTR) ? HB_DIRECTION_LTR : HB_DIRECTION_RTL;
308
                hb_script_t hbScript = hb_icu_script_to_script(textRun.script);
309
                std::string language = style.language().toStdString();
310
                hb_language_t hbLanguage = hb_language_from_string(language.c_str(), language.length());
311
 
312
                hb_buffer_t *hbBuffer = hb_buffer_create();
313
                hb_buffer_add_utf16(hbBuffer, m_text.utf16(), m_text.length(), textRun.start, textRun.len);
314
                hb_buffer_set_direction(hbBuffer, hbDirection);
315
                hb_buffer_set_script(hbBuffer, hbScript);
316
                hb_buffer_set_language(hbBuffer, hbLanguage);
317
                hb_buffer_set_cluster_level(hbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
318
 
319
                QVector<hb_feature_t> hbFeatures;
320
                QList<FeaturesRun> featuresRuns = itemizeFeatures(textRun);
321
                foreach (const FeaturesRun& featuresRun, featuresRuns)
322
                {
323
                        const QStringList& features = featuresRun.features;
324
                        hbFeatures.reserve(features.length());
325
                        foreach (const QString& feature, features) {
326
                                hb_feature_t hbFeature;
327
                                hb_bool_t ok = hb_feature_from_string(feature.toStdString().c_str(), feature.toStdString().length(), &hbFeature);
328
                                if (ok)
329
                                {
330
                                        hbFeature.start = featuresRun.start;
331
                                        hbFeature.end = featuresRun.len + featuresRun.start;
332
                                        hbFeatures.append(hbFeature);
333
                                }
334
                        }
335
                }
336
 
337
                // #14523: harfbuzz proritize graphite for graphite enabled fonts, however
338
                // at the point, shaping with graphite fonts is either buggy (harfbuzz 1.4.2)
339
                // or trigger weird results (harfbuzz 1.4.3), so disable graphite for now.
340
                // Prevent also use of platform specific shapers for cross-platform reasons
341
                const char* shapers[] = { "ot", "fallback", 0 };
342
                hb_shape_full(hbFont, hbBuffer, hbFeatures.data(), hbFeatures.length(), shapers);
343
 
344
                unsigned int count = hb_buffer_get_length(hbBuffer);
345
                hb_glyph_info_t *glyphs = hb_buffer_get_glyph_infos(hbBuffer, NULL);
346
                hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(hbBuffer, NULL);
347
 
348
                result.glyphs().reserve(result.glyphs().size() + count);
349
                for (size_t i = 0; i < count; )
350
                {
351
                        uint32_t firstCluster = glyphs[i].cluster;
352
                        uint32_t nextCluster = firstCluster;
353
                        if (hbDirection == HB_DIRECTION_LTR)
354
                        {
355
                                size_t j = i + 1;
356
                                while (j < count && nextCluster == firstCluster)
357
                                {
358
                                        nextCluster = glyphs[j].cluster;
359
                                        j++;
360
                                }
361
                                if (j == count && nextCluster == firstCluster)
362
                                        nextCluster = textRun.start + textRun.len;
363
                        }
364
                        else
365
                        {
366
                                int j = i - 1;
367
                                while (j >= 0 && nextCluster == firstCluster)
368
                                {
369
                                        nextCluster = glyphs[j].cluster;
370
                                        j--;
371
                                }
372
                                if (j <= 0 && nextCluster == firstCluster)
373
                                        nextCluster = textRun.start + textRun.len;
374
                        }
375
 
376
                        assert(m_textMap.contains(firstCluster));
377
                        assert(m_textMap.contains(nextCluster - 1));
378
                        int firstChar = m_textMap.value(firstCluster);
379
                        int lastChar = m_textMap.value(nextCluster - 1);
380
 
381
                        QChar ch = m_story.text(firstChar);
382
                        LayoutFlags flags = m_story.flags(firstChar);
383
                        const CharStyle& charStyle(m_story.charStyle(firstChar));
384
                        const StyleFlag& effects = charStyle.effects();
385
 
386
                        QString str = m_text.mid(firstChar, lastChar-firstChar+1);
387
                        GlyphCluster run(&charStyle, flags, firstChar, lastChar, m_story.object(firstChar), result.glyphs().length(), str);
388
 
389
                        run.clearFlag(ScLayout_HyphenationPossible);
390
                        if (m_story.hasFlag(lastChar, ScLayout_HyphenationPossible))
391
                                run.setFlag(ScLayout_HyphenationPossible);
392
 
393
                        if (textRun.dir == UBIDI_RTL)
394
                                run.setFlag(ScLayout_RightToLeft);
395
 
396
                        if (lineBreaks.contains(firstCluster))
397
                                run.setFlag(ScLayout_LineBoundary);
398
 
399
                        if (SpecialChars::isExpandingSpace(ch))
400
                                run.setFlag(ScLayout_ExpandingSpace);
401
                        else if (justificationTracking.contains(firstCluster))
402
                                run.setFlag(ScLayout_JustificationTracking);
403
 
404
                        if (effects & ScStyle_Underline)
405
                                run.setFlag(ScLayout_Underlined);
406
                        if (effects & ScStyle_UnderlineWords && !ch.isSpace())
407
                                run.setFlag(ScLayout_Underlined);
408
 
409
                        if (firstChar != 0 &&
410
                            SpecialChars::isCJK(m_story.text(firstChar).unicode()) &&
411
                            SpecialChars::isCJK(m_story.text(firstChar - 1).unicode()))
412
                                run.setFlag(ScLayout_ImplicitSpace);
413
 
22178 jghali 414
                        int firstStat = SpecialChars::getCJKAttr(m_story.text(firstChar));
415
                        int currStat  = (firstChar != lastChar) ? SpecialChars::getCJKAttr(m_story.text(lastChar)) : firstStat;
416
 
417
                        if (firstStat & SpecialChars::CJK_NOBREAK_BEFORE)
418
                                run.setFlag(ScLayout_NoBreakBefore);
419
 
420
                        if (currStat & SpecialChars::CJK_NOBREAK_AFTER)
421
                                run.setFlag(ScLayout_NoBreakAfter);
422
 
423
                        if ((firstChar > 0) && (firstStat != 0) && ((firstStat & SpecialChars::CJK_NOBREAK_BEFORE) == 0))
424
                        {
425
                                int prevStat = SpecialChars::getCJKAttr(m_story.text(firstChar - 1));
426
                                if (prevStat != 0 && ((prevStat & SpecialChars::CJK_NOBREAK_AFTER) == 0))
427
                                        run.setFlag(ScLayout_LineBoundary);
428
                        }
429
 
22017 jghali 430
                        run.setScaleH(charStyle.scaleH() / 1000.0);
431
                        run.setScaleV(charStyle.scaleV() / 1000.0);
432
 
433
                        while (i < count && glyphs[i].cluster == firstCluster)
434
                        {
435
                                GlyphLayout gl;
436
                                gl.glyph = glyphs[i].codepoint;
437
                                if (gl.glyph == 0 ||
438
                                    (ch == SpecialChars::LINEBREAK || ch == SpecialChars::PARSEP ||
439
                                     ch == SpecialChars::FRAMEBREAK || ch == SpecialChars::COLBREAK))
440
                                {
441
                                        gl.glyph = scFace.emulateGlyph(ch.unicode());
442
                                }
443
 
444
                                if (gl.glyph < ScFace::CONTROL_GLYPHS)
445
                                {
446
                                        gl.xoffset = positions[i].x_offset / 10.0;
447
                                        gl.yoffset = -positions[i].y_offset / 10.0;
448
                                        gl.xadvance = positions[i].x_advance / 10.0;
449
                                        gl.yadvance = positions[i].y_advance / 10.0;
450
                                }
451
 
452
#if 0
453
                                if (m_story.hasMark(firstChar))
454
                                {
455
                                        GlyphLayout control;
456
                                        control.glyph = SpecialChars::OBJECT.unicode() + ScFace::CONTROL_GLYPHS;
457
                                        run.append(control);
458
                                }
459
#endif
460
 
461
                                if (SpecialChars::isExpandingSpace(ch))
462
                                        gl.xadvance *= run.style().wordTracking();
463
 
464
                                if (m_story.hasObject(firstChar))
465
                                {
466
                                        m_contextNeeded = true;
467
                                        if (m_context != NULL)
468
                                                gl.xadvance = m_context->getVisualBoundingBox(m_story.object(firstChar)).width();
469
                                }
470
 
471
                                if ((effects & ScStyle_Superscript) || (effects & ScStyle_Subscript))
472
                                {
473
                                        m_contextNeeded = true;
474
                                        if (m_context != NULL)
475
                                        {
476
                                                double scale;
477
                                                double asce = style.font().ascent(style.fontSize() / 10.0);
478
                                                if (effects & ScStyle_Superscript)
479
                                                {
480
                                                        gl.yoffset -= asce * m_context->typographicPrefs().valueSuperScript / 100.0;
481
                                                        scale = qMax(m_context->typographicPrefs().scalingSuperScript / 100.0, 10.0 / style.fontSize());
482
                                                }
483
                                                else // effects & ScStyle_Subscript
484
                                                {
485
                                                        gl.yoffset += asce * m_context->typographicPrefs().valueSubScript / 100.0;
486
                                                        scale = qMax(m_context->typographicPrefs().scalingSubScript / 100.0, 10.0 / style.fontSize());
487
                                                }
488
 
489
                                                run.setScaleH(run.scaleH() * scale);
490
                                                run.setScaleV(run.scaleV() * scale);
491
                                        }
492
                                }
493
 
494
                                if (smallCaps.contains(firstCluster))
495
                                {
496
                                        m_contextNeeded = true;
497
                                        if (m_context != NULL)
498
                                        {
499
 
500
                                                double smallcapsScale = m_context->typographicPrefs().valueSmallCaps / 100.0;
501
                                                run.setScaleH(run.scaleH() * smallcapsScale);
502
                                                run.setScaleV(run.scaleV() * smallcapsScale);
503
                                        }
504
                                }
505
 
506
                                if (run.scaleH() == 0.0)
507
                                {
508
                                        gl.xadvance = 0.0;
509
                                        run.setScaleH(1.0);
510
                                }
511
 
512
                                run.append(gl);
513
                                i++;
514
                        }
515
 
516
                        // Apply CJK spacing according to JIS X4051
517
                        if (lastChar + 1 < m_story.length())
518
                        {
519
                                double halfEM = run.style().fontSize() / 10 / 2;
520
                                double quarterEM = run.style().fontSize() / 10 / 4;
521
 
522
                                int nextStat = SpecialChars::getCJKAttr(m_story.text(lastChar + 1));
523
 
524
                                // 1. add 1/4 aki (space) between a CJK letter and
525
                                //    - a latin letter
526
                                //    - an ASCII Digits
527
                                if (currStat != 0)
528
                                {
529
                                        // current char is CJK
530
                                        if (SpecialChars::isLetterRequiringSpaceAroundCJK(m_story.text(lastChar + 1).unicode())) {
531
                                                switch(currStat & SpecialChars::CJK_CHAR_MASK) {
532
                                                case SpecialChars::CJK_KANJI:
533
                                                case SpecialChars::CJK_KANA:
534
                                                case SpecialChars::CJK_NOTOP:
535
                                                        run.extraWidth += quarterEM;
536
                                                }
537
                                        }
538
                                }
539
                                else
540
                                {
541
                                        // current char is not CJK
542
                                        if (SpecialChars::isLetterRequiringSpaceAroundCJK(m_story.text(lastChar).unicode())) {
543
                                                switch(nextStat & SpecialChars::CJK_CHAR_MASK) {
544
                                                case SpecialChars::CJK_KANJI:
545
                                                case SpecialChars::CJK_KANA:
546
                                                case SpecialChars::CJK_NOTOP:
547
                                                        // use the size of the current char instead of the next one
548
                                                        run.extraWidth += quarterEM;
549
                                                }
550
                                        }
551
                                }
552
 
553
                                // 2. remove spaces from glyphs with the following CJK attributes
554
                                if (currStat != 0)
555
                                {       // current char is CJK
556
                                        switch(currStat & SpecialChars::CJK_CHAR_MASK) {
557
                                        case SpecialChars::CJK_FENCE_END:
558
                                                switch(nextStat & SpecialChars::CJK_CHAR_MASK) {
559
                                                case SpecialChars::CJK_FENCE_BEGIN:
560
                                                case SpecialChars::CJK_FENCE_END:
561
                                                case SpecialChars::CJK_COMMA:
562
                                                case SpecialChars::CJK_PERIOD:
563
                                                case SpecialChars::CJK_MIDPOINT:
564
                                                        run.extraWidth -= halfEM;
565
                                                }
566
                                                break;
567
                                        case SpecialChars::CJK_COMMA:
568
                                        case SpecialChars::CJK_PERIOD:
569
                                                switch(nextStat & SpecialChars::CJK_CHAR_MASK) {
570
                                                case SpecialChars::CJK_FENCE_BEGIN:
571
                                                case SpecialChars::CJK_FENCE_END:
572
                                                        run.extraWidth -= halfEM;;
573
                                                }
574
                                                break;
575
                                        case SpecialChars::CJK_MIDPOINT:
576
                                                switch(nextStat & SpecialChars::CJK_CHAR_MASK) {
577
                                                case SpecialChars::CJK_FENCE_BEGIN:
578
                                                        run.extraWidth -= halfEM;
579
                                                }
580
                                                break;
581
                                        case SpecialChars::CJK_FENCE_BEGIN:
582
                                                int prevStat = SpecialChars::getCJKAttr(m_story.text(lastChar - 1));
583
                                                if ((prevStat & SpecialChars::CJK_CHAR_MASK) == SpecialChars::CJK_FENCE_BEGIN)
584
                                                {
585
                                                        run.extraWidth -= halfEM;
586
                                                        run.xoffset -= halfEM;
587
                                                }
588
                                                else
589
                                                {
590
                                                        run.setFlag(ScLayout_CJKFence);
591
                                                }
592
                                                break;
593
                                        }
594
                                }
595
                        }
596
 
597
                        result.glyphs().append(run);
598
                }
599
                hb_buffer_destroy(hbBuffer);
600
 
601
        }
602
 
603
        m_textMap.clear();
604
        m_text = "";
605
        result.needsContext(m_contextNeeded);
606
        return result;
607
}