5#include "PartitioningTextImager.h"
9using namespace Stroika::Frameworks;
10using namespace Stroika::Frameworks::Led;
12using PartitionMarker = Partition::PartitionMarker;
19Partition::Partition (TextStore& textStore)
22 fFinalConstructCalled{false}
26 , fFindContainingPMCache{nullptr}
27 , fPartitionWatchers{}
28 , fMarkersToBeDeleted{}
30 , fPartitionMarkerCount{0}
32 , fPartitionMarkerFirst{nullptr}
33 , fPartitionMarkerLast{nullptr}
35 fTextStore.AddMarkerOwner (
this);
38Partition::~Partition ()
42 Assert (fMarkersToBeDeleted.IsEmpty ());
57 for (PartitionMarker* cur = fPartitionMarkerFirst; cur !=
nullptr;) {
58 Marker* markersToRemoveAtATime[1000];
59 const size_t kMaxBufMarkers = Memory::NEltsOf (markersToRemoveAtATime);
61 for (; i < kMaxBufMarkers and cur !=
nullptr; ++i, cur = cur->fNext) {
62 markersToRemoveAtATime[i] = cur;
64 fTextStore.RemoveMarkers (markersToRemoveAtATime, i);
65#if qStroika_Foundation_Debug_AssertionsChecked
66 fPartitionMarkerCount -= i;
69 delete (markersToRemoveAtATime[i - 1]);
72 fTextStore.RemoveMarkerOwner (
this);
73#if qStroika_Foundation_Debug_AssertionsChecked
74 Ensure (fPartitionMarkerCount == 0);
86void Partition::FinalConstruct ()
88#if qStroika_Foundation_Debug_AssertionsChecked
89 Require (not fFinalConstructCalled);
90 fFinalConstructCalled =
true;
91 Assert (fPartitionMarkerCount == 0);
93 Assert (fPartitionMarkerFirst ==
nullptr);
94 PartitionMarker* pm = MakeNewPartitionMarker (
nullptr);
95#if qStroika_Foundation_Debug_AssertionsChecked
96 ++fPartitionMarkerCount;
98 fTextStore.AddMarker (pm, 0, fTextStore.GetLength () + 1,
this);
99 Assert (fPartitionMarkerFirst == pm);
100 Assert (fPartitionMarkerLast == pm);
103TextStore* Partition::PeekAtTextStore ()
const
114PartitionMarker* Partition::GetPartitionMarkerContainingPosition (
size_t charPosition)
const
116#if qStroika_Foundation_Debug_AssertionsChecked
117 Require (fFinalConstructCalled);
119 Require (charPosition <= GetEnd () + 1);
124 PartitionMarker* pm = fFindContainingPMCache;
126 pm = fPartitionMarkerFirst;
130 if (charPosition < 10) {
131 pm = fPartitionMarkerFirst;
133 else if (charPosition + 10 > GetEnd ()) {
134 pm = fPartitionMarkerLast;
137 bool loopForwards = pm->GetStart () <= charPosition;
138 for (; pm !=
nullptr; pm = loopForwards ? pm->fNext : pm->fPrevious) {
139 if (Contains (*pm, charPosition)) {
140 if (pm != fPartitionMarkerFirst and pm != fPartitionMarkerLast) {
141 fFindContainingPMCache = pm;
156PartitionMarker* Partition::MakeNewPartitionMarker (PartitionMarker* insertAfterMe)
158#if qStroika_Foundation_Debug_AssertionsChecked
159 Require (fFinalConstructCalled);
161 return new PartitionMarker{*
this, insertAfterMe};
173void Partition::Split (PartitionMarker* pm,
size_t at)
175#if qStroika_Foundation_Debug_AssertionsChecked
176 Require (fFinalConstructCalled);
179 Require (pm->GetStart () < at);
180 Require (pm->GetEnd () > at);
182 vector<void*> watcherInfos;
183 DoAboutToSplitCalls (pm, at, &watcherInfos);
187 pm->GetRange (&start, &end);
188 Assert (at >= start and at <= end);
191 PartitionMarker* newPM = MakeNewPartitionMarker (pm);
192 Assert (pm->GetNext () == newPM);
193 Assert (newPM->GetPrevious () == pm);
200 fTextStore.SetMarkerEnd (pm, at);
202 fTextStore.AddMarker (newPM, at, end - at,
this);
210 fPartitionMarkerFirst = newPM->fNext;
213 pm->fNext = newPM->fNext;
216 if (newPM->fNext !=
nullptr) {
217 Assert (newPM->fNext->fPrevious == newPM);
218 newPM->fNext->fPrevious = pm;
220 if (newPM == fPartitionMarkerLast) {
221 fPartitionMarkerLast = newPM->fPrevious;
228 fTextStore.SetMarkerRange (pm, start, end);
232#if qStroika_Foundation_Debug_AssertionsChecked
233 ++fPartitionMarkerCount;
235 DoDidSplitCalls (watcherInfos);
247void Partition::Coalece (PartitionMarker* pm)
249#if qStroika_Foundation_Debug_AssertionsChecked
250 Require (fFinalConstructCalled);
253 vector<void*> watcherInfos;
254 DoAboutToCoaleceCalls (pm, &watcherInfos);
255 if (pm->fNext !=
nullptr) {
258 pm->GetRange (&start, &end);
259 size_t lengthToAdd = end - start;
261 PartitionMarker* successor = pm->fNext;
263 successor->GetRange (&start, &end);
264 Assert (start >= lengthToAdd);
265 fTextStore.SetMarkerStart (successor, start - lengthToAdd);
267 Assert (successor->fPrevious == pm);
268 successor->fPrevious = pm->fPrevious;
269 Assert ((pm->fPrevious ==
nullptr) or (pm->fPrevious->fNext == pm));
270 if (pm->fPrevious ==
nullptr) {
271 Assert (fPartitionMarkerFirst == pm);
272 fPartitionMarkerFirst = pm->fNext;
275 pm->fPrevious->fNext = pm->fNext;
277 if (pm->fNext ==
nullptr) {
278 Assert (fPartitionMarkerLast == pm);
279 fPartitionMarkerLast = pm->fPrevious;
281#if qStroika_Foundation_Debug_AssertionsChecked
282 --fPartitionMarkerCount;
284 AccumulateMarkerForDeletion (pm);
286 DoDidCoaleceCalls (watcherInfos);
294void Partition::AccumulateMarkerForDeletion (PartitionMarker* m)
296#if qStroika_Foundation_Debug_AssertionsChecked
297 Require (fFinalConstructCalled);
300 Assert (&m->GetOwner () ==
this);
301 fMarkersToBeDeleted.AccumulateMarkerForDeletion (m);
302 if (fFindContainingPMCache == m) {
303 fFindContainingPMCache =
nullptr;
307void Partition::AboutToUpdateText (
const UpdateInfo& updateInfo)
309#if qStroika_Foundation_Debug_AssertionsChecked
310 Require (fFinalConstructCalled);
312 Assert (fMarkersToBeDeleted.IsEmpty ());
316 inherited::AboutToUpdateText (updateInfo);
319void Partition::DidUpdateText (
const UpdateInfo& updateInfo)
noexcept
321#if qStroika_Foundation_Debug_AssertionsChecked
322 Require (fFinalConstructCalled);
324 fMarkersToBeDeleted.FinalizeMarkerDeletions ();
325 inherited::DidUpdateText (updateInfo);
329#if qStroika_Foundation_Debug_AssertionsChecked
330void Partition::Invariant_ ()
const
332#if qStroika_Foundation_Debug_AssertionsChecked
333 Require (fFinalConstructCalled);
335 size_t lastCharDrawn = 0;
336 Assert (fPartitionMarkerCount != 0);
337 size_t realPMCount = 0;
338 for (PartitionMarker* cur = fPartitionMarkerFirst; cur !=
nullptr; cur = cur->fNext) {
339 PartitionMarker* pm = cur;
341 Assert (&pm->GetOwner () ==
this);
342 size_t start = pm->GetStart ();
343 size_t end = pm->GetEnd ();
344 Assert (start == lastCharDrawn);
345 Assert (end <= GetEnd () + 1);
348 Assert (realPMCount <= fPartitionMarkerCount);
349 Assert (
static_cast<bool> (fPartitionMarkerLast == cur) ==
static_cast<bool> (cur->fNext ==
nullptr));
351 Assert (realPMCount == fPartitionMarkerCount);
352 Assert (lastCharDrawn == GetEnd () + 1);
356#if qStroika_Frameworks_Led_SupportGDI
362PartitioningTextImager::PartitioningTextImager ()
363 : fPartition{nullptr}
364#if qCacheTextMeasurementsForPM
365 , fMeasureTextCache ()
370PartitioningTextImager::~PartitioningTextImager ()
372 Require (fPartition.get () ==
nullptr);
382void PartitioningTextImager::SetPartition (
const PartitionPtr& partitionPtr)
384#if qCacheTextMeasurementsForPM
385 fMeasureTextCache.reset ();
387 fPartition = partitionPtr;
388#if qCacheTextMeasurementsForPM
389 if (partitionPtr.get () !=
nullptr) {
390 fMeasureTextCache = unique_ptr<MeasureTextCache> (
new MeasureTextCache (partitionPtr));
395#if qCacheTextMeasurementsForPM
400void PartitioningTextImager::InvalidateAllCaches ()
402 inherited::InvalidateAllCaches ();
403 if (fMeasureTextCache.get () !=
nullptr) {
404 fMeasureTextCache->ClearAll ();
415TextDirection PartitioningTextImager::GetPrimaryPartitionTextDirection (
size_t rowContainingCharPosition)
const
417 return GetTextDirection (GetStartOfPartitionContainingPosition (rowContainingCharPosition));
420TextLayoutBlock_Copy PartitioningTextImager::GetTextLayoutBlock (
size_t rowStart,
size_t rowEnd)
const
422 if (rowStart == GetStartOfPartitionContainingPosition (rowStart)) {
423 return inherited::GetTextLayoutBlock (rowStart, rowEnd);
426 size_t rowLen = rowEnd - rowStart;
428 CopyOut (rowStart, rowLen, rowBuf.data ());
429 return TextLayoutBlock_Copy (TextLayoutBlock_Basic{rowBuf.data (), rowBuf.data () + rowLen, GetPrimaryPartitionTextDirection (rowStart)});
437TextDirection PartitioningTextImager::GetTextDirection (
size_t charPosition)
const
439 size_t startOfRow = GetStartOfRowContainingPosition (charPosition);
440 size_t endOfRow = GetEndOfRowContainingPosition (startOfRow);
441 if (charPosition == endOfRow) {
446 return GetTextLayoutBlock (startOfRow, endOfRow).GetCharacterDirection (charPosition - startOfRow);
459DistanceType PartitioningTextImager::CalcSegmentSize (
size_t from,
size_t to)
const
461#if !qCacheTextMeasurementsForPM || qStroika_Foundation_Debug_AssertionsChecked
462 DistanceType referenceValue = CalcSegmentSize_REFERENCE (from, to);
465#if qCacheTextMeasurementsForPM
466 DistanceType value = CalcSegmentSize_CACHING (from, to);
467#if qStroika_Foundation_Debug_AssertionsChecked
468 Assert (value == referenceValue);
472 return referenceValue;
483DistanceType PartitioningTextImager::CalcSegmentSize_REFERENCE (
size_t from,
size_t to)
const
485 Require (from <= to);
491 size_t startOfRow = GetStartOfRowContainingPosition (from);
492 size_t rowEnd = GetEndOfRowContainingPosition (startOfRow);
493 Require (startOfRow <= from);
494 Require (to <= rowEnd);
495 size_t rowLen = rowEnd - startOfRow;
497 CalcSegmentSize_FillIn (startOfRow, rowEnd, distanceVector.data ());
498 Assert (to > startOfRow);
499 Assert (to - startOfRow - 1 < (GetEndOfRowContainingPosition (startOfRow) - startOfRow));
500 DistanceType result = distanceVector[to - startOfRow - 1];
501 if (from != startOfRow) {
502 result -= distanceVector[from - startOfRow - 1];
508#if qCacheTextMeasurementsForPM
515DistanceType PartitioningTextImager::CalcSegmentSize_CACHING (
size_t from,
size_t to)
const
517 Require (from <= to);
522 PartitionMarker* pm = GetPartitionMarkerContainingPosition (from);
523 Require (pm->GetEnd () == to or pm == GetPartitionMarkerContainingPosition (to));
525 size_t startOfRow = GetStartOfRowContainingPosition (from);
526 Require (GetEndOfRowContainingPosition (startOfRow) >= to);
528 MeasureTextCache::CacheElt ce = fMeasureTextCache->LookupValue (pm, startOfRow, [
this] (PartitionMarker* pm,
size_t startOfRow) {
529 MeasureTextCache::CacheElt newCE{MeasureTextCache::CacheElt::COMPARE_ITEM (pm, startOfRow)};
530 size_t rowEnd = GetEndOfRowContainingPosition (startOfRow);
531 size_t rowLen = rowEnd - startOfRow;
532 newCE.fMeasurementsCache.GrowToSize (rowLen);
533 CalcSegmentSize_FillIn (startOfRow, rowEnd, newCE.fMeasurementsCache.data ());
536 const DistanceType* measurementsCache = ce.fMeasurementsCache.data ();
538 Assert (to > startOfRow);
539 Assert (to - startOfRow - 1 < (GetEndOfRowContainingPosition (startOfRow) - startOfRow));
540 DistanceType result = measurementsCache[to - startOfRow - 1];
541 if (from != startOfRow) {
542 result -= measurementsCache[from - startOfRow - 1];
554void PartitioningTextImager::CalcSegmentSize_FillIn (
size_t rowStart,
size_t rowEnd, DistanceType* distanceVector)
const
556 Require (rowStart == GetStartOfRowContainingPosition (rowStart));
557 Require (rowEnd == GetEndOfRowContainingPosition (rowStart));
559 Require (rowStart <= rowEnd);
562 size_t len = rowEnd - rowStart;
565 CopyOut (rowStart, len, fullRowTextBuf.data ());
567 MeasureSegmentWidth (rowStart, rowEnd, fullRowTextBuf.data (), distanceVector);
568 (void)ResetTabStops (rowStart, fullRowTextBuf.data (), len, distanceVector, 0);
575void PartitioningTextImager::GetRowRelativeCharLoc (
size_t charLoc, DistanceType* lhs, DistanceType* rhs)
const
577 Require (charLoc <= GetEnd ());
585 size_t rowStart = GetStartOfRowContainingPosition (charLoc);
586 size_t rowEnd = GetEndOfRowContainingPosition (charLoc);
587 [[maybe_unused]]
size_t rowLen = rowEnd - rowStart;
588 TextLayoutBlock_Copy rowText = GetTextLayoutBlock (rowStart, rowEnd);
590 using ScriptRunElt = TextLayoutBlock::ScriptRunElt;
591 vector<ScriptRunElt> runs = rowText.GetScriptRuns ();
597 Assert (not runs.empty () or (rowLen == 0));
598 if (runs.size () > 1) {
600 sort (runs.begin (), runs.end (), TextLayoutBlock::LessThanVirtualStart ());
602 size_t rowRelCharLoc = charLoc - rowStart;
603 DistanceType spannedSoFar = 0;
604 for (
auto i = runs.begin (); i != runs.end (); ++i) {
605 const ScriptRunElt& se = *i;
606 size_t runLength = se.fRealEnd - se.fRealStart;
608 if ((se.fRealStart <= rowRelCharLoc) and ((rowRelCharLoc < se.fRealEnd) or ((rowRelCharLoc == se.fRealEnd) and (i + 1 == runs.end ())))) {
609 size_t absoluteSegStart = rowStart + se.fRealStart;
610 size_t subSegLen = rowRelCharLoc - se.fRealStart;
611 Assert (subSegLen <= runLength);
613 size_t nextPosition = FindNextCharacter (charLoc);
614 bool emptyChar = (nextPosition == charLoc);
616 Led_tChar lastChar =
'\0';
617 CopyOut (charLoc, 1, &lastChar);
618 emptyChar = (RemoveMappedDisplayCharacters (&lastChar, 1) == 0);
622 if (se.fDirection == eLeftToRight) {
623 *lhs = spannedSoFar + CalcSegmentSize (absoluteSegStart, absoluteSegStart + subSegLen);
628 *rhs = spannedSoFar + CalcSegmentSize (absoluteSegStart, FindNextCharacter (absoluteSegStart + subSegLen));
632 CoordinateType segRHS = spannedSoFar + CalcSegmentSize (absoluteSegStart, absoluteSegStart + runLength);
633 *rhs = segRHS - CalcSegmentSize (absoluteSegStart, absoluteSegStart + subSegLen);
638 *lhs = segRHS - CalcSegmentSize (absoluteSegStart, FindNextCharacter (absoluteSegStart + subSegLen));
644 spannedSoFar += CalcSegmentSize (rowStart + se.fRealStart, rowStart + se.fRealEnd);
647 Ensure (*lhs <= *rhs);
654size_t PartitioningTextImager::GetRowRelativeCharAtLoc (CoordinateType hOffset,
size_t rowStart)
const
656 Require (rowStart == GetStartOfRowContainingPosition (rowStart));
662 size_t rowEnd = GetEndOfRowContainingPosition (rowStart);
663 [[maybe_unused]]
size_t rowLen = rowEnd - rowStart;
664 TextLayoutBlock_Copy rowText = GetTextLayoutBlock (rowStart, rowEnd);
666 using ScriptRunElt = TextLayoutBlock::ScriptRunElt;
667 vector<ScriptRunElt> runs = rowText.GetScriptRuns ();
673 Assert (not runs.empty () or (rowLen == 0));
674 if (runs.size () > 1) {
676 sort (runs.begin (), runs.end (), TextLayoutBlock::LessThanVirtualStart{});
678 DistanceType spannedSoFar = 0;
679 TextDirection lastRunDir = eLeftToRight;
680 for (
auto i = runs.begin (); i != runs.end (); ++i) {
681 const ScriptRunElt& se = *i;
682 DistanceType thisSpanWidth = CalcSegmentSize (rowStart + se.fRealStart, rowStart + se.fRealEnd);
683 DistanceType segVisStart = spannedSoFar;
684 DistanceType segVisEnd = segVisStart + thisSpanWidth;
686 lastRunDir = se.fDirection;
688 if (hOffset <
static_cast<CoordinateType
> (segVisEnd)) {
696 size_t absoluteSegStart = rowStart + se.fRealStart;
699 size_t prevEnd = rowStart + se.fRealStart;
700 size_t segEnd = rowStart + se.fRealEnd;
701 for (
size_t curEnd = FindNextCharacter (prevEnd); curEnd < segEnd; (prevEnd = curEnd), (curEnd = FindNextCharacter (curEnd))) {
702 DistanceType hSize = CalcSegmentSize (absoluteSegStart, curEnd);
703 if (se.fDirection == eLeftToRight) {
704 if (
static_cast<CoordinateType
> (hSize + spannedSoFar) > hOffset) {
709 if (
static_cast<CoordinateType
> (segVisEnd) -
static_cast<CoordinateType
> (hSize) < hOffset) {
717 spannedSoFar += thisSpanWidth;
720 Assert (hOffset > 0 or runs.size () == 0);
721 if (lastRunDir == eLeftToRight) {
744size_t PartitioningTextImager::ResetTabStops (
size_t from,
const Led_tChar* text,
size_t nTChars, DistanceType* charLocations,
size_t startSoFar)
const
747 size_t lastTabIndex = 0;
748 CoordinateType tabAdjust = 0;
749 DistanceType widthAtStart = (startSoFar == 0 ? 0 : charLocations[startSoFar - 1]);
750 for (
size_t i = startSoFar; i < startSoFar + nTChars; i++) {
751 if (text[i] ==
'\t') {
752 DistanceType widthSoFar = (i == 0 ? 0 : charLocations[i - 1]);
753 tabAdjust = widthAtStart + GetTabStopList (from).ComputeTabStopAfterPosition (Tablet_Acquirer (
this), widthSoFar - widthAtStart) -
757 charLocations[i] += tabAdjust;
759 return (lastTabIndex);
762#if qStroika_Foundation_Debug_AssertionsChecked
763void PartitioningTextImager::Invariant_ ()
const
765 if (fPartition.get () !=
nullptr) {
766 fPartition->Invariant ();
767 Assert (fPartition->PeekAtTextStore () == PeekAtTextStore ());
772#if qCacheTextMeasurementsForPM
778PartitioningTextImager::MeasureTextCache::MeasureTextCache (
const PartitionPtr& partition)
779 : fPartition (partition)
782 Assert (partition.get () !=
nullptr);
783 fPartition->AddPartitionWatcher (
this);
784 TextStore& ts = partition->GetTextStore ();
785 ts.AddMarkerOwner (
this);
788PartitioningTextImager::MeasureTextCache::~MeasureTextCache ()
790 fPartition->RemovePartitionWatcher (
this);
791 TextStore& ts = fPartition->GetTextStore ();
792 ts.RemoveMarkerOwner (
this);
795void PartitioningTextImager::MeasureTextCache::AboutToSplit (PartitionMarker* pm,
size_t ,
void** infoRecord)
const noexcept
800void PartitioningTextImager::MeasureTextCache::DidSplit (
void* infoRecord)
const noexcept
802 PartitionMarker* pm =
reinterpret_cast<PartitionMarker*
> (infoRecord);
803 fCache.clear ([pm] (
const CacheElt::COMPARE_ITEM& c) {
return pm == c.fPM; });
806void PartitioningTextImager::MeasureTextCache::AboutToCoalece (PartitionMarker* pm,
void** infoRecord)
const noexcept
813void PartitioningTextImager::MeasureTextCache::DidCoalece (
void* infoRecord)
const noexcept
815 PartitionMarker* pm =
reinterpret_cast<PartitionMarker*
> (infoRecord);
816 fCache.clear ([pm] (
const CacheElt::COMPARE_ITEM& c) {
return pm == c.fPM; });
819TextStore* PartitioningTextImager::MeasureTextCache::PeekAtTextStore ()
const
821 return fPartition->PeekAtTextStore ();
824void PartitioningTextImager::MeasureTextCache::EarlyDidUpdateText (
const UpdateInfo& updateInfo)
noexcept
827 size_t cacheSize = 3;
828 size_t bufLen = GetTextStore ().GetLength ();
829 const size_t kBigThresh1_ = 1024;
830 const size_t kBigThresh2_ = 10 * 1024;
831 const size_t kBigThresh3_ = 20 * 1024;
832 if (bufLen > kBigThresh1_) {
834 if (bufLen > kBigThresh2_) {
836 if (bufLen > kBigThresh3_) {
841 fCache.SetMaxCacheSize (cacheSize);
848 for (PartitionMarker* pm = fPartition->GetPartitionMarkerContainingPosition (FindPreviousCharacter (updateInfo.fReplaceFrom));
849 pm !=
nullptr; pm = pm->GetNext ()) {
850 if (pm->GetStart () > updateInfo.GetResultingRHS ()) {
854 fCache.clear ([pm] (
const CacheElt::COMPARE_ITEM& c) {
return pm == c.fPM; });
856 MarkerOwner::EarlyDidUpdateText (updateInfo);
866void PartitionMarker::DidUpdateText (
const UpdateInfo& updateInfo)
noexcept
868 inherited::DidUpdateText (updateInfo);
869 GetOwner ().UpdatePartitions (
this, updateInfo);
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
#define RequireNotNull(p)
#define AssertNotReached()
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...