1 /*---------------------------------------------------------------------------*
2 Project: NintendoWare
3 File: lyt_TextBox.cpp
4
5 Copyright (C)2009-2011 Nintendo/HAL Laboratory, Inc. All rights reserved.
6
7 These coded instructions, statements, and computer programs contain proprietary
8 information of Nintendo and/or its licensed developers and are protected by
9 national and international copyright laws. They may not be disclosed to third
10 parties or copied or duplicated in any form, in whole or in part, without the
11 prior written consent of Nintendo.
12
13 The content herein is highly confidential and should be handled accordingly.
14
15 $Revision: 31411 $
16 *---------------------------------------------------------------------------*/
17
18 #include "precompiled.h"
19
20 #include <nw/lyt/lyt_DrawInfo.h>
21 #include <nw/lyt/lyt_GraphicsResource.h>
22 #include <nw/lyt/lyt_Layout.h>
23 #include <nw/lyt/lyt_TextBox.h>
24 #include <nw/lyt/lyt_Material.h>
25 #include <nw/lyt/lyt_Animation.h>
26 #include <nw/lyt/lyt_Common.h>
27 #include <nw/lyt/lyt_ResourceAccessor.h>
28 #include <nw/lyt/lyt_Stopwatch.h>
29 #include <nw/font/font_DispStringBuffer.h>
30
31 namespace nw
32 {
33 namespace lyt
34 {
35 namespace internal
36 {
37 namespace
38 {
39
40 inline u8
ClampColor(s16 colVal)41 ClampColor(s16 colVal)
42 {
43 return u8(colVal < 0 ? 0: (colVal > 255 ? 255: colVal));
44 }
45
46 /*!--------------------------------------------------------------------------*
47 @brief 基点が中央であるときのカーソル位置の値を求めます。
48
49 @param[in] value 考慮する幅/高さ
50 @param[in] isCeil 半分の値が浮動小数点にならないように整数値に
51 切り上げる場合は真。
52
53 @return 基点が中央であるときのカーソル位置補正の値を返します。
54 *---------------------------------------------------------------------------*/
55 inline
56 f32
AdjustCenterValue(f32 value,bool isCeil)57 AdjustCenterValue(
58 f32 value,
59 bool isCeil
60 )
61 {
62 f32 ret = value / 2;
63 return isCeil ? math::FCeil(ret): ret;
64 }
65
66 } // namespace nw::lyt::internal::{anonymous}
67
68 } // namespace nw::lyt::internal
69
70 using namespace math;
71
72 NW_UT_RUNTIME_TYPEINFO_DEFINITION(TextBox, TextBox::Base); // 実行時型情報の実体を定義
73
TextBox(u16 allocStrLen)74 TextBox::TextBox(u16 allocStrLen)
75 {
76 Init(allocStrLen);
77 InitMaterial();
78 }
79
TextBox(u16 allocStrLen,const wchar_t * str,const font::Font * pFont)80 TextBox::TextBox(
81 u16 allocStrLen,
82 const wchar_t* str,
83 const font::Font* pFont
84 )
85 {
86 Init(allocStrLen);
87 SetFont(pFont);
88 SetString(str);
89 InitMaterial();
90 }
91
TextBox(u16 allocStrLen,const wchar_t * str,u16 strLen,const font::Font * pFont)92 TextBox::TextBox(
93 u16 allocStrLen,
94 const wchar_t* str,
95 u16 strLen,
96 const font::Font* pFont
97 )
98 {
99 Init(allocStrLen);
100 SetFont(pFont);
101 SetString(str, 0, strLen);
102 InitMaterial();
103 }
104
TextBox(const res::TextBox * pBlock,const ResBlockSet & resBlockSet)105 TextBox::TextBox(
106 const res::TextBox* pBlock,
107 const ResBlockSet& resBlockSet
108 )
109 : Pane(pBlock)
110 {
111 u16 allocStrBufLen = static_cast<u16>(pBlock->textBufBytes / sizeof(wchar_t));
112 if (allocStrBufLen > 0)
113 {
114 allocStrBufLen -= 1;
115 }
116
117 Init(allocStrBufLen);
118
119 for (int i = 0; i < TEXTCOLOR_MAX; ++i)
120 {
121 m_TextColors[i] = pBlock->textCols[i];
122 }
123
124 m_FontSize = pBlock->fontSize;
125 m_TextPosition = pBlock->textPosition;
126 m_Bits.textAlignment = pBlock->textAlignment;
127 m_CharSpace = pBlock->charSpace;
128 m_LineSpace = pBlock->lineSpace;
129
130 // Fontの構築
131 NW_NULL_ASSERT(resBlockSet.pFontList);
132 NW_ASSERT(pBlock->fontIdx < resBlockSet.pFontList->fontNum);
133 const res::Font *const fonts = internal::ConvertOffsToPtr<res::Font>(resBlockSet.pFontList, sizeof(*resBlockSet.pFontList));
134 const char *const fontName = internal::ConvertOffsToPtr<char>(fonts, fonts[pBlock->fontIdx].nameStrOffset);
135
136 m_pFont = resBlockSet.pResAccessor->GetFont(fontName);
137
138 // 初期文字列のコピー
139 if (pBlock->textStrBytes >= sizeof(wchar_t) && m_TextBuf)
140 {
141 const wchar_t *const pBlockText = internal::ConvertOffsToPtr<wchar_t>(pBlock, pBlock->textStrOffset);
142 const u16 resStrLen = static_cast<u16>(pBlock->textStrBytes / sizeof(wchar_t) - 1);
143 SetString(pBlockText, 0, resStrLen);
144 }
145
146 // マテリアルの作成
147 {
148 NW_NULL_ASSERT(resBlockSet.pMaterialList);
149 const u32 *const matOffsTbl = internal::ConvertOffsToPtr<u32>(resBlockSet.pMaterialList, sizeof(*resBlockSet.pMaterialList));
150 const res::Material *const pResMaterial = internal::ConvertOffsToPtr<res::Material>(resBlockSet.pMaterialList, matOffsTbl[pBlock->materialIdx]);
151 m_pMaterial = Layout::NewObj<Material>(pResMaterial, resBlockSet);
152 }
153 }
154
155 void
Init(u16 allocStrLen)156 TextBox::Init(u16 allocStrLen)
157 {
158 m_TextBuf = 0;
159 m_TextBufBytes = 0;
160 m_TextLen = 0;
161 m_pFont = 0;
162 m_FontSize = Size(0, 0);
163 SetTextPositionH(HORIZONTALPOSITION_CENTER);
164 SetTextPositionV(VERTICALPOSITION_CENTER);
165 m_LineSpace = 0;
166 m_CharSpace = 0;
167 m_pTagProcessor = 0;
168 m_pDispStringBuf = 0;
169 std::memset(&m_Bits, 0, sizeof(m_Bits));
170
171 if (allocStrLen > 0)
172 {
173 AllocStringBuffer(allocStrLen);
174 }
175 }
176
177 void
InitMaterial()178 TextBox::InitMaterial()
179 {
180 // マテリアルの作成
181 m_pMaterial = Layout::NewObj<Material>();
182 if (m_pMaterial)
183 {
184 m_pMaterial->ReserveMem(0, 0, 0);
185 }
186 }
187
~TextBox()188 TextBox::~TextBox()
189 {
190 // OSReport("TextBox::~TextBox()\n");
191
192 if (m_pMaterial && !m_pMaterial->IsUserAllocated())
193 {
194 Layout::DeleteObj(m_pMaterial);
195 m_pMaterial = 0;
196 }
197
198 FreeStringBuffer();
199 }
200
201 u8
GetMaterialNum() const202 TextBox::GetMaterialNum() const
203 {
204 return m_pMaterial? 1 : 0;
205 }
206
207 Material*
GetMaterial(u32 idx) const208 TextBox::GetMaterial(u32 idx) const
209 {
210 NW_WARNING(idx < GetMaterialNum(), "idx >= GetMaterialNum() : %d >= %d", idx, GetMaterialNum());
211
212 return idx == 0 ? m_pMaterial : 0;
213 }
214
SetMaterial(Material * pMaterial)215 void TextBox::SetMaterial(Material* pMaterial)
216 {
217 if (m_pMaterial && !m_pMaterial->IsUserAllocated())
218 {
219 Layout::DeleteObj(m_pMaterial);
220 }
221 m_pMaterial = pMaterial;
222 }
223
224 const ut::Color8
GetVtxColor(u32 idx) const225 TextBox::GetVtxColor(u32 idx) const
226 {
227 NW_ASSERT(idx < VERTEXCOLOR_MAX);
228
229 return GetTextColor(idx / 2);
230 }
231
232 void
SetVtxColor(u32 idx,ut::Color8 value)233 TextBox::SetVtxColor(
234 u32 idx,
235 ut::Color8 value
236 )
237 {
238 NW_ASSERT(idx < VERTEXCOLOR_MAX);
239
240 SetTextColor(idx / 2, value);
241 }
242
243 u8
GetVtxColorElement(u32 idx) const244 TextBox::GetVtxColorElement(u32 idx) const
245 {
246 NW_ASSERT(idx < ANIMTARGET_VERTEXCOLOR_MAX);
247
248 return reinterpret_cast<const u8*>(&m_TextColors[idx / (2 * sizeof(ut::Color8))])[idx % sizeof(ut::Color8)];
249 }
250
251 void
SetVtxColorElement(u32 idx,u8 value)252 TextBox::SetVtxColorElement(u32 idx, u8 value)
253 {
254 NW_ASSERT(idx < ANIMTARGET_VERTEXCOLOR_MAX);
255
256 u8& elm =
257 reinterpret_cast<u8*>(&m_TextColors[idx / (2 * sizeof(ut::Color8))])[idx % sizeof(ut::Color8)];
258
259 elm = value;
260 }
261
262 const ut::Rect
GetTextDrawRect() const263 TextBox::GetTextDrawRect() const
264 {
265 if (m_pFont == NULL)
266 {
267 NW_WARNING(false, "m_pFont is NULL");
268 return ut::Rect();
269 }
270
271 font::WideTextWriter writer;
272 writer.SetCursor(0, 0);
273 SetFontInfo(&writer);
274
275 ut::Rect textRect;
276 writer.CalcStringRect(&textRect, m_TextBuf, m_TextLen);
277
278 const Size textSize(textRect.GetWidth(), textRect.GetHeight());
279
280 VEC2 ltPos = GetVtxPos();
281
282 const VEC2 curPos = AdjustTextPos(GetSize(), false);
283 const VEC2 textPos = AdjustTextPos(textSize, true);
284
285 ltPos.x += curPos.x - textPos.x;
286 ltPos.y -= curPos.y - textPos.y;
287
288 textRect.left = ltPos.x;
289 textRect.top = ltPos.y;
290 textRect.right = ltPos.x + textSize.width;
291 textRect.bottom = ltPos.y - textSize.height;
292
293 return textRect;
294 }
295
296 #ifdef NW_LYT_DMPGL_ENABLED
297 void
DrawSelf(const DrawInfo & drawInfo)298 TextBox::DrawSelf(const DrawInfo& drawInfo)
299 {
300 NW_LYT_STOPWATCH_MEASURE(-400, "nw::lyt::TextBox::DrawSelf");
301
302 if (m_TextLen <= 0 || !m_pFont || !m_pMaterial)
303 {
304 return;
305 }
306
307 // lytの描画設定がfontに影響しないように。
308 internal::FinalizeGraphics();
309
310 GraphicsResource& graphicsResource = *drawInfo.GetGraphicsResource();
311 graphicsResource.ResetGlState();
312
313 font::TextWriterResource& writerResource = graphicsResource.GetTextWriterResource();
314 font::WideTextWriter writer;
315
316 writer.SetTextWriterResource(&writerResource);
317 SetupTextWriter(&writer);
318
319 writerResource.ActiveGlProgram();
320 NW_GL_ASSERT();
321
322 // 行列
323 LoadMtx(drawInfo);
324
325 ut::Color8 minCol = m_pMaterial->GetColor(INTERPOLATECOLOR_BLACK);
326 ut::Color8 maxCol = m_pMaterial->GetColor(INTERPOLATECOLOR_WHITE);
327
328 writer.SetColorMapping(minCol, maxCol);
329 writer.SetAlpha(GetGlobalAlpha());
330
331 writer.SetupGX();
332
333 (void)writer.Print(m_TextBuf, m_TextLen);
334
335 // fontの描画設定がlytに影響しないように。
336 writer.FinalizeGX();
337 }
338 #endif // NW_LYT_DMPGL_ENABLED
339
340 u16
GetStringBufferLength() const341 TextBox::GetStringBufferLength() const
342 {
343 if (m_TextBufBytes == 0)
344 {
345 return 0;
346 }
347
348 NW_ASSERT(m_TextBufBytes >= sizeof(wchar_t));
349
350 return static_cast<u16>(m_TextBufBytes / sizeof(wchar_t) - 1);
351 }
352
353 void
AllocStringBuffer(u16 minLen)354 TextBox::AllocStringBuffer(u16 minLen)
355 {
356 if (minLen == 0)
357 {
358 return;
359 }
360
361 u32 allocLen = minLen;
362 ++allocLen; // 終端文字分インクリメント
363
364 const u32 textBufBytes = allocLen * sizeof(wchar_t);
365 if (textBufBytes >= 0x10000)
366 {
367 NW_WARNING(false, "minLen is too large. - %d\n", minLen);
368 }
369
370 // 要求する長さのバッファを既に確保している場合は何もしない
371 if (textBufBytes <= m_TextBufBytes)
372 {
373 return;
374 }
375
376 FreeStringBuffer();
377
378 // 指定した文字数分VBOバッファを確保
379 const u32 drawBufSize = font::CharWriter::GetDispStringBufferSize(minLen);
380
381 wchar_t* textBuf = Layout::NewArray<wchar_t>(allocLen);
382 void* pDispStringBuf = Layout::AllocMemory(drawBufSize);
383 if (NULL == textBuf || NULL == pDispStringBuf)
384 {
385 if (NULL != textBuf)
386 {
387 Layout::DeletePrimArray(textBuf);
388 }
389 if (NULL != pDispStringBuf)
390 {
391 Layout::FreeMemory(pDispStringBuf);
392 }
393 return;
394 }
395
396 m_TextBuf = textBuf;
397 m_TextBufBytes = static_cast<u16>(textBufBytes);
398 // 表示文字列バッファを初期化
399 m_pDispStringBuf = font::CharWriter::InitDispStringBuffer(
400 pDispStringBuf,
401 minLen);
402 }
403
404 void
FreeStringBuffer()405 TextBox::FreeStringBuffer()
406 {
407 if (m_TextBuf)
408 {
409 Layout::FreeMemory(m_pDispStringBuf);
410 Layout::DeletePrimArray(m_TextBuf);
411 m_pDispStringBuf = 0;
412 m_TextBuf = 0;
413 m_TextBufBytes = 0;
414 m_TextLen = 0;
415 }
416 }
417
418 u16
SetString(const wchar_t * str,u16 dstIdx)419 TextBox::SetString(
420 const wchar_t* str,
421 u16 dstIdx
422 )
423 {
424 return SetStringImpl(str, dstIdx, std::wcslen(str));
425 }
426
427 u16
SetString(const wchar_t * str,u16 dstIdx,u16 strLen)428 TextBox::SetString(
429 const wchar_t* str,
430 u16 dstIdx,
431 u16 strLen
432 )
433 {
434 return SetStringImpl(str, dstIdx, strLen);
435 }
436
437 u16
SetStringImpl(const wchar_t * str,u16 dstIdx,u32 strLen)438 TextBox::SetStringImpl(
439 const wchar_t* str,
440 u16 dstIdx,
441 u32 strLen
442 )
443 {
444 if (m_pFont == 0)
445 {
446 NW_WARNING(false, "m_pFont is NULL.\n");
447 return 0;
448 }
449
450 if (m_TextBuf == 0)
451 {
452 NW_WARNING(false, "m_TextBuf is NULL.\n");
453 return 0;
454 }
455
456 const u16 bufLen = GetStringBufferLength();
457
458 if (dstIdx >= bufLen) // バッファを超えているためコピーしない
459 {
460 NW_WARNING(false, "dstIdx is out of range.\n");
461 return 0;
462 }
463
464 u32 cpLen = bufLen;
465 cpLen -= dstIdx; // コピー可能な文字数
466
467 cpLen = ut::Min(strLen, cpLen);
468 NW_WARNING(cpLen >= strLen, "%d character(s) droped.\n", strLen - cpLen);
469
470 std::memcpy(m_TextBuf + dstIdx, str, cpLen * sizeof(wchar_t));
471
472 // m_TextLen にセットする値は 16bitの bufLen 以下の値。
473 m_TextLen = static_cast<u16>(dstIdx + cpLen);
474 m_TextBuf[m_TextLen] = 0;
475
476 UpdatePTDirty(true);
477
478 return static_cast<u16>(cpLen);
479 }
480
481 const font::Font*
GetFont() const482 TextBox::GetFont() const
483 {
484 return m_pFont;
485 }
486
487 void
SetFont(const font::Font * pFont)488 TextBox::SetFont(const font::Font* pFont)
489 {
490 if (pFont && pFont->GetCharacterCode() != font::CHARACTER_CODE_UNICODE)
491 {
492 NW_WARNING(false, "pFont is not uincode encoding.");
493 return;
494 }
495
496 if (UpdatePTDirty(m_pFont != pFont))
497 {
498 m_pFont = pFont;
499
500 if (m_pFont)
501 {
502 SetFontSize(Size(static_cast<f32>(m_pFont->GetWidth()), static_cast<f32>(m_pFont->GetHeight())));
503 }
504 else
505 {
506 SetFontSize(Size(0.f, 0.f));
507 }
508 }
509 }
510
511 #ifdef NW_LYT_DMPGL_ENABLED
512 void
LoadMtx(const DrawInfo & drawInfo)513 TextBox::LoadMtx(const DrawInfo& drawInfo)
514 {
515 MTX34 mtx;
516
517 GetTextGlobalMtx(&mtx);
518
519 drawInfo.GetGraphicsResource()->GetTextWriterResource().SetViewMtx(mtx);
520 NW_GL_ASSERT();
521 }
522 #endif
523
524 void
SetFontInfo(font::WideTextWriter * pWriter) const525 TextBox::SetFontInfo(font::WideTextWriter* pWriter) const
526 {
527 pWriter->SetFont(m_pFont);
528 if (m_pFont != NULL)
529 {
530 pWriter->SetFontSize(m_FontSize.width, m_FontSize.height);
531 pWriter->SetLineSpace(m_LineSpace);
532 pWriter->SetCharSpace(m_CharSpace);
533 pWriter->SetWidthLimit(GetSize().width);
534 }
535
536 if (m_pTagProcessor)
537 {
538 pWriter->SetTagProcessor(m_pTagProcessor);
539 }
540 }
541
542 void
SetTextPos(font::WideTextWriter * pWriter) const543 TextBox::SetTextPos(font::WideTextWriter* pWriter) const
544 {
545 u32 value = 0;
546
547 switch (GetTextAlignment())
548 {
549 case TEXTALIGNMENT_SYNCHRONOUS:
550 default:
551 switch (GetTextPositionH())
552 {
553 case HORIZONTALPOSITION_LEFT:
554 default: value = font::WideTextWriter::HORIZONTAL_ALIGN_LEFT; break;
555 case HORIZONTALPOSITION_CENTER: value = font::WideTextWriter::HORIZONTAL_ALIGN_CENTER; break;
556 case HORIZONTALPOSITION_RIGHT: value = font::WideTextWriter::HORIZONTAL_ALIGN_RIGHT; break;
557 }
558 break;
559
560 case TEXTALIGNMENT_LEFT: value = font::WideTextWriter::HORIZONTAL_ALIGN_LEFT; break;
561 case TEXTALIGNMENT_CENTER: value = font::WideTextWriter::HORIZONTAL_ALIGN_CENTER; break;
562 case TEXTALIGNMENT_RIGHT: value = font::WideTextWriter::HORIZONTAL_ALIGN_RIGHT; break;
563 }
564
565 switch (GetTextPositionH())
566 {
567 case HORIZONTALPOSITION_LEFT:
568 default:
569 value |= font::WideTextWriter::HORIZONTAL_ORIGIN_LEFT;
570 break;
571 case HORIZONTALPOSITION_CENTER:
572 value |= font::WideTextWriter::HORIZONTAL_ORIGIN_CENTER;
573 break;
574 case HORIZONTALPOSITION_RIGHT:
575 value |= font::WideTextWriter::HORIZONTAL_ORIGIN_RIGHT;
576 break;
577 }
578
579 switch (GetTextPositionV())
580 {
581 case VERTICALPOSITION_TOP:
582 default:
583 value |= font::WideTextWriter::VERTICAL_ORIGIN_TOP;
584 break;
585 case VERTICALPOSITION_CENTER:
586 value |= font::WideTextWriter::VERTICAL_ORIGIN_MIDDLE;
587 break;
588 case VERTICALPOSITION_BOTTOM:
589 value |= font::WideTextWriter::VERTICAL_ORIGIN_BOTTOM;
590 break;
591 }
592
593 pWriter->SetDrawFlag(value);
594 }
595
596 VEC2
AdjustTextPos(const Size & size,bool isCeil) const597 TextBox::AdjustTextPos(
598 const Size& size,
599 bool isCeil
600 ) const
601 {
602 VEC2 pos;
603
604 switch (GetTextPositionH())
605 {
606 case HORIZONTALPOSITION_LEFT:
607 default:
608 pos.x = 0.f;
609 break;
610 case HORIZONTALPOSITION_CENTER:
611 pos.x = internal::AdjustCenterValue(size.width, isCeil);
612 break;
613 case HORIZONTALPOSITION_RIGHT:
614 pos.x = size.width;
615 break;
616 }
617
618 switch (GetTextPositionV())
619 {
620 case VERTICALPOSITION_TOP:
621 default:
622 pos.y = 0.f;
623 break;
624 case VERTICALPOSITION_CENTER:
625 pos.y = internal::AdjustCenterValue(size.height, isCeil);
626 break;
627 case VERTICALPOSITION_BOTTOM:
628 pos.y = size.height;
629 break;
630 }
631
632 return pos;
633 }
634
635 void
GetTextGlobalMtx(nw::math::MTX34 * pMtx) const636 TextBox::GetTextGlobalMtx(nw::math::MTX34* pMtx) const
637 {
638 MTX34Copy(pMtx, &GetGlobalMtx());
639
640 // テキストの描画開始位置の調整をViewModel行列で行う。
641 VEC2 pos = GetVtxPos();
642 const VEC2 txtPos = AdjustTextPos(GetSize(), false);
643
644 pos.x += txtPos.x;
645 pos.y -= txtPos.y;
646
647 pMtx->m[0][3] += pMtx->m[0][0] * pos.x + pMtx->m[0][1] * pos.y;
648 pMtx->m[1][3] += pMtx->m[1][0] * pos.x + pMtx->m[1][1] * pos.y;
649 pMtx->m[2][3] += pMtx->m[2][0] * pos.x + pMtx->m[2][1] * pos.y;
650
651 // TextWriterはY軸正を下にしているので、YとZを反転させる。
652 // Yの反転。
653 pMtx->m[0][1] = - pMtx->m[0][1];
654 pMtx->m[1][1] = - pMtx->m[1][1];
655 pMtx->m[2][1] = - pMtx->m[2][1];
656
657 // Zの反転。
658 // Zは影響が無いので計算を省略。
659 /*
660 pMtx->m[0][2] = - pMtx->m[0][2];
661 pMtx->m[1][2] = - pMtx->m[1][2];
662 pMtx->m[2][2] = - pMtx->m[2][2];
663 */
664 }
665
666 #ifdef NW_LYT_DRAWER_ENABLE
667 void
SetupDrawCharData(Drawer * pDrawer)668 TextBox::SetupDrawCharData(Drawer* pDrawer)
669 {
670 font::WideTextWriter writer;
671
672 writer.SetDispStringBuffer(m_pDispStringBuf);
673 SetupTextWriter(&writer);
674
675 if (m_Bits.isPTDirty)
676 {
677 writer.StartPrint();
678 (void)writer.Print(m_TextBuf, m_TextLen);
679 writer.EndPrint();
680
681 m_Bits.isPTDirty = false;
682 }
683
684 // CharWriter::StartPrint() 後は、DispStringBuf::IsGeneratedCommand() は false になる。
685 if (! m_pDispStringBuf->IsGeneratedCommand() && pDrawer)
686 {
687 // 文字列のコマンドキャッシュを作成
688 pDrawer->BuildTextCommand(&writer);
689 }
690 }
691 #endif
692
693 void
SetupTextWriter(font::WideTextWriter * pWriter)694 TextBox::SetupTextWriter(font::WideTextWriter* pWriter)
695 {
696 SetFontInfo(pWriter);
697 SetTextPos(pWriter);
698
699 ut::Color8 topCol = m_TextColors[TEXTCOLOR_TOP];
700 ut::Color8 btmCol = m_TextColors[TEXTCOLOR_BOTTOM];
701 pWriter->SetGradationMode(topCol != btmCol ? font::CharWriter::GRADMODE_V: font::CharWriter::GRADMODE_NONE);
702 pWriter->SetTextColor(topCol, btmCol);
703 }
704
705 } // namespace nw::lyt
706 } // namespace nw
707