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