@@ -463,91 +463,53 @@ CATCH_RETURN();
463463 return result;
464464}
465465
466- [[nodiscard]] static HRESULT _ReadConsoleOutputWImplHelper (const SCREEN_INFORMATION& context,
467- std::span<CHAR_INFO> targetBuffer,
468- const Microsoft::Console::Types:: Viewport& requestRectangle,
469- Microsoft::Console::Types:: Viewport& readRectangle) noexcept
466+ [[nodiscard]] HRESULT ReadConsoleOutputWImplHelper (const SCREEN_INFORMATION& context,
467+ std::span<CHAR_INFO> targetBuffer,
468+ const Viewport& requestRectangle,
469+ Viewport& readRectangle) noexcept
470470{
471471 try
472472 {
473473 const auto & gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
474- const auto & storageBuffer = context.GetActiveBuffer ().GetTextBuffer ();
475- const auto storageSize = storageBuffer.GetSize ().Dimensions ();
476-
477- const auto targetSize = requestRectangle.Dimensions ();
474+ auto & storageBuffer = context.GetActiveBuffer ();
475+ const auto storageRectangle = storageBuffer.GetBufferSize ();
476+ const auto clippedRectangle = storageRectangle.Clamp (requestRectangle);
478477
479- // If either dimension of the request is too small, return an empty rectangle as read and exit early.
480- if (targetSize.width <= 0 || targetSize.height <= 0 )
478+ if (!clippedRectangle.IsValid ())
481479 {
482480 readRectangle = Viewport::FromDimensions (requestRectangle.Origin (), { 0 , 0 });
483481 return S_OK;
484482 }
485483
486- // The buffer given should be big enough to hold the dimensions of the request.
487- const auto targetArea = targetSize.area <size_t >();
488- RETURN_HR_IF (E_INVALIDARG, targetArea < targetBuffer.size ());
489-
490- // Clip the request rectangle to the size of the storage buffer
491- auto clip = requestRectangle.ToExclusive ();
492- clip.right = std::min (clip.right , storageSize.width );
493- clip.bottom = std::min (clip.bottom , storageSize.height );
494-
495- // Find the target point (where to write the user's buffer)
496- // It will either be 0,0 or offset into the buffer by the inverse of the negative values.
497- til::point targetPoint;
498- targetPoint.x = clip.left < 0 ? -clip.left : 0 ;
499- targetPoint.y = clip.top < 0 ? -clip.top : 0 ;
500-
501- // The clipped rect must be inside the buffer size, so it has a minimum value of 0. (max of itself and 0)
502- clip.left = std::max (clip.left , 0 );
503- clip.top = std::max (clip.top , 0 );
504-
505- // The final "request rectangle" or the area inside the buffer we want to read, is the clipped dimensions.
506- const auto clippedRequestRectangle = Viewport::FromExclusive (clip);
507-
508- // We will start reading the buffer at the point of the top left corner (origin) of the (potentially adjusted) request
509- const auto sourcePoint = clippedRequestRectangle.Origin ();
510-
511- // Get an iterator to the beginning of the return buffer
512- // We might have to seek this forward or skip around if we clipped the request.
513- auto targetIter = targetBuffer.begin ();
514- til::point targetPos;
515- const auto targetLimit = Viewport::FromDimensions (targetPoint, clippedRequestRectangle.Dimensions ());
516-
517- // Get an iterator to the beginning of the request inside the screen buffer
518- // This should walk exactly along every cell of the clipped request.
519- auto sourceIter = storageBuffer.GetCellDataAt (sourcePoint, clippedRequestRectangle);
520-
521- // Walk through every cell of the target, advancing the buffer.
522- // Validate that we always still have a valid iterator to the backing store,
523- // that we always are writing inside the user's buffer (before the end)
524- // and we're always targeting the user's buffer inside its original bounds.
525- while (sourceIter && targetIter < targetBuffer.end ())
484+ const auto bufferStride = gsl::narrow_cast<size_t >(std::max (0 , requestRectangle.Width ()));
485+ const auto width = gsl::narrow_cast<size_t >(clippedRectangle.Width ());
486+ const auto offsetY = clippedRectangle.Top () - requestRectangle.Top ();
487+ const auto offsetX = clippedRectangle.Left () - requestRectangle.Left ();
488+ // We always write the intersection between the valid `storageRectangle` and the given `requestRectangle`.
489+ // This means that if the `requestRectangle` is -3 rows above the top of the buffer, we'll start
490+ // reading from `buffer` at row offset 3, because the first 3 are outside the valid range.
491+ // clippedRectangle.Top/Left() cannot be negative due to the previous Clamp() call.
492+ auto totalOffset = offsetY * bufferStride + offsetX;
493+
494+ if (bufferStride <= 0 || targetBuffer.size () < gsl::narrow_cast<size_t >(clippedRectangle.Height () * bufferStride))
526495 {
527- // If the point we're trying to write is inside the limited buffer write zone...
528- if (targetLimit.IsInBounds (targetPos))
529- {
530- // Copy the data into position...
531- *targetIter = gci.AsCharInfo (*sourceIter);
532- // ... and advance the read iterator.
533- ++sourceIter;
534- }
496+ return E_INVALIDARG;
497+ }
535498
536- // Always advance the write iterator, we might have skipped it due to clipping.
537- ++targetIter;
499+ for (til::CoordType y = clippedRectangle.Top (); y <= clippedRectangle.BottomInclusive (); y++)
500+ {
501+ auto it = storageBuffer.GetCellDataAt ({ clippedRectangle.Left (), y });
538502
539- // Increment the target
540- targetPos.x ++;
541- if (targetPos.x >= targetSize.width )
503+ for (size_t i = 0 ; i < width; i++)
542504 {
543- targetPos. x = 0 ;
544- targetPos. y ++ ;
505+ targetBuffer[totalOffset + i] = gci. AsCharInfo (*it) ;
506+ ++it ;
545507 }
546- }
547508
548- // Reply with the region we read out of the backing buffer (potentially clipped)
549- readRectangle = clippedRequestRectangle;
509+ totalOffset += bufferStride;
510+ }
550511
512+ readRectangle = clippedRectangle;
551513 return S_OK;
552514 }
553515 CATCH_RETURN ();
@@ -566,7 +528,7 @@ CATCH_RETURN();
566528 const auto & gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
567529 const auto codepage = gci.OutputCP ;
568530
569- RETURN_IF_FAILED (_ReadConsoleOutputWImplHelper (context, buffer, sourceRectangle, readRectangle));
531+ RETURN_IF_FAILED (ReadConsoleOutputWImplHelper (context, buffer, sourceRectangle, readRectangle));
570532
571533 LOG_IF_FAILED (_ConvertCellsToAInplace (codepage, buffer, readRectangle));
572534
@@ -585,7 +547,7 @@ CATCH_RETURN();
585547
586548 try
587549 {
588- RETURN_IF_FAILED (_ReadConsoleOutputWImplHelper (context, buffer, sourceRectangle, readRectangle));
550+ RETURN_IF_FAILED (ReadConsoleOutputWImplHelper (context, buffer, sourceRectangle, readRectangle));
589551
590552 if (!context.GetActiveBuffer ().GetCurrentFont ().IsTrueTypeFont ())
591553 {
@@ -599,103 +561,59 @@ CATCH_RETURN();
599561 CATCH_RETURN ();
600562}
601563
602- [[nodiscard]] static HRESULT _WriteConsoleOutputWImplHelper (SCREEN_INFORMATION& context,
603- std::span<CHAR_INFO> buffer,
604- const Viewport& requestRectangle,
605- Viewport& writtenRectangle) noexcept
564+ [[nodiscard]] HRESULT WriteConsoleOutputWImplHelper (SCREEN_INFORMATION& context,
565+ std::span<const CHAR_INFO> buffer,
566+ til::CoordType bufferStride,
567+ const Viewport& requestRectangle,
568+ Viewport& writtenRectangle) noexcept
606569{
607570 try
608571 {
572+ if (bufferStride <= 0 )
573+ {
574+ return E_INVALIDARG;
575+ }
576+
609577 auto & storageBuffer = context.GetActiveBuffer ();
610578 const auto storageRectangle = storageBuffer.GetBufferSize ();
611- const auto storageSize = storageRectangle.Dimensions ( );
579+ const auto clippedRectangle = storageRectangle.Clamp (requestRectangle );
612580
613- const auto sourceSize = requestRectangle.Dimensions ();
614-
615- // If either dimension of the request is too small, return an empty rectangle as the read and exit early.
616- if (sourceSize.width <= 0 || sourceSize.height <= 0 )
581+ if (!clippedRectangle.IsValid ())
617582 {
618583 writtenRectangle = Viewport::FromDimensions (requestRectangle.Origin (), { 0 , 0 });
619584 return S_OK;
620585 }
621586
622- // If the top and left of the destination we're trying to write it outside the buffer,
623- // give the original request rectangle back and exit early OK.
624- if (requestRectangle.Left () >= storageSize.width || requestRectangle.Top () >= storageSize.height )
625- {
626- writtenRectangle = requestRectangle;
627- return S_OK;
628- }
629-
630- // Do clipping according to the legacy patterns.
631- auto writeRegion = requestRectangle.ToInclusive ();
632- til::inclusive_rect sourceRect;
633- if (writeRegion.right > storageSize.width - 1 )
634- {
635- writeRegion.right = storageSize.width - 1 ;
636- }
637- sourceRect.right = writeRegion.right - writeRegion.left ;
638- if (writeRegion.bottom > storageSize.height - 1 )
639- {
640- writeRegion.bottom = storageSize.height - 1 ;
641- }
642- sourceRect.bottom = writeRegion.bottom - writeRegion.top ;
643-
644- if (writeRegion.left < 0 )
645- {
646- sourceRect.left = -writeRegion.left ;
647- writeRegion.left = 0 ;
648- }
649- else
650- {
651- sourceRect.left = 0 ;
652- }
587+ const auto width = clippedRectangle.Width ();
588+ // We always write the intersection between the valid `storageRectangle` and the given `requestRectangle`.
589+ // This means that if the `requestRectangle` is -3 rows above the top of the buffer, we'll start
590+ // reading from `buffer` at row offset 3, because the first 3 are outside the valid range.
591+ // clippedRectangle.Top/Left() cannot be negative due to the previous Clamp() call.
592+ const auto offsetY = clippedRectangle.Top () - requestRectangle.Top ();
593+ const auto offsetX = clippedRectangle.Left () - requestRectangle.Left ();
594+ auto totalOffset = offsetY * bufferStride + offsetX;
653595
654- if (writeRegion.top < 0 )
655- {
656- sourceRect.top = -writeRegion.top ;
657- writeRegion.top = 0 ;
658- }
659- else
660- {
661- sourceRect.top = 0 ;
662- }
663-
664- if (sourceRect.left > sourceRect.right || sourceRect.top > sourceRect.bottom )
596+ if (bufferStride <= 0 || buffer.size () < gsl::narrow_cast<size_t >(clippedRectangle.Height () * bufferStride))
665597 {
666598 return E_INVALIDARG;
667599 }
668600
669- const auto writeRectangle = Viewport::FromInclusive (writeRegion);
670-
671- auto target = writeRectangle.Origin ();
672-
673- // For every row in the request, create a view into the clamped portion of just the one line to write.
674- // This allows us to restrict the width of the call without allocating/copying any memory by just making
675- // a smaller view over the existing big blob of data from the original call.
676- for (; target.y < writeRectangle.BottomExclusive (); target.y ++)
601+ for (til::CoordType y = clippedRectangle.Top (); y <= clippedRectangle.BottomInclusive (); y++)
677602 {
678- // We find the offset into the original buffer by the dimensions of the original request rectangle.
679- const auto rowOffset = (target.y - requestRectangle.Top ()) * requestRectangle.Width ();
680- const auto colOffset = target.x - requestRectangle.Left ();
681- const auto totalOffset = rowOffset + colOffset;
682-
683- // Now we make a subspan starting from that offset for as much of the original request as would fit
684- const auto subspan = buffer.subspan (totalOffset, writeRectangle.Width ());
685-
686- // Convert to a CHAR_INFO view to fit into the iterator
687- const auto charInfos = std::span<const CHAR_INFO>(subspan.data (), subspan.size ());
603+ const auto charInfos = buffer.subspan (totalOffset, width);
604+ const til::point target{ clippedRectangle.Left (), y };
688605
689606 // Make the iterator and write to the target position.
690- OutputCellIterator it (charInfos);
691- storageBuffer.Write (it, target);
607+ storageBuffer.Write (OutputCellIterator (charInfos), target);
608+
609+ totalOffset += bufferStride;
692610 }
693611
694612 // If we've overwritten image content, it needs to be erased.
695- ImageSlice::EraseBlock (storageBuffer.GetTextBuffer (), writeRectangle .ToExclusive ());
613+ ImageSlice::EraseBlock (storageBuffer.GetTextBuffer (), clippedRectangle .ToExclusive ());
696614
697615 // Since we've managed to write part of the request, return the clamped part that we actually used.
698- writtenRectangle = writeRectangle ;
616+ writtenRectangle = clippedRectangle ;
699617
700618 return S_OK;
701619 }
@@ -712,11 +630,12 @@ CATCH_RETURN();
712630
713631 try
714632 {
715- const auto & gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
633+ auto & gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
634+
716635 const auto codepage = gci.OutputCP ;
717636 LOG_IF_FAILED (_ConvertCellsToWInplace (codepage, buffer, requestRectangle));
718637
719- RETURN_IF_FAILED (_WriteConsoleOutputWImplHelper (context, buffer, requestRectangle, writtenRectangle));
638+ RETURN_IF_FAILED (WriteConsoleOutputWImplHelper (context, buffer, requestRectangle. Width () , requestRectangle, writtenRectangle));
720639
721640 return S_OK;
722641 }
@@ -738,11 +657,11 @@ CATCH_RETURN();
738657 // For compatibility reasons, we must maintain the behavior that munges the data if we are writing while a raster font is enabled.
739658 // This can be removed when raster font support is removed.
740659 auto translated = _ConvertCellsToMungedW (buffer, requestRectangle);
741- RETURN_IF_FAILED (_WriteConsoleOutputWImplHelper (context, translated, requestRectangle, writtenRectangle));
660+ RETURN_IF_FAILED (WriteConsoleOutputWImplHelper (context, translated, requestRectangle. Width () , requestRectangle, writtenRectangle));
742661 }
743662 else
744663 {
745- RETURN_IF_FAILED (_WriteConsoleOutputWImplHelper (context, buffer, requestRectangle, writtenRectangle));
664+ RETURN_IF_FAILED (WriteConsoleOutputWImplHelper (context, buffer, requestRectangle. Width () , requestRectangle, writtenRectangle));
746665 }
747666
748667 return S_OK;
0 commit comments