LibreOffice Module vcl (master) 1
CommonSalLayout.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <sal/config.h>
21
22#include <sal/log.hxx>
24#include <o3tl/temporary.hxx>
25
26#include <vcl/unohelp.hxx>
27#include <vcl/font/Feature.hxx>
29#include <vcl/svapp.hxx>
30
31#include <ImplLayoutArgs.hxx>
32#include <TextLayoutCache.hxx>
34#include <salgdi.hxx>
35#include <sallayout.hxx>
36
37#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
38
39#include <unicode/uchar.h>
40#include <hb-ot.h>
41#include <hb-graphite2.h>
42
43#include <memory>
44
46 : m_GlyphItems(rFont)
47 , mpVertGlyphs(nullptr)
48 , mbFuzzing(utl::ConfigManager::IsFuzzing())
49{
50}
51
53{
54 if (mpVertGlyphs)
55 hb_set_destroy(mpVertGlyphs);
56}
57
58void GenericSalLayout::ParseFeatures(std::u16string_view aName)
59{
61 const OUString& sLanguage = aParser.getLanguage();
62 if (!sLanguage.isEmpty())
63 msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);
64
65 for (auto const &rFeat : aParser.getFeatures())
66 {
67 hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
68 maFeatures.push_back(aFeature);
69 }
70}
71
72namespace {
73
74struct SubRun
75{
76 int32_t mnMin;
77 int32_t mnEnd;
78 hb_script_t maScript;
79 hb_direction_t maDirection;
80};
81
82}
83
84namespace {
85#if U_ICU_VERSION_MAJOR_NUM >= 63
86 enum class VerticalOrientation {
87 Upright = U_VO_UPRIGHT,
88 Rotated = U_VO_ROTATED,
89 TransformedUpright = U_VO_TRANSFORMED_UPRIGHT,
90 TransformedRotated = U_VO_TRANSFORMED_ROTATED
91 };
92#else
94
95 // These must match the values in the file included above.
96 enum class VerticalOrientation {
97 Upright = 0,
98 Rotated = 1,
99 TransformedUpright = 2,
100 TransformedRotated = 3
101 };
102#endif
103
104 VerticalOrientation GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
105 {
106 // Override orientation of fullwidth colon , semi-colon,
107 // and Bopomofo tonal marks.
108 if ((cCh == 0xff1a || cCh == 0xff1b
109 || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
110 && rTag.getLanguage() == "zh")
111 return VerticalOrientation::TransformedUpright;
112
113#if U_ICU_VERSION_MAJOR_NUM >= 63
114 int32_t nRet = u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
115#else
116 uint8_t nRet = 1;
117
118 if (cCh < 0x10000)
119 {
121 [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
122 }
123 else if (cCh < (kVerticalOrientationMaxPlane + 1) * 0x10000)
124 {
126 [(cCh & 0xffff) >> kVerticalOrientationCharBits]]
127 [cCh & ((1 << kVerticalOrientationCharBits) - 1)];
128 }
129 else
130 {
131 // Default value for unassigned
132 SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
133 }
134#endif
135
136 return VerticalOrientation(nRet);
137 }
138
139} // namespace
140
142{
143 SalLayoutGlyphs glyphs;
144 glyphs.AppendImpl(m_GlyphItems.clone());
145 return glyphs;
146}
147
148void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
149{
150 if (nCharPos < 0 || mbFuzzing)
151 return;
152
153 using namespace ::com::sun::star;
154
155 if (!mxBreak.is())
157
158 lang::Locale aLocale(rArgs.maLanguageTag.getLocale());
159
160 //if position nCharPos is missing in the font, grab the entire grapheme and
161 //mark all glyphs as missing so the whole thing is rendered with the same
162 //font
163 sal_Int32 nDone;
164 int nGraphemeEndPos =
165 mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale,
166 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
167 // Safely advance nCharPos in case it is a non-BMP character.
168 rArgs.mrStr.iterateCodePoints(&nCharPos);
169 int nGraphemeStartPos =
170 mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale,
171 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
172
173 //stay inside the Layout range (e.g. with tdf124116-1.odt)
174 nGraphemeStartPos = std::max(rArgs.mnMinCharPos, nGraphemeStartPos);
175 nGraphemeEndPos = std::min(rArgs.mnEndCharPos, nGraphemeEndPos);
176
177 rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
178}
179
181{
183
184 if (rArgs.mpNaturalDXArray)
186 else if (rArgs.mnLayoutWidth)
187 Justify(rArgs.mnLayoutWidth);
188 // apply asian kerning if the glyphs are not already formatted
189 else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
190 && !(rArgs.mnFlags & SalLayoutFlags::Vertical))
192}
193
195{
196 //call platform dependent DrawText functions
197 rSalGraphics.DrawTextLayout( *this );
198}
199
200// Find if the nominal glyph of the character is an input to “vert” feature.
201// We don’t check for a specific script or language as it shouldn’t matter
202// here; if the glyph would be the result from applying “vert” for any
203// script/language then we want to always treat it as upright glyph.
205{
206 sal_GlyphId nGlyphIndex = GetFont().GetGlyphIndex(aChar, aVariationSelector);
207 if (!nGlyphIndex)
208 return false;
209
210 if (!mpVertGlyphs)
211 {
212 hb_face_t* pHbFace = hb_font_get_face(GetFont().GetHbFont());
213 mpVertGlyphs = hb_set_create();
214
215 // Find all GSUB lookups for “vert” feature.
216 hb_set_t* pLookups = hb_set_create();
217 hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
218 hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
219 if (!hb_set_is_empty(pLookups))
220 {
221 // Find the output glyphs in each lookup (i.e. the glyphs that
222 // would result from applying this lookup).
223 hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
224 while (hb_set_next(pLookups, &nIdx))
225 {
226 hb_set_t* pGlyphs = hb_set_create();
227 hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
228 nullptr, // glyphs before
229 pGlyphs, // glyphs input
230 nullptr, // glyphs after
231 nullptr); // glyphs out
232 hb_set_union(mpVertGlyphs, pGlyphs);
233 }
234 }
235 hb_set_destroy(pLookups);
236 }
237
238 return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
239}
240
242{
243 // No need to touch m_GlyphItems at all for an empty string.
244 if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
245 return true;
246
247 if (pGlyphs)
248 {
249 // Work with pre-computed glyph items.
250 m_GlyphItems = *pGlyphs;
251 for(const GlyphItem& item : m_GlyphItems)
252 if(!item.glyphId())
253 SetNeedFallback(rArgs, item.charPos(), item.IsRTLGlyph());
254 // Some flags are set as a side effect of text layout, restore them here.
255 rArgs.mnFlags |= pGlyphs->GetFlags();
256 return true;
257 }
258
259 hb_font_t *pHbFont = GetFont().GetHbFont();
260 bool isGraphite = GetFont().IsGraphiteFont();
261
262 int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
263 m_GlyphItems.reserve(nGlyphCapacity);
264
265 const int nLength = rArgs.mrStr.getLength();
266 const sal_Unicode *pStr = rArgs.mrStr.getStr();
267
268 std::optional<vcl::text::TextLayoutCache> oNewScriptRun;
269 vcl::text::TextLayoutCache const* pTextLayout;
270 if (rArgs.m_pTextLayoutCache)
271 {
272 pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
273 }
274 else
275 {
276 oNewScriptRun.emplace(pStr, rArgs.mnEndCharPos);
277 pTextLayout = &*oNewScriptRun;
278 }
279
280 // nBaseOffset is used to align vertical text to the center of rotated
281 // horizontal text. That is the offset from original baseline to
282 // the center of EM box. Maybe we can use OpenType base table to improve this
283 // in the future.
284 DeviceCoordinate nBaseOffset = 0;
286 {
287 hb_font_extents_t extents;
288 if (hb_font_get_h_extents(pHbFont, &extents))
289 nBaseOffset = ( extents.ascender + extents.descender ) / 2;
290 }
291
292 hb_buffer_t* pHbBuffer = hb_buffer_create();
293 hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
294
297 {
298 SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName);
299 maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
300 }
301
303 {
304 SAL_INFO("vcl.harfbuzz", "Disabling ligatures for font: " << rFontSelData.maTargetName);
305
306 // Both of these are optional ligatures, enabled by default but not for
307 // orthographically-required ligatures.
308 maFeatures.push_back({ HB_TAG('l','i','g','a'), 0, 0, static_cast<unsigned int>(-1) });
309 maFeatures.push_back({ HB_TAG('c','l','i','g'), 0, 0, static_cast<unsigned int>(-1) });
310 }
311
312 ParseFeatures(rFontSelData.maTargetName);
313
314 double nXScale = 0;
315 double nYScale = 0;
316 GetFont().GetScale(&nXScale, &nYScale);
317
318 DevicePoint aCurrPos(0, 0);
319 while (true)
320 {
321 int nBidiMinRunPos, nBidiEndRunPos;
322 bool bRightToLeft;
323 if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
324 break;
325
326 // Find script subruns.
327 std::vector<SubRun> aSubRuns;
328 int nCurrentPos = nBidiMinRunPos;
329 size_t k = 0;
330 for (; k < pTextLayout->runs.size(); ++k)
331 {
332 vcl::text::Run const& rRun(pTextLayout->runs[k]);
333 if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
334 {
335 break;
336 }
337 }
338
339 if (isGraphite)
340 {
341 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
342 aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
343 }
344 else
345 {
346 while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
347 {
348 int32_t nMinRunPos = nCurrentPos;
349 int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
350 hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
351 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
352 // For vertical text, further divide the runs based on character
353 // orientation.
355 {
356 sal_Int32 nIdx = nMinRunPos;
357 while (nIdx < nEndRunPos)
358 {
359 sal_Int32 nPrevIdx = nIdx;
360 sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
361 VerticalOrientation aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
362
363 sal_UCS4 aVariationSelector = 0;
364 if (nIdx < nEndRunPos)
365 {
366 sal_Int32 nNextIdx = nIdx;
367 sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
368 if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
369 {
370 nIdx = nNextIdx;
371 aVariationSelector = aNextChar;
372 }
373 }
374
375 // Characters with U and Tu vertical orientation should
376 // be shaped in vertical direction. But characters
377 // with Tr should be shaped in vertical direction
378 // only if they have vertical alternates, otherwise
379 // they should be shaped in horizontal direction
380 // and then rotated.
381 // See http://unicode.org/reports/tr50/#vo
382 if (aVo == VerticalOrientation::Upright ||
383 aVo == VerticalOrientation::TransformedUpright ||
384 (aVo == VerticalOrientation::TransformedRotated &&
385 HasVerticalAlternate(aChar, aVariationSelector)))
386 {
387 aDirection = HB_DIRECTION_TTB;
388 }
389 else
390 {
391 aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
392 }
393
394 if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection)
395 aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
396 else
397 aSubRuns.back().mnEnd = nIdx;
398 }
399 }
400 else
401 {
402 aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
403 }
404
405 nCurrentPos = nEndRunPos;
406 ++k;
407 }
408 }
409
410 // RTL subruns should be reversed to ensure that final glyph order is
411 // correct.
412 if (bRightToLeft)
413 std::reverse(aSubRuns.begin(), aSubRuns.end());
414
415 for (const auto& aSubRun : aSubRuns)
416 {
417 hb_buffer_clear_contents(pHbBuffer);
418
419 const int nMinRunPos = aSubRun.mnMin;
420 const int nEndRunPos = aSubRun.mnEnd;
421 const int nRunLen = nEndRunPos - nMinRunPos;
422
423 int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
424#if HB_VERSION_ATLEAST(5, 1, 0)
425 // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below.
426 nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
427#endif
428 if (nMinRunPos == 0)
429 nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
430 if (nEndRunPos == nLength)
431 nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
432
433 hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
434 hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
435 if (!msLanguage.isEmpty())
436 {
437 hb_buffer_set_language(pHbBuffer, hb_language_from_string(msLanguage.getStr(), msLanguage.getLength()));
438 }
439 else
440 {
441 OString sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
442 hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), sLanguage.getLength()));
443 }
444 hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
445 hb_buffer_add_utf16(
446 pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
447 nMinRunPos, nRunLen);
448
449 // The shapers that we want HarfBuzz to use, in the order of
450 // preference.
451 const char*const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr };
452 bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
453 assert(ok);
454 (void) ok;
455
456 int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
457 hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
458 hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
459
460 for (int i = 0; i < nRunGlyphCount; ++i) {
461 int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
462 int32_t nCharPos = pHbGlyphInfos[i].cluster;
463 int32_t nCharCount = 0;
464 bool bInCluster = false;
465 bool bClusterStart = false;
466
467 // Find the number of characters that make up this glyph.
468 if (!bRightToLeft)
469 {
470 // If the cluster is the same as previous glyph, then this
471 // already consumed, skip.
472 if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
473 {
474 nCharCount = 0;
475 bInCluster = true;
476 }
477 else
478 {
479 // Find the next glyph with a different cluster, or the
480 // end of text.
481 int j = i;
482 int32_t nNextCharPos = nCharPos;
483 while (nNextCharPos == nCharPos && j < nRunGlyphCount)
484 nNextCharPos = pHbGlyphInfos[j++].cluster;
485
486 if (nNextCharPos == nCharPos)
487 nNextCharPos = nEndRunPos;
488 nCharCount = nNextCharPos - nCharPos;
489 if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
490 (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
491 bClusterStart = true;
492 }
493 }
494 else
495 {
496 // If the cluster is the same as previous glyph, then this
497 // will be consumed later, skip.
498 if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
499 {
500 nCharCount = 0;
501 bInCluster = true;
502 }
503 else
504 {
505 // Find the previous glyph with a different cluster, or
506 // the end of text.
507 int j = i;
508 int32_t nNextCharPos = nCharPos;
509 while (nNextCharPos == nCharPos && j >= 0)
510 nNextCharPos = pHbGlyphInfos[j--].cluster;
511
512 if (nNextCharPos == nCharPos)
513 nNextCharPos = nEndRunPos;
514 nCharCount = nNextCharPos - nCharPos;
515 if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
516 (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
517 bClusterStart = true;
518 }
519 }
520
521 // if needed request glyph fallback by updating LayoutArgs
522 if (!nGlyphIndex)
523 {
524 SetNeedFallback(rArgs, nCharPos, bRightToLeft);
526 continue;
527 }
528
530 if (bRightToLeft)
531 nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
532
533 if (bClusterStart)
535
536 if (bInCluster)
537 nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
538
539 sal_UCS4 aChar
540 = rArgs.mrStr.iterateCodePoints(&o3tl::temporary(sal_Int32(nCharPos)), 0);
541
542 if (u_isUWhiteSpace(aChar))
543 nGlyphFlags |= GlyphItemFlags::IS_SPACING;
544
545 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
547
548#if HB_VERSION_ATLEAST(5, 1, 0)
549 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)
551#else
552 // If support is not present, then allow kashida anywhere.
554#endif
555
556 DeviceCoordinate nAdvance, nXOffset, nYOffset;
557 if (aSubRun.maDirection == HB_DIRECTION_TTB)
558 {
559 nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
560
561 nAdvance = -pHbPositions[i].y_advance;
562 nXOffset = -pHbPositions[i].y_offset;
563 nYOffset = -pHbPositions[i].x_offset - nBaseOffset;
564
565 if (GetFont().NeedOffsetCorrection(pHbPositions[i].y_offset))
566 {
567 // We need glyph's advance, top bearing, and height to
568 // correct y offset.
569 tools::Rectangle aRect;
570 // Get cached bound rect value for the font,
571 GetFont().GetGlyphBoundRect(nGlyphIndex, aRect, true);
572
573 nXOffset = -(aRect.Top() / nXScale + ( pHbPositions[i].y_advance
574 + ( aRect.GetHeight() / nXScale ) ) / 2 );
575 }
576
577 }
578 else
579 {
580 nAdvance = pHbPositions[i].x_advance;
581 nXOffset = pHbPositions[i].x_offset;
582 nYOffset = -pHbPositions[i].y_offset;
583 }
584
585 nAdvance = std::lround(nAdvance * nXScale);
586 nXOffset = std::lround(nXOffset * nXScale);
587 nYOffset = std::lround(nYOffset * nYScale);
588
589 DevicePoint aNewPos(aCurrPos.getX() + nXOffset, aCurrPos.getY() + nYOffset);
590 const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
591 nAdvance, nXOffset, nYOffset);
592 m_GlyphItems.push_back(aGI);
593
594 aCurrPos.adjustX(nAdvance);
595 }
596 }
597 }
598
599 hb_buffer_destroy(pHbBuffer);
600
601 // Some flags are set as a side effect of text layout, save them here.
604
605 return true;
606}
607
608void GenericSalLayout::GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths, const OUString& rStr) const
609{
610 const int nCharCount = mnEndCharPos - mnMinCharPos;
611
612 rCharWidths.clear();
613 rCharWidths.resize(nCharCount, 0);
614
615 css::uno::Reference<css::i18n::XBreakIterator> xBreak;
616 auto aLocale(maLanguageTag.getLocale());
617
618 for (auto const& aGlyphItem : m_GlyphItems)
619 {
620 if (aGlyphItem.charPos() >= mnEndCharPos)
621 continue;
622
623 unsigned int nGraphemeCount = 0;
624 if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty())
625 {
626 // We are calculating DX array for cursor positions and this is a
627 // ligature, find out how many grapheme clusters are in it.
628 if (!xBreak.is())
630
631 // Count grapheme clusters in the ligature.
632 sal_Int32 nDone;
633 sal_Int32 nPos = aGlyphItem.charPos();
634 while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount())
635 {
636 nPos = xBreak->nextCharacters(rStr, nPos, aLocale,
637 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
638 nGraphemeCount++;
639 }
640 }
641
642 if (nGraphemeCount > 1)
643 {
644 // More than one grapheme cluster, we want to distribute the glyph
645 // width over them.
646 std::vector<DeviceCoordinate> aWidths(nGraphemeCount);
647
648 // Check if the glyph has ligature caret positions.
649 unsigned int nCarets = nGraphemeCount;
650 std::vector<hb_position_t> aCarets(nGraphemeCount);
651 hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(),
652 aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
653 aGlyphItem.glyphId(), 0, &nCarets, aCarets.data());
654
655 // Carets are 1-less than the grapheme count (since the last
656 // position is defined by glyph width), if the count does not
657 // match, ignore it.
658 if (nCarets == nGraphemeCount - 1)
659 {
660 // Scale the carets and apply glyph offset to them since they
661 // are based on the default glyph metrics.
662 double fScale = 0;
663 GetFont().GetScale(&fScale, nullptr);
664 for (size_t i = 0; i < nCarets; i++)
665 aCarets[i] = (aCarets[i] * fScale) + aGlyphItem.xOffset();
666
667 // Use the glyph width for the last caret.
668 aCarets[nCarets] = aGlyphItem.newWidth();
669
670 // Carets are absolute from the X origin of the glyph, turn
671 // them to relative widths that we need below.
672 for (size_t i = 0; i < nGraphemeCount; i++)
673 aWidths[i] = aCarets[i] - (i == 0 ? 0 : aCarets[i - 1]);
674
675 // Carets are in visual order, but we want widths in logical
676 // order.
677 if (aGlyphItem.IsRTLGlyph())
678 std::reverse(aWidths.begin(), aWidths.end());
679 }
680 else
681 {
682 // The glyph has no carets, distribute the width evenly.
683 auto nWidth = aGlyphItem.newWidth() / nGraphemeCount;
684 std::fill(aWidths.begin(), aWidths.end(), nWidth);
685
686 // Add rounding difference to the last component to maintain
687 // ligature width.
688 aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
689 }
690
691 // Set the width of each grapheme cluster.
692 sal_Int32 nDone;
693 sal_Int32 nPos = aGlyphItem.charPos();
694 for (auto nWidth : aWidths)
695 {
696 rCharWidths[nPos - mnMinCharPos] += nWidth;
697 nPos = xBreak->nextCharacters(rStr, nPos, aLocale,
698 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
699 }
700 }
701 else
702 rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth();
703 }
704}
705
706// - pDXArray: is the adjustments to glyph advances (usually due to
707// justification).
708// - pKashidaArray: is the places where kashidas are inserted (for Arabic
709// justification). The number of kashidas is calculated from the pDXArray.
710void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKashidaArray)
711{
712 int nCharCount = mnEndCharPos - mnMinCharPos;
713 std::vector<DeviceCoordinate> aOldCharWidths;
714 std::unique_ptr<double[]> const pNewCharWidths(new double[nCharCount]);
715
716 // Get the natural character widths (i.e. before applying DX adjustments).
717 GetCharWidths(aOldCharWidths, {});
718
719 // Calculate the character widths after DX adjustments.
720 for (int i = 0; i < nCharCount; ++i)
721 {
722 if (i == 0)
723 pNewCharWidths[i] = pDXArray[i];
724 else
725 pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1];
726 }
727
728 // Map of Kashida insertion points (in the glyph items vector) and the
729 // requested width.
730 std::map<size_t, DeviceCoordinate> pKashidas;
731
732 // The accumulated difference in X position.
733 double nDelta = 0;
734
735 // Apply the DX adjustments to glyph positions and widths.
736 size_t i = 0;
737 while (i < m_GlyphItems.size())
738 {
739 // Accumulate the width difference for all characters corresponding to
740 // this glyph.
741 int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos;
742 double nDiff = 0;
743 for (int j = 0; j < m_GlyphItems[i].charCount(); j++)
744 nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
745
746 if (!m_GlyphItems[i].IsRTLGlyph())
747 {
748 // Adjust the width and position of the first (leftmost) glyph in
749 // the cluster.
750 m_GlyphItems[i].addNewWidth(nDiff);
751 m_GlyphItems[i].adjustLinearPosX(nDelta);
752
753 // Adjust the position of the rest of the glyphs in the cluster.
754 while (++i < m_GlyphItems.size())
755 {
756 if (!m_GlyphItems[i].IsInCluster())
757 break;
758 m_GlyphItems[i].adjustLinearPosX(nDelta);
759 }
760 }
761 else if (m_GlyphItems[i].IsInCluster())
762 {
763 // RTL glyph in the middle of the cluster, will be handled in the
764 // loop below.
765 i++;
766 }
767 else // RTL
768 {
769 // Adjust the width and position of the first (rightmost) glyph in
770 // the cluster. This is RTL, so we put all the adjustment to the
771 // left of the glyph.
772 m_GlyphItems[i].addNewWidth(nDiff);
773 m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff);
774
775 // Warning:
776 // If you are tempted to improve the two loops below, think again.
777 // Even though I wrote this code, I no longer understand how it
778 // works, and every time I think I finally got it, I introduce a
779 // bug. - Khaled
780
781 // Adjust the X position of the rest of the glyphs in the cluster.
782 size_t j = i;
783 while (j > 0)
784 {
785 --j;
786 if (!m_GlyphItems[j].IsInCluster())
787 break;
788 m_GlyphItems[j].adjustLinearPosX(nDelta + nDiff);
789 }
790
791 // This is a Kashida insertion position, mark it. Kashida glyphs
792 // will be inserted below.
793 if (pKashidaArray && pKashidaArray[nCharPos])
794 pKashidas[i] = nDiff;
795
796 i++;
797 }
798
799 // Increment the delta, the loop above makes sure we do so only once
800 // for every character (cluster) not for every glyph (otherwise we
801 // would apply it multiple times for each glyph belonging to the same
802 // character which is wrong as DX adjustments are character based).
803 nDelta += nDiff;
804 }
805
806 // Insert Kashida glyphs.
807 if (pKashidas.empty())
808 return;
809
810 // Find Kashida glyph width and index.
811 sal_GlyphId nKashidaIndex = GetFont().GetGlyphIndex(0x0640);
812 double nKashidaWidth = GetFont().GetKashidaWidth();
813
814 if (nKashidaWidth <= 0)
815 {
816 SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width");
817 return;
818 }
819
820 size_t nInserted = 0;
821 for (auto const& pKashida : pKashidas)
822 {
823 auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
824
825 // The total Kashida width.
826 double nTotalWidth = pKashida.second;
827
828 // Number of times to repeat each Kashida.
829 int nCopies = 1;
830 if (nTotalWidth > nKashidaWidth)
831 nCopies = nTotalWidth / nKashidaWidth;
832
833 // See if we can improve the fit by adding an extra Kashidas and
834 // squeezing them together a bit.
835 double nOverlap = 0;
836 double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
837 if (nShortfall > 0)
838 {
839 ++nCopies;
840 double nExcess = nCopies * nKashidaWidth - nTotalWidth;
841 if (nExcess > 0)
842 nOverlap = nExcess / (nCopies - 1);
843 }
844
845 DevicePoint aPos(pGlyphIter->linearPos().getX() - nTotalWidth, 0);
846 int nCharPos = pGlyphIter->charPos();
848 while (nCopies--)
849 {
850 GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, nKashidaWidth, 0, 0);
851 pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
852 aPos.adjustX(nKashidaWidth - nOverlap);
853 ++pGlyphIter;
854 ++nInserted;
855 }
856 }
857}
858
859// Kashida will be inserted between nCharPos and nNextCharPos.
860bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
861{
862 // Search for glyph items corresponding to nCharPos and nNextCharPos.
863 auto const& rGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
864 [&](const GlyphItem& g) { return g.charPos() == nCharPos; });
865 auto const& rNextGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
866 [&](const GlyphItem& g) { return g.charPos() == nNextCharPos; });
867
868 // If either is not found then a ligature is created at this position, we
869 // can’t insert Kashida here.
870 if (rGlyph == m_GlyphItems.end() || rNextGlyph == m_GlyphItems.end())
871 return false;
872
873 // If the either character is not supported by this layout, return false so
874 // that fallback layouts would be checked for it.
875 if (rGlyph->glyphId() == 0 || rNextGlyph->glyphId() == 0)
876 return false;
877
878 // Lastly check if this position is kashida-safe.
879 return rNextGlyph->IsSafeToInsertKashida();
880}
881
882/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static const uint8_t sVerticalOrientationValues[34][128]
#define kVerticalOrientationCharBits
static const uint8_t sVerticalOrientationPages[4][512]
static const uint8_t sVerticalOrientationPlanes[16]
#define kVerticalOrientationMaxPlane
size_t mnEnd
LogicalFontInstance & GetFont() const
Definition: sallayout.hxx:125
SalLayoutGlyphsImpl m_GlyphItems
Definition: sallayout.hxx:157
void Justify(DeviceCoordinate nNewWidth)
Definition: sallayout.cxx:288
void SetNeedFallback(vcl::text::ImplLayoutArgs &, sal_Int32, bool)
bool IsKashidaPosValid(int nCharPos, int nNextCharPos) const final override
SalLayoutGlyphs GetGlyphs() const final override
void GetCharWidths(std::vector< DeviceCoordinate > &rCharWidths, const OUString &rStr) const
hb_set_t * mpVertGlyphs
Definition: sallayout.hxx:162
std::vector< hb_feature_t > maFeatures
Definition: sallayout.hxx:160
GenericSalLayout(LogicalFontInstance &)
OString msLanguage
Definition: sallayout.hxx:159
bool LayoutText(vcl::text::ImplLayoutArgs &, const SalLayoutGlyphsImpl *) final override
void DrawText(SalGraphics &) const final override
void ApplyAsianKerning(std::u16string_view rStr)
Definition: sallayout.cxx:406
const bool mbFuzzing
Definition: sallayout.hxx:163
void ParseFeatures(std::u16string_view name)
~GenericSalLayout() override
void AdjustLayout(vcl::text::ImplLayoutArgs &) final override
bool HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aNextChar)
css::uno::Reference< css::i18n::XBreakIterator > mxBreak
Definition: sallayout.hxx:155
void ApplyDXArray(const double *, const sal_Bool *)
const css::lang::Locale & getLocale(bool bResolveSystem=true) const
OUString getLanguage() const
const OUString & getBcp47(bool bResolveSystem=true) const
void GetScale(double *nXScale, double *nYScale) const
bool GetGlyphBoundRect(sal_GlyphId, tools::Rectangle &, bool) const
const vcl::font::FontSelectPattern & GetFontSelectPattern() const
sal_GlyphId GetGlyphIndex(uint32_t, uint32_t=0) const
virtual void DrawTextLayout(const GenericSalLayout &)=0
void SetFlags(SalLayoutFlags flags)
SalLayoutFlags GetFlags() const
SalLayoutGlyphsImpl * clone() const
void AppendImpl(SalLayoutGlyphsImpl *pImpl)
LanguageTag maLanguageTag
Definition: vcllayout.hxx:121
int mnEndCharPos
Definition: vcllayout.hxx:120
int mnMinCharPos
Definition: vcllayout.hxx:119
virtual void AdjustLayout(vcl::text::ImplLayoutArgs &)
Definition: sallayout.cxx:136
TYPE getX() const
void adjustX(TYPE fX)
TYPE getY() const
constexpr tools::Long Top() const
constexpr tools::Long GetHeight() const
std::vector< FeatureSetting > const & getFeatures() const
OUString const & getLanguage() const
vcl::text::TextLayoutCache const * m_pTextLayoutCache
bool GetNextRun(int *nMinRunPos, int *nEndRunPos, bool *bRTL)
DeviceCoordinate mnLayoutWidth
const sal_Bool * mpKashidaArray
const double * mpNaturalDXArray
void AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL)
std::vector< vcl::text::Run > runs
sal_Int32 DeviceCoordinate
uint32_t sal_GlyphId
Definition: glyphid.hxx:24
GlyphItemFlags
OUString aName
sal_uInt16 nPos
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
int i
constexpr T & temporary(T &&x)
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
VCL_DLLPUBLIC css::uno::Reference< css::i18n::XBreakIterator > CreateBreakIterator()
Definition: unohelp.cxx:37
css::drawing::Direction3D aDirection
unsigned char sal_Bool
sal_uInt16 sal_Unicode
sal_uInt32 sal_UCS4
Definition: vclenum.hxx:172
sal_Int32 nLength