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