Anton Tarasenko
2 years ago
13 changed files with 1674 additions and 267 deletions
@ -0,0 +1,404 @@ |
|||||||
|
/** |
||||||
|
* Object that is created from *formatted string* (or `Text`) and stores |
||||||
|
* information about formatting used in said string. Was introduced instead of |
||||||
|
* a simple method in `MutableText` to: |
||||||
|
* 1. Allow for reporting errors caused by badly specified colors; |
||||||
|
* 2. Allow for a more complicated case of specifying a color gradient |
||||||
|
* range. |
||||||
|
* Copyright 2022 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia 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, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia 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 Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class FormattedStringData extends AcediaObject |
||||||
|
dependson(Text) |
||||||
|
dependson(FormattingErrors) |
||||||
|
dependson(FormattingCommandList); |
||||||
|
|
||||||
|
struct FormattingInfo |
||||||
|
{ |
||||||
|
var bool colored; |
||||||
|
var Color plainColor; |
||||||
|
var bool gradient; |
||||||
|
var array<Color> gradientColors; |
||||||
|
var array<float> gradientPoints; |
||||||
|
var int gradientStart; |
||||||
|
var float gradientLength; |
||||||
|
}; |
||||||
|
// Formatted `string` can have an arbitrary level of folded format definitions, |
||||||
|
// this array is used as a stack to keep track of opened formatting blocks |
||||||
|
// when appending formatted `string`. |
||||||
|
var array<FormattingInfo> formattingStack; |
||||||
|
// Keep top element copied into a separate variable for quicker access. |
||||||
|
// Must maintain invariant: if `formattingStack.length > 0` |
||||||
|
// then `formattingStack[formattingStack.length - 1] == formattingStackHead`. |
||||||
|
var FormattingInfo formattingStackHead; |
||||||
|
|
||||||
|
var private FormattingCommandList commands; |
||||||
|
var private MutableText result; |
||||||
|
var private FormattingErrors errors; |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
formattingStack.length = 0; |
||||||
|
_.memory.Free(commands); |
||||||
|
_.memory.Free(errors); |
||||||
|
_.memory.Free(result); |
||||||
|
commands = none; |
||||||
|
errors = none; |
||||||
|
result = none; |
||||||
|
} |
||||||
|
|
||||||
|
public static final function FormattedStringData FromText( |
||||||
|
Text source, |
||||||
|
optional bool doReportErrors) |
||||||
|
{ |
||||||
|
local FormattedStringData newData; |
||||||
|
if (source == none) { |
||||||
|
return none; |
||||||
|
} |
||||||
|
newData = |
||||||
|
FormattedStringData(__().memory.Allocate(class'FormattedStringData')); |
||||||
|
if (doReportErrors) |
||||||
|
{ |
||||||
|
newData.errors = |
||||||
|
FormattingErrors(__().memory.Allocate(class'FormattingErrors')); |
||||||
|
} |
||||||
|
newData.commands = class'FormattingCommandList'.static |
||||||
|
.FromText(source, newData.errors); |
||||||
|
newData.result = __().text.Empty(); |
||||||
|
newData.BuildSelf(); |
||||||
|
__().memory.Free(newData.commands); |
||||||
|
newData.commands = none; |
||||||
|
return newData; |
||||||
|
} |
||||||
|
|
||||||
|
public final function Text GetResult() |
||||||
|
{ |
||||||
|
return result.Copy(); |
||||||
|
} |
||||||
|
|
||||||
|
public final function MutableText GetResultM() |
||||||
|
{ |
||||||
|
return result.MutableCopy(); |
||||||
|
} |
||||||
|
|
||||||
|
public final function FormattingErrors BorrowErrors() |
||||||
|
{ |
||||||
|
return errors; |
||||||
|
} |
||||||
|
|
||||||
|
private final function BuildSelf() |
||||||
|
{ |
||||||
|
local int i, j, nextCharacterIndex; |
||||||
|
local Text.Formatting defaultFormatting; |
||||||
|
local array<Text.Character> nextContents; |
||||||
|
local FormattingCommandList.FormattingCommand nextCommand; |
||||||
|
SetupFormattingStack(defaultFormatting); |
||||||
|
// First element of color stack is special and has no color information; |
||||||
|
// see `BuildFormattingStackCommands()` for details. |
||||||
|
nextCommand = commands.GetCommand(0); |
||||||
|
nextContents = nextCommand.contents; |
||||||
|
result.AppendManyRawCharacters(nextContents); |
||||||
|
nextCharacterIndex = nextContents.length; |
||||||
|
_.memory.Free(nextCommand.tag); |
||||||
|
for (i = 1; i < commands.GetAmount(); i += 1) |
||||||
|
{ |
||||||
|
nextCommand = commands.GetCommand(i); |
||||||
|
if (nextCommand.type == FST_StackPush) { |
||||||
|
PushIntoFormattingStack(nextCommand); |
||||||
|
} |
||||||
|
else if (nextCommand.type == FST_StackPop) { |
||||||
|
PopFormattingStack(); |
||||||
|
} |
||||||
|
else if (nextCommand.type == FST_StackSwap) { |
||||||
|
SwapFormattingStack(nextCommand.charTag); |
||||||
|
} |
||||||
|
nextContents = nextCommand.contents; |
||||||
|
if (IsCurrentFormattingGradient()) |
||||||
|
{ |
||||||
|
for (j = 0; j < nextContents.length; j += 1) |
||||||
|
{ |
||||||
|
result.AppendRawCharacter(nextContents[j], GetFormattingFor(nextCharacterIndex)); |
||||||
|
nextCharacterIndex += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
result.AppendManyRawCharacters(nextContents, GetFormattingFor(nextCharacterIndex)); |
||||||
|
nextCharacterIndex += nextContents.length; |
||||||
|
} |
||||||
|
_.memory.Free(nextCommand.tag); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Following four functions are to maintain a "color stack" that will |
||||||
|
// remember unclosed colors (new colors are obtained from formatting commands |
||||||
|
// sequence) defined in formatted string, in order. |
||||||
|
// Stack array always contains one element, defined by |
||||||
|
// the `SetupFormattingStack()` call. It corresponds to the default formatting |
||||||
|
// that will be used when we pop all the other elements. |
||||||
|
// It is necessary to deal with possible folded formatting definitions in |
||||||
|
// formatted strings. |
||||||
|
private final function SetupFormattingStack(Text.Formatting defaultFormatting) |
||||||
|
{ |
||||||
|
local FormattingInfo defaultFormattingInfo; |
||||||
|
defaultFormattingInfo.colored = defaultFormatting.isColored; |
||||||
|
defaultFormattingInfo.plainColor = defaultFormatting.color; |
||||||
|
if (formattingStack.length > 0) { |
||||||
|
formattingStack.length = 0; |
||||||
|
} |
||||||
|
formattingStack[0] = defaultFormattingInfo; |
||||||
|
formattingStackHead = defaultFormattingInfo; |
||||||
|
} |
||||||
|
|
||||||
|
private final function bool IsCurrentFormattingGradient() |
||||||
|
{ |
||||||
|
if (formattingStack.length <= 0) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return formattingStackHead.gradient; |
||||||
|
} |
||||||
|
|
||||||
|
private final function Text.Formatting GetFormattingFor(int index) |
||||||
|
{ |
||||||
|
local Text.Formatting emptyFormatting; |
||||||
|
if (formattingStack.length <= 0) return emptyFormatting; |
||||||
|
if (!formattingStackHead.colored) return emptyFormatting; |
||||||
|
|
||||||
|
return _.text.FormattingFromColor(GetColorFor(index)); |
||||||
|
} |
||||||
|
//FormattedStringData Package.FormattedStringData (Function AcediaCore.FormattedStringData.GetColorFor:00FC) Accessed array 'gradientColors' out of bounds (2/2) |
||||||
|
private final function Color GetColorFor(int index) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local float indexPosition, leftPosition, rightPosition; |
||||||
|
local array<float> points; |
||||||
|
local Color leftColor, rightColor, resultColor; |
||||||
|
if (formattingStack.length <= 0) { |
||||||
|
return resultColor; |
||||||
|
} |
||||||
|
if (!formattingStackHead.gradient) { |
||||||
|
return formattingStackHead.plainColor; |
||||||
|
} |
||||||
|
indexPosition = float(index - formattingStackHead.gradientStart) / |
||||||
|
formattingStackHead.gradientLength; |
||||||
|
points = formattingStackHead.gradientPoints; |
||||||
|
for (i = 1; i < points.length; i += 1) |
||||||
|
{ |
||||||
|
if (points[i - 1] <= indexPosition && indexPosition <= points[i]) |
||||||
|
{ |
||||||
|
leftPosition = points[i - 1]; |
||||||
|
rightPosition = points[i]; |
||||||
|
leftColor = formattingStackHead.gradientColors[i - 1]; |
||||||
|
rightColor = formattingStackHead.gradientColors[i]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
indexPosition = |
||||||
|
(indexPosition - leftPosition) / (rightPosition - leftPosition); |
||||||
|
resultColor.R = Lerp(indexPosition, leftColor.R, rightColor.R); |
||||||
|
resultColor.G = Lerp(indexPosition, leftColor.G, rightColor.G); |
||||||
|
resultColor.B = Lerp(indexPosition, leftColor.B, rightColor.B); |
||||||
|
resultColor.A = Lerp(indexPosition, leftColor.A, rightColor.A); |
||||||
|
return resultColor; |
||||||
|
} |
||||||
|
|
||||||
|
private final function PushIntoFormattingStack( |
||||||
|
FormattingCommandList.FormattingCommand formattingCommand) |
||||||
|
{ |
||||||
|
formattingStackHead = ParseFormattingInfo(formattingCommand.tag); |
||||||
|
formattingStackHead.gradientStart = formattingCommand.openIndex; |
||||||
|
formattingStackHead.gradientLength = |
||||||
|
float(formattingCommand.closeIndex - formattingCommand.openIndex); |
||||||
|
formattingStack[formattingStack.length] = formattingStackHead; |
||||||
|
} |
||||||
|
|
||||||
|
private final function SwapFormattingStack(Text.Character tagCharacter) |
||||||
|
{ |
||||||
|
local FormattingInfo updatedFormatting; |
||||||
|
if (formattingStack.length > 0) { |
||||||
|
updatedFormatting = formattingStackHead; |
||||||
|
} |
||||||
|
if (_.color.ResolveShortTagColor(tagCharacter, updatedFormatting.plainColor)) |
||||||
|
{ |
||||||
|
updatedFormatting.colored = true; |
||||||
|
updatedFormatting.gradient = false; |
||||||
|
} |
||||||
|
else { |
||||||
|
Report(FSE_BadShortColorTag, _.text.FromString("^" $ Chr(tagCharacter.codePoint))); |
||||||
|
} |
||||||
|
formattingStackHead = updatedFormatting; |
||||||
|
if (formattingStack.length > 0) { |
||||||
|
formattingStack[formattingStack.length - 1] = updatedFormatting; |
||||||
|
} |
||||||
|
else { |
||||||
|
formattingStack[0] = updatedFormatting; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private final function PopFormattingStack() |
||||||
|
{ |
||||||
|
// Remove the top of the stack |
||||||
|
if (formattingStack.length > 0) { |
||||||
|
formattingStack.length = formattingStack.length - 1; |
||||||
|
} |
||||||
|
// Update the stack head copy |
||||||
|
if (formattingStack.length > 0) { |
||||||
|
formattingStackHead = formattingStack[formattingStack.length - 1]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private final function FormattingInfo ParseFormattingInfo(Text colorTag) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Parser colorParser; |
||||||
|
local Color nextColor; |
||||||
|
local array<MutableText> specifiedColors; |
||||||
|
local Text.Character tildeCharacter; |
||||||
|
local array<Color> gradientColors; |
||||||
|
local array<float> gradientPoints; |
||||||
|
local FormattingInfo resultInfo; |
||||||
|
if (colorTag.IsEmpty()) |
||||||
|
{ |
||||||
|
Report(FSE_EmptyColorTag); |
||||||
|
return resultInfo; // not colored |
||||||
|
} |
||||||
|
tildeCharacter = _.text.GetCharacter("~"); |
||||||
|
specifiedColors = colorTag.SplitByCharacter(tildeCharacter, true); |
||||||
|
for (i = 0; i < specifiedColors.length; i += 1) |
||||||
|
{ |
||||||
|
colorParser = _.text.Parse(specifiedColors[i]); |
||||||
|
if (_.color.ParseWith(colorParser, nextColor)) |
||||||
|
{ |
||||||
|
colorParser.Confirm(); |
||||||
|
gradientColors[gradientColors.length] = nextColor; |
||||||
|
gradientPoints[gradientPoints.length] = ParsePoint(colorParser); |
||||||
|
} |
||||||
|
else { |
||||||
|
Report(FSE_BadColor, specifiedColors[i]); |
||||||
|
} |
||||||
|
_.memory.Free(colorParser); |
||||||
|
} |
||||||
|
_.memory.FreeMany(specifiedColors); |
||||||
|
gradientPoints = NormalizePoints(gradientPoints); |
||||||
|
resultInfo.colored = (gradientColors.length > 0); |
||||||
|
resultInfo.gradient = (gradientColors.length > 1); |
||||||
|
resultInfo.gradientColors = gradientColors; |
||||||
|
resultInfo.gradientPoints = gradientPoints; |
||||||
|
if (gradientColors.length > 0) { |
||||||
|
resultInfo.plainColor = gradientColors[0]; |
||||||
|
} |
||||||
|
return resultInfo; |
||||||
|
} |
||||||
|
|
||||||
|
private final function float ParsePoint(Parser parser) |
||||||
|
{ |
||||||
|
local float point; |
||||||
|
local Parser.ParserState initialState; |
||||||
|
if (!parser.Ok() || parser.HasFinished()) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
initialState = parser.GetCurrentState(); |
||||||
|
// [Necessary part] Should starts with "[" |
||||||
|
if (!parser.Match(P("[")).Ok()) |
||||||
|
{ |
||||||
|
Report(FSE_BadGradientPoint, parser.RestoreState(initialState).GetRemainder()); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
// [Necessary part] Try parsing number |
||||||
|
parser.MNumber(point).Confirm(); |
||||||
|
if (!parser.Ok()) |
||||||
|
{ |
||||||
|
Report(FSE_BadGradientPoint, parser.RestoreState(initialState).GetRemainder()); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
// [Optional part] Check if number is a percentage |
||||||
|
if (parser.Match(P("%")).Ok()) { |
||||||
|
point *= 0.01; |
||||||
|
} |
||||||
|
// This either confirms state of parsing "%" (on success) |
||||||
|
// or reverts to the previous state, just after parsing the number |
||||||
|
// (on failure) |
||||||
|
parser.Confirm(); |
||||||
|
parser.R(); |
||||||
|
// [Necessary part] Have to have closing parenthesis |
||||||
|
if (!parser.HasFinished()) { |
||||||
|
parser.Match(P("]")).Confirm(); |
||||||
|
} |
||||||
|
// Still return `point`, even if there was no closing parenthesis, |
||||||
|
// since that is likely what user wants |
||||||
|
if (!parser.Ok()) { |
||||||
|
Report(FSE_BadGradientPoint, parser.RestoreState(initialState).GetRemainder()); |
||||||
|
} |
||||||
|
return point; |
||||||
|
} |
||||||
|
/*FIRST-POPOPOINTS 0.00 -1.00 -1.00 -1.00 1.00 5 |
||||||
|
PRE-POPOPOINTS 0.00 -1.00 0.00 -1.00 1.00 5 */ |
||||||
|
private final function array<float> NormalizePoints(array<float> points) |
||||||
|
{ |
||||||
|
local int i, j; |
||||||
|
local int negativeSegmentStart, negativeSegmentLength; |
||||||
|
local float lowerBound, upperBound; |
||||||
|
local bool foundNegative; |
||||||
|
if (points.length > 1) |
||||||
|
{ |
||||||
|
points[0] = 0.0; |
||||||
|
points[points.length - 1] = 1.0; |
||||||
|
} |
||||||
|
for (i = 1; i < points.length - 1; i += 1) |
||||||
|
{ |
||||||
|
if (points[i] <= 0 || points[i] > 1 || points[i] <= points[i - 1]) { |
||||||
|
points[i] = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 1; i < points.length; i += 1) |
||||||
|
{ |
||||||
|
if (foundNegative && points[i] > 0) |
||||||
|
{ |
||||||
|
upperBound = points[i]; |
||||||
|
for (j = negativeSegmentStart; j < i; j += 1) |
||||||
|
{ |
||||||
|
points[j] = Lerp( float(j - negativeSegmentStart + 1) / float(negativeSegmentLength + 1), |
||||||
|
lowerBound, upperBound); |
||||||
|
} |
||||||
|
negativeSegmentLength = 0; |
||||||
|
} |
||||||
|
if (!foundNegative && points[i] < 0) |
||||||
|
{ |
||||||
|
lowerBound = points[i - 1]; |
||||||
|
negativeSegmentStart = i; |
||||||
|
} |
||||||
|
foundNegative = (points[i] < 0); |
||||||
|
if (foundNegative) { |
||||||
|
negativeSegmentLength += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return points; |
||||||
|
} |
||||||
|
|
||||||
|
public final function Report( |
||||||
|
FormattingErrors.FormattedDataErrorType type, |
||||||
|
optional Text cause) |
||||||
|
{ |
||||||
|
if (errors == none) { |
||||||
|
return; |
||||||
|
} |
||||||
|
errors.Report(type, cause); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,296 @@ |
|||||||
|
/** |
||||||
|
* Formatted string can be thought of as a string with a sequence of |
||||||
|
* formatting-changing commands specified within it (either by opening new |
||||||
|
* formatting block, swapping to color with "^" or by closing it and reverting |
||||||
|
* to the previous one). |
||||||
|
* This objects allows to directly access these commands. |
||||||
|
* Copyright 2022 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia 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, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia 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 Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class FormattingCommandList extends AcediaObject |
||||||
|
dependson(Text); |
||||||
|
|
||||||
|
enum FormattingCommandType |
||||||
|
{ |
||||||
|
// Push more data onto formatted stack |
||||||
|
FST_StackPush, |
||||||
|
// Pop data from formatted stack |
||||||
|
FST_StackPop, |
||||||
|
// Swap the top value on the formatting stack |
||||||
|
FST_StackSwap |
||||||
|
}; |
||||||
|
|
||||||
|
// Formatted `string` is separated into several (possibly nested) parts, |
||||||
|
// each with its own formatting. These can be easily handled with a formatting |
||||||
|
// stack: |
||||||
|
// * Each time a new section opens ("{<color_tag> ") we put another, |
||||||
|
// current formatting on top of the stack; |
||||||
|
// * Each time a section closes ("}") we pop the stack, returning to |
||||||
|
// a previous formatting. |
||||||
|
// * In a special case of "^" color swap that is supposed to last until |
||||||
|
// current block closes we simply swap the color of the formatting on |
||||||
|
// top of the stack. |
||||||
|
struct FormattingCommand |
||||||
|
{ |
||||||
|
// Only defined for `FST_StackPush` commands that correspond to section |
||||||
|
// openers ("{<color_tag> "). |
||||||
|
// Indices of first and last character belonging to block it opens. |
||||||
|
var int openIndex; |
||||||
|
var int closeIndex; |
||||||
|
// Did this block start by opening or closing formatted part? |
||||||
|
// Ignored for the very first block without any formatting. |
||||||
|
var FormattingCommandType type; |
||||||
|
// Full text inside the block, without any formatting |
||||||
|
var array<Text.Character> contents; |
||||||
|
// Formatting tag for the next block |
||||||
|
// (only used for `FST_StackPush` command type) |
||||||
|
var MutableText tag; |
||||||
|
// Formatting character for the "^"-type tag |
||||||
|
// (only used for `FST_StackSwap` command type) |
||||||
|
var Text.Character charTag; |
||||||
|
}; |
||||||
|
// Appending formatted `string` into the `MutableText` first requires its |
||||||
|
// transformation into series of `FormattingCommand` and then their |
||||||
|
// execution to assemble the `MutableText`. |
||||||
|
// First element of `commandList` is special and is used solely as |
||||||
|
// a container for unformatted data. It should not be used to execute |
||||||
|
// formatting stack commands. |
||||||
|
// This variable contains intermediary data. |
||||||
|
var private array<FormattingCommand> commandList; |
||||||
|
|
||||||
|
// Stack that keeps track of which (by index inside `commandList`) command |
||||||
|
// opened section we are currently parsing. This is needed to record positions |
||||||
|
// at which each block is opened and closed. |
||||||
|
var private array<int> pushCommandIndicesStack; |
||||||
|
// Store contents for the next command here, because appending array in |
||||||
|
// the struct is expensive |
||||||
|
var private array<Text.Character> currentContents; |
||||||
|
// `Parser` used to break input formatted string into commands, only used |
||||||
|
// during building this object (inside `BuildSelf()` method). |
||||||
|
var private Parser parser; |
||||||
|
// `FormattingErrors` object used to reports errors during building process. |
||||||
|
// It is "borrowed" - meaning that we do not really own it and should not |
||||||
|
// deallocate it. Only set as a field for convenience. |
||||||
|
var private FormattingErrors borrowedErrors; |
||||||
|
|
||||||
|
const CODEPOINT_ESCAPE = 27; // ASCII escape code |
||||||
|
const CODEPOINT_OPEN_FORMAT = 123; // '{' |
||||||
|
const CODEPOINT_CLOSE_FORMAT = 125; // '}' |
||||||
|
const CODEPOINT_FORMAT_ESCAPE = 38; // '&' |
||||||
|
const CODEPOINT_ACCENT = 94; // '^' |
||||||
|
const CODEPOINT_TILDE = 126; // '~' |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
_.memory.Free(parser); |
||||||
|
parser = none; |
||||||
|
borrowedErrors = none; |
||||||
|
if (currentContents.length > 0) { |
||||||
|
currentContents.length = 0; |
||||||
|
} |
||||||
|
if (pushCommandIndicesStack.length > 0) { |
||||||
|
pushCommandIndicesStack.length = 0; |
||||||
|
} |
||||||
|
for (i = 0; i < commandList.length; i += 1) { |
||||||
|
_.memory.Free(commandList[i].tag); |
||||||
|
} |
||||||
|
if (commandList.length > 0) { |
||||||
|
commandList.length = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create `FormattingCommandList` based on given `Text`. |
||||||
|
* |
||||||
|
* @param input `Text` that should be treated as "formatted" and |
||||||
|
* to be broken into formatting commands. |
||||||
|
* @param errorsReporter If specified, will be used to report errors |
||||||
|
* (can only report `FSE_UnmatchedClosingBrackets`). |
||||||
|
* @return New `FormattingCommandList` instance that allows us to have direct |
||||||
|
* access to formatting commands. |
||||||
|
*/ |
||||||
|
public final static function FormattingCommandList FromText( |
||||||
|
Text input, |
||||||
|
optional FormattingErrors errorsReporter) |
||||||
|
{ |
||||||
|
local FormattingCommandList newList; |
||||||
|
newList = FormattingCommandList( |
||||||
|
__().memory.Allocate(class'FormattingCommandList')); |
||||||
|
newList.parser = __().text.Parse(input); |
||||||
|
newList.borrowedErrors = errorsReporter; |
||||||
|
newList.BuildSelf(); |
||||||
|
__().memory.Free(newList.parser); |
||||||
|
newList.parser = none; |
||||||
|
newList.borrowedErrors = none; |
||||||
|
return newList; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns command with index `commandIndex`. |
||||||
|
* |
||||||
|
* @param commandIndex Index of the command to return. |
||||||
|
* Must be non-negative (`>= 0`) and less than `GetAmount()`. |
||||||
|
* @return Command with index `commandIndex`. |
||||||
|
* If given `commandIndex` is out of bounds - returns invalid command. |
||||||
|
* `tag` field is guaranteed to be non-`none` and should be deallocated. |
||||||
|
*/ |
||||||
|
public final function FormattingCommand GetCommand(int commandIndex) |
||||||
|
{ |
||||||
|
local MutableText resultTag; |
||||||
|
local FormattingCommand result; |
||||||
|
if (commandIndex < 0) return result; |
||||||
|
if (commandIndex >= commandList.length) return result; |
||||||
|
|
||||||
|
result = commandList[commandIndex]; |
||||||
|
resultTag = result.tag; |
||||||
|
if (resultTag != none) { |
||||||
|
result.tag = resultTag.MutableCopy(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns amount of commands inside caller `FormattingCommandList`. |
||||||
|
* |
||||||
|
* @return Amount of commands inside caller `FormattingCommandList`. |
||||||
|
*/ |
||||||
|
public final function int GetAmount() |
||||||
|
{ |
||||||
|
return commandList.length; |
||||||
|
} |
||||||
|
|
||||||
|
// Method that turns `parser` into proper `FormattingCommandList` object. |
||||||
|
private final function BuildSelf() |
||||||
|
{ |
||||||
|
//local int i; |
||||||
|
local int characterCounter; |
||||||
|
local Text.Character nextCharacter; |
||||||
|
local FormattingCommand nextCommand; |
||||||
|
while (!parser.HasFinished()) |
||||||
|
{ |
||||||
|
parser.MCharacter(nextCharacter); |
||||||
|
// New command by "{<formatting_info>" |
||||||
|
if (_.text.IsCodePoint(nextCharacter, CODEPOINT_OPEN_FORMAT)) |
||||||
|
{ |
||||||
|
nextCommand = AddCommand(nextCommand, FST_StackPush, characterCounter); |
||||||
|
parser.MUntil(nextCommand.tag,, true) |
||||||
|
.MCharacter(nextCommand.charTag); // Simply to skip a char |
||||||
|
continue; |
||||||
|
} |
||||||
|
// New command by "}" |
||||||
|
if (_.text.IsCodePoint(nextCharacter, CODEPOINT_CLOSE_FORMAT)) |
||||||
|
{ |
||||||
|
nextCommand = AddCommand(nextCommand, FST_StackPop, characterCounter); |
||||||
|
continue; |
||||||
|
} |
||||||
|
// New command by "^" |
||||||
|
if (_.text.IsCodePoint(nextCharacter, CODEPOINT_ACCENT)) |
||||||
|
{ |
||||||
|
nextCommand = AddCommand(nextCommand, FST_StackSwap, characterCounter); |
||||||
|
parser.MCharacter(nextCommand.charTag); |
||||||
|
if (!parser.Ok()) { |
||||||
|
break; |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
// Escaped sequence |
||||||
|
if (_.text.IsCodePoint(nextCharacter, CODEPOINT_FORMAT_ESCAPE)) { |
||||||
|
parser.MCharacter(nextCharacter); |
||||||
|
} |
||||||
|
if (!parser.Ok()) { |
||||||
|
break; |
||||||
|
} |
||||||
|
currentContents[currentContents.length] = nextCharacter; |
||||||
|
characterCounter += 1; |
||||||
|
} |
||||||
|
// Only put in empty command if there is nothing else. |
||||||
|
if (currentContents.length > 0 || commandList.length == 0) |
||||||
|
{ |
||||||
|
nextCommand.contents = currentContents; |
||||||
|
commandList[commandList.length] = nextCommand; |
||||||
|
} |
||||||
|
/*for (i = 0; i < commandList.length; i += 1) |
||||||
|
{ |
||||||
|
Log(">>>COMMAND LIST FOR" @ i $ "<<<"); |
||||||
|
Log("OPEN/CLOSE:" @ commandList[i].openIndex @ "/" @ commandList[i].closeIndex); |
||||||
|
Log("TYPE:" @ commandList[i].type); |
||||||
|
Log("CONTETS LENGTH:" @ commandList[i].contents.length); |
||||||
|
if (commandList[i].tag != none) { |
||||||
|
Log("TAG:" @ commandList[i].tag.ToString()); |
||||||
|
} |
||||||
|
else { |
||||||
|
Log("TAG: NONE"); |
||||||
|
} |
||||||
|
}*/ |
||||||
|
} |
||||||
|
|
||||||
|
// Helper method for a quick creation of a new `FormattingCommand` |
||||||
|
private final function FormattingCommand AddCommand( |
||||||
|
FormattingCommand nextCommand, |
||||||
|
FormattingCommandType newStackCommandType, |
||||||
|
optional int currentCharacterIndex) |
||||||
|
{ |
||||||
|
local int lastPushIndex; |
||||||
|
local FormattingCommand newCommand; |
||||||
|
nextCommand.contents = currentContents; |
||||||
|
if (currentContents.length > 0) { |
||||||
|
currentContents.length = 0; |
||||||
|
} |
||||||
|
commandList[commandList.length] = nextCommand; |
||||||
|
if (newStackCommandType == FST_StackPop) |
||||||
|
{ |
||||||
|
lastPushIndex = PopIndex(); |
||||||
|
if (lastPushIndex >= 0) { |
||||||
|
// BLABLA |
||||||
|
commandList[lastPushIndex].closeIndex = currentCharacterIndex - 1; |
||||||
|
} |
||||||
|
else if (borrowedErrors != none) { |
||||||
|
borrowedErrors.Report(FSE_UnmatchedClosingBrackets); |
||||||
|
} |
||||||
|
} |
||||||
|
newCommand.type = newStackCommandType; |
||||||
|
if (newStackCommandType == FST_StackPush) |
||||||
|
{ |
||||||
|
newCommand.openIndex = currentCharacterIndex; |
||||||
|
newCommand.closeIndex = -1; |
||||||
|
// BLABLA |
||||||
|
PushIndex(commandList.length); |
||||||
|
} |
||||||
|
return newCommand; |
||||||
|
} |
||||||
|
|
||||||
|
private final function PushIndex(int index) |
||||||
|
{ |
||||||
|
pushCommandIndicesStack[pushCommandIndicesStack.length] = |
||||||
|
commandList.length; |
||||||
|
} |
||||||
|
|
||||||
|
private final function int PopIndex() |
||||||
|
{ |
||||||
|
local int result; |
||||||
|
if (pushCommandIndicesStack.length <= 0) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
result = pushCommandIndicesStack[pushCommandIndicesStack.length - 1]; |
||||||
|
pushCommandIndicesStack.length = pushCommandIndicesStack.length - 1; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,179 @@ |
|||||||
|
/** |
||||||
|
* Simple aggregator object for errors that may arise during parsing of |
||||||
|
* formatted string. |
||||||
|
* Copyright 2022 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia 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, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia 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 Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class FormattingErrors extends AcediaObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* Errors that can occur during parsing of the formatted string. |
||||||
|
*/ |
||||||
|
enum FormattedDataErrorType |
||||||
|
{ |
||||||
|
// There was an unmatched closing figure bracket, e.g. |
||||||
|
// "{$red Hey} you, there}!" |
||||||
|
FSE_UnmatchedClosingBrackets, |
||||||
|
// Color tag was empty, e.g. "Why not { just kill them}?" |
||||||
|
FSE_EmptyColorTag, |
||||||
|
// Color tag cannot be parsed as a color or color gradient, |
||||||
|
// e.g. "Why not {just kill them}?" |
||||||
|
FSE_BadColor, |
||||||
|
// Gradient color tag contained bad point specified, e.g. |
||||||
|
// "That is SO {$red~$orange(what?)~$red AMAZING}!!!" or |
||||||
|
// "That is SO {$red~$orange(0.76~$red AMAZING}!!!" |
||||||
|
FSE_BadGradientPoint, |
||||||
|
FSE_BadShortColorTag |
||||||
|
}; |
||||||
|
|
||||||
|
// `FSE_UnmatchedClosingBrackets` and `FSE_EmptyColorTag` errors never have any |
||||||
|
// `Text` hint associated with them, so simply store how many times they were |
||||||
|
// invoked. |
||||||
|
var private int unmatchedClosingBracketsErrorCount; |
||||||
|
var private int emptyColorTagErrorCount; |
||||||
|
// `FSE_BadColor` and `FSE_BadGradientPoint` are always expected to have |
||||||
|
// a `Text` hint reported alongside them, so simply store that hint. |
||||||
|
var private array<Text> badColorTagErrorHints; |
||||||
|
var private array<Text> badGradientTagErrorHints; |
||||||
|
var private array<Text> badShortColorTagErrorHints; |
||||||
|
|
||||||
|
// We will report accumulated errors as an array of these structs. |
||||||
|
struct FormattedDataError |
||||||
|
{ |
||||||
|
// Type of the error |
||||||
|
var FormattedDataErrorType type; |
||||||
|
// How many times had this error happened? |
||||||
|
// Can be specified for `FSE_UnmatchedClosingBrackets` and |
||||||
|
// `FSE_EmptyColorTag` error types. Never negative. |
||||||
|
var int count; |
||||||
|
// `Text` hint that should help user understand where the error is |
||||||
|
// coming from. |
||||||
|
// Can be specified for `FSE_BadColor` and `FSE_BadGradientPoint` |
||||||
|
// error types. |
||||||
|
var Text cause; |
||||||
|
}; |
||||||
|
|
||||||
|
protected function Finalizer() |
||||||
|
{ |
||||||
|
unmatchedClosingBracketsErrorCount = 0; |
||||||
|
emptyColorTagErrorCount = 0; |
||||||
|
_.memory.FreeMany(badColorTagErrorHints); |
||||||
|
_.memory.FreeMany(badShortColorTagErrorHints); |
||||||
|
_.memory.FreeMany(badGradientTagErrorHints); |
||||||
|
badColorTagErrorHints.length = 0; |
||||||
|
badShortColorTagErrorHints.length = 0; |
||||||
|
badGradientTagErrorHints.length = 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds new error to the caller `FormattingErrors` object. |
||||||
|
* |
||||||
|
* @param type Type of the new error. |
||||||
|
* @param cause Auxiliary `Text` that might give user additional hint about |
||||||
|
* what exactly went wrong. |
||||||
|
* If this parameter is `none` for errors `FSE_BadColor` or |
||||||
|
* `FSE_BadGradientPoint` - method will do nothing. |
||||||
|
*/ |
||||||
|
public final function Report(FormattedDataErrorType type, optional Text cause) |
||||||
|
{ |
||||||
|
switch (type) |
||||||
|
{ |
||||||
|
case FSE_UnmatchedClosingBrackets: |
||||||
|
unmatchedClosingBracketsErrorCount += 1; |
||||||
|
break; |
||||||
|
case FSE_EmptyColorTag: |
||||||
|
emptyColorTagErrorCount += 1; |
||||||
|
break; |
||||||
|
case FSE_BadColor: |
||||||
|
if (cause != none) { |
||||||
|
badColorTagErrorHints[badColorTagErrorHints.length] = cause.Copy(); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FSE_BadShortColorTag: |
||||||
|
if (cause != none) |
||||||
|
{ |
||||||
|
badShortColorTagErrorHints[badShortColorTagErrorHints.length] = |
||||||
|
cause.Copy(); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FSE_BadGradientPoint: |
||||||
|
if (cause != none) |
||||||
|
{ |
||||||
|
badGradientTagErrorHints[badGradientTagErrorHints.length] = |
||||||
|
cause.Copy(); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns array of errors collected so far. |
||||||
|
* |
||||||
|
* @return Array of errors collected so far. |
||||||
|
* Each `FormattedDataError` in array has either non-`none` `cause` field |
||||||
|
* or strictly positive `count > 0` field (but not both). |
||||||
|
* `count` field is always guaranteed to not be negative. |
||||||
|
* WARNING: `FormattedDataError` struct may contain `Text` objects that |
||||||
|
* should be deallocated. |
||||||
|
*/ |
||||||
|
public final function array<FormattedDataError> GetErrors() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local FormattedDataError newError; |
||||||
|
local array<FormattedDataError> errors; |
||||||
|
// We overwrite old `cause` in `newError` with new one each time we |
||||||
|
// add new error, so it should be fine to not set it to `none` after |
||||||
|
// "moving it" into `errors`. |
||||||
|
newError.type = FSE_BadColor; |
||||||
|
for (i = 0; i < badColorTagErrorHints.length; i += 1) |
||||||
|
{ |
||||||
|
newError.cause = badColorTagErrorHints[i].Copy(); |
||||||
|
errors[errors.length] = newError; |
||||||
|
} |
||||||
|
newError.type = FSE_BadShortColorTag; |
||||||
|
for (i = 0; i < badShortColorTagErrorHints.length; i += 1) |
||||||
|
{ |
||||||
|
newError.cause = badShortColorTagErrorHints[i].Copy(); |
||||||
|
errors[errors.length] = newError; |
||||||
|
} |
||||||
|
newError.type = FSE_BadGradientPoint; |
||||||
|
for (i = 0; i < badGradientTagErrorHints.length; i += 1) |
||||||
|
{ |
||||||
|
newError.cause = badGradientTagErrorHints[i].Copy(); |
||||||
|
errors[errors.length] = newError; |
||||||
|
} |
||||||
|
// Need to reset `cause` here, to avoid duplicating it in |
||||||
|
// following two errors |
||||||
|
newError.cause = none; |
||||||
|
if (unmatchedClosingBracketsErrorCount > 0) |
||||||
|
{ |
||||||
|
newError.type = FSE_UnmatchedClosingBrackets; |
||||||
|
newError.count = unmatchedClosingBracketsErrorCount; |
||||||
|
errors[errors.length] = newError; |
||||||
|
} |
||||||
|
if (emptyColorTagErrorCount > 0) |
||||||
|
{ |
||||||
|
newError.type = FSE_EmptyColorTag; |
||||||
|
newError.count = emptyColorTagErrorCount; |
||||||
|
errors[errors.length] = newError; |
||||||
|
} |
||||||
|
return errors; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,565 @@ |
|||||||
|
/** |
||||||
|
* Set of tests for functionality of parsing formatted strings. |
||||||
|
* Copyright 2022 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia 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, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia 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 Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class TEST_FormattedStrings extends TestCase |
||||||
|
abstract; |
||||||
|
|
||||||
|
protected static function MutableText GetRGBText( |
||||||
|
optional bool noFormattingReset) |
||||||
|
{ |
||||||
|
local int wordIndex; |
||||||
|
local MutableText result; |
||||||
|
result = __().text.FromStringM("This is red, green and blue!"); |
||||||
|
wordIndex = result.IndexOf(P("red")); |
||||||
|
result.ChangeFormatting(__().text.FormattingFromColor(__().color.red), |
||||||
|
wordIndex, 3); |
||||||
|
wordIndex = result.IndexOf(P("green")); |
||||||
|
result.ChangeFormatting(__().text.FormattingFromColor(__().color.lime), |
||||||
|
wordIndex, 5); |
||||||
|
wordIndex = result.IndexOf(P("blue")); |
||||||
|
result.ChangeFormatting(__().text.FormattingFromColor(__().color.blue), |
||||||
|
wordIndex, 4); |
||||||
|
// also color ", " and " and " parts white, and "!" part blue |
||||||
|
if (noFormattingReset) |
||||||
|
{ |
||||||
|
result.ChangeFormatting(__().text.FormattingFromColor(__().color.blue), |
||||||
|
wordIndex, -1); |
||||||
|
wordIndex = result.IndexOf(P(", ")); |
||||||
|
result.ChangeFormatting(__().text.FormattingFromColor(__().color.white), |
||||||
|
wordIndex, 2); |
||||||
|
wordIndex = result.IndexOf(P(" and ")); |
||||||
|
result.ChangeFormatting(__().text.FormattingFromColor(__().color.white), |
||||||
|
wordIndex, 5); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
protected static function TESTS() |
||||||
|
{ |
||||||
|
Test_Simple(); |
||||||
|
Test_Gradient(); |
||||||
|
Test_Errors(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_Errors() |
||||||
|
{ |
||||||
|
Context("Testing error reporting for formatted strings."); |
||||||
|
SubTest_ErrorUnmatchedClosingBrackets(); |
||||||
|
SubTest_ErrorEmptyColorTag(); |
||||||
|
SubTest_ErrorBadColor(); |
||||||
|
SubTest_ErrorBadShortColorTag(); |
||||||
|
SubTest_ErrorBadGradientPoint(); |
||||||
|
SubTest_AllErrors(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ErrorUnmatchedClosingBrackets() |
||||||
|
{ |
||||||
|
local array<FormattingErrors.FormattedDataError> errors; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Unmatched closing brackets are not reported."); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("Testing {$pink pink text}}!"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 1); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_UnmatchedClosingBrackets); |
||||||
|
TEST_ExpectTrue(errors[0].count == 1); |
||||||
|
TEST_ExpectNone(errors[0].cause); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("Testing regular text!}"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 1); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_UnmatchedClosingBrackets); |
||||||
|
TEST_ExpectTrue(errors[0].count == 1); |
||||||
|
TEST_ExpectNone(errors[0].cause); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {rgb(255,0,0) red{rgb(255,255,255) , }}}" |
||||||
|
$ "{rgb(0,255,0) gr}een{rgb(255,255,255) and }}}}{rgb(0,0,255)" |
||||||
|
$ " blue!}}}"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 1); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_UnmatchedClosingBrackets); |
||||||
|
TEST_ExpectTrue(errors[0].count == 6); |
||||||
|
TEST_ExpectNone(errors[0].cause); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ErrorEmptyColorTag() |
||||||
|
{ |
||||||
|
local array<FormattingErrors.FormattedDataError> errors; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Empty color tags are not reported."); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("Testing { pink text}!"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 1); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_EmptyColorTag); |
||||||
|
TEST_ExpectTrue(errors[0].count == 1); |
||||||
|
TEST_ExpectNone(errors[0].cause); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("Testing {$red regu{ lar tex}t!}"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 1); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_EmptyColorTag); |
||||||
|
TEST_ExpectTrue(errors[0].count == 1); |
||||||
|
TEST_ExpectNone(errors[0].cause); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is { {rgb(255,255,255)~$green , }" |
||||||
|
$ "{#800c37 ^ggre^gen{ and }}}^bblue!"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 1); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_EmptyColorTag); |
||||||
|
TEST_ExpectTrue(errors[0].count == 2); |
||||||
|
TEST_ExpectNone(errors[0].cause); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ErrorBadColor() |
||||||
|
{ |
||||||
|
local array<FormattingErrors.FormattedDataError> errors; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Bad color is not reported."); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("Testing {$cat pink text}!"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 1); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_BadColor); |
||||||
|
TEST_ExpectTrue(errors[0].cause.ToString() == "$cat"); |
||||||
|
TEST_ExpectTrue(errors[0].count == 0); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("Testing {dog regular} {#wicked text!}"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 2); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_BadColor); |
||||||
|
TEST_ExpectTrue(errors[1].type == FSE_BadColor); |
||||||
|
TEST_ExpectTrue(errors[0].cause.ToString() == "dog"); |
||||||
|
TEST_ExpectTrue(errors[1].cause.ToString() == "#wicked"); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {goat red{rgb(255,255,255)~lol~$green , }" |
||||||
|
$ "{#800c37 ^ggre^gen{324sd and }}}^bblue!"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 3); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_BadColor); |
||||||
|
TEST_ExpectTrue(errors[1].type == FSE_BadColor); |
||||||
|
TEST_ExpectTrue(errors[2].type == FSE_BadColor); |
||||||
|
TEST_ExpectTrue(errors[0].cause.ToString() == "goat"); |
||||||
|
TEST_ExpectTrue(errors[1].cause.ToString() == "lol"); |
||||||
|
TEST_ExpectTrue(errors[2].cause.ToString() == "324sd"); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ErrorBadShortColorTag() |
||||||
|
{ |
||||||
|
local array<FormattingErrors.FormattedDataError> errors; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Bad short color tag is not reported."); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("This is ^xred^w, ^ugreen^x and ^zblue!"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 4); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_BadShortColorTag); |
||||||
|
TEST_ExpectTrue(errors[0].cause.ToString() == "^x"); |
||||||
|
TEST_ExpectTrue(errors[0].count == 0); |
||||||
|
TEST_ExpectTrue(errors[1].type == FSE_BadShortColorTag); |
||||||
|
TEST_ExpectTrue(errors[1].cause.ToString() == "^u"); |
||||||
|
TEST_ExpectTrue(errors[1].count == 0); |
||||||
|
TEST_ExpectTrue(errors[2].type == FSE_BadShortColorTag); |
||||||
|
TEST_ExpectTrue(errors[2].cause.ToString() == "^x"); |
||||||
|
TEST_ExpectTrue(errors[2].count == 0); |
||||||
|
TEST_ExpectTrue(errors[3].type == FSE_BadShortColorTag); |
||||||
|
TEST_ExpectTrue(errors[3].cause.ToString() == "^z"); |
||||||
|
TEST_ExpectTrue(errors[3].count == 0); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_ErrorBadGradientPoint() |
||||||
|
{ |
||||||
|
local array<FormattingErrors.FormattedDataError> errors; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Bad gradient point is not reported."); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("Testing {$pink[dog] pink text}!"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 1); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_BadGradientPoint); |
||||||
|
TEST_ExpectTrue(errors[0].cause.ToString() == "[dog]"); |
||||||
|
TEST_ExpectTrue(errors[0].count == 0); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("Testing {45,2,241[bad] regular} {#ffaacd~rgb(2,3,4)45worse] text!}"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 2); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_BadGradientPoint); |
||||||
|
TEST_ExpectTrue(errors[1].type == FSE_BadGradientPoint); |
||||||
|
TEST_ExpectTrue(errors[0].cause.ToString() == "[bad]"); |
||||||
|
TEST_ExpectTrue(errors[1].cause.ToString() == "45worse]"); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {$red[45%%] red{rgb(255,255,255)~45,3,128point~$green , }" |
||||||
|
$ "{#800c37 ^ggre^gen{#43fa6b3c and }}}^bblue!"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
TEST_ExpectTrue(errors.length == 3); |
||||||
|
TEST_ExpectTrue(errors[0].type == FSE_BadGradientPoint); |
||||||
|
TEST_ExpectTrue(errors[1].type == FSE_BadGradientPoint); |
||||||
|
TEST_ExpectTrue(errors[2].type == FSE_BadGradientPoint); |
||||||
|
TEST_ExpectTrue(errors[0].cause.ToString() == "[45%%]"); |
||||||
|
TEST_ExpectTrue(errors[1].cause.ToString() == "point"); |
||||||
|
TEST_ExpectTrue(errors[2].cause.ToString() == "3c"); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_AllErrors() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool foundUnmatched, foundEmpty, foundBadColor, foundBadPoint, foundBadShortTag; |
||||||
|
local array<FormattingErrors.FormattedDataError> errors; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("COMPLEX."); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("This} is {$cat~$green[%7] red{$white , }{ green^z and }}{$blue blue!}}"), true); |
||||||
|
errors = data.BorrowErrors().GetErrors(); |
||||||
|
for (i = 0; i < errors.length; i += 1) |
||||||
|
{ |
||||||
|
if (errors[i].type == FSE_UnmatchedClosingBrackets) |
||||||
|
{ |
||||||
|
foundUnmatched = true; |
||||||
|
TEST_ExpectTrue(errors[i].count == 2); |
||||||
|
} |
||||||
|
if (errors[i].type == FSE_EmptyColorTag) |
||||||
|
{ |
||||||
|
foundEmpty = true; |
||||||
|
TEST_ExpectTrue(errors[i].count == 1); |
||||||
|
} |
||||||
|
if (errors[i].type == FSE_BadColor) |
||||||
|
{ |
||||||
|
foundBadColor = true; |
||||||
|
TEST_ExpectTrue(errors[i].cause.ToString() == "$cat"); |
||||||
|
} |
||||||
|
if (errors[i].type == FSE_BadGradientPoint) |
||||||
|
{ |
||||||
|
foundBadPoint = true; |
||||||
|
TEST_ExpectTrue(errors[i].cause.ToString() == "[%7]"); |
||||||
|
} |
||||||
|
if (errors[i].type == FSE_BadShortColorTag) |
||||||
|
{ |
||||||
|
foundBadShortTag = true; |
||||||
|
TEST_ExpectTrue(errors[i].cause.ToString() == "^z"); |
||||||
|
} |
||||||
|
} |
||||||
|
}//^z, $cat, [%7] |
||||||
|
/* // There was an unmatched closing figure bracket, e.g. |
||||||
|
// "{$red Hey} you, there}!" |
||||||
|
FSE_UnmatchedClosingBrackets, |
||||||
|
// Color tag was empty, e.g. "Why not { just kill them}?" |
||||||
|
FSE_EmptyColorTag, |
||||||
|
// Color tag cannot be parsed as a color or color gradient, |
||||||
|
// e.g. "Why not {just kill them}?" |
||||||
|
FSE_BadColor, |
||||||
|
// Gradient color tag contained bad point specified, e.g. |
||||||
|
// "That is SO {$red~$orange(what?)~$red AMAZING}!!!" or |
||||||
|
// "That is SO {$red~$orange(0.76~$red AMAZING}!!!" |
||||||
|
FSE_BadGradientPoint, |
||||||
|
FSE_BadShortColorTag */ |
||||||
|
protected static function Test_Simple() |
||||||
|
{ |
||||||
|
Context("Testing parsing formatted strings with plain colors."); |
||||||
|
SubTest_SimpleNone(); |
||||||
|
SubTest_SimpleAlias(); |
||||||
|
SubTest_SimpleRGB(); |
||||||
|
SubTest_SimpleHEX(); |
||||||
|
SubTest_SimpleTag(); |
||||||
|
SubTest_SimpleMix(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_SimpleNone() |
||||||
|
{ |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Empty formatted strings are handled incorrectly."); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("")); |
||||||
|
TEST_ExpectNotNone(data.GetResult()); |
||||||
|
TEST_ExpectTrue(data.GetResult().IsEmpty()); |
||||||
|
|
||||||
|
Issue("Formatted strings with no content are handled incorrectly."); |
||||||
|
data = class'FormattedStringData'.static.FromText(P("{$red }")); |
||||||
|
TEST_ExpectNotNone(data.GetResult()); |
||||||
|
TEST_ExpectTrue(data.GetResult().IsEmpty()); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("{#ff03a5 {$blue }}^3{$lime }")); |
||||||
|
TEST_ExpectNotNone(data.GetResult()); |
||||||
|
TEST_ExpectTrue(data.GetResult().IsEmpty()); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_SimpleAlias() |
||||||
|
{ |
||||||
|
local MutableText example; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Formatted strings with aliases are handled incorrectly."); |
||||||
|
example = GetRGBText(); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {$red red}, {$lime green} and {$blue blue}!")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
example = GetRGBText(true); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {$red red{$white , }{$lime green{$white and }}}" |
||||||
|
$ "{$blue blue!}")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_SimpleRGB() |
||||||
|
{ |
||||||
|
local MutableText example; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Formatted strings with rgb definitions are handled incorrectly."); |
||||||
|
example = GetRGBText(); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {rgb(255,0,0) red}, {rgb(0,255,0) green} and" |
||||||
|
@ "{rgb(0,0,255) blue}!")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
example = GetRGBText(true); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {rgb(255,0,0) red{rgb(255,255,255) , }" |
||||||
|
$ "{rgb(0,255,0) green{rgb(255,255,255) and }}}{rgb(0,0,255)" |
||||||
|
$ " blue!}")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_SimpleHEX() |
||||||
|
{ |
||||||
|
local MutableText example; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Formatted strings with hex definitions are handled incorrectly."); |
||||||
|
example = GetRGBText(); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {#ff0000 red}, {#00ff00 green} and" |
||||||
|
@ "{#0000ff blue}!")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
example = GetRGBText(true); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {#ff0000 red{#ffffff , }" |
||||||
|
$ "{#00ff00 green{#ffffff and }}}{#0000ff blue!}")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_SimpleTag() |
||||||
|
{ |
||||||
|
local MutableText example; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Formatted strings with rag definitions are handled incorrectly."); |
||||||
|
example = GetRGBText(true); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is ^rred^w, ^2green^w and ^4blue!")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_SimpleMix() |
||||||
|
{ |
||||||
|
local MutableText example; |
||||||
|
local FormattedStringData data; |
||||||
|
Issue("Formatted strings with mixed definitions are handled incorrectly."); |
||||||
|
example = GetRGBText(); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {rgb(255,0,0) red}, {$lime green} and" |
||||||
|
@ "{#af4378 ^bblue}!")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
example = GetRGBText(true); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("This is {$red red{rgb(255,255,255) , }" |
||||||
|
$ "{#800c37d ^ggre^gen{#ffffff and }}}^bblue!")); |
||||||
|
TEST_ExpectTrue(example.Compare(data.GetResult(),, SFORM_SENSITIVE)); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_Gradient() |
||||||
|
{ |
||||||
|
Context("Testing parsing formatted strings with gradient."); |
||||||
|
SubTest_TestGradientTwoColors(); |
||||||
|
SubTest_TestGradientThreeColors(); |
||||||
|
SubTest_TestGradientFiveColors(); |
||||||
|
SubTest_TestGradientPoints(); |
||||||
|
SubTest_TestGradientPointsBad(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_TestGradientTwoColors() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Text result; |
||||||
|
local FormattedStringData data; |
||||||
|
local Color previousColor, currentColor; |
||||||
|
Issue("Simple (two color) gradient block does not color intermediate" |
||||||
|
@ "characters correctly."); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("{rgb(255,128,56)~rgb(0,255,56)" |
||||||
|
@ "Simple shit to test out gradient}")); |
||||||
|
result = data.GetResult(); |
||||||
|
previousColor = result.GetFormatting(0).color; |
||||||
|
TEST_ExpectTrue(result.GetFormatting(0).isColored); |
||||||
|
for (i = 1; i < result.GetLength(); i += 1) |
||||||
|
{ |
||||||
|
TEST_ExpectTrue(result.GetFormatting(i).isColored); |
||||||
|
currentColor = result.GetFormatting(i).color; |
||||||
|
TEST_ExpectTrue(previousColor.r > currentColor.r); |
||||||
|
TEST_ExpectTrue(previousColor.g < currentColor.g); |
||||||
|
TEST_ExpectTrue(previousColor.b == currentColor.b); |
||||||
|
previousColor = currentColor; |
||||||
|
} |
||||||
|
Issue("Gradient (two color) block does not color edge characters" |
||||||
|
@ "correctly."); |
||||||
|
previousColor = result.GetFormatting(0).color; |
||||||
|
currentColor = result.GetFormatting(result.GetLength() - 1).color; |
||||||
|
TEST_ExpectTrue(previousColor.r == 255); |
||||||
|
TEST_ExpectTrue(previousColor.g == 128); |
||||||
|
TEST_ExpectTrue(previousColor.b == 56); |
||||||
|
TEST_ExpectTrue(currentColor.r == 0); |
||||||
|
TEST_ExpectTrue(currentColor.g == 255); |
||||||
|
TEST_ExpectTrue(currentColor.b == 56); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function CheckRedDecrease(Text sample, int from, int to) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Color previousColor, currentColor; |
||||||
|
previousColor = sample.GetFormatting(from).color; |
||||||
|
TEST_ExpectTrue(sample.GetFormatting(from).isColored); |
||||||
|
for (i = from + 1; i < to; i += 1) |
||||||
|
{ |
||||||
|
TEST_ExpectTrue(sample.GetFormatting(i).isColored); |
||||||
|
currentColor = sample.GetFormatting(i).color; |
||||||
|
TEST_ExpectTrue(previousColor.r > currentColor.r); |
||||||
|
previousColor = currentColor; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static function CheckRedIncrease(Text sample, int from, int to) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local Color previousColor, currentColor; |
||||||
|
previousColor = sample.GetFormatting(from).color; |
||||||
|
TEST_ExpectTrue(sample.GetFormatting(from).isColored); |
||||||
|
for (i = from + 1; i < to; i += 1) |
||||||
|
{ |
||||||
|
TEST_ExpectTrue(sample.GetFormatting(i).isColored); |
||||||
|
currentColor = sample.GetFormatting(i).color; |
||||||
|
TEST_ExpectTrue(previousColor.r < currentColor.r); |
||||||
|
previousColor = currentColor; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_TestGradientThreeColors() |
||||||
|
{ |
||||||
|
local Text result; |
||||||
|
local FormattedStringData data; |
||||||
|
local Color borderColor; |
||||||
|
Issue("Gradient block with three colors does not color intermediate" |
||||||
|
@ "characters correctly."); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("{rgb(255,0,0)~#000000~$red" |
||||||
|
@ "Simple shit to test out gradient!}")); |
||||||
|
result = data.GetResult(); |
||||||
|
CheckRedDecrease(result, 0, 16); |
||||||
|
CheckRedIncrease(result, 17, result.GetLength()); |
||||||
|
Issue("Gradient block with three colors does not color edge characters" |
||||||
|
@ "correctly."); |
||||||
|
borderColor = result.GetFormatting(0).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 255); |
||||||
|
borderColor = result.GetFormatting(result.GetLength() - 1).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 255); |
||||||
|
borderColor = result.GetFormatting(16).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 0); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_TestGradientFiveColors() |
||||||
|
{ |
||||||
|
local Text result; |
||||||
|
local FormattedStringData data; |
||||||
|
local Color borderColor; |
||||||
|
Issue("Gradient block with five colors does not color intermediate" |
||||||
|
@ "characters correctly."); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("Check this wacky shit out: {rgb(255,0,0)~rgb(200,0,0)~rgb(180,0,0)~rgb(210,0,0)~rgb(97,0,0) Go f yourself}!?!?!"));//27 SHIFT |
||||||
|
result = data.GetResult(); |
||||||
|
CheckRedDecrease(result, 0 + 27, 6 + 27); |
||||||
|
CheckRedIncrease(result, 7 + 27, 9 + 27); |
||||||
|
CheckRedDecrease(result, 9 + 27, 12 + 27); |
||||||
|
Issue("Gradient block with five colors does not color edge characters" |
||||||
|
@ "correctly."); |
||||||
|
borderColor = result.GetFormatting(0 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 255); |
||||||
|
borderColor = result.GetFormatting(3 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 200); |
||||||
|
borderColor = result.GetFormatting(6 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 180); |
||||||
|
borderColor = result.GetFormatting(9 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 210); |
||||||
|
borderColor = result.GetFormatting(12 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 97); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_TestGradientPoints() |
||||||
|
{ |
||||||
|
local Text result; |
||||||
|
local FormattedStringData data; |
||||||
|
local Color borderColor; |
||||||
|
Issue("Gradient points are incorrectly handled."); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("Check this wacky shit out: {rgb(255,0,0)~rgb(0,0,0)[25%]~rgb(123,0,0) Go f yourself}!?!?!")); |
||||||
|
result = data.GetResult(); |
||||||
|
CheckRedDecrease(result, 0 + 27, 3 + 27); |
||||||
|
CheckRedIncrease(result, 3 + 27, 12 + 27); |
||||||
|
borderColor = result.GetFormatting(0 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 255); |
||||||
|
borderColor = result.GetFormatting(3 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 0); |
||||||
|
borderColor = result.GetFormatting(12 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 123); |
||||||
|
Issue("Gradient block does not color intermediate characters correctly."); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("Check this wacky shit out: {rgb(255,0,0)~rgb(0,0,0)[0.75]~rgb(45,0,0) Go f yourself}!?!?!")); |
||||||
|
result = data.GetResult(); |
||||||
|
CheckRedDecrease(result, 0 + 27, 9 + 27); |
||||||
|
CheckRedIncrease(result, 9 + 27, 12 + 27); |
||||||
|
borderColor = result.GetFormatting(0 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 255); |
||||||
|
borderColor = result.GetFormatting(9 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 0); |
||||||
|
borderColor = result.GetFormatting(12 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 45); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_TestGradientPointsBad() |
||||||
|
{ |
||||||
|
local Text result; |
||||||
|
local FormattedStringData data; |
||||||
|
local Color borderColor; |
||||||
|
Issue("Bad gradient points are incorrectly handled."); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("Check this wacky shit out: {rgb(255,0,0)~rgb(128,0,0)[50%]~rgb(150,0,0)[0.3]~rgb(123,0,0) Go f yourself}!?!?!")); |
||||||
|
result = data.GetResult(); |
||||||
|
CheckRedDecrease(result, 0 + 27, 6 + 27); |
||||||
|
CheckRedIncrease(result, 6 + 27, 9 + 27); |
||||||
|
CheckRedDecrease(result, 9 + 27, 12 + 27); |
||||||
|
borderColor = result.GetFormatting(0 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 255); |
||||||
|
borderColor = result.GetFormatting(6 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 128); |
||||||
|
borderColor = result.GetFormatting(9 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 150); |
||||||
|
borderColor = result.GetFormatting(12 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 123); |
||||||
|
data = class'FormattedStringData'.static |
||||||
|
.FromText(P("Check this wacky shit out: {rgb(200,0,0)~rgb(255,0,0)[EDF]~rgb(0,0,0)[0.50]~rgb(45,0,0) Go f yourself}!?!?!")); |
||||||
|
result = data.GetResult(); |
||||||
|
CheckRedIncrease(result, 0 + 27, 3 + 27); |
||||||
|
CheckRedDecrease(result, 3 + 27, 6 + 27); |
||||||
|
CheckRedIncrease(result, 6 + 27, 12 + 27); |
||||||
|
borderColor = result.GetFormatting(0 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 200); |
||||||
|
borderColor = result.GetFormatting(3 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 255); |
||||||
|
borderColor = result.GetFormatting(6 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 0); |
||||||
|
borderColor = result.GetFormatting(12 + 27).color; |
||||||
|
TEST_ExpectTrue(borderColor.r == 45); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
caseName = "FormattedStrings" |
||||||
|
caseGroup = "Text" |
||||||
|
} |
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue