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 | } |