[MMIXER] Fix additional data size initialization for different audio formats (#6753)
[reactos.git] / sdk / lib / conutils / pager.c
1 /*
2 * PROJECT: ReactOS Console Utilities Library
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Console/terminal paging functionality.
5 * COPYRIGHT: Copyright 2017-2021 Hermes Belusca-Maito
6 * Copyright 2021 Katayama Hirofumi MZ
7 */
8
9 /**
10 * @file pager.c
11 * @ingroup ConUtils
12 *
13 * @brief Console/terminal paging functionality.
14 **/
15
16 /* FIXME: Temporary HACK before we cleanly support UNICODE functions */
17 #define UNICODE
18 #define _UNICODE
19
20 #include <windef.h>
21 #include <winbase.h>
22 // #include <winnls.h>
23 #include <wincon.h> // Console APIs (only if kernel32 support included)
24 #include <winnls.h> // for WideCharToMultiByte
25 #include <strsafe.h>
26
27 #include "conutils.h"
28 #include "stream.h"
29 #include "screen.h"
30 #include "pager.h"
31
32 // Temporary HACK
33 #define CON_STREAM_WRITE ConStreamWrite
34
35 #define CP_SHIFTJIS 932 // Japanese Shift-JIS
36 #define CP_HANGUL 949 // Korean Hangul/Wansung
37 #define CP_JOHAB 1361 // Korean Johab
38 #define CP_GB2312 936 // Chinese Simplified (GB2312)
39 #define CP_BIG5 950 // Chinese Traditional (Big5)
40
41 /* IsFarEastCP(CodePage) */
42 #define IsCJKCodePage(CodePage) \
43 ((CodePage) == CP_SHIFTJIS || (CodePage) == CP_HANGUL || \
44 /* (CodePage) == CP_JOHAB || */ \
45 (CodePage) == CP_BIG5 || (CodePage) == CP_GB2312)
46
47 static inline INT
48 GetWidthOfCharCJK(
49 IN UINT nCodePage,
50 IN WCHAR ch)
51 {
52 INT ret = WideCharToMultiByte(nCodePage, 0, &ch, 1, NULL, 0, NULL, NULL);
53 if (ret == 0)
54 ret = 1;
55 else if (ret > 2)
56 ret = 2;
57 return ret;
58 }
59
60 /**
61 * @brief Retrieves a new text line, or continue fetching the current one.
62 *
63 * @remark Manages setting Pager's CurrentLine, ichCurr, iEndLine, and the
64 * line cache (CachedLine, cchCachedLine). Other functions must not
65 * modify these values.
66 **/
67 static BOOL
68 GetNextLine(
69 IN OUT PCON_PAGER Pager,
70 IN PCTCH TextBuff,
71 IN SIZE_T cch)
72 {
73 SIZE_T ich = Pager->ich;
74 SIZE_T ichStart;
75 SIZE_T cchLine;
76 BOOL bCacheLine;
77
78 Pager->ichCurr = 0;
79 Pager->iEndLine = 0;
80
81 /*
82 * If we already had an existing line, then we can safely start a new one
83 * and getting rid of any current cached line. Otherwise, we don't have
84 * a current line and we may be caching a new one, in which case, continue
85 * caching it until it becomes complete.
86 */
87 // INVESTIGATE: Do that only if (ichStart >= iEndLine) ??
88 if (Pager->CurrentLine)
89 {
90 // ASSERT(Pager->CurrentLine == Pager->CachedLine);
91 if (Pager->CachedLine)
92 {
93 HeapFree(GetProcessHeap(), 0, (PVOID)Pager->CachedLine);
94 Pager->CachedLine = NULL;
95 Pager->cchCachedLine = 0;
96 }
97
98 Pager->CurrentLine = NULL;
99 }
100
101 /* Nothing else to read if we are past the end of the buffer */
102 if (ich >= cch)
103 {
104 /* If we have a pending cached line, terminate it now */
105 if (Pager->CachedLine)
106 goto TerminateLine;
107
108 /* Otherwise, bail out */
109 return FALSE;
110 }
111
112 /* Start a new line, or continue an existing one */
113 ichStart = ich;
114
115 /* Find where this line ends, looking for a NEWLINE character.
116 * (NOTE: We cannot use strchr because the buffer is not NULL-terminated) */
117 for (; ich < cch; ++ich)
118 {
119 if (TextBuff[ich] == TEXT('\n'))
120 {
121 ++ich;
122 break;
123 }
124 }
125 Pager->ich = ich;
126
127 cchLine = (ich - ichStart);
128
129 //
130 // FIXME: Impose a maximum string limit when the line is cached, in order
131 // not to potentially grow memory indefinitely. When the limit is reached,
132 // terminate the line.
133 //
134
135 /*
136 * If we have stopped because we have exhausted the text buffer
137 * and we have not found an end-of-line character, this may mean
138 * that the text line spans across different text buffers. If we
139 * have been told so, cache this line: we will complete it during
140 * the next call(s) and only then, display it.
141 * Otherwise, consider the line to be terminated now.
142 */
143 bCacheLine = ((Pager->dwFlags & CON_PAGER_CACHE_INCOMPLETE_LINE) &&
144 (ich >= cch) && (TextBuff[ich - 1] != TEXT('\n')));
145
146 /* Allocate, or re-allocate, the cached line buffer */
147 if (bCacheLine && !Pager->CachedLine)
148 {
149 /* We start caching, allocate the cached line buffer */
150 Pager->CachedLine = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
151 cchLine * sizeof(TCHAR));
152 Pager->cchCachedLine = 0;
153
154 if (!Pager->CachedLine)
155 {
156 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
157 return FALSE;
158 }
159 }
160 else if (Pager->CachedLine)
161 {
162 /* We continue caching, re-allocate the cached line buffer */
163 PVOID ptr = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
164 (PVOID)Pager->CachedLine,
165 (Pager->cchCachedLine + cchLine) * sizeof(TCHAR));
166 if (!ptr)
167 {
168 HeapFree(GetProcessHeap(), 0, (PVOID)Pager->CachedLine);
169 Pager->CachedLine = NULL;
170 Pager->cchCachedLine = 0;
171
172 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
173 return FALSE;
174 }
175 Pager->CachedLine = ptr;
176 }
177 if (Pager->CachedLine)
178 {
179 /* Copy/append the text to the cached line buffer */
180 RtlCopyMemory((PVOID)&Pager->CachedLine[Pager->cchCachedLine],
181 &TextBuff[ichStart],
182 cchLine * sizeof(TCHAR));
183 Pager->cchCachedLine += cchLine;
184 }
185 if (bCacheLine)
186 {
187 /* The line is currently incomplete, don't proceed further for now */
188 return FALSE;
189 }
190
191 TerminateLine:
192 /* The line should be complete now. If we have an existing cached line,
193 * it has been completed by appending the remaining text to it. */
194
195 /* We are starting a new line */
196 Pager->ichCurr = 0;
197 if (Pager->CachedLine)
198 {
199 Pager->iEndLine = Pager->cchCachedLine;
200 Pager->CurrentLine = Pager->CachedLine;
201 }
202 else
203 {
204 Pager->iEndLine = cchLine;
205 Pager->CurrentLine = &TextBuff[ichStart];
206 }
207
208 /* Increase only when we have got a NEWLINE */
209 if ((Pager->iEndLine > 0) && (Pager->CurrentLine[Pager->iEndLine - 1] == TEXT('\n')))
210 Pager->lineno++;
211
212 return TRUE;
213 }
214
215 /**
216 * @brief Does the main paging work: fetching text lines and displaying them.
217 **/
218 static BOOL
219 ConPagerWorker(
220 IN PCON_PAGER Pager,
221 IN PCTCH TextBuff,
222 IN DWORD cch)
223 {
224 const DWORD PageColumns = Pager->PageColumns;
225 const DWORD ScrollRows = Pager->ScrollRows;
226
227 BOOL bFinitePaging = ((PageColumns > 0) && (Pager->PageRows > 0));
228 LONG nTabWidth = Pager->nTabWidth;
229
230 PCTCH Line;
231 SIZE_T ich;
232 SIZE_T ichStart;
233 SIZE_T iEndLine;
234 DWORD iColumn = Pager->iColumn;
235
236 UINT nCodePage = GetConsoleOutputCP();
237 BOOL IsCJK = IsCJKCodePage(nCodePage);
238 UINT nWidthOfChar = 1;
239 BOOL IsDoubleWidthCharTrailing = FALSE;
240
241 /* Normalize the tab width: if negative or too large,
242 * cap it to the number of columns. */
243 if (PageColumns > 0) // if (bFinitePaging)
244 {
245 if (nTabWidth < 0)
246 nTabWidth = PageColumns - 1;
247 else
248 nTabWidth = min(nTabWidth, PageColumns - 1);
249 }
250 else
251 {
252 /* If no column width is known, default to 8 spaces if the
253 * original value is negative; otherwise keep the current one. */
254 if (nTabWidth < 0)
255 nTabWidth = 8;
256 }
257
258
259 /* Continue displaying the previous line, if any, or start a new one */
260 Line = Pager->CurrentLine;
261 ichStart = Pager->ichCurr;
262 iEndLine = Pager->iEndLine;
263
264 ProcessLine:
265
266 /* Stop now if we have displayed more page lines than requested */
267 if (bFinitePaging && (Pager->iLine >= ScrollRows))
268 goto End;
269
270 if (!Line || (ichStart >= iEndLine))
271 {
272 /* Start a new line */
273 if (!GetNextLine(Pager, TextBuff, cch))
274 goto End;
275
276 Line = Pager->CurrentLine;
277 ichStart = Pager->ichCurr;
278 iEndLine = Pager->iEndLine;
279 }
280 else
281 {
282 /* Continue displaying the current line */
283 }
284
285 // ASSERT(Line && ((ichStart < iEndLine) || (ichStart == iEndLine && iEndLine == 0)));
286
287 /* Determine whether this line segment (from the current position till the end) should be displayed */
288 Pager->iColumn = iColumn;
289 if (Pager->PagerLine && Pager->PagerLine(Pager, &Line[ichStart], iEndLine - ichStart))
290 {
291 iColumn = Pager->iColumn;
292
293 /* Done with this line; start a new one */
294 Pager->nSpacePending = 0; // And reset any pending space.
295 ichStart = iEndLine;
296 goto ProcessLine;
297 }
298 // else: Continue displaying the line.
299
300
301 /* Print out any pending TAB expansion */
302 if (Pager->nSpacePending > 0)
303 {
304 ExpandTab:
305 while (Pager->nSpacePending > 0)
306 {
307 /* Print filling spaces */
308 CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1);
309 --(Pager->nSpacePending);
310 ++iColumn;
311
312 /* Check whether we are going across the column */
313 if ((PageColumns > 0) && (iColumn % PageColumns == 0))
314 {
315 // Pager->nSpacePending = 0; // <-- This is the mode of most text editors...
316
317 /* Reposition the cursor to the next line, first column */
318 if (!bFinitePaging || (PageColumns < Pager->Screen->csbi.dwSize.X))
319 CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1);
320
321 Pager->iLine++;
322
323 /* Restart at the character */
324 // ASSERT(ichStart == ich);
325 goto ProcessLine;
326 }
327 }
328 }
329
330
331 /* Find, within this line segment (starting from its
332 * beginning), until where we can print to the page. */
333 for (ich = ichStart; ich < iEndLine; ++ich)
334 {
335 /* NEWLINE character */
336 if (Line[ich] == TEXT('\n'))
337 {
338 /* We should stop now */
339 // ASSERT(ich == iEndLine - 1);
340 break;
341 }
342
343 /* TAB character */
344 if (Line[ich] == TEXT('\t') &&
345 (Pager->dwFlags & CON_PAGER_EXPAND_TABS))
346 {
347 /* We should stop now */
348 break;
349 }
350
351 /* FORM-FEED character */
352 if (Line[ich] == TEXT('\f') &&
353 (Pager->dwFlags & CON_PAGER_EXPAND_FF))
354 {
355 /* We should stop now */
356 break;
357 }
358
359 /* Other character - Handle double-width for CJK */
360
361 if (IsCJK)
362 nWidthOfChar = GetWidthOfCharCJK(nCodePage, Line[ich]);
363
364 /* Care about CJK character presentation only when outputting
365 * to a device where the number of columns is known. */
366 if ((PageColumns > 0) && IsCJK)
367 {
368 IsDoubleWidthCharTrailing = (nWidthOfChar == 2) &&
369 ((iColumn + 1) % PageColumns == 0);
370 if (IsDoubleWidthCharTrailing)
371 {
372 /* Reserve this character for the next line */
373 ++iColumn; // Count a blank instead.
374 /* We should stop now */
375 break;
376 }
377 }
378
379 iColumn += nWidthOfChar;
380
381 /* Check whether we are going across the column */
382 if ((PageColumns > 0) && (iColumn % PageColumns == 0))
383 {
384 ++ich;
385 break;
386 }
387 }
388
389 /* Output the pending line segment */
390 if (ich - ichStart > 0)
391 CON_STREAM_WRITE(Pager->Screen->Stream, &Line[ichStart], ich - ichStart);
392
393 /* Have we finished the line segment? */
394 if (ich >= iEndLine)
395 {
396 /* Restart at the character */
397 ichStart = ich;
398 goto ProcessLine;
399 }
400
401 /* Handle special characters */
402
403 /* NEWLINE character */
404 if (Line[ich] == TEXT('\n'))
405 {
406 // ASSERT(ich == iEndLine - 1);
407
408 /* Reposition the cursor to the next line, first column */
409 CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1);
410
411 Pager->iLine++;
412 iColumn = 0;
413
414 /* Done with this line; start a new one */
415 Pager->nSpacePending = 0; // And reset any pending space.
416 ichStart = iEndLine;
417 goto ProcessLine;
418 }
419
420 /* TAB character */
421 if (Line[ich] == TEXT('\t') &&
422 (Pager->dwFlags & CON_PAGER_EXPAND_TABS))
423 {
424 /* Perform TAB expansion, unless the tab width is zero */
425 if (nTabWidth == 0)
426 {
427 ichStart = ++ich;
428 goto ProcessLine;
429 }
430
431 ichStart = ++ich;
432 /* Reset the number of spaces needed to develop this TAB character */
433 Pager->nSpacePending = nTabWidth - (iColumn % nTabWidth);
434 goto ExpandTab;
435 }
436
437 /* FORM-FEED character */
438 if (Line[ich] == TEXT('\f') &&
439 (Pager->dwFlags & CON_PAGER_EXPAND_FF))
440 {
441 if (bFinitePaging)
442 {
443 /* Clear until the end of the page */
444 while (Pager->iLine < ScrollRows)
445 {
446 /* Call the user paging function in order to know
447 * whether we need to output the blank lines. */
448 Pager->iColumn = iColumn;
449 if (Pager->PagerLine && Pager->PagerLine(Pager, TEXT("\n"), 1))
450 {
451 /* Only one blank line displayed, that counts in the line count */
452 Pager->iLine++;
453 break;
454 }
455 else
456 {
457 CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1);
458 Pager->iLine++;
459 }
460 }
461 }
462 else
463 {
464 /* Just output a FORM-FEED and a NEWLINE */
465 CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\f\n"), 2);
466 Pager->iLine++;
467 }
468
469 iColumn = 0;
470 Pager->nSpacePending = 0; // And reset any pending space.
471
472 /* Skip and restart past the character */
473 ichStart = ++ich;
474 goto ProcessLine;
475 }
476
477 /* If we output a double-width character that goes across the column,
478 * fill with blank and display the character on the next line. */
479 if (IsDoubleWidthCharTrailing)
480 {
481 IsDoubleWidthCharTrailing = FALSE; // Reset the flag.
482 CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1);
483 /* Fall back below */
484 }
485
486 /* Are we wrapping the line? */
487 if ((PageColumns > 0) && (iColumn % PageColumns == 0))
488 {
489 /* Reposition the cursor to the next line, first column */
490 if (!bFinitePaging || (PageColumns < Pager->Screen->csbi.dwSize.X))
491 CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1);
492
493 Pager->iLine++;
494 }
495
496 /* Restart at the character */
497 ichStart = ich;
498 goto ProcessLine;
499
500
501 End:
502 /*
503 * We are exiting, either because we displayed all the required lines
504 * (iLine >= ScrollRows), or, because we don't have more data to display.
505 */
506
507 Pager->ichCurr = ichStart;
508 Pager->iColumn = iColumn;
509 // INVESTIGATE: Can we get rid of CurrentLine here? // if (ichStart >= iEndLine) ...
510
511 /* Return TRUE if we displayed all the required lines; FALSE otherwise */
512 if (bFinitePaging && (Pager->iLine >= ScrollRows))
513 {
514 Pager->iLine = 0; /* Reset the count of lines being printed */
515 return TRUE;
516 }
517 else
518 {
519 return FALSE;
520 }
521 }
522
523
524 /**
525 * @name ConWritePaging
526 * Pages the contents of a user-specified character buffer on the screen.
527 *
528 * @param[in] Pager
529 * Pager object that describes where the paged output is issued.
530 *
531 * @param[in] PagePrompt
532 * A user-specific callback, called when a page has been displayed.
533 *
534 * @param[in] StartPaging
535 * Set to TRUE for initializing the paging operation; FALSE during paging.
536 *
537 * @param[in] szStr
538 * Pointer to the character buffer whose contents are to be paged.
539 *
540 * @param[in] len
541 * Length of the character buffer pointed by @p szStr, specified
542 * in number of characters.
543 *
544 * @return
545 * TRUE when all the contents of the character buffer has been displayed;
546 * FALSE if the paging operation has been stopped (controlled via @p PagePrompt).
547 **/
548 BOOL
549 ConWritePaging(
550 IN PCON_PAGER Pager,
551 IN PAGE_PROMPT PagePrompt,
552 IN BOOL StartPaging,
553 IN PCTCH szStr,
554 IN DWORD len)
555 {
556 CONSOLE_SCREEN_BUFFER_INFO csbi;
557 BOOL bIsConsole;
558
559 /* Parameters validation */
560 if (!Pager)
561 return FALSE;
562
563 /* Get the size of the visual screen that can be printed to */
564 bIsConsole = ConGetScreenInfo(Pager->Screen, &csbi);
565 if (bIsConsole)
566 {
567 /* Calculate the console screen extent */
568 Pager->PageColumns = csbi.dwSize.X;
569 Pager->PageRows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
570 }
571 else
572 {
573 /* We assume it's a file handle */
574 Pager->PageColumns = 0;
575 Pager->PageRows = 0;
576 }
577
578 if (StartPaging)
579 {
580 if (bIsConsole && (Pager->PageRows >= 2))
581 {
582 /* Reset to display one page by default */
583 Pager->ScrollRows = Pager->PageRows - 1;
584 }
585 else
586 {
587 /* File output, or single line: all lines are displayed at once; reset to a default value */
588 Pager->ScrollRows = 0;
589 }
590
591 /* Reset the internal data buffer */
592 Pager->CachedLine = NULL;
593 Pager->cchCachedLine = 0;
594
595 /* Reset the paging state */
596 Pager->CurrentLine = NULL;
597 Pager->ichCurr = 0;
598 Pager->iEndLine = 0;
599 Pager->nSpacePending = 0;
600 Pager->iColumn = 0;
601 Pager->iLine = 0;
602 Pager->lineno = 0;
603 }
604
605 /* Reset the reading index in the user-provided source buffer */
606 Pager->ich = 0;
607
608 /* Run the pager even when the user-provided source buffer is
609 * empty, in case we need to flush any remaining cached line. */
610 if (!Pager->CachedLine)
611 {
612 /* No cached line, bail out now */
613 if (len == 0 || szStr == NULL)
614 return TRUE;
615 }
616
617 while (ConPagerWorker(Pager, szStr, len))
618 {
619 /* Prompt the user only when we display to a console and the screen
620 * is not too small: at least one line for the actual paged text and
621 * one line for the prompt. */
622 if (bIsConsole && (Pager->PageRows >= 2))
623 {
624 /* Reset to display one page by default */
625 Pager->ScrollRows = Pager->PageRows - 1;
626
627 /* Prompt the user; give him some values for statistics */
628 // FIXME: Doesn't reflect what's currently being displayed.
629 if (!PagePrompt(Pager, Pager->ich, len))
630 return FALSE;
631 }
632
633 /* If we display to a console, recalculate its screen extent
634 * in case the user has redimensioned it during the prompt. */
635 if (bIsConsole && ConGetScreenInfo(Pager->Screen, &csbi))
636 {
637 Pager->PageColumns = csbi.dwSize.X;
638 Pager->PageRows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
639 }
640 }
641
642 return TRUE;
643 }
644
645 BOOL
646 ConPutsPaging(
647 IN PCON_PAGER Pager,
648 IN PAGE_PROMPT PagePrompt,
649 IN BOOL StartPaging,
650 IN PCTSTR szStr)
651 {
652 DWORD len;
653
654 /* Return if no string has been given */
655 if (szStr == NULL)
656 return TRUE;
657
658 len = wcslen(szStr);
659 return ConWritePaging(Pager, PagePrompt, StartPaging, szStr, len);
660 }
661
662 BOOL
663 ConResPagingEx(
664 IN PCON_PAGER Pager,
665 IN PAGE_PROMPT PagePrompt,
666 IN BOOL StartPaging,
667 IN HINSTANCE hInstance OPTIONAL,
668 IN UINT uID)
669 {
670 INT Len;
671 PCWSTR szStr = NULL;
672
673 Len = K32LoadStringW(hInstance, uID, (PWSTR)&szStr, 0);
674 if (szStr && Len)
675 return ConWritePaging(Pager, PagePrompt, StartPaging, szStr, Len);
676 else
677 return TRUE;
678 }
679
680 BOOL
681 ConResPaging(
682 IN PCON_PAGER Pager,
683 IN PAGE_PROMPT PagePrompt,
684 IN BOOL StartPaging,
685 IN UINT uID)
686 {
687 return ConResPagingEx(Pager, PagePrompt, StartPaging,
688 NULL /*GetModuleHandleW(NULL)*/, uID);
689 }
690
691 /* EOF */