Subversion Repositories Scribus

Rev

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