Skip to content

Commit 11ffb1d

Browse files
committed
feat: text rendering is better
1 parent b127c34 commit 11ffb1d

File tree

1 file changed

+107
-79
lines changed

1 file changed

+107
-79
lines changed

lib/features/models/canvas_models/canvas_painter.dart

Lines changed: 107 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -42,32 +42,44 @@ class CanvasPainter extends CustomPainter {
4242
}
4343
}
4444

45-
// CHANGE: More robust style parsing
45+
// FIX: Added robust font size parsing
46+
double _getFontSize(dynamic size) {
47+
if (size == null) return 14.0;
48+
if (size is double) return size;
49+
if (size is int) return size.toDouble();
50+
if (size is String) {
51+
switch (size) {
52+
case 'small':
53+
return 10.0;
54+
case 'large':
55+
return 18.0;
56+
case 'huge':
57+
return 22.0;
58+
default:
59+
return double.tryParse(size) ?? 14.0;
60+
}
61+
}
62+
return 14.0;
63+
}
64+
4665
TextStyle _getTextStyle(Map<String, dynamic>? attributes) {
4766
if (attributes == null) return const TextStyle(fontSize: 14.0, color: Colors.black);
4867

49-
double fontSize = 14.0;
50-
if (attributes['header'] == 1) {
51-
fontSize = 28.0;
52-
} else if (attributes['header'] == 2) {
53-
fontSize = 22.0;
54-
} else if (attributes.containsKey('size')) {
55-
// You can add more specific size handling if needed
56-
fontSize = 14.0;
57-
}
58-
59-
final isCode = attributes['code-block'] == true;
68+
final isLink = attributes['link'] != null;
69+
final isCode = attributes['code'] == true || attributes['code-block'] == true;
6070

6171
return TextStyle(
6272
fontWeight: attributes['bold'] == true ? FontWeight.bold : FontWeight.normal,
6373
fontStyle: attributes['italic'] == true ? FontStyle.italic : FontStyle.normal,
64-
color: _parseColor(attributes['color'] as String?),
65-
fontSize: fontSize,
74+
// FIX: Handle links correctly
75+
color: isLink ? Colors.blue : _parseColor(attributes['color'] as String?),
76+
fontSize: _getFontSize(attributes['size'] ?? (attributes['header'] == 1 ? 'huge' : (attributes['header'] == 2 ? 'large' : null))),
6677
fontFamily: isCode ? 'monospace' : (attributes['font'] as String?),
67-
decoration: attributes['underline'] == true ? TextDecoration.underline : TextDecoration.none,
78+
// FIX: Handle links and inline code underline
79+
decoration: attributes['underline'] == true || isLink ? TextDecoration.underline : TextDecoration.none,
6880
backgroundColor: attributes['background'] != null
6981
? _parseColor(attributes['background'] as String?)
70-
: null,
82+
: (isCode && attributes['code-block'] != true ? Colors.grey.shade300 : null),
7183
);
7284
}
7385

@@ -95,34 +107,17 @@ class CanvasPainter extends CustomPainter {
95107
canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(canvasObject.cornerRadius)), paint);
96108
} else if (canvasObject is Diamond) {
97109
final path = Path()
98-
..moveTo(rect.center.dx, rect.top)
99-
..lineTo(rect.right, rect.center.dy)
100-
..lineTo(rect.center.dx, rect.bottom)
101-
..lineTo(rect.left, rect.center.dy)
102-
..close();
110+
..moveTo(rect.center.dx, rect.top)..lineTo(rect.right, rect.center.dy)..lineTo(rect.center.dx, rect.bottom)..lineTo(rect.left, rect.center.dy)..close();
103111
canvas.drawPath(path, paint);
104112
} else if (canvasObject is Triangle) {
105-
final path = Path()
106-
..moveTo(rect.center.dx, rect.top)
107-
..lineTo(rect.right, rect.bottom)
108-
..lineTo(rect.left, rect.bottom)
109-
..close();
113+
final path = Path()..moveTo(rect.center.dx, rect.top)..lineTo(rect.right, rect.bottom)..lineTo(rect.left, rect.bottom)..close();
110114
canvas.drawPath(path, paint);
111115
} else if (canvasObject is InvertedTriangle) {
112-
final path = Path()
113-
..moveTo(rect.left, rect.top)
114-
..lineTo(rect.right, rect.top)
115-
..lineTo(rect.center.dx, rect.bottom)
116-
..close();
116+
final path = Path()..moveTo(rect.left, rect.top)..lineTo(rect.right, rect.top)..lineTo(rect.center.dx, rect.bottom)..close();
117117
canvas.drawPath(path, paint);
118118
} else if (canvasObject is Parallelogram) {
119119
final skew = rect.width * 0.25;
120-
final path = Path()
121-
..moveTo(rect.left + skew, rect.top)
122-
..lineTo(rect.right, rect.top)
123-
..lineTo(rect.right - skew, rect.bottom)
124-
..lineTo(rect.left, rect.bottom)
125-
..close();
120+
final path = Path()..moveTo(rect.left + skew, rect.top)..lineTo(rect.right, rect.top)..lineTo(rect.right - skew, rect.bottom)..lineTo(rect.left, rect.bottom)..close();
126121
canvas.drawPath(path, paint);
127122
} else if (canvasObject is Cylinder) {
128123
final ellipseHeight = min(rect.height * 0.3, 40.0);
@@ -135,61 +130,99 @@ class CanvasPainter extends CustomPainter {
135130

136131
final bool isEditingText = interactionMode == InteractionMode.editingText && currentlySelectedObjectId == canvasObject.id;
137132

138-
// CHANGE: Rewrote text rendering logic to be more robust.
133+
// FIX: Complete rewrite of the text rendering logic.
139134
if (canvasObject.textDelta != null && canvasObject.textDelta!.isNotEmpty && !isEditingText) {
140135
try {
141136
final List<dynamic> delta = jsonDecode(canvasObject.textDelta!);
137+
final double textPadding = 8.0;
138+
double yOffset = rect.top + textPadding;
142139

143-
final List<InlineSpan> textSpans = [];
140+
List<Map<String, dynamic>> currentLineOps = [];
144141
for (final op in delta) {
145-
textSpans.add(TextSpan(
146-
text: op['insert'],
147-
style: _getTextStyle(op['attributes'] as Map<String, dynamic>?),
148-
));
142+
final String text = op['insert'];
143+
final Map<String, dynamic>? attributes = op['attributes'] as Map<String, dynamic>?;
144+
145+
if (text.contains('\n')) {
146+
final lines = text.split('\n');
147+
for (int i = 0; i < lines.length; i++) {
148+
if (lines[i].isNotEmpty) {
149+
currentLineOps.add({'insert': lines[i], 'attributes': attributes});
150+
}
151+
152+
if (i < lines.length - 1) { // This is a line break
153+
final lineSpans = currentLineOps.map((o) => TextSpan(text: o['insert'], style: _getTextStyle(o['attributes']))).toList();
154+
155+
// Check for block attributes on the line break
156+
final blockAttributes = attributes ?? {};
157+
String prefix = '';
158+
double indent = 0;
159+
if(blockAttributes['list'] == 'bullet') {
160+
prefix = '• ';
161+
indent = 10.0;
162+
} else if(blockAttributes['list'] == 'ordered') {
163+
prefix = '1. '; // This is simplified, a proper implementation needs a counter
164+
indent = 10.0;
165+
} else if (blockAttributes['blockquote'] == true) {
166+
indent = 20.0;
167+
}
168+
169+
if(blockAttributes['code-block'] == true) {
170+
final blockPaint = Paint()..color = Colors.grey.shade200;
171+
// This is a simplified block drawing, would need to calculate total block height for a perfect rect
172+
// For now, it draws a rect behind each line of the code block.
173+
canvas.drawRect(Rect.fromLTWH(rect.left, yOffset, rect.width, 20), blockPaint); // Approximate height
174+
}
175+
176+
final textPainter = TextPainter(
177+
text: TextSpan(children: [TextSpan(text: prefix), ...lineSpans]),
178+
textDirection: TextDirection.ltr,
179+
);
180+
181+
final availableWidth = rect.width - (2 * textPadding) - indent;
182+
if (availableWidth > 0) {
183+
textPainter.layout(maxWidth: availableWidth);
184+
textPainter.paint(canvas, Offset(rect.left + textPadding + indent, yOffset));
185+
yOffset += textPainter.height;
186+
}
187+
currentLineOps = [];
188+
}
189+
}
190+
} else {
191+
currentLineOps.add(op);
192+
}
149193
}
150-
final richText = TextSpan(children: textSpans);
151-
152-
final textPainter = TextPainter(
153-
text: richText,
154-
textDirection: TextDirection.ltr,
155-
);
156-
157-
final double textPadding = 8.0;
158-
final double availableWidth = rect.width - (2 * textPadding);
159-
if (availableWidth > 0) {
160-
textPainter.layout(maxWidth: availableWidth);
161-
canvas.save();
162-
canvas.clipRect(rect);
163-
textPainter.paint(canvas, rect.topLeft.translate(textPadding, textPadding));
164-
canvas.restore();
194+
195+
// Paint any remaining text that didn't end with a newline
196+
if (currentLineOps.isNotEmpty) {
197+
final lineSpans = currentLineOps.map((o) => TextSpan(text: o['insert'], style: _getTextStyle(o['attributes']))).toList();
198+
final textPainter = TextPainter(
199+
text: TextSpan(children: lineSpans),
200+
textDirection: TextDirection.ltr,
201+
);
202+
final availableWidth = rect.width - (2 * textPadding);
203+
if (availableWidth > 0) {
204+
textPainter.layout(maxWidth: availableWidth);
205+
textPainter.paint(canvas, Offset(rect.left + textPadding, yOffset));
206+
}
165207
}
166208

167209
} catch (e) {
168210
print("Error painting text: $e");
169211
}
170212
}
171213

172-
if (canvasObject.id == currentlySelectedObjectId && !isEditingText) {
173-
final handlePaint = Paint()
174-
..color = Colors.blue
175-
..style = PaintingStyle.fill;
176214

215+
if (canvasObject.id == currentlySelectedObjectId && !isEditingText) {
216+
final handlePaint = Paint()..color = Colors.blue..style = PaintingStyle.fill;
177217
canvas.drawCircle(rect.topLeft, handleRadius, handlePaint);
178218
canvas.drawCircle(rect.topRight, handleRadius, handlePaint);
179219
canvas.drawCircle(rect.bottomLeft, handleRadius, handlePaint);
180220
canvas.drawCircle(rect.bottomRight, handleRadius, handlePaint);
181221

182-
final borderPaint = Paint()
183-
..color = Colors.blue
184-
..style = PaintingStyle.stroke
185-
..strokeWidth = 2.0;
186-
222+
final borderPaint = Paint()..color = Colors.blue..style = PaintingStyle.stroke..strokeWidth = 2.0;
187223
if (canvasObject is TextBoxObject && canvasObject.color == Colors.transparent) {
188224
final path = Path()..addRect(rect);
189-
canvas.drawPath(
190-
dashPath(path, dashArray: CircularIntervalList<double>([5.0, 3.0])),
191-
borderPaint,
192-
);
225+
canvas.drawPath(dashPath(path, dashArray: CircularIntervalList<double>([5.0, 3.0])), borderPaint);
193226
} else {
194227
canvas.drawRect(rect, borderPaint);
195228
}
@@ -198,15 +231,10 @@ class CanvasPainter extends CustomPainter {
198231

199232
for (final userCursor in userCursors.values) {
200233
final position = userCursor.position;
201-
final paint = Paint()..color = userCursor.color;
234+
final paint = Paint()..color = userCursor.color..strokeWidth = 2;
202235
final path = Path()
203-
..moveTo(position.dx, position.dy)
204-
..lineTo(position.dx, position.dy + 20)
205-
..lineTo(position.dx + 5, position.dy + 15)
206-
..moveTo(position.dx, position.dy + 20)
207-
..lineTo(position.dx + 10, position.dy + 20)
208-
..close();
209-
canvas.drawPath(path, paint..strokeWidth = 2);
236+
..moveTo(position.dx, position.dy)..lineTo(position.dx, position.dy + 20)..lineTo(position.dx + 5, position.dy + 15)..moveTo(position.dx, position.dy + 20)..lineTo(position.dx + 10, position.dy + 20)..close();
237+
canvas.drawPath(path, paint);
210238
}
211239
}
212240

0 commit comments

Comments
 (0)