Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
StyledTextImager.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
7
8#include "StyledTextImager.h"
9
10using namespace Stroika::Foundation;
11using namespace Stroika::Frameworks;
12using namespace Stroika::Frameworks::Led;
13
14/*
15 ********************************************************************************
16 ********************************** StyleMarker *********************************
17 ********************************************************************************
18 */
19/*
20@METHOD: StyledTextImager::StyleMarker::GetPriority
21@DESCRIPTION: <p>Since we can have style markers overlap, we need someway to deal with conflicting
22 style information. Since some style elements can be arbitrary drawing code, like
23 an OpenDoc part, or a picture, we cannot genericly write code to merge styles.
24 So we invoke a somewhat hackish priority scheme, where the marker with the highest
25 priority is what is asked todo the drawing.</p>
26 <p>The priority of StandardStyleMarker is eBaselinePriority - ZERO - as a reference. So you can specify
27 easily either markers that take precedence over, or are always superseded by the
28 standard style markers. And this returns eBaselinePriority - ZERO - by default.</p>
29 */
30int StyleMarker::GetPriority () const
31{
32 return eBaselinePriority;
33}
34
35/*
36 ********************************************************************************
37 **************************** StyleMarkerSummarySink ****************************
38 ********************************************************************************
39 */
40StyleMarkerSummarySink::StyleMarkerSummarySink (size_t from, size_t to)
41 : fBuckets{}
42 , fText{nullptr}
43 , fFrom{from}
44 , fTo{to}
45{
46 // See SPR#1293 - may want to get rid of this eventually
47 Require (from <= to);
48 if (from != to) {
49 fBuckets.push_back (StyleRunElement{nullptr, to - from});
50 }
51}
52
53StyleMarkerSummarySink::StyleMarkerSummarySink (size_t from, size_t to, const TextLayoutBlock& text)
54 : inherited ()
55 , fBuckets ()
56 , fText (&text)
57 , fFrom (from)
58 , fTo (to)
59{
60 Require (from <= to);
61 if (from != to) {
62 fBuckets.push_back (StyleRunElement{nullptr, to - from});
63 }
64 using ScriptRunElt = TextLayoutBlock::ScriptRunElt;
65 vector<ScriptRunElt> scriptRuns = text.GetScriptRuns ();
66 for (auto i = scriptRuns.begin (); i != scriptRuns.end (); ++i) {
67 Assert ((*i).fRealEnd <= (to - from));
68 SplitIfNeededAt (from + (*i).fRealEnd);
69 }
70}
71
72void StyleMarkerSummarySink::Append (Marker* m)
73{
75 StyleMarker* styleMarker = dynamic_cast<StyleMarker*> (m);
76 if (styleMarker != nullptr) {
77 size_t start = max (styleMarker->GetStart (), fFrom);
78 size_t end = min (styleMarker->GetEnd (), fTo);
79
80 /*
81 * Assure the marker were adding will fit neatly.
82 */
83 SplitIfNeededAt (start);
84 SplitIfNeededAt (end);
85
86 /*
87 * Now walk the buckets and fit the new marker into each bucket as appropriate.
88 */
89 size_t upTo = fFrom;
90 for (auto i = fBuckets.begin (); i != fBuckets.end (); ++i) {
91 if (start <= upTo and upTo + (*i).fLength <= end) {
92 CombineElements (Traversal::Iterator2Pointer (i), styleMarker);
93 }
94 upTo += (*i).fLength;
95 }
96 }
97}
98
99/*
100@METHOD: StyledTextImager::StyleMarkerSummarySink::SplitIfNeededAt
101@DESCRIPTION: <p>Private routine to split the current list of buckets at a particular position. Called with
102 the endpoints of a new marker.</p>
103 <p>Note that this routine keeps the buckets in order sorted by their REAL OFFSET (not visual display)
104 index. We don't even keep track of the index explicitly in the buckets: we compute it based on the fLength
105 field in the buckets and their offset from the summary sink start ('this->fFrom').</p>
106*/
107void StyleMarkerSummarySink::SplitIfNeededAt (size_t markerPos)
108{
109 Require (markerPos >= fFrom);
110 Require (markerPos <= fTo);
111 size_t upTo = fFrom;
112 for (auto i = fBuckets.begin (); i != fBuckets.end (); ++i) {
113 size_t eltStart = upTo;
114 size_t eltEnd = upTo + (*i).fLength;
115 if (markerPos >= eltStart and markerPos <= eltEnd and markerPos != eltStart and markerPos != eltEnd) {
116#if qStroika_Foundation_Debug_AssertionsChecked
117 size_t oldLength = (*i).fLength;
118#endif
119 // then we need a split at that position.
120 StyleRunElement newElt = *i;
121 (*i).fLength = markerPos - eltStart;
122 newElt.fLength = eltEnd - markerPos;
123#if qStroika_Foundation_Debug_AssertionsChecked
124 Assert (oldLength == (*i).fLength + newElt.fLength);
125#endif
126 Assert ((*i).fLength != 0);
127 Assert (newElt.fLength != 0);
128 fBuckets.insert (i + 1, newElt);
129 break;
130 }
131 upTo += (*i).fLength;
132 }
133}
134
135/*
136@METHOD: StyledTextImager::StyleMarkerSummarySink::CombineElements
137@DESCRIPTION: <p>When two style markers overlap, which one gets todo the drawing? As part of the summarizing
138 process (@'StyledTextImager::SummarizeStyleMarkers'), we must chose how to combine markers.</p>
139 <p>This default algorithm simply chooses the one with the higher priority.</p>
140 <p>Subclassers can OVERRIDE this behavior, and, for instance, restrict paying attention to only a particular
141 subtype of '@StyleMarker's, or maybe to set particular values into one (chosen subtype marker to connote the overlap, and
142 allow this to effect the draw. Or, perhaps, a subclass might ingnore markers with a particular owner value.</p>
143 <p>Note that markers NOT used can be placed in the @'StyleRunElement's 'fSupercededMarkers' array,
144 so that the eventual marker which does the drawing <em>can</em> delegate or combine the drawing behaviors of
145 different kinds of markers.</p>
146 <p>Note also that this routine will somewhat randomly deal with ties. The first element of a given
147 priority wins. But - because of how this is typcially called - as the result of a collection of markers
148 from a TextStore - that results in random choices. That can cause trouble - so try to avoid ties
149 without GOOD motivation.</p>
150*/
151void StyleMarkerSummarySink::CombineElements (StyleRunElement* runElement, StyleMarker* newStyleMarker)
152{
153 RequireNotNull (runElement);
154 RequireNotNull (newStyleMarker);
155
156 if (runElement->fMarker == nullptr) {
157 runElement->fMarker = newStyleMarker;
158 }
159 else {
160 bool newEltStronger = runElement->fMarker->GetPriority () < newStyleMarker->GetPriority ();
161#if qStroika_Frameworks_Led_AssertWarningForEqualPriorityMarkers
162 Assert (runElement->fMarker->GetPriority () != newStyleMarker->GetPriority ());
163#endif
164 if (newEltStronger) {
165 runElement->fSupercededMarkers.push_back (runElement->fMarker);
166 runElement->fMarker = newStyleMarker;
167 }
168 else {
169 runElement->fSupercededMarkers.push_back (newStyleMarker);
170 }
171 }
172}
173
174/*
175@METHOD: StyledTextImager::StyleMarkerSummarySink::ProduceOutputSummary
176@DESCRIPTION: <p>Create a vector of @'StyleRunElement's. Each of these contains a list of
177 marker objects for the region and a length field. The elements are returned in VIRTUAL (LTR display)
178 order - NOT logical (internal memory buffer) order. The elements are guarantied not to cross
179 any directional boundaries (as returned from the @'TextLayoutBlock::GetScriptRuns' API)</p>
180*/
181vector<StyleRunElement> StyleMarkerSummarySink::ProduceOutputSummary () const
182{
183 using ScriptRunElt = TextLayoutBlock::ScriptRunElt;
184 // Soon fix to use fText as a REFERENCE. Then we probably should have this code assure its re-ordering is done only once and then cached,
185 // LGP 2002-12-16
186 if (fText != nullptr) {
187 vector<StyleRunElement> runElements;
188 vector<ScriptRunElt> scriptRuns = fText->GetScriptRuns ();
189 if (scriptRuns.size () > 1) {
190 // sort by virtual start
191 sort (scriptRuns.begin (), scriptRuns.end (), TextLayoutBlock::LessThanVirtualStart{});
192 }
193 for (auto i = scriptRuns.begin (); i != scriptRuns.end (); ++i) {
194 // Grab all StyleRunElement elements from this run and copy them out
195 const ScriptRunElt& se = *i;
196 size_t styleRunStart = 0;
197 size_t runEltsBucketStart = runElements.size ();
198 for (auto j = fBuckets.begin (); j != fBuckets.end (); ++j) {
199 size_t styleRunEnd = styleRunStart + (*j).fLength;
200 if (se.fRealStart <= styleRunStart and styleRunEnd <= se.fRealEnd) {
201 if (se.fDirection == eLeftToRight) {
202 runElements.push_back (*j);
203 }
204 else {
205 runElements.insert (runElements.begin () + runEltsBucketStart, *j);
206 }
207 }
208 styleRunStart = styleRunEnd;
209 }
210 }
211
212 Ensure (runElements.size () == fBuckets.size ());
213 return runElements;
214 }
215 return fBuckets;
216}
217
218/*
219 ********************************************************************************
220 ******************** StyleMarkerSummarySinkForSingleOwner **********************
221 ********************************************************************************
222 */
223StyleMarkerSummarySinkForSingleOwner::StyleMarkerSummarySinkForSingleOwner (const MarkerOwner& owner, size_t from, size_t to)
224 : inherited{from, to}
225 , fOwner{owner}
226{
227}
228
229StyleMarkerSummarySinkForSingleOwner::StyleMarkerSummarySinkForSingleOwner (const MarkerOwner& owner, size_t from, size_t to, const TextLayoutBlock& text)
230 : inherited{from, to, text}
231 , fOwner{owner}
232{
233}
234
235/*
236@METHOD: StyledTextImager::StyleMarkerSummarySinkForSingleOwner::CombineElements
237@DESCRIPTION: <p>Like @'StyledTextImager::StyleMarkerSummarySink::CombineElements', except that matching
238 the MarkerOwner is more important than the Marker Priority.</p>
239*/
240void StyleMarkerSummarySinkForSingleOwner::CombineElements (StyleRunElement* runElement, StyleMarker* newStyleMarker)
241{
242 RequireNotNull (runElement);
243 RequireNotNull (newStyleMarker);
244
245 if (runElement->fMarker == nullptr) {
246 runElement->fMarker = newStyleMarker;
247 }
248 else {
249 bool newEltStronger = runElement->fMarker->GetPriority () < newStyleMarker->GetPriority ();
250 bool newMatchesOwner = newStyleMarker->GetOwner () == &fOwner;
251 bool oldMatchesOwner = runElement->fMarker->GetOwner () == &fOwner;
252 if (newMatchesOwner != oldMatchesOwner) {
253 newEltStronger = newMatchesOwner;
254 }
255 if (newEltStronger) {
256 runElement->fSupercededMarkers.push_back (runElement->fMarker);
257 runElement->fMarker = newStyleMarker;
258 }
259 else {
260 runElement->fSupercededMarkers.push_back (newStyleMarker);
261 }
262 }
263}
264
265/*
266 ********************************************************************************
267 ************************** TrivialFontSpecStyleMarker **************************
268 ********************************************************************************
269 */
270int TrivialFontSpecStyleMarker::GetPriority () const
271{
272 return eBaselinePriority + 1;
273}
274
275#if qStroika_Frameworks_Led_SupportGDI
276
277/*
278 ********************************************************************************
279 ****************************** StyledTextImager ********************************
280 ********************************************************************************
281 */
282/*
283@METHOD: StyledTextImager::SummarizeStyleMarkers
284@DESCRIPTION: <p>Create a summary of the style markers applied to a given range of text (by default using
285 @'StyledTextImager::StyleMarkerSummarySink') into @'StyleRunElement's.</p>
286*/
287vector<StyleRunElement> StyledTextImager::SummarizeStyleMarkers (size_t from, size_t to) const
288{
289 // See SPR#1293 - may want to get rid of this eventually
290 StyleMarkerSummarySink summary (from, to);
291 GetTextStore ().CollectAllMarkersInRangeInto (from, to, TextStore::kAnyMarkerOwner, summary);
292 return summary.ProduceOutputSummary ();
293}
294
295/*
296@METHOD: StyledTextImager::SummarizeStyleMarkers
297@DESCRIPTION: <p>Create a summary of the style markers applied to a given range of text (by default using
298 @'StyledTextImager::StyleMarkerSummarySink') into @'StyleRunElement's.</p>
299*/
300vector<StyleRunElement> StyledTextImager::SummarizeStyleMarkers (size_t from, size_t to, const TextLayoutBlock& text) const
301{
302 StyleMarkerSummarySink summary (from, to, text);
303 GetTextStore ().CollectAllMarkersInRangeInto (from, to, TextStore::kAnyMarkerOwner, summary);
304 return summary.ProduceOutputSummary ();
305}
306
307/*
308@METHOD: StyledTextImager::DrawSegment
309@DESCRIPTION: <p>Override @'StyledTextImager::DrawSegment' to break the given segment into subsets based on
310 what @'StyledTextImager::StyleMarker' are present in the text. This breakup is done by
311 @'StyledTextImager::SummarizeStyleMarkers'.</p>
312*/
313void StyledTextImager::DrawSegment (Tablet* tablet, size_t from, size_t to, const TextLayoutBlock& text, const Led_Rect& drawInto,
314 const Led_Rect& invalidRect, CoordinateType useBaseLine, DistanceType* pixelsDrawn)
315{
316 /*
317 * Note that SummarizeStyleMarkers assures 'outputSummary' comes out in VIRTUAL order.
318 * Must display text in LTR virtual display order.
319 */
320 vector<StyleRunElement> outputSummary = SummarizeStyleMarkers (from, to, text);
321
322 Led_Rect tmpDrawInto = drawInto;
323 size_t outputSummaryLength = outputSummary.size ();
324 size_t indexIntoText_VISUAL = 0;
325 if (pixelsDrawn != nullptr) {
326 *pixelsDrawn = 0;
327 }
328
329 for (size_t i = 0; i < outputSummaryLength; ++i) {
330 const StyleRunElement& re = outputSummary[i];
331 size_t reLength = re.fLength;
332 size_t reFrom = text.MapVirtualOffsetToReal (indexIntoText_VISUAL) + from; // IN LOGICAL OFFSETS!!!
333 size_t reTo = reFrom + reLength; // ""
334 Assert (indexIntoText_VISUAL <= to - from);
335 Assert (reLength > 0);
336 /*
337 * Do logical clipping across segments.
338 *
339 * We could be more aggressive, and do logical clipping WITHIN segments, but that would have
340 * little potential benefit. The most important cases are embeddings which are one-char
341 * long and all-or-nothing draws. Also, todo so would impose greater complexity in
342 * dealing with the following:
343 *
344 * We cannot even totally do logical clipping at the segment boundaries. This is because
345 * we allow a character to draw into an adjacent character cell (but we only allow it to
346 * overwrite the ONE ADJACENT CELL). This happens, for example, with italics, and ligatures,
347 * etc.
348 *
349 * So in our logical clipping, we must not clip out segments which are only outside the
350 * logical clipping rect by a single character.
351 *
352 *
353 * NB: Doing this RIGHT will be easy if we have access to the whole measured-text. But for now we don't.
354 * So simply (as a temp hack) use some fixed number of pixels to optimize by.
355 *
356 */
357 const CoordinateType kSluffToLeaveRoomForOverhangs = 20; // cannot imagine more pixel overhang than this,
358 // and its a tmp hack anyhow - LGP 960516
359 if (tmpDrawInto.left - GetHScrollPos () < invalidRect.right + kSluffToLeaveRoomForOverhangs) {
360 DistanceType pixelsDrawnHere = 0;
361
362 /*
363 * This is a BIT of a kludge. No time to throughly clean this up right now. This is vaguely
364 * related to SPR#1210 and SPR#1108.
365 *
366 * All our code REALLY pays attention to (for the most part) is the LHS of the rectangle. But occasionally
367 * we pay attention to the RHS. We have funky sloppy semantics with respect to the GetHScrollPos and this rectangle.
368 * The rectange - I believe - is supposed to be in WINDOW coordinates. But - its implicitly offset
369 * for drawing purposes by the GetHScrollPos (SOMEWHAT - not totally because of tabstops).
370 *
371 * Anyhow - some code wants it one way - and other another way. CLEAN THIS UP in the future - probably when
372 * I support scaling - probably be RE-STRUCTRURING my coordinate systems (all floats? and inches or TWIPS?).
373 *
374 */
375 tmpDrawInto.right = drawInto.right + GetHScrollPos ();
376 if (tmpDrawInto.left < tmpDrawInto.right) {
377 if (re.fMarker == nullptr) {
378 DrawSegment_ (tablet, GetDefaultFont (), reFrom, reTo,
379 TextLayoutBlock_VirtualSubset (text, indexIntoText_VISUAL, indexIntoText_VISUAL + reLength), tmpDrawInto,
380 useBaseLine, &pixelsDrawnHere);
381 }
382 else {
383 re.fMarker->DrawSegment (this, re, tablet, reFrom, reTo,
384 TextLayoutBlock_VirtualSubset (text, indexIntoText_VISUAL, indexIntoText_VISUAL + reLength),
385 tmpDrawInto, invalidRect, useBaseLine, &pixelsDrawnHere);
386 }
387 }
388 if (pixelsDrawn != nullptr) {
389 *pixelsDrawn += pixelsDrawnHere;
390 }
391 tmpDrawInto.left += pixelsDrawnHere;
392 }
393 indexIntoText_VISUAL += reLength;
394 }
395}
396
397void StyledTextImager::MeasureSegmentWidth (size_t from, size_t to, const Led_tChar* text, DistanceType* distanceResults) const
398{
399 // See SPR#1293 - may want to pass in TextLayoutBlock here - instead of just plain text...
400 vector<StyleRunElement> outputSummary = SummarizeStyleMarkers (from, to);
401
402 size_t outputSummaryLength = outputSummary.size ();
403 size_t indexIntoText = 0;
404 for (size_t i = 0; i < outputSummaryLength; ++i) {
405 const StyleRunElement& re = outputSummary[i];
406 size_t reFrom = indexIntoText + from;
407 size_t reLength = re.fLength;
408 size_t reTo = reFrom + reLength;
409 Assert (indexIntoText <= to - from);
410 if (re.fMarker == nullptr) {
411 MeasureSegmentWidth_ (GetDefaultFont (), reFrom, reTo, &text[indexIntoText], &distanceResults[indexIntoText]);
412 }
413 else {
414 re.fMarker->MeasureSegmentWidth (this, re, reFrom, reTo, &text[indexIntoText], &distanceResults[indexIntoText]);
415 }
416 if (indexIntoText != 0) {
417 DistanceType addX = distanceResults[indexIntoText - 1];
418 for (size_t j = 0; j < reLength; ++j) {
419 distanceResults[indexIntoText + j] += addX;
420 }
421 }
422 indexIntoText += reLength;
423 }
424}
425
426DistanceType StyledTextImager::MeasureSegmentHeight (size_t from, size_t to) const
427{
428 // See SPR#1293 - may want to pass in TextLayoutBlock here ... and then pass that to SummarizeStyleMarkers ()
429 Require (from <= to);
430 if (from == to) { // HACK/TMP? SO WE GET AT LEAST ONE SUMMARY RECORD?? LGP 951018
431 to = from + 1;
432 }
433
434 vector<StyleRunElement> outputSummary = SummarizeStyleMarkers (from, to);
435
436 /*
437 * This is somewhat subtle.
438 *
439 * If we have a mixture of pictures and text on the same line, we want to
440 * have the pictures resting on the baseline (aligned along the bottom edge
441 * even if the picts have different height).
442 *
443 * We also want decenders (like the bottom of a g) to go BELOW the picture.
444 *
445 * This behavior isn't anything I dreamed up. And its not what I implemented
446 * originally. I've copied the behavior of other editors. So presumably
447 * somebody put some thought into the reasons for this. They are lost
448 * on me :-) -- LGP 960314
449 */
450 size_t outputSummaryLength = outputSummary.size ();
451 Assert (outputSummaryLength != 0);
452 DistanceType maxHeightAbove = 0;
453 DistanceType maxHeightBelow = 0;
454 size_t indexIntoText = 0;
455 for (size_t i = 0; i < outputSummaryLength; ++i) {
456 const StyleRunElement& re = outputSummary[i];
457 size_t reFrom = indexIntoText + from;
458 size_t reLength = re.fLength;
459 size_t reTo = reFrom + reLength;
460 Assert (indexIntoText <= to - from);
461 DistanceType itsBaseline;
462 DistanceType itsHeight;
463 if (re.fMarker == nullptr) {
464 itsBaseline = MeasureSegmentBaseLine_ (GetDefaultFont (), reFrom, reTo);
465 itsHeight = MeasureSegmentHeight_ (GetDefaultFont (), reFrom, reTo);
466 }
467 else {
468 itsBaseline = re.fMarker->MeasureSegmentBaseLine (this, re, reFrom, reTo);
469 itsHeight = re.fMarker->MeasureSegmentHeight (this, re, reFrom, reTo);
470 }
471 maxHeightAbove = max (maxHeightAbove, itsBaseline);
472 maxHeightBelow = max (maxHeightBelow, (itsHeight - itsBaseline));
473 indexIntoText += reLength;
474 }
475 return maxHeightAbove + maxHeightBelow;
476}
477
478DistanceType StyledTextImager::MeasureSegmentBaseLine (size_t from, size_t to) const
479{
480 // See SPR#1293 - may want to pass in TextLayoutBlock here ... and then pass that to SummarizeStyleMarkers ()
481 Require (from <= to);
482 if (from == to) { // HACK/TMP? SO WE GET AT LEAST ONE SUMMARY RECORD?? LGP 951018
483 to = from + 1;
484 }
485
486 vector<StyleRunElement> outputSummary = SummarizeStyleMarkers (from, to);
487 size_t outputSummaryLength = outputSummary.size ();
488 Assert (outputSummaryLength != 0);
489 DistanceType maxHeight = 0;
490 size_t indexIntoText = 0;
491 for (size_t i = 0; i < outputSummaryLength; ++i) {
492 const StyleRunElement& re = outputSummary[i];
493 size_t reFrom = indexIntoText + from;
494 size_t reLength = re.fLength;
495 size_t reTo = reFrom + reLength;
496 Assert (indexIntoText <= to - from);
497 if (re.fMarker == nullptr) {
498 maxHeight = max (maxHeight, MeasureSegmentBaseLine_ (GetDefaultFont (), reFrom, reTo));
499 }
500 else {
501 maxHeight = max (maxHeight, re.fMarker->MeasureSegmentBaseLine (this, re, reFrom, reTo));
502 }
503 indexIntoText += reLength;
504 }
505 return maxHeight;
506}
507
508#if qStroika_Foundation_Debug_AssertionsChecked
509void StyledTextImager::Invariant_ () const
510{
511}
512#endif
513
514#endif
#define RequireNotNull(p)
Definition Assertions.h:347