@@ -16,6 +16,7 @@ import 'package:cookethflow/features/models/canvas_models/objects/text_box_objec
1616import 'package:cookethflow/features/models/canvas_models/objects/triangle_object.dart' ;
1717import 'package:cookethflow/features/models/canvas_models/user_cursor.dart' ;
1818import 'package:flutter/material.dart' ;
19+ import 'package:path_drawing/path_drawing.dart' ;
1920
2021class CanvasPainter extends CustomPainter {
2122 final Map <String , UserCursor > userCursors;
@@ -42,7 +43,6 @@ class CanvasPainter extends CustomPainter {
4243 }
4344 }
4445
45- // FIX: Added robust font size parsing
4646 double _getFontSize (dynamic size) {
4747 if (size == null ) return 14.0 ;
4848 if (size is double ) return size;
@@ -67,15 +67,23 @@ class CanvasPainter extends CustomPainter {
6767
6868 final isLink = attributes['link' ] != null ;
6969 final isCode = attributes['code' ] == true || attributes['code-block' ] == true ;
70+ final headerLevel = attributes['header' ];
71+
72+ double fontSize = 14.0 ;
73+ if (headerLevel == 1 ) {
74+ fontSize = 22.0 ;
75+ } else if (headerLevel == 2 ) {
76+ fontSize = 18.0 ;
77+ } else if (attributes['size' ] != null ) {
78+ fontSize = _getFontSize (attributes['size' ]);
79+ }
7080
7181 return TextStyle (
7282 fontWeight: attributes['bold' ] == true ? FontWeight .bold : FontWeight .normal,
7383 fontStyle: attributes['italic' ] == true ? FontStyle .italic : FontStyle .normal,
74- // FIX: Handle links correctly
7584 color: isLink ? Colors .blue : _parseColor (attributes['color' ] as String ? ),
76- fontSize: _getFontSize (attributes[ 'size' ] ?? (attributes[ 'header' ] == 1 ? 'huge' : (attributes[ 'header' ] == 2 ? 'large' : null ))) ,
85+ fontSize: fontSize ,
7786 fontFamily: isCode ? 'monospace' : (attributes['font' ] as String ? ),
78- // FIX: Handle links and inline code underline
7987 decoration: attributes['underline' ] == true || isLink ? TextDecoration .underline : TextDecoration .none,
8088 backgroundColor: attributes['background' ] != null
8189 ? _parseColor (attributes['background' ] as String ? )
@@ -130,82 +138,88 @@ class CanvasPainter extends CustomPainter {
130138
131139 final bool isEditingText = interactionMode == InteractionMode .editingText && currentlySelectedObjectId == canvasObject.id;
132140
133- // FIX: Complete rewrite of the text rendering logic.
134141 if (canvasObject.textDelta != null && canvasObject.textDelta! .isNotEmpty && ! isEditingText) {
135142 try {
136143 final List <dynamic > delta = jsonDecode (canvasObject.textDelta! );
137144 final double textPadding = 8.0 ;
138145 double yOffset = rect.top + textPadding;
139146
147+ final List <Map <String , dynamic >> lines = [];
140148 List <Map <String , dynamic >> currentLineOps = [];
149+
141150 for (final op in delta) {
142151 final String text = op['insert' ];
143152 final Map <String , dynamic >? attributes = op['attributes' ] as Map <String , dynamic >? ;
144153
145154 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});
155+ final textLines = text.split ('\n ' );
156+ for (int i = 0 ; i < textLines .length; i++ ) {
157+ if (textLines [i].isNotEmpty) {
158+ currentLineOps.add ({'insert' : textLines [i], 'attributes' : attributes});
150159 }
151160
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 = [];
161+ if (i < textLines.length - 1 ) { // Line break
162+ lines.add ({
163+ 'ops' : List .from (currentLineOps),
164+ 'attributes' : attributes ?? {},
165+ });
166+ currentLineOps.clear ();
188167 }
189168 }
190169 } else {
191170 currentLineOps.add (op);
192171 }
193172 }
194-
195- // Paint any remaining text that didn't end with a newline
196173 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- }
174+ lines.add ({'ops' : currentLineOps, 'attributes' : {}});
207175 }
208176
177+ int orderedListCounter = 1 ;
178+ for (final line in lines) {
179+ // FIX: Safely create a typed list from the dynamic list.
180+ final lineOps = List <Map <String , dynamic >>.from (line['ops' ] as List );
181+ final blockAttributes = line['attributes' ] as Map <String , dynamic >;
182+
183+ final lineSpans = lineOps.map ((o) => TextSpan (text: o['insert' ], style: _getTextStyle (o['attributes' ] as Map <String , dynamic >? ))).toList ();
184+
185+ String prefix = '' ;
186+ double indent = 0 ;
187+ if (blockAttributes['list' ] == 'bullet' ) {
188+ prefix = '• ' ;
189+ indent = 10.0 ;
190+ orderedListCounter = 1 ; // Reset ordered list
191+ } else if (blockAttributes['list' ] == 'ordered' ) {
192+ prefix = '$orderedListCounter . ' ;
193+ indent = 10.0 ;
194+ orderedListCounter++ ;
195+ } else {
196+ orderedListCounter = 1 ; // Reset
197+ }
198+
199+ if (blockAttributes['blockquote' ] == true ) {
200+ indent = 20.0 ;
201+ final blockPaint = Paint ()..color = Colors .grey.shade300..strokeWidth = 2 ;
202+ canvas.drawLine (Offset (rect.left + textPadding, yOffset), Offset (rect.left + textPadding, yOffset + 20 ), blockPaint); // Approximate height
203+ }
204+
205+ if (blockAttributes['code-block' ] == true ) {
206+ final blockPaint = Paint ()..color = Colors .grey.shade200;
207+ canvas.drawRect (Rect .fromLTWH (rect.left, yOffset, rect.width, 20 ), blockPaint); // Approximate height
208+ }
209+
210+ final textPainter = TextPainter (
211+ text: TextSpan (children: [TextSpan (text: prefix, style: _getTextStyle (blockAttributes)), ...lineSpans]),
212+ textDirection: TextDirection .ltr,
213+ textAlign: TextAlign .start,
214+ );
215+
216+ final availableWidth = rect.width - (2 * textPadding) - indent;
217+ if (availableWidth > 0 ) {
218+ textPainter.layout (maxWidth: availableWidth);
219+ textPainter.paint (canvas, Offset (rect.left + textPadding + indent, yOffset));
220+ yOffset += textPainter.height;
221+ }
222+ }
209223 } catch (e) {
210224 print ("Error painting text: $e " );
211225 }
@@ -220,12 +234,8 @@ class CanvasPainter extends CustomPainter {
220234 canvas.drawCircle (rect.bottomRight, handleRadius, handlePaint);
221235
222236 final borderPaint = Paint ()..color = Colors .blue..style = PaintingStyle .stroke..strokeWidth = 2.0 ;
223- if (canvasObject is TextBoxObject && canvasObject.color == Colors .transparent) {
224- final path = Path ()..addRect (rect);
225- canvas.drawPath (dashPath (path, dashArray: CircularIntervalList <double >([5.0 , 3.0 ])), borderPaint);
226- } else {
227- canvas.drawRect (rect, borderPaint);
228- }
237+ final path = Path ()..addRect (rect);
238+ canvas.drawPath (dashPath (path, dashArray: CircularIntervalList <double >([5.0 , 3.0 ])), borderPaint);
229239 }
230240 }
231241
@@ -258,33 +268,4 @@ class CanvasPainter extends CustomPainter {
258268 }
259269 return false ;
260270 }
261- }
262-
263- Path dashPath (Path source, {required CircularIntervalList <double > dashArray}) {
264- final Path dest = Path ();
265- for (final metric in source.computeMetrics ()) {
266- double distance = 0.0 ;
267- bool draw = true ;
268- while (distance < metric.length) {
269- final len = dashArray.next;
270- if (draw) {
271- dest.addPath (metric.extractPath (distance, distance + len), Offset .zero);
272- }
273- distance += len;
274- draw = ! draw;
275- }
276- }
277- return dest;
278- }
279-
280- class CircularIntervalList <T > {
281- CircularIntervalList (this ._values);
282- final List <T > _values;
283- int _idx = 0 ;
284- T get next {
285- if (_idx >= _values.length) {
286- _idx = 0 ;
287- }
288- return _values[_idx++ ];
289- }
290271}
0 commit comments