libgui/source/gui/GuiText.cpp

558 lines
15 KiB
C++
Raw Normal View History

2017-10-29 10:28:14 +01:00
/****************************************************************************
* Copyright (C) 2015 Dimok
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
2018-06-21 20:44:58 +02:00
#include <gui/GuiText.h>
#include <gui/FreeTypeGX.h>
2019-08-14 23:24:55 +02:00
#include <gui/video/CVideo.h>
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
FreeTypeGX *GuiText::presentFont = NULL;
2018-06-21 20:44:58 +02:00
int32_t GuiText::presetSize = 28;
2017-10-29 10:28:14 +01:00
float GuiText::presetInternalRenderingScale = 2.0f; //Lets render the font at the doubled size. This make it even smoother!
2018-06-21 20:44:58 +02:00
int32_t GuiText::presetMaxWidth = 0xFFFF;
int32_t GuiText::presetAlignment = ALIGN_CENTER | ALIGN_MIDDLE;
GX2ColorF32 GuiText::presetColor = (GX2ColorF32) {
2020-08-13 12:38:07 +02:00
1.0f, 1.0f, 1.0f, 1.0f
2018-06-21 20:44:58 +02:00
};
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
#define TEXT_SCROLL_DELAY 6
#define TEXT_SCROLL_INITIAL_DELAY 10
#define MAX_LINES_TO_DRAW 10
2017-10-29 10:28:14 +01:00
/**
* Constructor for the GuiText class.
*/
2018-06-21 20:44:58 +02:00
GuiText::GuiText() {
text = NULL;
size = presetSize;
currentSize = size;
color = glm::vec4(presetColor.r, presetColor.g, presetColor.b, presetColor.a);
alpha = presetColor.a;
alignment = presetAlignment;
maxWidth = presetMaxWidth;
wrapMode = 0;
textWidth = 0;
font = presentFont;
linestodraw = MAX_LINES_TO_DRAW;
textScrollPos = 0;
textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY;
textScrollDelay = TEXT_SCROLL_DELAY;
defaultBlur = 4.0f;
blurGlowIntensity = 0.0f;
blurAlpha = 0.0f;
blurGlowColor = glm::vec4(0.0f);
internalRenderingScale = presetInternalRenderingScale;
2017-10-29 10:28:14 +01:00
}
2020-08-13 12:38:07 +02:00
GuiText::GuiText(const char *t, int32_t s, const glm::vec4 &c) {
2018-06-21 20:44:58 +02:00
text = NULL;
size = s;
currentSize = size;
color = c;
alpha = c[3];
alignment = ALIGN_CENTER | ALIGN_MIDDLE;
maxWidth = presetMaxWidth;
wrapMode = 0;
textWidth = 0;
font = presentFont;
linestodraw = MAX_LINES_TO_DRAW;
textScrollPos = 0;
textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY;
textScrollDelay = TEXT_SCROLL_DELAY;
defaultBlur = 4.0f;
blurGlowIntensity = 0.0f;
blurAlpha = 0.0f;
blurGlowColor = glm::vec4(0.0f);
internalRenderingScale = presetInternalRenderingScale;
2020-08-13 12:38:07 +02:00
if (t) {
2018-06-21 20:44:58 +02:00
text = FreeTypeGX::charToWideChar(t);
2020-08-13 12:38:07 +02:00
if (!text)
2018-06-21 20:44:58 +02:00
return;
textWidth = font->getWidth(text, currentSize);
}
2017-10-29 10:28:14 +01:00
}
2020-08-13 12:38:07 +02:00
GuiText::GuiText(const wchar_t *t, int32_t s, const glm::vec4 &c) {
2018-06-21 20:44:58 +02:00
text = NULL;
size = s;
currentSize = size;
color = c;
alpha = c[3];
alignment = ALIGN_CENTER | ALIGN_MIDDLE;
maxWidth = presetMaxWidth;
wrapMode = 0;
textWidth = 0;
font = presentFont;
linestodraw = MAX_LINES_TO_DRAW;
textScrollPos = 0;
textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY;
textScrollDelay = TEXT_SCROLL_DELAY;
defaultBlur = 4.0f;
blurGlowIntensity = 0.0f;
blurAlpha = 0.0f;
blurGlowColor = glm::vec4(0.0f);
internalRenderingScale = presetInternalRenderingScale;
2020-08-13 12:38:07 +02:00
if (t) {
text = new(std::nothrow) wchar_t[wcslen(t) + 1];
if (!text)
2018-06-21 20:44:58 +02:00
return;
wcscpy(text, t);
textWidth = font->getWidth(text, currentSize);
}
2017-10-29 10:28:14 +01:00
}
/**
* Constructor for the GuiText class, uses presets
*/
2020-08-13 12:38:07 +02:00
GuiText::GuiText(const char *t) {
2018-06-21 20:44:58 +02:00
text = NULL;
size = presetSize;
currentSize = size;
color = glm::vec4(presetColor.r, presetColor.g, presetColor.b, presetColor.a);
alpha = presetColor.a;
alignment = presetAlignment;
maxWidth = presetMaxWidth;
wrapMode = 0;
textWidth = 0;
font = presentFont;
linestodraw = MAX_LINES_TO_DRAW;
textScrollPos = 0;
textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY;
textScrollDelay = TEXT_SCROLL_DELAY;
defaultBlur = 4.0f;
blurGlowIntensity = 0.0f;
blurAlpha = 0.0f;
blurGlowColor = glm::vec4(0.0f);
internalRenderingScale = presetInternalRenderingScale;
2020-08-13 12:38:07 +02:00
if (t) {
2018-06-21 20:44:58 +02:00
text = FreeTypeGX::charToWideChar(t);
2020-08-13 12:38:07 +02:00
if (!text)
2018-06-21 20:44:58 +02:00
return;
textWidth = font->getWidth(text, currentSize);
}
2017-10-29 10:28:14 +01:00
}
/**
* Destructor for the GuiText class.
*/
2018-06-21 20:44:58 +02:00
GuiText::~GuiText() {
2020-08-13 12:38:07 +02:00
if (text)
delete[] text;
2018-06-21 20:44:58 +02:00
text = NULL;
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
clearDynamicText();
2017-10-29 10:28:14 +01:00
}
2020-08-13 12:38:07 +02:00
void GuiText::setText(const char *t) {
if (text)
delete[] text;
2018-06-21 20:44:58 +02:00
text = NULL;
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
clearDynamicText();
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
textScrollPos = 0;
textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY;
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (t) {
2018-06-21 20:44:58 +02:00
text = FreeTypeGX::charToWideChar(t);
2020-08-13 12:38:07 +02:00
if (!text)
2018-06-21 20:44:58 +02:00
return;
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
textWidth = font->getWidth(text, currentSize);
}
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
void GuiText::setTextf(const char *format, ...) {
2020-08-13 12:38:07 +02:00
if (!format) {
2018-06-21 20:44:58 +02:00
setText((char *) NULL);
return;
}
int32_t max_len = strlen(format) + 8192;
char *tmp = new char[max_len];
va_list va;
va_start(va, format);
2020-08-13 12:38:07 +02:00
if ((vsnprintf(tmp, max_len, format, va) >= 0) && tmp) {
2018-06-21 20:44:58 +02:00
setText(tmp);
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
va_end(va);
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (tmp)
delete[] tmp;
2017-10-29 10:28:14 +01:00
}
2020-08-13 12:38:07 +02:00
void GuiText::setText(const wchar_t *t) {
if (text)
delete[] text;
2018-06-21 20:44:58 +02:00
text = NULL;
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
clearDynamicText();
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
textScrollPos = 0;
textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY;
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (t) {
text = new(std::nothrow) wchar_t[wcslen(t) + 1];
if (!text)
2018-06-21 20:44:58 +02:00
return;
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
wcscpy(text, t);
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
textWidth = font->getWidth(text, currentSize);
}
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
void GuiText::clearDynamicText() {
2020-08-13 12:38:07 +02:00
for (uint32_t i = 0; i < textDyn.size(); i++) {
if (textDyn[i])
delete[] textDyn[i];
2018-06-21 20:44:58 +02:00
}
textDyn.clear();
textDynWidth.clear();
2017-10-29 10:28:14 +01:00
}
2020-08-13 12:38:07 +02:00
void GuiText::setPresets(int32_t sz, const glm::vec4 &c, int32_t w, int32_t a) {
2018-06-21 20:44:58 +02:00
presetSize = sz;
presetColor = (GX2ColorF32) {
2020-08-13 12:38:07 +02:00
(float) c.r / 255.0f, (float) c.g / 255.0f, (float) c.b / 255.0f, (float) c.a / 255.0f
2018-06-21 20:44:58 +02:00
};
presetMaxWidth = w;
presetAlignment = a;
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
void GuiText::setPresetFont(FreeTypeGX *f) {
presentFont = f;
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
void GuiText::setFontSize(int32_t s) {
size = s;
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
void GuiText::setMaxWidth(int32_t width, int32_t w) {
maxWidth = width;
wrapMode = w;
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (w == SCROLL_HORIZONTAL) {
2018-06-21 20:44:58 +02:00
textScrollPos = 0;
textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY;
textScrollDelay = TEXT_SCROLL_DELAY;
}
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
clearDynamicText();
2017-10-29 10:28:14 +01:00
}
2020-08-13 12:38:07 +02:00
void GuiText::setColor(const glm::vec4 &c) {
2018-06-21 20:44:58 +02:00
color = c;
alpha = c[3];
2017-10-29 10:28:14 +01:00
}
2020-08-13 12:38:07 +02:00
void GuiText::setBlurGlowColor(float blur, const glm::vec4 &c) {
2018-06-21 20:44:58 +02:00
blurGlowColor = c;
blurGlowIntensity = blur;
blurAlpha = c[3];
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
int32_t GuiText::getTextWidth(int32_t ind) {
2020-08-13 12:38:07 +02:00
if (ind < 0 || ind >= (int32_t) textDyn.size())
2018-06-21 20:44:58 +02:00
return this->getTextWidth();
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
return font->getWidth(textDyn[ind], currentSize);
2017-10-29 10:28:14 +01:00
}
2020-08-13 12:38:07 +02:00
const wchar_t *GuiText::getDynText(int32_t ind) {
if (ind < 0 || ind >= (int32_t) textDyn.size())
2018-06-21 20:44:58 +02:00
return text;
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
return textDyn[ind];
2017-10-29 10:28:14 +01:00
}
/**
* Change font
*/
2018-06-21 20:44:58 +02:00
bool GuiText::setFont(FreeTypeGX *f) {
2020-08-13 12:38:07 +02:00
if (!f)
2018-06-21 20:44:58 +02:00
return false;
font = f;
textWidth = font->getWidth(text, currentSize);
return true;
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
std::string GuiText::toUTF8(void) const {
2020-08-13 12:38:07 +02:00
if (!text)
2018-06-21 20:44:58 +02:00
return std::string();
2017-10-29 10:28:14 +01:00
char *pUtf8 = FreeTypeGX::wideCharToUTF8(text);
2020-08-13 12:38:07 +02:00
if (!pUtf8)
2018-06-21 20:44:58 +02:00
return std::string();
2017-10-29 10:28:14 +01:00
std::string strOutput(pUtf8);
2020-08-13 12:38:07 +02:00
delete[] pUtf8;
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
return strOutput;
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
void GuiText::makeDottedText() {
int32_t pos = textDyn.size();
textDyn.resize(pos + 1);
int32_t i = 0, currentWidth = 0;
2020-08-13 12:38:07 +02:00
textDyn[pos] = new(std::nothrow) wchar_t[maxWidth];
if (!textDyn[pos]) {
2018-06-21 20:44:58 +02:00
textDyn.resize(pos);
return;
}
while (text[i]) {
currentWidth += font->getCharWidth(text[i], currentSize, i > 0 ? text[i - 1] : 0);
if (currentWidth >= maxWidth && i > 2) {
textDyn[pos][i - 2] = '.';
textDyn[pos][i - 1] = '.';
textDyn[pos][i] = '.';
i++;
break;
}
textDyn[pos][i] = text[i];
i++;
}
textDyn[pos][i] = 0;
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
void GuiText::scrollText(uint32_t frameCount) {
if (textDyn.size() == 0) {
int32_t pos = textDyn.size();
int32_t i = 0, currentWidth = 0;
textDyn.resize(pos + 1);
2020-08-13 12:38:07 +02:00
textDyn[pos] = new(std::nothrow) wchar_t[maxWidth];
if (!textDyn[pos]) {
2018-06-21 20:44:58 +02:00
textDyn.resize(pos);
return;
}
while (text[i] && currentWidth < maxWidth) {
textDyn[pos][i] = text[i];
currentWidth += font->getCharWidth(text[i], currentSize, i > 0 ? text[i - 1] : 0);
++i;
}
textDyn[pos][i] = 0;
return;
}
if (frameCount % textScrollDelay != 0) {
return;
}
if (textScrollInitialDelay) {
--textScrollInitialDelay;
return;
}
int32_t stringlen = wcslen(text);
++textScrollPos;
if (textScrollPos > stringlen) {
textScrollPos = 0;
textScrollInitialDelay = TEXT_SCROLL_INITIAL_DELAY;
}
int32_t ch = textScrollPos;
int32_t pos = textDyn.size() - 1;
if (!textDyn[pos])
2020-08-13 12:38:07 +02:00
textDyn[pos] = new(std::nothrow) wchar_t[maxWidth];
2018-06-21 20:44:58 +02:00
2020-08-13 12:38:07 +02:00
if (!textDyn[pos]) {
2018-06-21 20:44:58 +02:00
textDyn.resize(pos);
return;
}
int32_t i = 0, currentWidth = 0;
while (currentWidth < maxWidth) {
if (ch > stringlen - 1) {
textDyn[pos][i++] = ' ';
currentWidth += font->getCharWidth(L' ', currentSize, ch > 0 ? text[ch - 1] : 0);
textDyn[pos][i++] = ' ';
currentWidth += font->getCharWidth(L' ', currentSize, L' ');
textDyn[pos][i++] = ' ';
currentWidth += font->getCharWidth(L' ', currentSize, L' ');
ch = 0;
2020-08-13 12:38:07 +02:00
if (currentWidth >= maxWidth)
2018-06-21 20:44:58 +02:00
break;
}
textDyn[pos][i] = text[ch];
currentWidth += font->getCharWidth(text[ch], currentSize, ch > 0 ? text[ch - 1] : 0);
++ch;
++i;
}
textDyn[pos][i] = 0;
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
void GuiText::wrapText() {
if (textDyn.size() > 0) return;
int32_t i = 0;
int32_t ch = 0;
int32_t linenum = 0;
int32_t lastSpace = -1;
int32_t lastSpaceIndex = -1;
int32_t currentWidth = 0;
while (text[ch] && linenum < linestodraw) {
if (linenum >= (int32_t) textDyn.size()) {
textDyn.resize(linenum + 1);
2020-08-13 12:38:07 +02:00
textDyn[linenum] = new(std::nothrow) wchar_t[maxWidth];
if (!textDyn[linenum]) {
2018-06-21 20:44:58 +02:00
textDyn.resize(linenum);
break;
}
}
textDyn[linenum][i] = text[ch];
textDyn[linenum][i + 1] = 0;
currentWidth += font->getCharWidth(text[ch], currentSize, ch > 0 ? text[ch - 1] : 0x0000);
if (currentWidth >= maxWidth || (text[ch] == '\n')) {
2020-08-13 12:38:07 +02:00
if (text[ch] == '\n') {
2018-06-21 20:44:58 +02:00
lastSpace = -1;
lastSpaceIndex = -1;
} else if (lastSpace >= 0) {
textDyn[linenum][lastSpaceIndex] = 0; // discard space, and everything after
ch = lastSpace; // go backwards to the last space
lastSpace = -1; // we have used this space
lastSpaceIndex = -1;
2017-10-29 10:28:14 +01:00
}
2018-06-21 20:44:58 +02:00
if (linenum + 1 == linestodraw && text[ch + 1] != 0x0000) {
2020-08-13 12:38:07 +02:00
if (i < 2)
2017-10-29 10:28:14 +01:00
i = 2;
2018-06-21 20:44:58 +02:00
textDyn[linenum][i - 2] = '.';
textDyn[linenum][i - 1] = '.';
textDyn[linenum][i] = '.';
textDyn[linenum][i + 1] = 0;
}
currentWidth = 0;
++linenum;
i = -1;
}
if (text[ch] == ' ' && i >= 0) {
lastSpace = ch;
lastSpaceIndex = i;
}
++ch;
++i;
}
2017-10-29 10:28:14 +01:00
}
/**
* Draw the text on screen
*/
2018-06-21 20:44:58 +02:00
void GuiText::draw(CVideo *pVideo) {
2020-08-13 12:38:07 +02:00
if (!text)
2018-06-21 20:44:58 +02:00
return;
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (!isVisible())
2018-06-21 20:44:58 +02:00
return;
2017-10-29 10:28:14 +01:00
color[3] = getAlpha();
blurGlowColor[3] = blurAlpha * getAlpha();
float finalRenderingScale = 2.0f * internalRenderingScale;
2018-06-21 20:44:58 +02:00
int32_t newSize = size * getScale() * finalRenderingScale;
int32_t normal_size = size * getScale();
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (newSize != currentSize) {
2018-06-21 20:44:58 +02:00
currentSize = normal_size;
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (text)
2018-06-21 20:44:58 +02:00
textWidth = font->getWidth(text, normal_size);
}
2017-10-29 10:28:14 +01:00
2018-06-21 20:44:58 +02:00
float x_pos = getCenterX() * finalRenderingScale;
float y_pos = getCenterY() * finalRenderingScale;
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (maxWidth > 0 && maxWidth <= textWidth) {
if (wrapMode == DOTTED) { // text dotted
if (textDyn.size() == 0)
2018-06-21 20:44:58 +02:00
makeDottedText();
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (textDynWidth.size() != textDyn.size()) {
2017-10-29 10:28:14 +01:00
textDynWidth.resize(textDyn.size());
2020-08-13 12:38:07 +02:00
for (uint32_t i = 0; i < textDynWidth.size(); i++)
2017-10-29 10:28:14 +01:00
textDynWidth[i] = font->getWidth(textDyn[i], newSize);
}
2020-08-13 12:38:07 +02:00
if (textDyn.size() > 0)
font->drawText(pVideo, x_pos, y_pos, getDepth(), textDyn[textDyn.size() - 1], newSize, color, alignment, textDynWidth[textDyn.size() - 1], defaultBlur, blurGlowIntensity, blurGlowColor, finalRenderingScale);
} else if (wrapMode == SCROLL_HORIZONTAL) {
2018-06-21 20:44:58 +02:00
scrollText(pVideo->getFrameCount());
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (textDyn.size() > 0)
font->drawText(pVideo, x_pos, y_pos, getDepth(), textDyn[textDyn.size() - 1], newSize, color, alignment, maxWidth * finalRenderingScale, defaultBlur, blurGlowIntensity, blurGlowColor, finalRenderingScale);
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
} else if (wrapMode == WRAP) {
2018-06-21 20:44:58 +02:00
int32_t lineheight = newSize + 6;
int32_t yoffset = 0;
int32_t voffset = 0;
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (textDyn.size() == 0)
2018-06-21 20:44:58 +02:00
wrapText();
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
if (textDynWidth.size() != textDyn.size()) {
2017-10-29 10:28:14 +01:00
textDynWidth.resize(textDyn.size());
2020-08-13 12:38:07 +02:00
for (uint32_t i = 0; i < textDynWidth.size(); i++)
2017-10-29 10:28:14 +01:00
textDynWidth[i] = font->getWidth(textDyn[i], newSize);
}
2020-08-13 12:38:07 +02:00
if (alignment & ALIGN_MIDDLE)
voffset = (lineheight * (textDyn.size() - 1)) >> 1;
2017-10-29 10:28:14 +01:00
2020-08-13 12:38:07 +02:00
for (uint32_t i = 0; i < textDyn.size(); i++) {
font->drawText(pVideo, x_pos, y_pos + voffset + yoffset, getDepth(), textDyn[i], newSize, color, alignment, textDynWidth[i], defaultBlur, blurGlowIntensity, blurGlowColor, finalRenderingScale);
2017-10-29 10:28:14 +01:00
yoffset -= lineheight;
2018-06-21 20:44:58 +02:00
}
}
} else {
uint16_t newtextWidth = font->getWidth(text, newSize);
2020-08-13 12:38:07 +02:00
font->drawText(pVideo, x_pos, y_pos, getDepth(), text, newSize, color, alignment, newtextWidth, defaultBlur, blurGlowIntensity, blurGlowColor, finalRenderingScale);
2018-06-21 20:44:58 +02:00
}
2017-10-29 10:28:14 +01:00
}