diff --git a/core/lib/presentation/utils/html_transformer/dom/normalize_line_height_in_style_transformer.dart b/core/lib/presentation/utils/html_transformer/dom/normalize_line_height_in_style_transformer.dart
index 5415daf1a..07119143c 100644
--- a/core/lib/presentation/utils/html_transformer/dom/normalize_line_height_in_style_transformer.dart
+++ b/core/lib/presentation/utils/html_transformer/dom/normalize_line_height_in_style_transformer.dart
@@ -6,6 +6,8 @@ import 'package:html/dom.dart';
class NormalizeLineHeightInStyleTransformer extends DomTransformer {
const NormalizeLineHeightInStyleTransformer();
+ static const _removableLineHeights = ['1px', '100%'];
+
@override
Future process({
required Document document,
@@ -13,19 +15,28 @@ class NormalizeLineHeightInStyleTransformer extends DomTransformer {
Map? mapUrlDownloadCID,
}) async {
try {
+ final pattern = RegExp(
+ r'line-height\s*:\s*(?:' + _removableLineHeights.join('|') + r')\s*;?',
+ caseSensitive: false,
+ );
+
final elements = document.querySelectorAll('[style*="line-height"]');
for (final element in elements) {
final originalStyle = element.attributes['style'];
if (originalStyle == null) continue;
- final updatedStyle = originalStyle.replaceAllMapped(
- RegExp(r'line-height\s*:\s*1px\s*;?', caseSensitive: false),
- (match) => '',
- );
+ var updatedStyle = originalStyle.replaceAll(pattern, '').trim();
+
+ // Remove extra spaces (>=2 spaces → 1 space)
+ updatedStyle = updatedStyle.replaceAll(RegExp(r'\s{2,}'), ' ');
if (updatedStyle != originalStyle) {
- element.attributes['style'] = updatedStyle;
+ if (updatedStyle.isEmpty) {
+ element.attributes.remove('style');
+ } else {
+ element.attributes['style'] = updatedStyle;
+ }
}
}
} catch (e) {
diff --git a/core/test/utils/normalize_line_height_in_style_transformer_test.dart b/core/test/utils/normalize_line_height_in_style_transformer_test.dart
new file mode 100644
index 000000000..29f38773d
--- /dev/null
+++ b/core/test/utils/normalize_line_height_in_style_transformer_test.dart
@@ -0,0 +1,108 @@
+import 'package:core/presentation/utils/html_transformer/dom/normalize_line_height_in_style_transformer.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:html/parser.dart' show parse;
+import 'package:html/dom.dart';
+import 'package:core/data/network/dio_client.dart';
+import 'package:mockito/annotations.dart';
+
+import 'normalize_line_height_in_style_transformer_test.mocks.dart';
+
+@GenerateNiceMocks([MockSpec()])
+void main() {
+ group('NormalizeLineHeightInStyleTransformer', () {
+ const transformer = NormalizeLineHeightInStyleTransformer();
+ final dioClient = MockDioClient();
+
+ Future run(String html) async {
+ final doc = parse(html);
+ await transformer.process(document: doc, dioClient: dioClient);
+ return doc;
+ }
+
+ test('Should removes line-height:1px', () async {
+ final doc = await run('Hello
');
+ expect(doc.querySelector('p')!.attributes['style'], 'color:red;');
+ });
+
+ test('Should removes line-height:100%', () async {
+ final doc = await run(
+ 'Hi
',
+ );
+ expect(doc.querySelector('p')!.attributes['style'], 'font-size:14px;');
+ });
+
+ test('Should keeps other line-height values', () async {
+ final doc = await run(
+ 'Hi
',
+ );
+ expect(
+ doc.querySelector('p')!.attributes['style'],
+ 'line-height:150%; font-weight:bold;',
+ );
+ });
+
+ test('Should removes style attribute if only line-height existed',
+ () async {
+ final doc = await run('');
+ expect(
+ doc.querySelector('div')!.attributes.containsKey('style'),
+ isFalse,
+ );
+ });
+
+ test('Should handles missing style gracefully', () async {
+ final doc = await run('No style here');
+ expect(
+ doc.querySelector('span')!.attributes.containsKey('style'),
+ isFalse,
+ );
+ });
+
+ test('Should removes multiple unwanted line-heights', () async {
+ final doc = await run(
+ '',
+ );
+ expect(
+ doc.querySelector('p')!.attributes['style'],
+ 'color:blue; font-size:12px;',
+ );
+ });
+
+ test('Should removes line-height even with irregular spacing', () async {
+ final doc = await run(
+ '',
+ );
+ expect(doc.querySelector('p')!.attributes['style'], 'color: green;');
+ });
+
+ test('Should keep line-height:auto', () async {
+ final doc = await run(
+ '',
+ );
+ expect(
+ doc.querySelector('p')!.attributes['style'],
+ 'line-height:auto; font-size:16px;',
+ );
+ });
+
+ test('Should not touch unrelated CSS properties', () async {
+ final doc = await run(
+ '',
+ );
+ expect(
+ doc.querySelector('p')!.attributes['style'],
+ 'margin:0; padding:5px;',
+ );
+ });
+
+ test('Should trim trailing semicolon after removal', () async {
+ final doc = await run(
+ '',
+ );
+ expect(
+ doc.querySelector('p')!.attributes.containsKey('style'),
+ isFalse,
+ );
+ });
+ });
+}