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