Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
TextImager.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include "Stroika/Frameworks/Led/GDI.h"
7
8#include "TextImager.h"
9
10using namespace Stroika::Foundation;
11
12using namespace Stroika::Foundation;
13using namespace Stroika::Frameworks;
14using namespace Stroika::Frameworks::Led;
15
16#if qStroika_Frameworks_Led_SupportGDI
17
18/*
19 ********************************************************************************
20 *********************** TextImager::FontCacheInfoUpdater ***********************
21 ********************************************************************************
22 */
23#if qStroika_Platform_Windows
24namespace {
25 inline bool LogFontsEqual_ (LOGFONT lhs, LOGFONT rhs)
26 {
27 size_t bytesToCompare = offsetof (LOGFONT, lfFaceName) + (::_tcslen (lhs.lfFaceName) + 1) * sizeof (Characters::SDKChar);
28 Require (bytesToCompare <= sizeof (LOGFONT)); // else we were passed bogus LogFont (and we should validate them before here!)
29 return ::memcmp (&lhs, &rhs, bytesToCompare) == 0;
30 }
31 inline bool LogFontsEqual_ (const FontSpecification& lhs, const FontSpecification& rhs)
32 {
33 if (lhs.GetStyle_SubOrSuperScript () == rhs.GetStyle_SubOrSuperScript ()) {
34 LOGFONT lhslf;
35 lhs.GetOSRep (&lhslf);
36 LOGFONT rhslf;
37 rhs.GetOSRep (&rhslf);
38 return LogFontsEqual_ (lhslf, rhslf);
39 }
40 else {
41 return false;
42 }
43 }
44}
45#endif
46TextImager::FontCacheInfoUpdater::FontCacheInfoUpdater (const TextImager* imager, Tablet* tablet, const FontSpecification& fontSpec)
47 : fImager{imager}
48#if qStroika_Platform_Windows
49 , fTablet{tablet}
50 , fRestoreObject{nullptr}
51 , fRestoreAttribObject{nullptr}
52#endif
53{
54#if qStroika_Platform_Windows
55 /*
56 * For Windows:
57 *
58 * Just set the font using the font-spec. Cache font, so we don't keep creating. But select it
59 * in to tablet each time. And on DTOR, restore old font into tablet.
60 */
61 bool changed = false;
62 if (imager->fCachedFont == nullptr or !LogFontsEqual_ (fontSpec, imager->fCachedFontSpec)) {
63 changed = true;
64 delete imager->fCachedFont;
65 imager->fCachedFont = nullptr;
66 imager->fCachedFont = new FontObject ();
67 LOGFONT lf;
68 fontSpec.GetOSRep (&lf);
69 if (fontSpec.GetStyle_SubOrSuperScript () != FontSpecification::eNoSubOrSuperscript) {
70 // See SPR#1523- was 'lf.lfHeight /= 2'
71 // Careful to sync this with TextImager::DrawSegment_ () 'drawTop' adjustment
72 lf.lfHeight = lf.lfHeight * 2 / 3;
73 if (lf.lfHeight == 0) {
74 lf.lfHeight = 1;
75 }
76 }
77 Verify (imager->fCachedFont->CreateFontIndirect (&lf));
78 imager->fCachedFontSpec = fontSpec;
79 }
80 AssertNotNull (imager->fCachedFont);
81
82 // See CDC::SelectObject for the logic..., but we do better than thiers and restore
83 // right object to right DC!!!! - LGP 950525
84 if (tablet->m_hDC != tablet->m_hAttribDC) {
85 fRestoreObject = ::SelectObject (tablet->m_hDC, imager->fCachedFont->m_hObject);
86 // At one point i had Asserts() here that fRestoreObject != nullptr - but at least for PrintDC's, apparently
87 // fRestoreObject can be nullptr, and apparently it isn't a problem...
88 }
89 if (tablet->m_hAttribDC != nullptr) {
90 fRestoreAttribObject = ::SelectObject (tablet->m_hAttribDC, imager->fCachedFont->m_hObject);
91 // At one point i had Asserts() here that fRestoreAttribObject != nullptr - but at least for PrintDC's, apparently
92 // fRestoreAttribObject can be nullptr, and apparently it isn't a problem...
93 }
94 if (changed) {
95 imager->fCachedFontInfo = tablet->GetFontMetrics ();
96 }
97#endif
98}
99
100/*
101 ********************************************************************************
102 ************************************** TextImager ******************************
103 ********************************************************************************
104 */
105TextImager::TextImager ()
106 : fTextStore{nullptr}
107 , fDefaultFont{GetStaticDefaultFont ()}
108 , fForceAllRowsShowing{true}
109 , fImageUsingOffscreenBitmaps{qStroika_Frameworks_Led_UseOffscreenBitmapsToReduceFlicker}
110 , fHScrollPos{0}
111 , fSuppressGoalColumnRecompute{false}
112 , fSelectionGoalColumn{TWIPS{0}}
113 , fUseEOLBOLRowHilightStyle{true}
114 , fSelectionShown{false}
115 , fWindowRect{Led_Rect{0, 0, 0, 0}}
116 , fHiliteMarker{nullptr}
117 , fWeAllocedHiliteMarker{false} //, fDefaultColorIndex{},
118 , fCachedFontSpec{}
119 , fCachedFontInfo{}
120 ,
121#if qStroika_Platform_Windows
122 fCachedFont{nullptr}
123#else
124 fCachedFontValid{false}
125#endif
126{
127 for (Color** i = &fDefaultColorIndex[0]; i < &fDefaultColorIndex[eMaxDefaultColorIndex]; ++i) {
128 *i = nullptr;
129 }
130}
131
132TextImager::~TextImager ()
133{
134 Require (fTextStore == nullptr);
135 Require (fHiliteMarker == nullptr);
136 for (Color** i = &fDefaultColorIndex[0]; i < &fDefaultColorIndex[eMaxDefaultColorIndex]; ++i) {
137 delete *i;
138 *i = nullptr;
139 }
140#if qStroika_Platform_Windows
141 delete fCachedFont;
142#endif
143}
144
145TextStore* TextImager::PeekAtTextStore () const
146{
147 return fTextStore; // Can return nullptr if no markers owned
148}
149
150/*
151@METHOD: TextImager::SpecifyTextStore
152@DESCRIPTION: <p>Associate this TextImager with the given @'TextStore'.</p>
153 <p>Note that this will - as a side-effect - call
154 @'TextStore::AddMarkerOwner'. So be sure that this @'TextImager' has not yet been added as a @'MarkerOwner' for
155 any other (or this given) TextStore.</p>
156*/
157void TextImager::SpecifyTextStore (TextStore* useTextStore)
158{
159 if (fTextStore != useTextStore) {
160 if (fTextStore != nullptr) {
161 HookLosingTextStore ();
162 }
163 fTextStore = useTextStore;
164 if (fTextStore != nullptr) {
165 HookGainedNewTextStore ();
166 }
167 }
168}
169
170void TextImager::HookLosingTextStore ()
171{
172 HookLosingTextStore_ ();
173}
174
175void TextImager::HookLosingTextStore_ ()
176{
177 // BE MORE CAREFUL HERE - NO NEED TO DELETE HILIGHT MARKER - JUST REMOVE AND RE_ADD!!!
178 AssertNotNull (fTextStore);
179 if (fHiliteMarker != nullptr) {
180 Assert (fWeAllocedHiliteMarker); // otherwise setter better have unset!!!
181 GetTextStore ().RemoveMarker (fHiliteMarker);
182 delete fHiliteMarker;
183 fHiliteMarker = nullptr;
184 fTextStore->RemoveMarkerOwner (this);
185 }
186}
187
188void TextImager::HookGainedNewTextStore ()
189{
190 HookGainedNewTextStore_ ();
191}
192
193void TextImager::HookGainedNewTextStore_ ()
194{
195 AssertNotNull (fTextStore);
196 if (fHiliteMarker == nullptr) {
197 fTextStore->AddMarkerOwner (this);
198 SetHilightMarker (nullptr); // forces creation of default one...
199 }
200}
201
202/*
203@METHOD: TextImager::PurgeUnneededMemory
204@DESCRIPTION: <p>Call when you want to have the text engine save a little bit of memory. Frees up data
205 that isn't required. This doesn't NEED to be called, but can be called by memory-restricted applications.
206 The default implementation calls @'TextImager::InvalidateAllCaches'.
207 </p>
208*/
209void TextImager::PurgeUnneededMemory ()
210{
211 InvalidateAllCaches ();
212}
213
214/*
215@METHOD: TextImager::InvalidateAllCaches
216@DESCRIPTION: <p>This is called - for example - when you change the wrap width for the entire document, or font information
217 for the entire document, or maybe something about the @'Tablet*' metrics you are imaging to.
218 TextImager::InvalidateAllCaches is called automatically from @'MultiRowTextImager::TabletChangedMetrics ()'
219 </p>
220*/
221void TextImager::InvalidateAllCaches ()
222{
223// Classes which cache font-based information must OVERRIDE and invalidate it...
224#if qStroika_Platform_Windows
225 delete fCachedFont;
226 fCachedFont = nullptr;
227#else
228 fCachedFontValid = false;
229#endif
230}
231
232/*
233@METHOD: TextImager::SetDefaultFont
234@DESCRIPTION: <p>Sets the default font associated with the given imager. This is NOT necessarily the one you will
235 see displayed, as the font displayed maybe overriden by more specific font information from
236 @'StyledTextImager::StyleMarker' or @'StandardStyledTextImager'.
237 </p>
238 <p>Note that the semantics of SetDefaultFontChanged in 3.1a4. If you want to set the font
239 of a specific range of styled text (e.g. in a word-processor class) - then you may want to
240 use @'StandardStyledTextInteractor::InteractiveSetFont'.
241 </p>
242*/
243void TextImager::SetDefaultFont (const IncrementalFontSpecification& defaultFont)
244{
245 SetDefaultFont_ (defaultFont);
246}
247
248void TextImager::SetDefaultFont_ (const IncrementalFontSpecification& defaultFont)
249{
250 if (PeekAtTextStore () != nullptr) {
251 TextStore::SimpleUpdater u (GetTextStore (), 0, GetTextStore ().GetEnd (), false); // update buffer so cached measurement values refreshed
252 fDefaultFont.MergeIn (defaultFont);
253 if (defaultFont.GetTextColor_Valid ()) {
254 SetDefaultTextColor (eDefaultTextColor, defaultFont.GetTextColor ());
255 }
256 }
257 else {
258 fDefaultFont.MergeIn (defaultFont);
259 if (defaultFont.GetTextColor_Valid ()) {
260 SetDefaultTextColor (eDefaultTextColor, defaultFont.GetTextColor ());
261 }
262 }
263}
264
265/*
266@METHOD: TextImager::GetDefaultSelectionFont
267@DESCRIPTION: <p>Some applications - such as updating the font of the IME - need to know the
268 current selections font. For some @'TextImager's this is easy - its just the font used
269 for the entire widget (or default implementation). But for others - say an imager with
270 multiple runs of fonts - it can be ambiguous. There is no strictly <em>right</em> answer
271 for what to return if there are overlapping runs of font information. Just return the best
272 fit possible.
273 </p>
274 <p>The default implementation just returns @'TextImager::GetDefaultFont', but
275 should be subclassed by @'StandardStyledTextImager::GetDefaultSelectionFont' to reflect just
276 the selection's current font.
277 </p>
278*/
279FontSpecification TextImager::GetDefaultSelectionFont () const
280{
281 return GetDefaultFont ();
282}
283
284void TextImager::SetSelectionShown (bool shown)
285{
286 fSelectionShown = shown;
287}
288
289/*
290@METHOD: TextImager::GetTabStopList
291@DESCRIPTION: <p>Return the tabstop list (@'TabStopList') active at a particular position in the text. Typically this will
292 apply to an entire region (often a paragraph/partitionmarker). And we only need specify
293 one point in that range.</p>
294 <p>Override this to provide a different tabstop list. The default is a @'TextImager::SimpleTabStopList' of width
295 roughly 1/3 of an inch.</p>
296*/
297const TabStopList& TextImager::GetTabStopList (size_t /*containingPos*/) const
298{
299 // 1/3 inch tabstops by default (roughly 4 chars wide on Mac in Courier 10-point)
300 static SimpleTabStopList sDefaultTabStopList = SimpleTabStopList (TWIPS (1440 / 3));
301 return sDefaultTabStopList;
302}
303
304/*
305@METHOD: TextImager::SetWindowRect
306@DESCRIPTION: <p>See also @'TextImager::GetWindowRect'.
307*/
308void TextImager::SetWindowRect (const Led_Rect& windowRect)
309{
310 SetWindowRect_ (windowRect);
311}
312
313/*
314@METHOD: TextImager::ScrollSoShowingHHelper
315@DESCRIPTION: <p>You probably should NOT call this directly. This is a helper to share code in implementing
316 @'TextImager::ScrollSoShowing' in subclasses.</p>
317*/
318void TextImager::ScrollSoShowingHHelper (size_t markerPos, size_t andTryToShowMarkerPos)
319{
320 CoordinateType maxHScrollPos = ComputeMaxHScrollPos ();
321 CoordinateType hsp = GetHScrollPos ();
322
323 /*
324 * Speed tweek - avoid alot of computations which are unneeded if this is true.
325 */
326 if (maxHScrollPos == 0) {
327 if (hsp != 0) {
328 SetHScrollPos (0);
329 }
330 return;
331 }
332
333 Led_Rect windowRect = GetWindowRect ();
334
335 {
336 // Try to see if we can accomodate the 'andTryToShowMarkerPos'.
337 Led_Rect andTryRRR = GetCharWindowLocation (andTryToShowMarkerPos);
338 CoordinateType whereAtInGlobalCoords = windowRect.left - andTryRRR.left;
339 if (andTryRRR.left < windowRect.left) {
340 Assert (hsp >= whereAtInGlobalCoords);
341 hsp -= whereAtInGlobalCoords;
342 }
343 else if (andTryRRR.right > windowRect.right) {
344 CoordinateType howFarOffRight = andTryRRR.right - windowRect.right;
345 hsp += howFarOffRight; // now the char should be just barely showing.
346 hsp = min (hsp, maxHScrollPos);
347 }
348 }
349 Assert (hsp >= 0);
350 Assert (hsp <= maxHScrollPos);
351
352 {
353 // Make sure the 'markerPos' is shown. Do this second - in case there is a conflict between 'markerPos' and 'andTryToShowMarkerPos'
354 Led_Rect rrr = GetCharWindowLocation (markerPos);
355
356 {
357 CoordinateType adjustRRRBy = GetHScrollPos () - hsp;
358 rrr += Led_Point (0, adjustRRRBy);
359 }
360
361 CoordinateType whereAtInGlobalCoords = windowRect.GetLeft () - rrr.GetLeft ();
362 if (rrr.GetLeft () < windowRect.GetLeft ()) {
363 Assert (hsp >= whereAtInGlobalCoords);
364 hsp -= whereAtInGlobalCoords;
365 }
366 else if (rrr.GetRight () > windowRect.GetRight ()) {
367 CoordinateType howFarOffRight = rrr.GetRight () - windowRect.GetRight ();
368 hsp += howFarOffRight; // now the char should be just barely showing.
369 hsp = min (hsp, maxHScrollPos);
370 }
371 }
372
373 Assert (hsp >= 0);
374 Assert (hsp <= maxHScrollPos);
375 SetHScrollPos (hsp);
376}
377
378void TextImager::SetHScrollPos (CoordinateType hScrollPos)
379{
380 if (hScrollPos != GetHScrollPos ()) {
381 SetHScrollPos_ (hScrollPos);
382 InvalidateScrollBarParameters ();
383 }
384}
385
386/*
387@METHOD: TextImager::ComputeMaxHScrollPos
388@DESCRIPTION: <p>This routine is used for horizontal scrolling (though not from within this class).
389 It is mainly called by @'TextInteractor' or @'Led_Win32' etc methods to handle display/positioning
390 of the scrollbar. Subclasses can OVERRIDE this to implement whatever horizontal scrolling
391 they might want to.
392 </p>
393 <p>A plausible OVERRIDE
394 of this routine might return (roughly - taking care of min'ing out to zero)
395 <code>CalculateLongestRowInWindowPixelWidth () - GetWindowRect ().GetWidth ()</code>
396 or perhaps even better:
397 <code>CalculateLongestRowInDocumentPixelWidth () - GetWindowRect ().GetWidth ()</code>
398 </p>
399 <p>Some subclasses - such as @'WordProcessor' - already contain their own implementation
400 (@'WordProcessor::ComputeMaxHScrollPos').
401 </p>
402*/
403DistanceType TextImager::ComputeMaxHScrollPos () const
404{
405 return 0;
406}
407
408/*
409@METHOD: TextImager::CalculateLongestRowInWindowPixelWidth
410@DESCRIPTION: <p>This is a utility methods, very handy for implementing horizontal scrolling.
411 It can (and should be) overriden in certain subclasses for efficiency. But the default implementation will work.</p>
412*/
413DistanceType TextImager::CalculateLongestRowInWindowPixelWidth () const
414{
415 size_t startOfWindow = GetMarkerPositionOfStartOfWindow ();
416 size_t endOfWindow = GetMarkerPositionOfEndOfWindow ();
417
418 DistanceType longestRowWidth = 0;
419 for (size_t curOffset = startOfWindow; curOffset < endOfWindow;) {
420 DistanceType thisRowWidth = CalcSegmentSize (curOffset, GetEndOfRowContainingPosition (curOffset));
421 longestRowWidth = max (longestRowWidth, thisRowWidth);
422 {
423 size_t newOffset = GetStartOfNextRowFromRowContainingPosition (curOffset);
424 if (newOffset <= curOffset) {
425 break; // can happen at end of doc...
426 }
427 curOffset = newOffset;
428 }
429 }
430 return longestRowWidth;
431}
432
433/*
434@METHOD: TextImager::TabletChangedMetrics
435@DESCRIPTION: <p>Call this method when something external, Led cannot detect, has happened to the tablet which would
436 invalidate any information the @'TextImager' has cached. This is called automaticly, internal to Led, by anything
437 Led knows about which would change the metrics.</p>
438*/
439void TextImager::TabletChangedMetrics ()
440{
441 InvalidateAllCaches ();
442}
443
444void TextImager::SetSelection (size_t start, size_t end)
445{
446 Assert (start >= 0);
447 Assert (end <= GetEnd ()); // char 1 between positions 1..2
448 if (start != GetSelectionStart () or end != GetSelectionEnd ()) {
449 SetSelection_ (start, end);
450 }
451}
452
453size_t TextImager::GetSelectionStart () const
454{
455 RequireNotNull (PeekAtTextStore ()); // Must specify TextStore before calling this, or any routine that calls it.
456 return (fHiliteMarker->GetStart ());
457}
458
459size_t TextImager::GetSelectionEnd () const
460{
461 RequireNotNull (PeekAtTextStore ()); // Must specify TextStore before calling this, or any routine that calls it.
462 return (fHiliteMarker->GetEnd ());
463}
464
465void TextImager::GetSelection (size_t* start, size_t* end) const
466{
467 RequireNotNull (PeekAtTextStore ()); // Must specify TextStore before calling this, or any routine that calls it.
468 AssertNotNull (start);
469 AssertNotNull (end);
470 fHiliteMarker->GetRange (start, end);
471}
472
473void TextImager::SetSelection_ (size_t start, size_t end)
474{
475 Require (start >= 0);
476 Require (end <= GetEnd ());
477 Require (start <= end);
478 GetTextStore ().SetMarkerRange (fHiliteMarker, start, end);
479}
480
481void TextImager::SetHilightMarker (HilightMarker* newHilightMarker)
482{
483 size_t start = 0;
484 size_t end = 0;
485 if (fHiliteMarker != nullptr) {
486 fHiliteMarker->GetRange (&start, &end);
487 GetTextStore ().RemoveMarker (fHiliteMarker);
488 if (fWeAllocedHiliteMarker) {
489 delete fHiliteMarker;
490 }
491 }
492 fHiliteMarker = newHilightMarker;
493 fWeAllocedHiliteMarker = bool (fHiliteMarker == nullptr);
494 if (fHiliteMarker == nullptr) {
495 fHiliteMarker = new HilightMarker ();
496 }
497 AssertNotNull (fHiliteMarker);
498 GetTextStore ().AddMarker (fHiliteMarker, start, end - start, this);
499}
500
501/*
502@METHOD: TextImager::RecomputeSelectionGoalColumn
503@DESCRIPTION: <p></p>
504*/
505void TextImager::RecomputeSelectionGoalColumn ()
506{
507 if (not fSuppressGoalColumnRecompute) {
508 // We now maintain a goal-column-target using pixel offsets within the row, rather than
509 // character offsets, cuz thats what LEC/Alan Pollack prefers, and I think most
510 // Texteditors seem todo likewise - LedSPR#0315
511 DistanceType lhs = 0;
512 DistanceType rhs = 0;
513 GetRowRelativeCharLoc (GetSelectionStart (), &lhs, &rhs);
514 SetSelectionGoalColumn (Tablet_Acquirer (this)->CvtToTWIPSH (lhs + (rhs - lhs) / 2));
515 }
516}
517
518/*
519@METHOD: TextImager::ComputeRelativePosition
520@DESCRIPTION: <p></p>
521*/
522size_t TextImager::ComputeRelativePosition (size_t fromPos, CursorMovementDirection direction, CursorMovementUnit movementUnit)
523{
524 /*
525 * Handle all the different cases of movement directions (back, forward etc) and units (by char, word etc).
526 * Take the given starting point, and produce no side effects - returning the new resulting position.
527 */
528 switch (direction) {
529 case eCursorBack: {
530 switch (movementUnit) {
531 case eCursorByChar: {
532 return (FindPreviousCharacter (fromPos));
533 } break;
534
535 case eCursorByWord: {
536 return (GetTextStore ().FindFirstWordStartStrictlyBeforePosition (fromPos));
537 } break;
538
539 case eCursorByRow: {
540 size_t startOfStartRow = GetStartOfRowContainingPosition (fromPos);
541 size_t startOfPrevRow = GetStartOfPrevRowFromRowContainingPosition (fromPos);
542 if (startOfStartRow == startOfPrevRow) {
543 // no change
544 return (fromPos);
545 }
546 else {
547 return GetRowRelativeCharAtLoc (Tablet_Acquirer (this)->CvtFromTWIPSH (GetSelectionGoalColumn ()), startOfPrevRow);
548 }
549 } break;
550
551 case eCursorByLine: {
552 size_t fromLine = GetTextStore ().GetLineContainingPosition (fromPos);
553 size_t newLine = (fromLine > 0) ? fromLine - 1 : 0;
554 if (newLine == fromLine) {
555 // no change
556 return (fromPos);
557 }
558 else {
559 // try to maintain the same horizontal position across lines
560 size_t positionInLine = fromPos - GetTextStore ().GetStartOfLine (fromLine); // ZERO RELATIVE
561 Assert (positionInLine <= GetTextStore ().GetLineLength (fromLine));
562 positionInLine = min (positionInLine, GetTextStore ().GetLineLength (newLine)); // don't go past end of new line...
563 return GetTextStore ().GetStartOfLine (newLine) + positionInLine;
564 }
565 } break;
566
567 case eCursorByWindow: {
568 Assert (false); // nyi
569 } break;
570
571 case eCursorByBuffer: {
572 Assert (false); // makes no sense - use toStart...
573 } break;
574
575 default:
576 Assert (false);
577 }
578 } break;
579
580 case eCursorForward: {
581 switch (movementUnit) {
582 case eCursorByChar: {
583 return (FindNextCharacter (fromPos));
584 } break;
585
586 case eCursorByWord: {
587 TextStore& ts = GetTextStore ();
588 return (ts.FindFirstWordStartAfterPosition (ts.FindNextCharacter (fromPos)));
589 } break;
590
591 case eCursorByRow: {
592 size_t startOfStartRow = GetStartOfRowContainingPosition (fromPos);
593 size_t startOfNextRow = GetStartOfNextRowFromRowContainingPosition (fromPos);
594 if (startOfStartRow == startOfNextRow) {
595 // no change
596 return (fromPos);
597 }
598 else {
599 return GetRowRelativeCharAtLoc (Tablet_Acquirer (this)->CvtFromTWIPSH (GetSelectionGoalColumn ()), startOfNextRow);
600 }
601 } break;
602
603 case eCursorByLine: {
604 size_t fromLine = GetTextStore ().GetLineContainingPosition (fromPos);
605 size_t newLine = (fromLine == GetTextStore ().GetLineCount () - 1) ? fromLine : (fromLine + 1);
606 Assert (newLine <= GetTextStore ().GetLineCount () - 1);
607 if (newLine == fromLine) {
608 // no change
609 return (fromPos);
610 }
611 else {
612 // try to maintain the same horizontal position across rows
613 size_t positionInLine = fromPos - GetTextStore ().GetStartOfLine (fromLine); // ZERO RELATIVE
614 Assert (positionInLine <= GetTextStore ().GetLineLength (fromLine));
615 positionInLine = min (positionInLine, GetTextStore ().GetLineLength (newLine)); // don't go past end of new line...
616 return (GetTextStore ().GetStartOfLine (newLine) + positionInLine);
617 }
618 } break;
619
620 case eCursorByWindow: {
621 Assert (false); // nyi
622 } break;
623
624 case eCursorByBuffer: {
625 Assert (false); // makes no sense - use eCursorToEnd...
626 } break;
627
628 default:
629 Assert (false);
630 }
631 } break;
632
633 case eCursorToStart: {
634 switch (movementUnit) {
635 case eCursorByChar: {
636 Assert (false); // to start of character - does this make sense???
637 } break;
638
639 case eCursorByWord: {
640 return (GetTextStore ().FindFirstWordStartStrictlyBeforePosition (FindNextCharacter (fromPos)));
641 } break;
642
643 case eCursorByRow: {
644 size_t fromRow = GetRowContainingPosition (fromPos);
645 return (GetStartOfRow (fromRow));
646 } break;
647
648 case eCursorByLine: {
649 size_t result = GetTextStore ().GetStartOfLineContainingPosition (fromPos);
650 if (fromPos == result) {
651 result = GetTextStore ().GetStartOfLineContainingPosition (GetTextStore ().FindPreviousCharacter (result));
652 }
653 return result;
654 } break;
655
656 case eCursorByWindow: {
657 return (GetMarkerPositionOfStartOfWindow ());
658 } break;
659
660 case eCursorByBuffer: {
661 return (0);
662 } break;
663
664 default:
665 Assert (false);
666 }
667 } break;
668
669 case eCursorToEnd: {
670 switch (movementUnit) {
671 case eCursorByChar: {
672 Assert (false); // to start of character - does this make sense???
673 } break;
674
675 case eCursorByWord: {
676 return (GetTextStore ().FindFirstWordEndAfterPosition (fromPos));
677 } break;
678
679 case eCursorByRow: {
680 size_t fromRow = GetRowContainingPosition (fromPos);
681 return (GetEndOfRow (fromRow));
682 } break;
683
684 case eCursorByLine: {
685 size_t result = GetTextStore ().GetEndOfLineContainingPosition (fromPos);
686 if (fromPos == result) {
687 result = GetTextStore ().GetEndOfLineContainingPosition (GetTextStore ().FindNextCharacter (result));
688 }
689 return result;
690 } break;
691
692 case eCursorByWindow: {
693 return (GetMarkerPositionOfEndOfWindow ());
694 } break;
695
696 case eCursorByBuffer: {
697 return (GetTextStore ().GetEnd ());
698 } break;
699
700 default:
701 Assert (false);
702 }
703 } break;
704
705 default:
706 Assert (false);
707 }
708 Assert (false); // not reached...
709 return (fromPos);
710}
711
712/*
713@METHOD: TextImager::GetTextWindowBoundingRect
714@DESCRIPTION: <p>GetTextWindowBoundingRect () return a @'Led_Rect' which bounds
715 the characters defined by the given marker positions. The will fit nicely around the
716 characters if they all fit in one row (and one directional run), but may have sluff around the left/right sides
717 if the range crosses row boundaries or directional runs (since the shape wouldn't be a rectangle, but a region -
718 try GetSelectionWindowRegion () for that).</p>
719 <p>This function operates on CharacterCells.</p>
720 <p>This function operates in Window coordinates (ie window relative, calling @'TextImager::GetCharWindowLocation').</p>
721 <p>Return value is pinned to be within the WindowRect.</p>
722 <p>See also @'TextImager::GetIntraRowTextWindowBoundingRect'</p>
723*/
724Led_Rect TextImager::GetTextWindowBoundingRect (size_t fromMarkerPos, size_t toMarkerPos) const
725{
726 Require (fromMarkerPos <= toMarkerPos);
727
728 Led_Rect windowRect = GetWindowRect ();
729 Led_Rect r1 = GetCharWindowLocation (fromMarkerPos);
730 Led_Rect r2 = r1;
731 if (fromMarkerPos != toMarkerPos) {
732 r2 = GetCharWindowLocation (FindPreviousCharacter (toMarkerPos));
733 }
734
735 size_t realEndOfRowOfFromMarkerPos = GetRealEndOfRowContainingPosition (fromMarkerPos);
736
737 Led_Rect boundingRect;
738 boundingRect.top = r1.GetTop ();
739 boundingRect.bottom = r2.GetBottom (); // too aggressive??? for case of end of row it is...
740
741 if (realEndOfRowOfFromMarkerPos >= toMarkerPos) {
742 /*
743 * One ROW case
744 *
745 * This is pretty complicated. We must worry about multiple overlapping runs, and about the
746 * extensions from the start/end of the text to the window borders (depending on the
747 * segmentHilightedAtStart/segmentHilightedAtEnd flags).
748 *
749 * I'm not terribly confident this code is all right, but it really doesn't need to be perfect (though
750 * it SHOULD be). Its just important that it returns a rectangle BIG ENOUGH to wrap ALL the releveant text.
751 * Being a little TOO big is only inelegant, and not tragic.
752 *
753 * See SPR#1237 for some details (and a test case).
754 */
755
756 // A bit of a sloppy hack to make sure any drawing to the right or left of the text (up to the margin)
757 // gets erased as well.
758 size_t startOfRow = GetStartOfRowContainingPosition (fromMarkerPos);
759 size_t endOfRow = GetEndOfRowContainingPosition (fromMarkerPos);
760 bool segmentHilightedAtStart = (fromMarkerPos == startOfRow);
761 bool segmentHilightedAtEnd = endOfRow < toMarkerPos;
762
763 boundingRect.left = min (r1.GetLeft (), r2.GetLeft ());
764 boundingRect.right = max (r1.GetRight (), r2.GetRight ());
765
766 /*
767 *
768 * Trouble is we could have something like:
769 * 1 2 7 8 3 4 5 6 9
770 * and if I select from 6 to 9, I'll get just the right side of the row, and miss the
771 * characters from 7-8.
772 * Really I want all the way from the LHS of 7 to the RHS of 9 in this case.
773 */
774 TextLayoutBlock_Copy text = GetTextLayoutBlock (startOfRow, endOfRow);
775
776 using ScriptRunElt = TextLayoutBlock::ScriptRunElt;
777 vector<ScriptRunElt> runs = text.GetScriptRuns ();
778 if (runs.size () > 1) {
779 // Only needed if there are nested runs...
780 for (auto i = runs.begin (); i != runs.end (); ++i) {
781 const ScriptRunElt& se = *i;
782 if (TextStore::Overlap (startOfRow + se.fRealStart, startOfRow + se.fRealEnd, fromMarkerPos, toMarkerPos)) {
783 /*
784 * OK - there is SOME overlap between this run and the [fromMarkerPos, toMarkerPos] range.
785 *
786 * Now see if the run endpoints are in the from/to range, and if so, then extend the
787 * bounding rectangle to accomodate them.
788 */
789 if (fromMarkerPos <= startOfRow + se.fRealStart and startOfRow + se.fRealStart <= toMarkerPos) {
790 Led_Rect t1 = GetCharWindowLocation (startOfRow + se.fRealStart);
791 boundingRect.left = min (boundingRect.left, t1.GetLeft ());
792 boundingRect.right = max (boundingRect.right, t1.GetRight ());
793 }
794
795 if (fromMarkerPos <= startOfRow + se.fRealEnd and startOfRow + se.fRealEnd <= toMarkerPos) {
796 Led_Rect t2 = GetCharWindowLocation (FindPreviousCharacter (startOfRow + se.fRealEnd));
797 boundingRect.left = min (boundingRect.left, t2.GetLeft ());
798 boundingRect.right = max (boundingRect.right, t2.GetRight ());
799 }
800 }
801 }
802 }
803
804 if (segmentHilightedAtStart) {
805 boundingRect.left = windowRect.left;
806 }
807 if (segmentHilightedAtEnd) {
808 boundingRect.right = windowRect.right;
809 }
810 }
811 else {
812 /*
813 * Two or more ROWS case
814 */
815 boundingRect.left = windowRect.left;
816 boundingRect.right = windowRect.right;
817 }
818
819 // pin the results to be within the boundingRect (left and right)
820 boundingRect.left = max (boundingRect.left, windowRect.left);
821 boundingRect.right = min (boundingRect.right, windowRect.right);
822
823 Ensure (boundingRect.right >= boundingRect.left);
824 return (boundingRect);
825}
826
827/*
828@METHOD: TextImager::GetIntraRowTextWindowBoundingRect
829@ACCESS: public
830@DESCRIPTION: <p>GetIntraRowTextWindowBoundingRect () return a Rect which bounds
831 the characters defined by the given marker positions. The marker positions are required to fall
832 within a single row.
833 <p>This function operates on CharacterCells.</p>
834 <p>This function operates in Window coordinates (ie the same coordinates as the WindowRect,
835 is specified in - calling @'TextImager::GetCharWindowLocation').</p>
836 <p>See also @'TextImager::GetTextBoundingRect',
837 @'TextImager::GetTextWindowBoundingRect' and @'TextImager::GetIntraRowTextWindowBoundingRect'</p>
838 <p>You CAN call this function with any range of 'fromMarkerPos' to 'toMarkerPos' within a row, but it only REALLY
839 makes sense if you call it within a directional segment.</p>
840*/
841Led_Rect TextImager::GetIntraRowTextWindowBoundingRect (size_t fromMarkerPos, size_t toMarkerPos) const
842{
843 Require (fromMarkerPos <= toMarkerPos); // and they must be within the same row!!! Assert later...
844
845 Led_Rect windowRect = GetWindowRect ();
846
847 /*
848 * Note that we could use one side of one character and the same side of the other (e.g. for LTR text
849 * the left side of the first and second charaters). The trouble with this is that the character at 'toMarkerPos'
850 * can sometimes be on another line - not RIGHT after the one we are interested in. Furthermore - now that
851 * we support BIDI text - it could be left or right or almost anywhere if it was the start of another run.
852 *
853 * So - instead - we measure (taking the LTR case) from the LEFT side of the 'fromMarkerPos' character
854 * to the RIGHT side of the character PRECEDING the 'toMarkerPos' character (with the special case of when the two
855 * markerPos positions are the same.
856 */
857 Led_Rect r1 = GetCharWindowLocation (fromMarkerPos);
858 Led_Rect r2 = r1;
859 if (fromMarkerPos != toMarkerPos) {
860 r2 = GetCharWindowLocation (FindPreviousCharacter (toMarkerPos));
861 }
862
863 Led_Rect boundingRect = r1;
864
865 if (GetTextDirection (fromMarkerPos) == eLeftToRight) {
866 boundingRect.left = r1.GetLeft ();
867 boundingRect.right = (fromMarkerPos == toMarkerPos) ? boundingRect.left : r2.GetRight ();
868 }
869 else {
870 boundingRect.right = r1.GetRight ();
871 boundingRect.left = (fromMarkerPos == toMarkerPos) ? boundingRect.right : r2.GetLeft ();
872 }
873 Ensure (boundingRect.right >= boundingRect.left); // If this is triggered, its probably cuz your from/to crossed
874 // a directional segment boundary?
875 return (boundingRect);
876}
877
878/*
879@METHOD: TextImager::GetRowHilightRects
880@ACCESS: public
881@DESCRIPTION: <p>Compute the subregion of the row demarcated by 'rowStart' and 'rowEnd' which interesects with the given
882 'hilightStart' and 'hilightEnd'. If the hilights extend past the row (either start or end) this is OK- and the
883 hilight rectangle list is adjusted accordingly to display that extended hilgiht
884 (if @'TextImager::GetUseSelectEOLBOLRowHilightStyle' is set).</p>
885 <p>This routine is called by @'TextImager::GetSelectionWindowRegion' and @'TextImager::DrawRowHilight' to figure out what areas
886 of the screen to hilight.</p>
887*/
888vector<Led_Rect> TextImager::GetRowHilightRects (const TextLayoutBlock& text, size_t rowStart, size_t rowEnd, size_t hilightStart, size_t hilightEnd) const
889{
890 Require (rowEnd == GetEndOfRowContainingPosition (rowStart)); // passed in for performance reasons - so not computed multiple times
891
892 vector<Led_Rect> result;
893
894 size_t realEndOfRow = GetRealEndOfRowContainingPosition (rowStart);
895 bool segmentHilighted = max (rowStart, hilightStart) < min (realEndOfRow, hilightEnd);
896 if (segmentHilighted) {
897 bool segmentHilightedAtStart = false;
898 bool segmentHilightedAtEnd = false;
899 if (GetUseSelectEOLBOLRowHilightStyle ()) {
900 segmentHilightedAtStart = (hilightStart < rowStart) or (hilightStart == 0);
901 segmentHilightedAtEnd = rowEnd < hilightEnd;
902 if (segmentHilightedAtEnd and rowEnd >= GetEnd ()) {
903 segmentHilightedAtEnd = false; // last row always contains no NL - so no invert off to the right...
904 }
905 }
906
907 hilightStart = max (hilightStart, rowStart);
908
909 using ScriptRunElt = TextLayoutBlock::ScriptRunElt;
910 vector<ScriptRunElt> runs = text.GetScriptRuns ();
911 //NB: it doesn't matter what order we iterate over the blocks and draw their hilight
912 for (auto i = runs.begin (); i != runs.end (); ++i) {
913 const ScriptRunElt& se = *i;
914 size_t hRunStart = max (se.fRealStart + rowStart, hilightStart);
915 size_t hRunEnd = min (se.fRealEnd + rowStart, hilightEnd);
916 if (hRunStart < hRunEnd) {
917 Led_Rect hilightRect = GetIntraRowTextWindowBoundingRect (hRunStart, hRunEnd);
918 Assert (hilightRect.GetWidth () >= 0);
919 Assert (hilightRect.GetHeight () >= 0);
920 if (not hilightRect.IsEmpty ()) { // don't add empty rectangles
921 result.push_back (hilightRect);
922 }
923 }
924 }
925
926 // Add extra rects before or after all the text to the start/end of the row, as needed
927 if (segmentHilightedAtStart) {
928 size_t realOffsetOfVirtualRowStart = rowStart;
929 if (text.GetTextLength () != 0) {
930 realOffsetOfVirtualRowStart += text.MapVirtualOffsetToReal (0);
931 }
932 // Make sure the 'segmentHilightAtStart is drawn even if the selection only comes up to just before first char in row
933 Led_Rect hilightRect = GetCharWindowLocation (realOffsetOfVirtualRowStart);
934 hilightRect.right = hilightRect.left;
935 hilightRect.left = min (GetWindowRect ().GetLeft (), hilightRect.left);
936 Assert (hilightRect.GetWidth () >= 0);
937 Assert (hilightRect.GetHeight () >= 0);
938 if (not hilightRect.IsEmpty ()) { // don't add empty rectangles
939 result.push_back (hilightRect);
940 }
941 }
942 if (segmentHilightedAtEnd) {
943 size_t realOffsetOfVirtualRowEnd = rowStart;
944 if (text.GetTextLength () != 0) {
945 realOffsetOfVirtualRowEnd += text.MapVirtualOffsetToReal (text.GetTextLength () - 1);
946 }
947 // Make sure the 'segmentHilightAtStart is drawn even if the selection only comes up to just before first char in row
948 Led_Rect hilightRect = GetCharWindowLocation (realOffsetOfVirtualRowEnd);
949 hilightRect.left = hilightRect.GetRight ();
950 hilightRect.right = max (hilightRect.right, GetWindowRect ().GetRight ());
951 Assert (hilightRect.GetWidth () >= 0);
952 Assert (hilightRect.GetHeight () >= 0);
953 if (not hilightRect.IsEmpty ()) { // don't add empty rectangles
954 result.push_back (hilightRect);
955 }
956 }
957 }
958
960 // Make sure rectangles don't overlap with one another (can share an edge) -- SPR#1226
961 for (auto orit = result.begin (); orit != result.end (); ++orit) {
962 Ensure ((*orit).GetWidth () > 0);
963 Ensure ((*orit).GetHeight () > 0);
964 for (auto irit = orit + 1; irit != result.end (); ++irit) {
965 Led_Rect hr = *irit;
966 Ensure (hr.GetWidth () > 0);
967 Ensure (hr.GetHeight () > 0);
968 Ensure (not Intersect (hr, *orit));
969 }
970 }
971 }
972
973 return result;
974}
975
976/*
977@METHOD: TextImager::GetTextLayoutBlock
978@ACCESS: public
979@DESCRIPTION: <p>REQUIRE that rowStart and rowEnd are valid rowstart/end values</p>
980*/
981TextLayoutBlock_Copy TextImager::GetTextLayoutBlock (size_t rowStart, size_t rowEnd) const
982{
983 size_t rowLen = rowEnd - rowStart;
984 Memory::StackBuffer<Led_tChar> rowBuf{Memory::eUninitialized, rowLen};
985 CopyOut (rowStart, rowLen, rowBuf.data ());
986 TextLayoutBlock_Basic text{rowBuf.data (), rowBuf.data () + rowLen};
987 return TextLayoutBlock_Copy (text);
988}
989
990/*
991@METHOD: TextImager::GetSelectionWindowRects
992@ACCESS: public
993@DESCRIPTION: <p>Compute the window-relative region (list of rectangles) bounding the
994 given segment of text. This is useful for displaying some sort of text hilight,
995 in addition (or apart from) the standard hilighting of text.</p>
996 <p>This function uses @'TextImager::GetRowHilightRects' to figure out what areas of the
997 screen to hilight. This routine also tries to take into account interline space by
998 extending the hilight from succeeding rows back to the bottom of the preceeding row.</p>
999 <p>See also @'TextImager::GetSelectionWindowRegion'</p>
1000*/
1001vector<Led_Rect> TextImager::GetSelectionWindowRects (size_t from, size_t to) const
1002{
1003 Require (from <= to);
1004
1005 vector<Led_Rect> result;
1006
1007 from = max (from, GetMarkerPositionOfStartOfWindow ());
1008 to = min (to, FindNextCharacter (GetMarkerPositionOfEndOfWindow ()));
1009
1010 if (from >= to) {
1011 return result;
1012 }
1013 Assert (from < to);
1014
1015 size_t topRow = GetRowContainingPosition (from);
1016 size_t bottomRow = GetRowContainingPosition (to);
1017 Assert (topRow <= bottomRow);
1018
1019 // If to is at the start of a row (remember - we wanted the to select char UP-TO that
1020 // MARKER POS) then we've gone one row too far
1021 if (GetStartOfRow (bottomRow) == to) {
1022 // then use end of previous row
1023 Assert (topRow < bottomRow);
1024 --bottomRow;
1025 }
1026
1027 CoordinateType lastRowBottom = 0; // Keep track of last row's bottom for interline-space support
1028 for (size_t curRow = topRow;;) {
1029 size_t firstCharInRow = from;
1030 if (topRow != curRow) {
1031 firstCharInRow = GetStartOfRow (curRow);
1032 }
1033
1034 size_t startOfRow = GetStartOfRowContainingPosition (firstCharInRow);
1035 size_t endOfRow = GetEndOfRowContainingPosition (startOfRow);
1036#if 1
1037 TextLayoutBlock_Copy text = GetTextLayoutBlock (startOfRow, endOfRow);
1038#else
1039 size_t rowLen = endOfRow - startOfRow;
1040 Memory::StackBuffer<Led_tChar> rowBuf{Memory::eUninitialized, rowLen};
1041 CopyOut (startOfRow, rowLen, rowBuf);
1042 TextLayoutBlock_Basic text{rowBuf, rowBuf + rowLen};
1043#endif
1044
1045 vector<Led_Rect> hilightRects = GetRowHilightRects (text, startOfRow, endOfRow, GetSelectionStart (), GetSelectionEnd ());
1046 CoordinateType newMinTop = lastRowBottom;
1047 CoordinateType newMaxBottom = lastRowBottom;
1048 for (auto i = hilightRects.begin (); i != hilightRects.end (); ++i) {
1049 Led_Rect hilightRect = *i;
1050 Require (hilightRect.GetWidth () >= 0);
1051 Assert (hilightRect.GetHeight () > 0);
1052 if (not hilightRect.IsEmpty ()) {
1053 result.push_back (hilightRect);
1054 }
1055 newMinTop = min (newMinTop, hilightRect.top);
1056 newMaxBottom = max (newMaxBottom, hilightRect.bottom);
1057 }
1058
1059 /*
1060 * Now that we've kept that lastRowBottom and this emitted rows minTop and maxBottom, we can
1061 * see if there is any gap between the lastRowBottom and the newMinTop. If yes, then fill
1062 * that gap.
1063 */
1064 if (lastRowBottom < newMinTop) {
1065 // Compute this hScrollAdjustedWR inside loop since it should happen extremely rarely, and doing it
1066 // outside would mean it gets called more (cuz we can only do the test inside).
1067 Led_Rect hScrollAdjustedWR = GetWindowRect () - Led_Point (0, GetHScrollPos ());
1068 result.push_back (Led_Rect (lastRowBottom, hScrollAdjustedWR.GetLeft (), newMinTop - lastRowBottom, hScrollAdjustedWR.GetWidth ()));
1069 }
1070 lastRowBottom = newMaxBottom;
1071
1072 if (curRow == bottomRow) {
1073 break;
1074 }
1075 ++curRow;
1076 }
1077
1079 // Make sure rectangles don't overlap with one another (can share an edge) -- SPR#1226
1080 for (auto orit = result.begin (); orit != result.end (); ++orit) {
1081 Ensure ((*orit).GetWidth () > 0);
1082 Ensure ((*orit).GetHeight () > 0);
1083 for (auto irit = orit + 1; irit != result.end (); ++irit) {
1084 Led_Rect hr = *irit;
1085 Ensure (hr.GetWidth () > 0);
1086 Ensure (hr.GetHeight () > 0);
1087 Ensure (not Intersect (hr, *orit));
1088 }
1089 }
1090 }
1091 return result;
1092}
1093
1094/*
1095@METHOD: TextImager::GetSelectionWindowRegion
1096@ACCESS: public
1097@DESCRIPTION: <p>Figure the region bounding the given segment of text. Useful for displaying
1098 some sort of text hilight, in addition (or apart from) the standard hilighting
1099 of text. Note we use a VAR parameter for the region rather than returing it
1100 cuz MFC's CRgn class doesn't support being copied.</p>
1101 <p>This routine is a simple wrapper on @'TextImager::GetSelectionWindowRects'</p>
1102*/
1103void TextImager::GetSelectionWindowRegion (Region* r, size_t from, size_t to) const
1104{
1105 RequireNotNull (r);
1106 vector<Led_Rect> selRects = GetSelectionWindowRects (from, to);
1107 for (auto i = selRects.begin (); i != selRects.end (); ++i) {
1108 AddRectangleToRegion (*i, r);
1109 }
1110}
1111
1112/*
1113@METHOD: TextImager::EraseBackground
1114@DESCRIPTION: <p>EraseBackground () is called internally by TextImagers to get arbitrary subsets of the current
1115 window being drawn to erased. This erasure must only apply to the argument 'subsetToDraw'. Clipping is
1116 not used to gaurantee this. Failure to follow that rule can produce undesirably results where other bits of
1117 surrounding text get erased.</p>
1118 <p>The erasebackground call is made virtual so that you can use this as a hook to provide some sort
1119 of multi-media, picture or whatever as your background. Note that the is called during the draw
1120 process as a side-effect. So to have a changing background, you would need to both force periodic
1121 updates, and OVERRIDE this routine.</p>
1122 <p>NB: the argument 'subsetToDraw' can occasionally be outside of the WindowRect. If so - then DO draw where this
1123 function says to draw. This is because you maybe asked to erase window margins/borders outside the Led 'WindowRect' using
1124 the same color/algorithm as that used inside the Led WindowRect.</p>
1125 <p>By default - this simply calls @'Tablet::EraseBackground_SolidHelper' with @'Led_GetTextBackgroundColor' ().</p>
1126 <p>Note - typically when you OVERRIDE this - you will want to OVERRIDE @'TextImager::HilightARectangle' to change
1127 its implementation to specify a new background color (so inverting works properly). Perhaps see SPR#0708 for details.</p>
1128*/
1129void TextImager::EraseBackground (Tablet* tablet, const Led_Rect& subsetToDraw, bool printing)
1130{
1131 RequireNotNull (tablet);
1132 // Don't erase when printing - at least by default. Tends to screw up most print drivers.
1133 if (not printing) {
1134 tablet->EraseBackground_SolidHelper (subsetToDraw, GetEffectiveDefaultTextColor (TextImager::eDefaultBackgroundColor));
1135 }
1136}
1137
1138/*
1139@METHOD: TextImager::HilightArea
1140@DESCRIPTION: <p>Hilight the given rectangle of the screen, after its been drawn. This is typically done via
1141 some sort of pixel or color invesion. The default implemtation uses @'Tablet::HilightArea_SolidHelper'.</p>
1142 <p>Override this mostly if you want different hilighting behavior, or if you want your hilighting behavior
1143 to remain in sync with other changes to the EraseBackground behavior.</p>
1144*/
1145void TextImager::HilightArea (Tablet* tablet, Led_Rect hiliteArea)
1146{
1147 RequireNotNull (tablet);
1148 tablet->HilightArea_SolidHelper (hiliteArea, GetEffectiveDefaultTextColor (eDefaultSelectedTextBackgroundColor),
1149 GetEffectiveDefaultTextColor (eDefaultSelectedTextColor),
1150 GetEffectiveDefaultTextColor (eDefaultBackgroundColor), GetEffectiveDefaultTextColor (eDefaultTextColor));
1151}
1152
1153/*
1154@METHOD: TextImager::HilightArea
1155@DESCRIPTION: <p>Hilight the given region of the screen, after its been drawn. This is typically done via
1156 some sort of pixel or color invesion. The default implemtation uses @'Tablet::HilightArea_SolidHelper'.</p>
1157 <p>Override this mostly if you want different hilighting behavior, or if you want your hilighting behavior
1158 to remain in sync with other changes to the EraseBackground behavior.</p>
1159*/
1160void TextImager::HilightArea (Tablet* tablet, const Region& hiliteArea)
1161{
1162 RequireNotNull (tablet);
1163 tablet->HilightArea_SolidHelper (hiliteArea, GetEffectiveDefaultTextColor (eDefaultSelectedTextBackgroundColor),
1164 GetEffectiveDefaultTextColor (eDefaultSelectedTextColor),
1165 GetEffectiveDefaultTextColor (eDefaultBackgroundColor), GetEffectiveDefaultTextColor (eDefaultTextColor));
1166}
1167
1168/*
1169@METHOD: TextImager::DrawRow
1170@DESCRIPTION: <p>Draw the given row of text. Erase the background (by calling @'TextImager::EraseBackground'), and
1171 then draw the segments of the row by calling @'TextImager::DrawRowSegments'. Then draw any hilighting.
1172 This routine CAN cause flicker. Flicker is eliminated at a higher level (caller) by using offscreen bitmaps (see @'MultiRowTextImager::Draw'
1173 or @'TextImager::GetImageUsingOffscreenBitmaps').</p>
1174 <p>NB: This bit about flicker changed in Led 2.2. Earlier Led versions tried to elminate flicker at all levels
1175 and avoid offscreen bitmaps. But problems with kerning made this difficult.</p>
1176 <p>Note, only the invalidRect subset of currentRowRect need be drawn, though the rest CAN be.</p>
1177 <p>Renamed to @'TextImager::DrawRowSegments' from MutliRowTextImager::DrawRowSegments for Led 3.1a3 release.</p>
1178*/
1179void TextImager::DrawRow (Tablet* tablet, const Led_Rect& currentRowRect, const Led_Rect& invalidRowRect, const TextLayoutBlock& text,
1180 size_t rowStart, size_t rowEnd, bool printing)
1181{
1182 RequireNotNull (tablet);
1183 Require (rowEnd == GetEndOfRowContainingPosition (rowStart)); // passed in for performance reasons - so not computed multiple times
1184
1185 /*
1186 * Could CONSIDER doing something like:
1187 *
1188 * Tablet::ClipNarrowAndRestore clipFurtherTo (tablet, currentRowRect);
1189 *
1190 * here, but it might have too much of a performance cost. Perhaps I should test this. See SPR#?????
1191 */
1192
1193 EraseBackground (tablet, currentRowRect, printing);
1194
1195 DrawRowSegments (tablet, currentRowRect, invalidRowRect, text, rowStart, rowEnd);
1196
1197 /*
1198 * Only draw hilighting if we aren't printing, because this doesn't show up well on printers.
1199 */
1200 if (not printing) {
1201 DrawRowHilight (tablet, currentRowRect, invalidRowRect, text, rowStart, rowEnd);
1202 }
1203}
1204
1205/*
1206@METHOD: TextImager::DrawRowSegments
1207@DESCRIPTION: <p>Called by @'TextImager::DrawRow' to draw (in or mode - don't worry about erasing) all the segments
1208 which make up the row of text. Here, we take care of any sort of justification, or indending (in subclasses which OVERRIDE
1209 this method). Its rowRect its called with represents the entire row. Subclasses can call DrawSegment () with adjusted row-rects
1210 taking into account indents, etc.</p>
1211 <p>Note, only the invalidRect subset of currentRowRect need be drawn, though the rest CAN be.</p>
1212 <p>Renamed to @'TextImager::DrawRowSegments' from MutliRowTextImager::DrawRowSegments for Led 3.1a3 release.</p>
1213*/
1214void TextImager::DrawRowSegments (Tablet* tablet, const Led_Rect& currentRowRect, const Led_Rect& invalidRowRect,
1215 const TextLayoutBlock& text, size_t rowStart, size_t rowEnd)
1216{
1217 RequireNotNull (tablet);
1218
1219#if qStroika_Foundation_Debug_AssertionsChecked && 0
1220 // try to get this code enabled again - even here!!! LGP 2002-12-02
1221 {
1222 size_t startOfRow = GetStartOfRow (row);
1223 size_t endOfRow = GetEndOfRow (row);
1224 size_t realEndOfRow = GetRealEndOfRow (row);
1225 Assert (startOfRow == start);
1226 Assert (endOfRow <= end);
1227 Assert (end <= realEndOfRow);
1228 }
1229#endif
1230
1231 /*
1232 * We always want to draw all the characters in the row - including the character
1233 * that terminates the row. Typically - this is a space in a word-wrap so it
1234 * cannot be seen. Or a NEWLINE char. In the case of a NEWLINE char - these
1235 * don't really display properly - so we skip drawing those.
1236 *
1237 * An earlier attempt at this said - DONT BOTHER drawing the wrap character. The problem
1238 * with this is that in SOME languages (e.g. Japanese) the character used as a wrap-char
1239 * may be a real useful (Japanese) character!
1240 */
1241 size_t segEnd = rowEnd;
1242 CoordinateType baseLine = currentRowRect.top + MeasureSegmentBaseLine (rowStart, segEnd);
1243
1244 /*
1245 * Its OK for the baseline to be outside of the currentRowRect. But the text display of this
1246 * seems to mimic MSWord better if you pin the baseLine inside the rowRect.
1247 */
1248 baseLine = min (baseLine, currentRowRect.bottom);
1249
1250 DrawSegment (tablet, rowStart, segEnd, text, currentRowRect, invalidRowRect, baseLine, nullptr);
1251}
1252
1253/*
1254@METHOD: TextImager::DrawRowHilight
1255@DESCRIPTION: <p>Called by @'TextImager::DrawRow' to draw any necessary hilighting for the current selection
1256 for the given row.</p>
1257 <p>Note, only the invalidRect subset of currentRowRect need be drawn, though the rest CAN be.</p>
1258 <p>Renamed/Moved to @'TextImager::DrawRowHilight' from MutliRowTextImager::DrawRowHilight for Led 3.1a3 release.</p>
1259*/
1260void TextImager::DrawRowHilight (Tablet* tablet, [[maybe_unused]] const Led_Rect& currentRowRect, const Led_Rect& /*invalidRowRect*/,
1261 const TextLayoutBlock& text, size_t rowStart, size_t rowEnd)
1262{
1263 Require (rowEnd == GetEndOfRowContainingPosition (rowStart)); // passed in for performance reasons - so not computed multiple times
1264
1265 if (GetSelectionShown ()) {
1266 vector<Led_Rect> hilightRects = GetRowHilightRects (text, rowStart, rowEnd, GetSelectionStart (), GetSelectionEnd ());
1267 for (auto i = hilightRects.begin (); i != hilightRects.end (); ++i) {
1268 Led_Rect hilightRect = *i;
1270 // Funky test - see SPR# 0470 for details...
1271 if (Intersect (hilightRect, currentRowRect) or hilightRect.IsEmpty ()) {
1272 Led_Rect x = hilightRect;
1273 Led_Rect y = currentRowRect;
1274 x.left = y.left;
1275 x.right = y.right;
1276 Assert (Intersect (x, y) or x.IsEmpty ());
1277 }
1278 }
1279 HilightArea (tablet, hilightRect);
1280 }
1281 }
1282}
1283
1284/*
1285@METHOD: TextImager::DrawInterLineSpace
1286@DESCRIPTION: <p>Typically called by @'MultiRowTextImager::DrawPartitionElement' or @'SimpleTextImager::DrawPartitionElement' to draw space between paragraphs (lines).
1287 Typically this is nothing. But this hook can be used to draw various sorts of annotations on
1288 paragraphs (as in LECs LVEJ side-by-side mode).</p>
1289 <p>Renamed to @'TextImager::DrawInterLineSpace' from MutliRowTextImager::DrawInterLineSpace for Led 3.1a3 release.</p>
1290*/
1291void TextImager::DrawInterLineSpace (DistanceType interlineSpace, Tablet* tablet, CoordinateType vPosOfTopOfInterlineSpace,
1292 bool segmentHilighted, bool printing)
1293{
1294 // This code not been checked/tested since I rewrote the erasing code etc.. Maybe wrong - probably wrong? No matter, anybody
1295 // ever using interline space would probably OVERRIDE this anyhow..
1296 // LGP 960516
1297 AssertNotNull (tablet);
1298 if (interlineSpace != 0) {
1299 Led_Rect fillRect = GetWindowRect ();
1300 fillRect.top = vPosOfTopOfInterlineSpace;
1301 fillRect.bottom = vPosOfTopOfInterlineSpace + interlineSpace;
1302 EraseBackground (tablet, fillRect, printing);
1303 if (segmentHilighted) {
1304 HilightArea (tablet, fillRect);
1305 }
1306 }
1307}
1308
1309/*
1310@METHOD: TextImager::ContainsMappedDisplayCharacters
1311@DESCRIPTION: <p>Override this to specify if any of the given characters should be hidden, removed,
1312 or mapped to some other display character.
1313 You can change the behavior of this
1314 function at runtime (depending on user settings). But when its changed - you should invalidate all cached information,
1315 since cached font metrics will we invalid (often with @'MultiRowTextImager::InvalidateAllCaches ()').</p>
1316 <p>Note - this function doesn't REPLACE the given character from the text. Instead -
1317 it merely causes the @'TextImager::ReplaceMappedDisplayCharacters', @'TextImager::RemoveMappedDisplayCharacters',
1318 and @'TextImager::RemoveMappedDisplayCharacters' () methods to get called.</p>
1319 <p>Note also that this function takes an array of characters as a performance optimization (so it doesn't need to
1320 be called too many times, and to avoid copying buffers when nothing need be done). It could have been more logically
1321 (though less efficiently) implemented as bool IsMappedDisplayCharacter (Led_tChar).</p>
1322 <p>If you OVERRIDE this - you may find it handy to call @'TextImager::ContainsMappedDisplayCharacters_HelperForChar'
1323 to do most of the work.</p>
1324*/
1325bool TextImager::ContainsMappedDisplayCharacters (const Led_tChar* /*text*/, size_t /*nTChars*/) const
1326{
1327 return false;
1328}
1329
1330/*
1331@METHOD: TextImager::ReplaceMappedDisplayCharacters
1332@DESCRIPTION: <p>Override this to specify any characters which should be mapped to different values at the last minute for
1333 display purposes only. This can be used to give a (simple to implement) funny display for particular special characters.
1334 For example, this technique can be used to make a NEWLINE character display as a special end-of-paragraph marker.</p>
1335 <p>If you OVERRIDE this - you may find it handy to call @'TextImager::ReplaceMappedDisplayCharacters_HelperForChar'
1336 to do most of the work.</p>
1337 <p>See also @'TextImager::ContainsMappedDisplayCharacters'.</p>
1338*/
1339void TextImager::ReplaceMappedDisplayCharacters (const Led_tChar* srcText, Led_tChar* copyText, size_t nTChars) const
1340{
1341 // Default to none replaced- just plain copy...
1342 (void)::memcpy (copyText, srcText, nTChars * sizeof (Led_tChar));
1343}
1344
1345/*
1346@METHOD: TextImager::RemoveMappedDisplayCharacters
1347@DESCRIPTION: <p>Override this to specify any characters which should be removed at the last minute for
1348 display purposes only. This can be used to give a (simple to implement) way to hide some special characters.
1349 For example, this technique can be used to implement SHIFT-RETURN soft line breaks (as is done in
1350 @'WordWrappedTextImager::RemoveMappedDisplayCharacters').</p>
1351 <p>If you OVERRIDE this - you may find it handy to call @'TextImager::RemoveMappedDisplayCharacters_HelperForChar'
1352 to do most of the work.</p>
1353 <p>See also @'TextImager::ContainsMappedDisplayCharacters'.</p>
1354*/
1355size_t TextImager::RemoveMappedDisplayCharacters (Led_tChar* /*copyText*/, size_t nTChars) const
1356{
1357 // Default to none removed
1358 return nTChars;
1359}
1360
1361/*
1362@METHOD: TextImager::PatchWidthRemoveMappedDisplayCharacters
1363@DESCRIPTION: <p>Override this to specify any characters which should be removed at the last minute for
1364 display purposes only. This particular function patches the distanceResults to zero out the removed characters.
1365 This can be used to give a (simple to implement) way to hide some special characters.</p>
1366 <p>For example, this technique can be used to implement SHIFT-RETURN soft line breaks (as is done in
1367 @'WordWrappedTextImager::RemoveMappedDisplayCharacters').</p>
1368 <p>If you OVERRIDE this - you may find it handy to call @'TextImager::PatchWidthRemoveMappedDisplayCharacters_HelperForChar'
1369 to do most of the work.</p>
1370 <p>See also @'TextImager::ContainsMappedDisplayCharacters'.</p>
1371*/
1372void TextImager::PatchWidthRemoveMappedDisplayCharacters (const Led_tChar* /*srcText*/, DistanceType* /*distanceResults*/, size_t /*nTChars*/) const
1373{
1374}
1375
1376/*
1377@METHOD: TextImager::ContainsMappedDisplayCharacters_HelperForChar
1378@DESCRIPTION: <p>Simple implementation of See also @'TextImager::ContainsMappedDisplayCharacters' which is frequently applicable.
1379 Just specify the special character you are looking for.</p>
1380*/
1381bool TextImager::ContainsMappedDisplayCharacters_HelperForChar (const Led_tChar* text, size_t nTChars, Led_tChar charToMap)
1382{
1383 // 'charToMap' characters can appear anywhere in a segment of text (cuz this gets called to compute widths for an entire paragraph at a time).
1384 const Led_tChar* end = &text[nTChars];
1385 for (const Led_tChar* cur = text; cur < end; cur = Led_NextChar (cur)) {
1386 if (*cur == charToMap) {
1387 return true;
1388 }
1389 }
1390 return false;
1391}
1392
1393/*
1394@METHOD: TextImager::ReplaceMappedDisplayCharacters_HelperForChar
1395@DESCRIPTION: <p>Simple implementation of See also @'TextImager::ReplaceMappedDisplayCharacters_HelperForChar' which is frequently applicable.
1396 Just specify the special character you are looking for, and the one you are mapping to.</p>
1397 <p>See also @'TextImager::ContainsMappedDisplayCharacters'.</p>
1398*/
1399void TextImager::ReplaceMappedDisplayCharacters_HelperForChar (Led_tChar* copyText, size_t nTChars, Led_tChar charToMap, Led_tChar charToMapTo)
1400{
1401 // 'charToMap' characters can appear anywhere in a segment of text (cuz this gets called to compute widths for an entire paragraph at a time).
1402 Led_tChar* end = &copyText[nTChars];
1403 for (Led_tChar* cur = copyText; cur < end; cur = Led_NextChar (cur)) {
1404 if (*cur == charToMap) {
1405 *cur = charToMapTo;
1406 }
1407 }
1408}
1409
1410/*
1411@METHOD: TextImager::RemoveMappedDisplayCharacters_HelperForChar
1412@DESCRIPTION: <p>Simple implementation of See also @'TextImager::RemoveMappedDisplayCharacters' which is frequently applicable.
1413 Just specify the special character you are looking to remove.</p>
1414 <p>See also @'TextImager::ContainsMappedDisplayCharacters'.</p>
1415*/
1416size_t TextImager::RemoveMappedDisplayCharacters_HelperForChar (Led_tChar* copyText, size_t nTChars, Led_tChar charToRemove)
1417{
1418 // Trim out any kSoftLineBreakChar characters
1419 Led_tChar* outPtr = copyText;
1420 Led_tChar* end = copyText + nTChars;
1421 for (const Led_tChar* cur = copyText; cur < end; cur = Led_NextChar (cur)) {
1422 if (*cur != charToRemove) {
1423 *outPtr = *cur;
1424 outPtr = Led_NextChar (outPtr);
1425 }
1426 }
1427 size_t newLen = outPtr - copyText;
1428 Assert (newLen <= nTChars);
1429 return newLen;
1430}
1431
1432/*
1433@METHOD: TextImager::PatchWidthRemoveMappedDisplayCharacters_HelperForChar
1434@DESCRIPTION: <p>Simple implementation of See also @'TextImager::PatchWidthRemoveMappedDisplayCharacters' which is frequently applicable.
1435 Just specify the special character you are looking to remove.</p>
1436 <p>See also @'TextImager::ContainsMappedDisplayCharacters'.</p>
1437*/
1438void TextImager::PatchWidthRemoveMappedDisplayCharacters_HelperForChar (const Led_tChar* srcText, DistanceType* distanceResults,
1439 size_t nTChars, Led_tChar charToRemove)
1440{
1441 // Each of these kSoftLineBreakChar will be mapped to ZERO-WIDTH. So walk text (and distanceResults) and when
1442 // I see a softlinebreak - zero its size, and subtrace from start point total amount of zero-ed softlinebreaks.
1443 DistanceType cumSubtract = 0;
1444 const Led_tChar* end = srcText + nTChars;
1445 for (const Led_tChar* cur = srcText; cur < end; cur = Led_NextChar (cur)) {
1446 size_t i = cur - srcText;
1447 Assert (i < nTChars);
1448 if (*cur == charToRemove) {
1449 DistanceType thisSoftBreakWidth = i == 0 ? distanceResults[0] : (distanceResults[i] - distanceResults[i - 1]);
1450 cumSubtract = thisSoftBreakWidth;
1451 }
1452 distanceResults[i] -= cumSubtract;
1453 }
1454}
1455
1456/*
1457@METHOD: TextImager::DrawSegment
1458@DESCRIPTION: <p>DrawSegment () is responsible for rendering the text within a segment (subset of a row).
1459 Note that because of bidirectional display, the 'from' and 'to' are LOGICAL offsets (what are
1460 used to lookup in @'TextStore'), but they may NOT be the same as the display-order offsets. That is to
1461 say - a character between offset 10-11 could be either to the right or left of one at offset 13-14.
1462 </p>
1463 <p>Note we REQUIRE that useBaseLine be contained within drawInto invalidRect specified the subset of the drawInto
1464 rect which really must be filled in. This can be ignored, or used for logical clipping.</p>
1465 <p>See also @'TextImager::DrawSegment_'. The variable 'pixelsDrawn' is OPTIONAL parameter
1466 (ie filled in if non-nullptr)</p>
1467 <p>See also @'TextImager::DrawSegment_'.</p>
1468*/
1469void TextImager::DrawSegment (Tablet* tablet, size_t from, size_t to, const TextLayoutBlock& text, const Led_Rect& drawInto,
1470 const Led_Rect& /*invalidRect*/, CoordinateType useBaseLine, DistanceType* pixelsDrawn)
1471{
1472 DrawSegment_ (tablet, GetDefaultFont (), from, to, text, drawInto, useBaseLine, pixelsDrawn);
1473}
1474
1475/*
1476@DESCRIPTION: <p>Helper function to access tablet text drawing. This function takes care of setting the background/foreground colors,
1477 and setting up a font object to be used in the Tablet::TabbedTextOut calls (maybe can go away if we do better integrating
1478 font code into Tablet. LedGDI and out of TextImager).
1479 <p>The 'from' marker position must be legit, since it is used to grab the tabstop list.
1480 The 'end' marker position is OK to fake (passing in other text), as it is just used to determine the string length. Note
1481 text in 'text' argument need not match the REAL text in the TextStore buffer.
1482 <p>See also @'TextImager::MeasureSegmentWidth_'.</p>
1483*/
1484void TextImager::DrawSegment_ (Tablet* tablet, const FontSpecification& fontSpec, size_t from, size_t to, const TextLayoutBlock& text,
1485 const Led_Rect& drawInto, CoordinateType useBaseLine, DistanceType* pixelsDrawn) const
1486{
1487 RequireNotNull (tablet);
1488 Assert (from <= to);
1489
1490 /*
1491 * In the presence of multiple markers, you might VERY plaisbly OVERRIDE this method and
1492 * change how the layout of text is done within a segment.
1493 *
1494 * I've thought long and hard about how to automatically combine the drawing effects
1495 * of different markers which overlap, and have come up with noting reasonable. So its
1496 * up to YOU by overriding this method todo what you want.
1497 */
1498 [[maybe_unused]] size_t length = to - from;
1499
1500 /*
1501 * Setup the colors to be drawn.
1502 */
1503 Color foreColor = fontSpec.GetTextColor ();
1504 Color backColor = GetEffectiveDefaultTextColor (eDefaultBackgroundColor);
1505 tablet->SetBackColor (backColor);
1506 tablet->SetForeColor (foreColor);
1507
1508 FontCacheInfoUpdater fontCacheUpdater{this, tablet, fontSpec};
1509
1510 DistanceType ascent = fCachedFontInfo.GetAscent ();
1511 Assert (useBaseLine >= drawInto.top);
1512
1513 //Assert (useBaseLine <= drawInto.bottom); Now allowed... LGP 2000-06-12 - see SPR#0760 - and using EXACT-height of a small height, and use a large font
1514 CoordinateType drawCharTop = useBaseLine - ascent; // our PortableGDI_TabbedTextOut() assumes draw in topLeft
1515 //Require (drawCharTop >= drawInto.top); // Same deal as for useBaseLine - LGP 2000-06-12
1516
1517 if (fontSpec.GetStyle_SubOrSuperScript () == FontSpecification::eSuperscript) {
1518 // See SPR#1523- was 'drawCharTop -= fCachedFontInfo.GetAscent ()'
1519 // Careful to sync this with FontCacheInfoUpdater::CTOR () height adjustment
1520 // get back to (roughly - round down) original ascent. If we did TIMES 2/3 now
1521 // mutliply by reciprocal to get back (again - rounding down so we don't go
1522 // up too high).
1523 drawCharTop = useBaseLine - ascent * 3 / 2;
1524 }
1525 else if (fontSpec.GetStyle_SubOrSuperScript () == FontSpecification::eSubscript) {
1526 drawCharTop += fCachedFontInfo.GetDescent ();
1527 }
1528
1529 using ScriptRunElt = TextLayoutBlock::ScriptRunElt;
1530 vector<ScriptRunElt> runs = text.GetScriptRuns ();
1531 Assert (not runs.empty () or (length == 0));
1532 if (runs.size () > 1) {
1533 // sort by virtual start
1534 sort (runs.begin (), runs.end (), TextLayoutBlock::LessThanVirtualStart ());
1535 }
1536
1537 const Led_tChar* fullVirtualText = text.PeekAtVirtualText ();
1538 Led_Point outputAt = Led_Point (drawCharTop, drawInto.left);
1539 if (pixelsDrawn != nullptr) {
1540 *pixelsDrawn = 0;
1541 }
1542 for (auto i = runs.begin (); i != runs.end (); ++i) {
1543 const ScriptRunElt& se = *i;
1544 size_t runLength = se.fVirtualEnd - se.fVirtualStart;
1545
1546 /*
1547 * Fill in the useVirtualText buffer with the text to draw.
1548 */
1549 Memory::StackBuffer<Led_tChar> useVirtualText{Memory::eUninitialized, runLength};
1550 (void)::memcpy (static_cast<Led_tChar*> (useVirtualText), &fullVirtualText[se.fVirtualStart], runLength * sizeof (Led_tChar));
1551
1552 /*
1553 * Process 'mapped display characters'
1554 */
1555 Led_tChar* drawText = useVirtualText.data ();
1556 size_t drawTextLen = runLength;
1557 Memory::StackBuffer<Led_tChar> mappedDisplayBuf{1};
1558 if (ContainsMappedDisplayCharacters (drawText, drawTextLen)) {
1559 mappedDisplayBuf.GrowToSize (drawTextLen);
1560 ReplaceMappedDisplayCharacters (drawText, mappedDisplayBuf.data (), drawTextLen);
1561 size_t newLength = RemoveMappedDisplayCharacters (mappedDisplayBuf.data (), drawTextLen);
1562 Assert (newLength <= drawTextLen);
1563 drawText = mappedDisplayBuf.data ();
1564 drawTextLen = newLength;
1565 }
1566
1567 /*
1568 * Actually draw the text.
1569 */
1570 DistanceType amountDrawn = 0;
1571 tablet->TabbedTextOut (fCachedFontInfo, drawText, drawTextLen, se.fDirection, outputAt, GetWindowRect ().left,
1572 GetTabStopList (from), &amountDrawn, GetHScrollPos ());
1573 outputAt.h += amountDrawn;
1574 if (pixelsDrawn != nullptr) {
1575 *pixelsDrawn += amountDrawn;
1576 }
1577 }
1578}
1579
1580void TextImager::MeasureSegmentWidth (size_t from, size_t to, const Led_tChar* text, DistanceType* distanceResults) const
1581{
1582 MeasureSegmentWidth_ (GetDefaultFont (), from, to, text, distanceResults);
1583}
1584
1585/*
1586@METHOD: TextImager::MeasureSegmentWidth_
1587@DESCRIPTION: <p>Helper function to access tablet text measurement.</p>
1588 <p>The 'from' and 'to' marker positions are ignored, except to compute the width. There is no
1589 requirement that the 'text' argument refer to the same text as that stored in the TextStore object.
1590 <p>See also @'TextImager::DrawSegment_'.</p>
1591*/
1592void TextImager::MeasureSegmentWidth_ (const FontSpecification& fontSpec, size_t from, size_t to, const Led_tChar* text, DistanceType* distanceResults) const
1593{
1594 Require (to > from);
1595
1596 Tablet_Acquirer tablet (this);
1597
1598 size_t length = to - from;
1599 Assert (length > 0);
1600
1601 FontCacheInfoUpdater fontCacheUpdater{this, tablet, fontSpec};
1602
1603 if (ContainsMappedDisplayCharacters (text, length)) {
1604 Memory::StackBuffer<Led_tChar> buf2{Memory::eUninitialized, length};
1605 ReplaceMappedDisplayCharacters (text, buf2.data (), length);
1606 tablet->MeasureText (fCachedFontInfo, buf2.data (), length, distanceResults);
1607 PatchWidthRemoveMappedDisplayCharacters (buf2.data (), distanceResults, length);
1608 }
1609 else {
1610 tablet->MeasureText (fCachedFontInfo, text, length, distanceResults);
1611 }
1612}
1613
1614DistanceType TextImager::MeasureSegmentHeight (size_t from, size_t to) const
1615{
1616 return (MeasureSegmentHeight_ (GetDefaultFont (), from, to));
1617}
1618
1619DistanceType TextImager::MeasureSegmentHeight_ (const FontSpecification& fontSpec, size_t /*from*/, size_t /*to*/) const
1620{
1621 Tablet_Acquirer tablet (this);
1622 AssertNotNull (static_cast<Tablet*> (tablet));
1623 FontCacheInfoUpdater fontCacheUpdater{this, tablet, fontSpec};
1624 return (fCachedFontInfo.GetLineHeight ());
1625}
1626
1627DistanceType TextImager::MeasureSegmentBaseLine (size_t from, size_t to) const
1628{
1629 return (MeasureSegmentBaseLine_ (GetDefaultFont (), from, to));
1630}
1631
1632DistanceType TextImager::MeasureSegmentBaseLine_ (const FontSpecification& fontSpec, size_t /*from*/, size_t /*to*/) const
1633{
1634 Tablet_Acquirer tablet (this);
1635 AssertNotNull (static_cast<Tablet*> (tablet));
1636 FontCacheInfoUpdater fontCacheUpdater{this, tablet, fontSpec};
1637 return fCachedFontInfo.GetAscent ();
1638}
1639
1640FontMetrics TextImager::GetFontMetricsAt ([[maybe_unused]] size_t charAfterPos) const
1641{
1642 Tablet_Acquirer tablet (this);
1643 AssertNotNull (static_cast<Tablet*> (tablet));
1644
1645 FontSpecification fontSpec = GetDefaultFont ();
1646
1647 FontCacheInfoUpdater fontCacheUpdater{this, tablet, fontSpec};
1648 return fCachedFontInfo;
1649}
1650
1651#endif
#define AssertNotNull(p)
Definition Assertions.h:336
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define RequireNotNull(p)
Definition Assertions.h:350
#define Verify(c)
Definition Assertions.h:422
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual pointer data() noexcept
returns a (possibly const) pointer to the start of the live buffer data. This return value can be inv...
nonvirtual void GrowToSize(size_t nElements)
conditional_t< qStroika_Foundation_Characters_SDKUseswchar_t, wchar_t, char > SDKChar
Definition SDKChar.h:77