2019-09-22 21:52:05 +00:00
|
|
|
#include "Runtime/GuiSys/CGuiTextSupport.hpp"
|
|
|
|
|
|
|
|
#include "Runtime/CSimplePool.hpp"
|
|
|
|
#include "Runtime/Graphics/CGraphics.hpp"
|
|
|
|
#include "Runtime/Graphics/CGraphicsPalette.hpp"
|
|
|
|
#include "Runtime/GuiSys/CFontImageDef.hpp"
|
|
|
|
#include "Runtime/GuiSys/CGuiSys.hpp"
|
|
|
|
#include "Runtime/GuiSys/CRasterFont.hpp"
|
|
|
|
#include "Runtime/GuiSys/CTextExecuteBuffer.hpp"
|
|
|
|
#include "Runtime/GuiSys/CTextParser.hpp"
|
2016-03-19 00:07:31 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
namespace urde {
|
|
|
|
|
|
|
|
CGuiTextSupport::CGuiTextSupport(CAssetId fontId, const CGuiTextProperties& props, const zeus::CColor& fontCol,
|
|
|
|
const zeus::CColor& outlineCol, const zeus::CColor& geomCol, s32 padX, s32 padY,
|
|
|
|
CSimplePool* store, CGuiWidget::EGuiModelDrawFlags drawFlags)
|
|
|
|
: x14_props(props)
|
|
|
|
, x24_fontColor(fontCol)
|
|
|
|
, x28_outlineColor(outlineCol)
|
|
|
|
, x2c_geometryColor(geomCol)
|
|
|
|
, x34_extentX(padX)
|
|
|
|
, x38_extentY(padY)
|
|
|
|
, x5c_fontId(fontId)
|
|
|
|
, m_drawFlags(drawFlags) {
|
|
|
|
x2cc_font = store->GetObj({SBIG('FONT'), fontId});
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 01:33:41 +00:00
|
|
|
CTextRenderBuffer* CGuiTextSupport::GetCurrentPageRenderBuffer() {
|
|
|
|
if (x60_renderBuf && !x308_multipageFlag) {
|
|
|
|
return &*x60_renderBuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!x308_multipageFlag || x2ec_renderBufferPages.size() <= x304_pageCounter) {
|
2016-12-16 23:05:29 +00:00
|
|
|
return nullptr;
|
2020-03-30 01:33:41 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
int idx = 0;
|
2020-03-30 01:33:41 +00:00
|
|
|
for (CTextRenderBuffer& buf : x2ec_renderBufferPages) {
|
|
|
|
if (idx++ == x304_pageCounter) {
|
|
|
|
return &buf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
const CTextRenderBuffer* CGuiTextSupport::GetCurrentPageRenderBuffer() const {
|
|
|
|
if (x60_renderBuf && !x308_multipageFlag) {
|
|
|
|
return &*x60_renderBuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!x308_multipageFlag || x2ec_renderBufferPages.size() <= x304_pageCounter) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int idx = 0;
|
|
|
|
for (const CTextRenderBuffer& buf : x2ec_renderBufferPages) {
|
|
|
|
if (idx++ == x304_pageCounter) {
|
|
|
|
return &buf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
return nullptr;
|
2016-12-16 23:05:29 +00:00
|
|
|
}
|
2016-03-21 05:02:56 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
float CGuiTextSupport::GetCurrentAnimationOverAge() const {
|
|
|
|
float ret = 0.f;
|
2020-03-30 01:33:41 +00:00
|
|
|
if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {
|
2018-12-08 05:30:43 +00:00
|
|
|
if (x50_typeEnable) {
|
|
|
|
if (x40_primStartTimes.size()) {
|
2020-05-08 22:41:27 +00:00
|
|
|
const auto& lastTime = x40_primStartTimes.back();
|
2018-12-08 05:30:43 +00:00
|
|
|
ret = std::max(ret, (buf->GetPrimitiveCount() - lastTime.second) / x58_chRate + lastTime.first);
|
|
|
|
} else {
|
|
|
|
ret = std::max(ret, buf->GetPrimitiveCount() / x58_chRate);
|
|
|
|
}
|
2016-03-21 22:01:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
return ret;
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
float CGuiTextSupport::GetNumCharsTotal() const {
|
2020-03-30 01:33:41 +00:00
|
|
|
if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {
|
|
|
|
if (x50_typeEnable) {
|
2018-12-08 05:30:43 +00:00
|
|
|
return buf->GetPrimitiveCount();
|
2020-03-30 01:33:41 +00:00
|
|
|
}
|
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
return 0.f;
|
2017-01-09 03:44:00 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
float CGuiTextSupport::GetNumCharsPrinted() const {
|
2020-03-30 01:33:41 +00:00
|
|
|
if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {
|
2018-12-08 05:30:43 +00:00
|
|
|
if (x50_typeEnable) {
|
2020-03-30 01:33:41 +00:00
|
|
|
const float charsPrinted = x3c_curTime * x58_chRate;
|
2018-12-08 05:30:43 +00:00
|
|
|
return std::min(charsPrinted, float(buf->GetPrimitiveCount()));
|
2016-12-16 23:05:29 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
return 0.f;
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
float CGuiTextSupport::GetTotalAnimationTime() const {
|
2020-03-30 01:33:41 +00:00
|
|
|
if (const CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {
|
|
|
|
if (x50_typeEnable) {
|
2018-12-08 05:30:43 +00:00
|
|
|
return buf->GetPrimitiveCount() / x58_chRate;
|
2020-03-30 01:33:41 +00:00
|
|
|
}
|
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
return 0.f;
|
2016-12-16 04:35:49 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
bool CGuiTextSupport::IsAnimationDone() const { return x3c_curTime >= GetTotalAnimationTime(); }
|
|
|
|
|
|
|
|
void CGuiTextSupport::SetTypeWriteEffectOptions(bool enable, float chFadeTime, float chRate) {
|
|
|
|
x50_typeEnable = enable;
|
|
|
|
x54_chFadeTime = std::max(chFadeTime, 0.0001f);
|
|
|
|
x58_chRate = std::max(chRate, 1.f);
|
|
|
|
if (enable) {
|
|
|
|
if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {
|
|
|
|
float chStartTime = 0.f;
|
2019-06-12 02:05:17 +00:00
|
|
|
for (u32 i = 0; i < buf->GetPrimitiveCount(); ++i) {
|
2018-12-08 05:30:43 +00:00
|
|
|
for (const std::pair<float, int>& p : x40_primStartTimes) {
|
|
|
|
if (p.second < i)
|
|
|
|
continue;
|
|
|
|
if (p.second != i)
|
|
|
|
break;
|
|
|
|
chStartTime = p.first;
|
|
|
|
break;
|
2018-11-24 08:09:35 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
buf->SetPrimitiveOpacity(i, std::min(std::max(0.f, (x3c_curTime - chStartTime) / x54_chFadeTime), 1.f));
|
|
|
|
chStartTime += 1.f / x58_chRate;
|
|
|
|
}
|
2018-11-24 08:09:35 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::Update(float dt) {
|
|
|
|
if (x50_typeEnable) {
|
|
|
|
if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {
|
|
|
|
float chStartTime = 0.f;
|
2019-06-12 02:05:17 +00:00
|
|
|
for (u32 i = 0; i < buf->GetPrimitiveCount(); ++i) {
|
2018-12-08 05:30:43 +00:00
|
|
|
for (const std::pair<float, int>& p : x40_primStartTimes) {
|
|
|
|
if (p.second < i)
|
|
|
|
continue;
|
|
|
|
if (p.second != i)
|
|
|
|
break;
|
|
|
|
chStartTime = p.first;
|
|
|
|
break;
|
2016-03-21 22:01:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
buf->SetPrimitiveOpacity(i, std::min(std::max(0.f, (x3c_curTime - chStartTime) / x54_chFadeTime), 1.f));
|
|
|
|
chStartTime += 1.f / x58_chRate;
|
|
|
|
}
|
2016-03-21 22:01:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
x3c_curTime += dt;
|
|
|
|
}
|
2016-03-21 22:01:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
x10_curTimeMod900 = std::fmod(x10_curTimeMod900 + dt, 900.f);
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::ClearRenderBuffer() {
|
2019-06-12 02:05:17 +00:00
|
|
|
x60_renderBuf = std::nullopt;
|
2018-12-08 05:30:43 +00:00
|
|
|
x2ec_renderBufferPages.clear();
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::CheckAndRebuildTextBuffer() {
|
|
|
|
g_TextExecuteBuf->Clear();
|
|
|
|
g_TextExecuteBuf->x18_textState.x7c_enableWordWrap = x14_props.x0_wordWrap;
|
|
|
|
g_TextExecuteBuf->BeginBlock(0, 0, x34_extentX, x38_extentY, x30_imageBaseline,
|
|
|
|
ETextDirection(!x14_props.x1_horizontal), x14_props.x4_justification,
|
|
|
|
x14_props.x8_vertJustification);
|
|
|
|
g_TextExecuteBuf->AddColor(EColorType::Main, x24_fontColor);
|
|
|
|
g_TextExecuteBuf->AddColor(EColorType::Outline, x28_outlineColor);
|
2016-03-21 22:01:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
std::u16string initStr;
|
|
|
|
if (x5c_fontId.IsValid())
|
2020-04-11 22:51:39 +00:00
|
|
|
initStr = fmt::format(FMT_STRING(u"&font={};"), x5c_fontId);
|
2018-12-08 05:30:43 +00:00
|
|
|
initStr += x0_string;
|
2016-03-21 22:01:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
g_TextParser->ParseText(*g_TextExecuteBuf, initStr.c_str(), initStr.size(), x14_props.xc_txtrMap);
|
2016-03-21 22:01:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
g_TextExecuteBuf->EndBlock();
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
bool CGuiTextSupport::CheckAndRebuildRenderBuffer() {
|
|
|
|
if (x308_multipageFlag || x60_renderBuf)
|
|
|
|
if (!x308_multipageFlag || x2ec_renderBufferPages.size())
|
|
|
|
return true;
|
2016-12-30 06:37:01 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
CheckAndRebuildTextBuffer();
|
|
|
|
x2bc_assets = g_TextExecuteBuf->GetAssets();
|
2016-12-30 06:37:01 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
if (!_GetIsTextSupportFinishedLoading())
|
|
|
|
return false;
|
2016-12-30 06:37:01 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
CheckAndRebuildTextBuffer();
|
|
|
|
if (x308_multipageFlag) {
|
|
|
|
zeus::CVector2i extent(x34_extentX, x38_extentY);
|
|
|
|
x2ec_renderBufferPages = g_TextExecuteBuf->BuildRenderBufferPages(extent, m_drawFlags);
|
|
|
|
} else {
|
|
|
|
x60_renderBuf.emplace(g_TextExecuteBuf->BuildRenderBuffer(m_drawFlags));
|
|
|
|
x2dc_oneBufBounds = x60_renderBuf->AccumulateTextBounds();
|
|
|
|
}
|
|
|
|
g_TextExecuteBuf->Clear();
|
|
|
|
Update(0.f);
|
|
|
|
|
|
|
|
return true;
|
2016-12-31 00:51:51 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
const std::pair<zeus::CVector2i, zeus::CVector2i>& CGuiTextSupport::GetBounds() {
|
|
|
|
CheckAndRebuildRenderBuffer();
|
|
|
|
return x2dc_oneBufBounds;
|
2016-12-31 00:51:51 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::AutoSetExtent() {
|
2020-04-23 06:36:45 +00:00
|
|
|
const auto& bounds = GetBounds();
|
2018-12-08 05:30:43 +00:00
|
|
|
x34_extentX = bounds.second.x;
|
|
|
|
x38_extentY = bounds.second.y;
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 08:36:52 +00:00
|
|
|
void CGuiTextSupport::Render() {
|
|
|
|
CheckAndRebuildRenderBuffer();
|
2018-12-08 05:30:43 +00:00
|
|
|
if (CTextRenderBuffer* buf = GetCurrentPageRenderBuffer()) {
|
2020-04-14 17:34:47 +00:00
|
|
|
SCOPED_GRAPHICS_DEBUG_GROUP("CGuiTextSupport::Render", zeus::skBlue);
|
2018-12-08 05:30:43 +00:00
|
|
|
zeus::CTransform oldModel = CGraphics::g_GXModelMatrix;
|
|
|
|
CGraphics::SetModelMatrix(oldModel * zeus::CTransform::Scale(1.f, 1.f, -1.f));
|
|
|
|
buf->Render(x2c_geometryColor, x10_curTimeMod900);
|
|
|
|
CGraphics::SetModelMatrix(oldModel);
|
|
|
|
}
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetGeometryColor(const zeus::CColor& col) { x2c_geometryColor = col; }
|
2016-03-21 05:02:56 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetOutlineColor(const zeus::CColor& col) {
|
2020-04-23 06:39:37 +00:00
|
|
|
if (col == x28_outlineColor) {
|
|
|
|
return;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2020-04-23 06:39:37 +00:00
|
|
|
|
|
|
|
ClearRenderBuffer();
|
|
|
|
x28_outlineColor = col;
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetFontColor(const zeus::CColor& col) {
|
2020-04-23 06:39:37 +00:00
|
|
|
if (col == x24_fontColor) {
|
|
|
|
return;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2020-04-23 06:39:37 +00:00
|
|
|
|
|
|
|
ClearRenderBuffer();
|
|
|
|
x24_fontColor = col;
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::AddText(std::u16string_view str) {
|
|
|
|
if (x60_renderBuf) {
|
2020-01-28 05:22:16 +00:00
|
|
|
const float t = GetCurrentAnimationOverAge();
|
|
|
|
x40_primStartTimes.emplace_back(std::max(t, x3c_curTime), x60_renderBuf->GetPrimitiveCount());
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
x0_string += str;
|
|
|
|
ClearRenderBuffer();
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetText(std::u16string_view str, bool multipage) {
|
2020-04-23 06:39:37 +00:00
|
|
|
if (x0_string == str) {
|
|
|
|
return;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2020-04-23 06:39:37 +00:00
|
|
|
|
|
|
|
x40_primStartTimes.clear();
|
|
|
|
x3c_curTime = 0.f;
|
|
|
|
x0_string = str;
|
|
|
|
ClearRenderBuffer();
|
|
|
|
x308_multipageFlag = multipage;
|
|
|
|
x304_pageCounter = 0;
|
2016-03-21 05:02:56 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetText(std::string_view str, bool multipage) { SetText(hecl::UTF8ToChar16(str), multipage); }
|
2017-01-30 04:16:20 +00:00
|
|
|
|
2020-03-30 01:33:41 +00:00
|
|
|
bool CGuiTextSupport::_GetIsTextSupportFinishedLoading() {
|
|
|
|
for (CToken& tok : x2bc_assets) {
|
|
|
|
tok.Lock();
|
|
|
|
|
|
|
|
if (!tok.IsLoaded()) {
|
2018-12-08 05:30:43 +00:00
|
|
|
return false;
|
2020-03-30 01:33:41 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2020-03-30 01:33:41 +00:00
|
|
|
|
|
|
|
if (!x2cc_font) {
|
2018-12-08 05:30:43 +00:00
|
|
|
return true;
|
2020-03-30 01:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (x2cc_font.IsLoaded()) {
|
2018-12-08 05:30:43 +00:00
|
|
|
return x2cc_font->IsFinishedLoading();
|
2020-03-30 01:33:41 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
return false;
|
2017-01-30 04:16:20 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetJustification(EJustification j) {
|
2020-04-23 06:39:37 +00:00
|
|
|
if (j == x14_props.x4_justification) {
|
|
|
|
return;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2020-04-23 06:39:37 +00:00
|
|
|
|
|
|
|
x14_props.x4_justification = j;
|
|
|
|
ClearRenderBuffer();
|
2017-01-30 04:16:20 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetVerticalJustification(EVerticalJustification j) {
|
2020-04-23 06:39:37 +00:00
|
|
|
if (j == x14_props.x8_vertJustification) {
|
|
|
|
return;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2020-04-23 06:39:37 +00:00
|
|
|
|
|
|
|
x14_props.x8_vertJustification = j;
|
|
|
|
ClearRenderBuffer();
|
2016-03-19 00:07:31 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetImageBaseline(bool b) {
|
2020-04-23 06:39:37 +00:00
|
|
|
if (b == x30_imageBaseline) {
|
|
|
|
return;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2020-04-23 06:39:37 +00:00
|
|
|
|
|
|
|
x30_imageBaseline = b;
|
|
|
|
ClearRenderBuffer();
|
2016-12-30 06:37:01 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 01:33:41 +00:00
|
|
|
bool CGuiTextSupport::GetIsTextSupportFinishedLoading() {
|
|
|
|
CheckAndRebuildRenderBuffer();
|
2018-12-08 05:30:43 +00:00
|
|
|
return _GetIsTextSupportFinishedLoading();
|
2017-04-02 03:03:37 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetControlTXTRMap(const std::vector<std::pair<CAssetId, CAssetId>>* txtrMap) {
|
2020-04-23 06:39:37 +00:00
|
|
|
if (x14_props.xc_txtrMap == txtrMap) {
|
|
|
|
return;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2020-04-23 06:39:37 +00:00
|
|
|
|
|
|
|
x14_props.xc_txtrMap = txtrMap;
|
|
|
|
ClearRenderBuffer();
|
2017-05-07 19:35:52 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
int CGuiTextSupport::GetTotalPageCount() {
|
|
|
|
if (CheckAndRebuildRenderBuffer())
|
|
|
|
return x2ec_renderBufferPages.size();
|
|
|
|
return -1;
|
2017-05-07 19:35:52 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void CGuiTextSupport::SetPage(int page) {
|
|
|
|
x304_pageCounter = page;
|
|
|
|
x40_primStartTimes.clear();
|
|
|
|
x3c_curTime = 0.f;
|
2016-03-19 00:07:31 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
} // namespace urde
|