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