@@ -235,6 +235,12 @@ CATCH_LOG_RETURN_HR(E_FAIL);
235235// - S_OK, else an appropriate HRESULT for failing to allocate or write.
236236[[nodiscard]] HRESULT UiaEngine::EndPaint () noexcept
237237{
238+ // Snap this now while we're still under lock
239+ // so present can work on the copy while another
240+ // thread might start filling the next "frame"
241+ // worth of text data.
242+ _queuedOutput = _newOutput;
243+ _newOutput.clear ();
238244 return S_OK;
239245}
240246
@@ -283,11 +289,21 @@ void UiaEngine::WaitUntilCanRender() noexcept
283289 }
284290 CATCH_LOG ();
285291 }
286- if (!_newOutput .empty ())
292+ if (!_queuedOutput .empty ())
287293 {
288294 try
289295 {
290- _dispatcher->NotifyNewOutput (_newOutput);
296+ // The speech API (SAPI) is limited to 1000 characters at a time.
297+ // Break up the output into 1000 character chunks to ensure
298+ // the output isn't cut off by SAPI.
299+ static const size_t sapiLimit{ 1000 };
300+ while (_queuedOutput.size () > sapiLimit)
301+ {
302+ const auto croppedText{ _queuedOutput.substr (0 , sapiLimit) };
303+ _dispatcher->NotifyNewOutput (croppedText);
304+ _queuedOutput = _queuedOutput.substr (sapiLimit);
305+ }
306+ _dispatcher->NotifyNewOutput (_queuedOutput);
291307 }
292308 CATCH_LOG ();
293309 }
@@ -296,7 +312,7 @@ void UiaEngine::WaitUntilCanRender() noexcept
296312 _textBufferChanged = false ;
297313 _cursorChanged = false ;
298314 _isPainting = false ;
299- _newOutput .clear ();
315+ _queuedOutput .clear ();
300316
301317 return S_OK;
302318}
0 commit comments