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
425 // Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below.
426 nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
427
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_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)
550
551 DeviceCoordinate nAdvance, nXOffset, nYOffset;
552 if (aSubRun.maDirection == HB_DIRECTION_TTB)
553 {
554 nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
555
556 nAdvance = -pHbPositions[i].y_advance;
557 nXOffset = -pHbPositions[i].y_offset;
558 nYOffset = -pHbPositions[i].x_offset - nBaseOffset;
559
560 if (GetFont().NeedOffsetCorrection(pHbPositions[i].y_offset))
561 {
562 // We need glyph's advance, top bearing, and height to
563 // correct y offset.
564 tools::Rectangle aRect;
565 // Get cached bound rect value for the font,
566 GetFont().GetGlyphBoundRect(nGlyphIndex, aRect, true);
567
568 nXOffset = -(aRect.Top() / nXScale + ( pHbPositions[i].y_advance
569 + ( aRect.GetHeight() / nXScale ) ) / 2 );
570 }
571
572 }
573 else
574 {
575 nAdvance = pHbPositions[i].x_advance;
576 nXOffset = pHbPositions[i].x_offset;
577 nYOffset = -pHbPositions[i].y_offset;
578 }
579
580 nAdvance = std::lround(nAdvance * nXScale);
581 nXOffset = std::lround(nXOffset * nXScale);
582 nYOffset = std::lround(nYOffset * nYScale);
583
584 DevicePoint aNewPos(aCurrPos.getX() + nXOffset, aCurrPos.getY() + nYOffset);
585 const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
586 nAdvance, nXOffset, nYOffset);
587 m_GlyphItems.push_back(aGI);
588
589 aCurrPos.adjustX(nAdvance);
590 }
591 }
592 }
593
594 hb_buffer_destroy(pHbBuffer);
595
596 // Some flags are set as a side effect of text layout, save them here.
599
600 return true;
601}
602
603void GenericSalLayout::GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths, const OUString& rStr) const
604{
605 const int nCharCount = mnEndCharPos - mnMinCharPos;
606
607 rCharWidths.clear();
608 rCharWidths.resize(nCharCount, 0);
609
610 css::uno::Reference<css::i18n::XBreakIterator> xBreak;
611 auto aLocale(maLanguageTag.getLocale());
612
613 for (auto const& aGlyphItem : m_GlyphItems)
614 {
615 if (aGlyphItem.charPos() >= mnEndCharPos)
616 continue;
617
618 unsigned int nGraphemeCount = 0;
619 if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty())
620 {
621 // We are calculating DX array for cursor positions and this is a
622 // ligature, find out how many grapheme clusters are in it.
623 if (!xBreak.is())
625
626 // Count grapheme clusters in the ligature.
627 sal_Int32 nDone;
628 sal_Int32 nPos = aGlyphItem.charPos();
629 while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount())
630 {
631 nPos = xBreak->nextCharacters(rStr, nPos, aLocale,
632 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
633 nGraphemeCount++;
634 }
635 }
636
637 if (nGraphemeCount > 1)
638 {
639 // More than one grapheme cluster, we want to distribute the glyph
640 // width over them.
641 std::vector<DeviceCoordinate> aWidths(nGraphemeCount);
642
643 // Check if the glyph has ligature caret positions.
644 unsigned int nCarets = nGraphemeCount;
645 std::vector<hb_position_t> aCarets(nGraphemeCount);
646 hb_ot_layout_get_ligature_carets(GetFont().GetHbFont(),
647 aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
648 aGlyphItem.glyphId(), 0, &nCarets, aCarets.data());
649
650 // Carets are 1-less than the grapheme count (since the last
651 // position is defined by glyph width), if the count does not
652 // match, ignore it.
653 if (nCarets == nGraphemeCount - 1)
654 {
655 // Scale the carets and apply glyph offset to them since they
656 // are based on the default glyph metrics.
657 double fScale = 0;
658 GetFont().GetScale(&fScale, nullptr);
659 for (size_t i = 0; i < nCarets; i++)
660 aCarets[i] = (aCarets[i] * fScale) + aGlyphItem.xOffset();
661
662 // Use the glyph width for the last caret.
663 aCarets[nCarets] = aGlyphItem.newWidth();
664
665 // Carets are absolute from the X origin of the glyph, turn
666 // them to relative widths that we need below.
667 for (size_t i = 0; i < nGraphemeCount; i++)
668 aWidths[i] = aCarets[i] - (i == 0 ? 0 : aCarets[i - 1]);
669
670 // Carets are in visual order, but we want widths in logical
671 // order.
672 if (aGlyphItem.IsRTLGlyph())
673 std::reverse(aWidths.begin(), aWidths.end());
674 }
675 else
676 {
677 // The glyph has no carets, distribute the width evenly.
678 auto nWidth = aGlyphItem.newWidth() / nGraphemeCount;
679 std::fill(aWidths.begin(), aWidths.end(), nWidth);
680
681 // Add rounding difference to the last component to maintain
682 // ligature width.
683 aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
684 }
685
686 // Set the width of each grapheme cluster.
687 sal_Int32 nDone;
688 sal_Int32 nPos = aGlyphItem.charPos();
689 for (auto nWidth : aWidths)
690 {
691 rCharWidths[nPos - mnMinCharPos] += nWidth;
692 nPos = xBreak->nextCharacters(rStr, nPos, aLocale,
693 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
694 }
695 }
696 else
697 rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth();
698 }
699}
700
701// - pDXArray: is the adjustments to glyph advances (usually due to
702// justification).
703// - pKashidaArray: is the places where kashidas are inserted (for Arabic
704// justification). The number of kashidas is calculated from the pDXArray.
705void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKashidaArray)
706{
707 int nCharCount = mnEndCharPos - mnMinCharPos;
708 std::vector<DeviceCoordinate> aOldCharWidths;
709 std::unique_ptr<double[]> const pNewCharWidths(new double[nCharCount]);
710
711 // Get the natural character widths (i.e. before applying DX adjustments).
712 GetCharWidths(aOldCharWidths, {});
713
714 // Calculate the character widths after DX adjustments.
715 for (int i = 0; i < nCharCount; ++i)
716 {
717 if (i == 0)
718 pNewCharWidths[i] = pDXArray[i];
719 else
720 pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1];
721 }
722
723 // Map of Kashida insertion points (in the glyph items vector) and the
724 // requested width.
725 std::map<size_t, std::pair<DeviceCoordinate, DeviceCoordinate>> pKashidas;
726
727 // The accumulated difference in X position.
728 double nDelta = 0;
729
730 // Apply the DX adjustments to glyph positions and widths.
731 size_t i = 0;
732 while (i < m_GlyphItems.size())
733 {
734 // Accumulate the width difference for all characters corresponding to
735 // this glyph.
736 int nCharPos = m_GlyphItems[i].charPos() - mnMinCharPos;
737 double nDiff = 0;
738 for (int j = 0; j < m_GlyphItems[i].charCount(); j++)
739 nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
740
741 if (!m_GlyphItems[i].IsRTLGlyph())
742 {
743 // Adjust the width and position of the first (leftmost) glyph in
744 // the cluster.
745 m_GlyphItems[i].addNewWidth(nDiff);
746 m_GlyphItems[i].adjustLinearPosX(nDelta);
747
748 // Adjust the position of the rest of the glyphs in the cluster.
749 while (++i < m_GlyphItems.size())
750 {
751 if (!m_GlyphItems[i].IsInCluster())
752 break;
753 m_GlyphItems[i].adjustLinearPosX(nDelta);
754 }
755 }
756 else if (m_GlyphItems[i].IsInCluster())
757 {
758 // RTL glyph in the middle of the cluster, will be handled in the
759 // loop below.
760 i++;
761 }
762 else // RTL
763 {
764 // Adjust the width and position of the first (rightmost) glyph in
765 // the cluster. This is RTL, so we put all the adjustment to the
766 // left of the glyph.
767 m_GlyphItems[i].addNewWidth(nDiff);
768 m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff);
769
770 // Adjust the X position of the rest of the glyphs in the cluster.
771 // We iterate backwards since this is an RTL glyph.
772 for (int j = i - 1; j >= 0 && m_GlyphItems[j].IsInCluster(); j--)
773 m_GlyphItems[j].adjustLinearPosX(nDelta + nDiff);
774
775 // This is a Kashida insertion position, mark it. Kashida glyphs
776 // will be inserted below.
777 if (pKashidaArray && pKashidaArray[nCharPos])
778 pKashidas[i] = { nDiff, pNewCharWidths[nCharPos] };
779
780 i++;
781 }
782
783 // Increment the delta, the loop above makes sure we do so only once
784 // for every character (cluster) not for every glyph (otherwise we
785 // would apply it multiple times for each glyph belonging to the same
786 // character which is wrong as DX adjustments are character based).
787 nDelta += nDiff;
788 }
789
790 // Insert Kashida glyphs.
791 if (pKashidas.empty())
792 return;
793
794 // Find Kashida glyph width and index.
795 sal_GlyphId nKashidaIndex = GetFont().GetGlyphIndex(0x0640);
796 double nKashidaWidth = GetFont().GetKashidaWidth();
797
798 if (nKashidaWidth <= 0)
799 {
800 SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with bogus Kashida width");
801 return;
802 }
803
804 size_t nInserted = 0;
805 for (auto const& pKashida : pKashidas)
806 {
807 auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
808
809 // The total Kashida width.
810 auto const& [nTotalWidth, nClusterWidth] = pKashida.second;
811
812 // Number of times to repeat each Kashida.
813 int nCopies = 1;
814 if (nTotalWidth > nKashidaWidth)
815 nCopies = nTotalWidth / nKashidaWidth;
816
817 // See if we can improve the fit by adding an extra Kashidas and
818 // squeezing them together a bit.
819 double nOverlap = 0;
820 double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
821 if (nShortfall > 0)
822 {
823 ++nCopies;
824 double nExcess = nCopies * nKashidaWidth - nTotalWidth;
825 if (nExcess > 0)
826 nOverlap = nExcess / (nCopies - 1);
827 }
828
829 DevicePoint aPos = pGlyphIter->linearPos();
830 int nCharPos = pGlyphIter->charPos();
832 // Move to the left side of the adjusted width and start inserting
833 // glyphs there.
834 aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth());
835 while (nCopies--)
836 {
837 GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0);
838 pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
839 aPos.adjustX(nKashidaWidth - nOverlap);
840 ++pGlyphIter;
841 ++nInserted;
842 }
843 }
844}
845
846// Kashida will be inserted between nCharPos and nNextCharPos.
847bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
848{
849 // Search for glyph items corresponding to nCharPos and nNextCharPos.
850 auto const& rGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
851 [&](const GlyphItem& g) { return g.charPos() == nCharPos; });
852 auto const& rNextGlyph = std::find_if(m_GlyphItems.begin(), m_GlyphItems.end(),
853 [&](const GlyphItem& g) { return g.charPos() == nNextCharPos; });
854
855 // If either is not found then a ligature is created at this position, we
856 // can’t insert Kashida here.
857 if (rGlyph == m_GlyphItems.end() || rNextGlyph == m_GlyphItems.end())
858 return false;
859
860 // If the either character is not supported by this layout, return false so
861 // that fallback layouts would be checked for it.
862 if (rGlyph->glyphId() == 0 || rNextGlyph->glyphId() == 0)
863 return false;
864
865 // Lastly check if this position is kashida-safe.
866 return rNextGlyph->IsSafeToInsertKashida();
867}
868
869/* 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:279
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:397
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:160
sal_Int32 nLength