Isovel/rtf color parsing (#27)

* feat: improve RTF color parsing to be more consistent with Resonite

* chore: optimize some regex used in RTF tag parsing

---------

Co-authored-by: Garrett Watson <toast@isota.ch>
This commit is contained in:
Nutcake 2024-01-27 16:15:27 +01:00 committed by GitHub
parent 76e32887e4
commit cc92786af2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 101 additions and 42 deletions

View file

@ -164,7 +164,7 @@ class _ReConState extends State<ReCon> {
},
child: DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) => MaterialApp(
debugShowCheckedModeBanner: false,
debugShowCheckedModeBanner: true,
title: 'ReCon',
theme: ThemeData(
useMaterial3: true,

View file

@ -40,9 +40,7 @@ class FormatNode {
}
}
if (substr.isNotEmpty) {
root.children.add(
FormatNode.buildFromStyles(activeTags, substr)
);
root.children.add(FormatNode.buildFromStyles(activeTags, substr));
}
}
return root;
@ -50,9 +48,9 @@ class FormatNode {
TextSpan toTextSpan({required TextStyle baseStyle}) {
final spanTree = TextSpan(
text: text,
style: format.isUnformatted ? baseStyle : format.style(),
children: children.map((e) => e.toTextSpan(baseStyle: baseStyle)).toList()
text: text,
style: format.isUnformatted ? baseStyle : format.style(),
children: children.map((e) => e.toTextSpan(baseStyle: baseStyle)).toList(),
);
return spanTree;
}
@ -87,8 +85,10 @@ class FormatTag {
required this.format,
});
static final _tagRegExp = RegExp(r"<(.+?)>");
static List<FormatTag> parseTags(String text) {
final startMatches = RegExp(r"<(.+?)>").allMatches(text);
final startMatches = _tagRegExp.allMatches(text);
final spans = <FormatTag>[];
@ -96,13 +96,11 @@ class FormatTag {
final fullTag = startMatch.group(1);
if (fullTag == null) continue;
final tag = FormatData.parse(fullTag);
spans.add(
FormatTag(
startIndex: startMatch.start,
endIndex: startMatch.end,
format: tag,
)
);
spans.add(FormatTag(
startIndex: startMatch.start,
endIndex: startMatch.end,
format: tag,
));
}
return spans;
}
@ -116,20 +114,79 @@ class FormatAction {
}
class FormatData {
static Color? tryParseColor(String? text) {
if (text == null) return null;
var color = cc.RgbColor.namedColors[text];
if (color != null) {
return Color.fromARGB(255, color.r.round(), color.g.round(), color.b.round());
}
static final Map<String, Map<String, Color>> _platformColorPalette = {
"neutrals": {
"dark": const Color(0xFF11151D),
"mid": const Color(0xFF86888B),
"light": const Color(0xFFE1E1E0),
},
"hero": {
"yellow": const Color(0xFFF8F770),
"green": const Color(0xFF59EB5C),
"red": const Color(0xFFFF7676),
"purple": const Color(0xFFBA64F2),
"cyan": const Color(0xFF61D1FA),
"orange": const Color(0xFFE69E50),
},
"sub": {
"yellow": const Color(0xFF484A2C),
"green": const Color(0xFF24512C),
"red": const Color(0xFF5D323A),
"purple": const Color(0xFF492F64),
"cyan": const Color(0xFF284C5D),
"orange": const Color(0xFF48392A),
},
"dark": {
"yellow": const Color(0xFF2B2E26),
"green": const Color(0xFF192D24),
"red": const Color(0xFF1A1318),
"purple": const Color(0xFF241E35),
"cyan": const Color(0xFF1A2A36),
"orange": const Color(0xFF292423),
},
};
static final _hexColorRegExp = RegExp(r"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
static final _platformColorRegExp = RegExp(r"^([a-zA-Z]+)\.([a-zA-Z]+)$");
static Color? _parseHexColor(String text) {
try {
color = cc.HexColor(text);
if (text.startsWith("#")) text = text.substring(1);
if (text.length == 3) text = text.split("").map((e) => e + e).join("");
final color = cc.HexColor(text);
return Color.fromARGB(255, color.r.round(), color.g.round(), color.b.round());
} catch (_) {
return null;
}
}
static Color? tryParseColor(String text) {
// is it a hex color?
if (_hexColorRegExp.hasMatch(text)) {
return _parseHexColor(text);
}
// is it one of Resonite's color constants?
if (_platformColorRegExp.hasMatch(text)) {
final parts = text.split(".");
if (parts.length == 2) {
final palette = _platformColorPalette[parts[0]];
if (palette != null) {
return palette[parts[1]];
}
}
}
// is it a named color?
final color = cc.RgbColor.namedColors[text];
if (color != null) {
return Color.fromARGB(255, color.r.round(), color.g.round(), color.b.round());
}
// whatever it is, it's probably safe to assume it's not a color
return null;
}
static final Map<String, FormatAction> _richTextTags = {
"align": FormatAction(),
"alpha": FormatAction(style: (param, baseStyle) {
@ -153,8 +210,12 @@ class FormatData {
"line-height": FormatAction(),
"line-indent": FormatAction(),
"link": FormatAction(),
"lowercase": FormatAction(transform: (input, parameter) => input.toLowerCase(),),
"uppercase": FormatAction(transform: (input, parameter) => input.toUpperCase(),),
"lowercase": FormatAction(
transform: (input, parameter) => input.toLowerCase(),
),
"uppercase": FormatAction(
transform: (input, parameter) => input.toUpperCase(),
),
"smallcaps": FormatAction(),
"margin": FormatAction(),
"mark": FormatAction(style: (param, baseStyle) {
@ -168,22 +229,20 @@ class FormatData {
"nobr": FormatAction(),
"page": FormatAction(),
"pos": FormatAction(),
"size": FormatAction(
style: (param, baseStyle) {
if (param == null) return baseStyle;
final baseSize = baseStyle.fontSize ?? 12;
if (param.endsWith("%")) {
final percentage = int.tryParse(param.replaceAll("%", ""));
if (percentage == null || percentage <= 0) return baseStyle;
return baseStyle.copyWith(fontSize: baseSize * (percentage / 100));
} else {
final size = num.tryParse(param);
if (size == null || size <= 0) return baseStyle;
final realSize = baseSize * (size / 1000);
return baseStyle.copyWith(fontSize: realSize.toDouble().clamp(8, 400));
}
}
),
"size": FormatAction(style: (param, baseStyle) {
if (param == null) return baseStyle;
final baseSize = baseStyle.fontSize ?? 12;
if (param.endsWith("%")) {
final percentage = int.tryParse(param.replaceAll("%", ""));
if (percentage == null || percentage <= 0) return baseStyle;
return baseStyle.copyWith(fontSize: baseSize * (percentage / 100));
} else {
final size = num.tryParse(param);
if (size == null || size <= 0) return baseStyle;
final realSize = baseSize * (size / 1000);
return baseStyle.copyWith(fontSize: realSize.toDouble().clamp(8, 400));
}
}),
"space": FormatAction(),
"sprite": FormatAction(),
"s": FormatAction(style: (param, baseStyle) => baseStyle.copyWith(decoration: TextDecoration.lineThrough)),
@ -224,4 +283,4 @@ class FormatData {
String? apply(String? text) => text == null ? null : _richTextTags[name]?.transform?.call(text, parameter);
TextStyle style() => _richTextTags[name]?.style?.call(parameter, const TextStyle()) ?? const TextStyle();
}
}

View file

@ -109,4 +109,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 3efd3b4b57928fa6a5be6b71a1f5dc6e2a2b54af
COCOAPODS: 1.12.1
COCOAPODS: 1.14.3