@@ -32,20 +32,45 @@ class CanvasPainter extends CustomPainter {
3232 required this .interactionMode,
3333 });
3434
35- // Helper method to parse color from string
3635 Color _parseColor (String ? colorString) {
3736 if (colorString == null ) return Colors .black;
3837 try {
3938 final hex = colorString.replaceAll ('#' , '' );
40- if (hex.length == 6 ) {
41- return Color (int .parse ('FF$hex ' , radix: 16 ));
42- }
43- return Color (int .parse (hex, radix: 16 ));
39+ return Color (int .parse ('FF$hex ' , radix: 16 ));
4440 } catch (e) {
4541 return Colors .black;
4642 }
4743 }
4844
45+ // CHANGE: More robust style parsing
46+ TextStyle _getTextStyle (Map <String , dynamic >? attributes) {
47+ if (attributes == null ) return const TextStyle (fontSize: 14.0 , color: Colors .black);
48+
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 ;
60+
61+ return TextStyle (
62+ fontWeight: attributes['bold' ] == true ? FontWeight .bold : FontWeight .normal,
63+ fontStyle: attributes['italic' ] == true ? FontStyle .italic : FontStyle .normal,
64+ color: _parseColor (attributes['color' ] as String ? ),
65+ fontSize: fontSize,
66+ fontFamily: isCode ? 'monospace' : (attributes['font' ] as String ? ),
67+ decoration: attributes['underline' ] == true ? TextDecoration .underline : TextDecoration .none,
68+ backgroundColor: attributes['background' ] != null
69+ ? _parseColor (attributes['background' ] as String ? )
70+ : null ,
71+ );
72+ }
73+
4974 @override
5075 void paint (Canvas canvas, Size size) {
5176 for (final canvasObject in canvasObjects.values) {
@@ -54,8 +79,7 @@ class CanvasPainter extends CustomPainter {
5479
5580 if (canvasObject is Circle ) {
5681 canvas.drawCircle (canvasObject.center, canvasObject.radius, paint);
57- rect = Rect .fromCircle (
58- center: canvasObject.center, radius: canvasObject.radius);
82+ rect = Rect .fromCircle (center: canvasObject.center, radius: canvasObject.radius);
5983 } else if (canvasObject is TextBoxObject ) {
6084 rect = canvasObject.getBounds ();
6185 if (canvasObject.color != Colors .transparent) {
@@ -68,10 +92,7 @@ class CanvasPainter extends CustomPainter {
6892 } else if (canvasObject is Square ) {
6993 canvas.drawRect (rect, paint);
7094 } else if (canvasObject is RoundedSquare ) {
71- canvas.drawRRect (
72- RRect .fromRectAndRadius (
73- rect, Radius .circular (canvasObject.cornerRadius)),
74- paint);
95+ canvas.drawRRect (RRect .fromRectAndRadius (rect, Radius .circular (canvasObject.cornerRadius)), paint);
7596 } else if (canvasObject is Diamond ) {
7697 final path = Path ()
7798 ..moveTo (rect.center.dx, rect.top)
@@ -105,134 +126,46 @@ class CanvasPainter extends CustomPainter {
105126 canvas.drawPath (path, paint);
106127 } else if (canvasObject is Cylinder ) {
107128 final ellipseHeight = min (rect.height * 0.3 , 40.0 );
108- final bodyRect = Rect .fromLTRB (rect.left,
109- rect.top + ellipseHeight / 2 , rect.right, rect.bottom - ellipseHeight / 2 );
129+ final bodyRect = Rect .fromLTRB (rect.left, rect.top + ellipseHeight / 2 , rect.right, rect.bottom - ellipseHeight / 2 );
110130 canvas.drawRect (bodyRect, paint);
111- canvas.drawOval (
112- Rect .fromCenter (
113- center: bodyRect.topCenter,
114- width: rect.width,
115- height: ellipseHeight),
116- paint);
117- canvas.drawOval (
118- Rect .fromCenter (
119- center: bodyRect.bottomCenter,
120- width: rect.width,
121- height: ellipseHeight),
122- paint);
131+ canvas.drawOval (Rect .fromCenter (center: bodyRect.topCenter, width: rect.width, height: ellipseHeight), paint);
132+ canvas.drawOval (Rect .fromCenter (center: bodyRect.bottomCenter, width: rect.width, height: ellipseHeight), paint);
123133 }
124134 }
125135
126- final bool isEditingText = interactionMode == InteractionMode .editingText &&
127- currentlySelectedObjectId == canvasObject.id;
136+ final bool isEditingText = interactionMode == InteractionMode .editingText && currentlySelectedObjectId == canvasObject.id;
128137
129- // CHANGE: Rewrote text rendering logic for proper styling.
130- if (canvasObject.textDelta != null &&
131- canvasObject.textDelta! .isNotEmpty &&
132- ! isEditingText) {
138+ // CHANGE: Rewrote text rendering logic to be more robust.
139+ if (canvasObject.textDelta != null && canvasObject.textDelta! .isNotEmpty && ! isEditingText) {
133140 try {
134- final List <dynamic > deltaJson = jsonDecode (canvasObject.textDelta! );
135- final List <TextSpan > textSpans = [];
136- int listCounter = 1 ;
137-
138- for (final op in deltaJson) {
139- if (op is Map && op.containsKey ('insert' )) {
140- String text = op['insert' ];
141- final Map <String , dynamic >? attributes =
142- op['attributes' ] as Map <String , dynamic >? ;
143-
144- double fontSize = 14.0 ;
145- FontWeight fontWeight = FontWeight .normal;
146- FontStyle fontStyle = FontStyle .normal;
147- TextDecoration textDecoration = TextDecoration .none;
148- Color color = Colors .black;
149- Color ? backgroundColor;
150- String ? listType;
151-
152- if (attributes != null ) {
153- fontWeight = attributes['bold' ] == true
154- ? FontWeight .bold
155- : FontWeight .normal;
156- fontStyle = attributes['italic' ] == true
157- ? FontStyle .italic
158- : FontStyle .normal;
159- textDecoration = attributes['underline' ] == true
160- ? TextDecoration .underline
161- : TextDecoration .none;
162- color = _parseColor (attributes['color' ] as String ? );
163- backgroundColor =
164- _parseColor (attributes['background' ] as String ? );
165- if (attributes['header' ] == 1 ) fontSize = 24.0 ;
166- if (attributes['header' ] == 2 ) fontSize = 20.0 ;
167- if (attributes['list' ] != null ) {
168- listType = attributes['list' ];
169- }
170- }
171-
172- if (text.endsWith ('\n ' ) && listType != null ) {
173- if (listType == 'bullet' ) {
174- text = '• ${text .substring (0 , text .length -1 )}\n ' ;
175- } else if (listType == 'ordered' ) {
176- text = '$listCounter . ${text .substring (0 , text .length -1 )}\n ' ;
177- listCounter++ ;
178- }
179- } else if (listType == null ) {
180- listCounter = 1 ; // Reset counter when not in a list
181- }
182-
183- textSpans.add (
184- TextSpan (
185- text: text,
186- style: TextStyle (
187- fontSize: fontSize,
188- fontWeight: fontWeight,
189- fontStyle: fontStyle,
190- decoration: textDecoration,
191- color: color,
192- backgroundColor: backgroundColor,
193- ),
194- ),
195- );
196- }
141+ final List <dynamic > delta = jsonDecode (canvasObject.textDelta! );
142+
143+ final List <InlineSpan > textSpans = [];
144+ for (final op in delta) {
145+ textSpans.add (TextSpan (
146+ text: op['insert' ],
147+ style: _getTextStyle (op['attributes' ] as Map <String , dynamic >? ),
148+ ));
197149 }
198-
199150 final richText = TextSpan (children: textSpans);
151+
200152 final textPainter = TextPainter (
201153 text: richText,
202154 textDirection: TextDirection .ltr,
203- textAlign: TextAlign .start,
204155 );
205-
206- double textPadding = 5.0 ;
207- double availableWidth = rect.width - 2 * textPadding;
208-
209- if (availableWidth <= 0 ) continue ;
210-
211- textPainter.layout (maxWidth: availableWidth);
212156
213- final textOffset = Offset (
214- rect.left + textPadding,
215- rect.top + textPadding,
216- );
217-
218- canvas.save ( );
219- canvas. clipRect ( rect);
220- textPainter. paint (canvas, textOffset );
221- canvas. restore ();
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 ( );
165+ }
222166
223167 } catch (e) {
224- print (
225- "Warning: Could not parse Quill Delta, rendering as plain text: $e " );
226- // Fallback for plain text
227- final textPainter = TextPainter (
228- text: TextSpan (
229- text: canvasObject.textDelta,
230- style: const TextStyle (color: Colors .black, fontSize: 14.0 ),
231- ),
232- textDirection: TextDirection .ltr,
233- );
234- textPainter.layout (maxWidth: rect.width);
235- textPainter.paint (canvas, rect.topLeft);
168+ print ("Error painting text: $e " );
236169 }
237170 }
238171
@@ -251,11 +184,8 @@ class CanvasPainter extends CustomPainter {
251184 ..style = PaintingStyle .stroke
252185 ..strokeWidth = 2.0 ;
253186
254- if (canvasObject is TextBoxObject &&
255- canvasObject.color == Colors .transparent) {
256- // Draw dashed border for transparent text box
257- final path = Path ()
258- ..addRect (rect);
187+ if (canvasObject is TextBoxObject && canvasObject.color == Colors .transparent) {
188+ final path = Path ()..addRect (rect);
259189 canvas.drawPath (
260190 dashPath (path, dashArray: CircularIntervalList <double >([5.0 , 3.0 ])),
261191 borderPaint,
@@ -289,8 +219,7 @@ class CanvasPainter extends CustomPainter {
289219 _hasCanvasObjectsChanged (oldPainter.canvasObjects, canvasObjects);
290220 }
291221
292- bool _hasCanvasObjectsChanged (
293- Map <String , CanvasObject > oldObjects, Map <String , CanvasObject > newObjects) {
222+ bool _hasCanvasObjectsChanged (Map <String , CanvasObject > oldObjects, Map <String , CanvasObject > newObjects) {
294223 if (oldObjects.length != newObjects.length) return true ;
295224 for (final id in newObjects.keys) {
296225 final newObj = newObjects[id];
@@ -303,11 +232,7 @@ class CanvasPainter extends CustomPainter {
303232 }
304233}
305234
306- // Copied from path_drawing package to avoid adding a dependency
307- Path dashPath (
308- Path source, {
309- required CircularIntervalList <double > dashArray,
310- }) {
235+ Path dashPath (Path source, {required CircularIntervalList <double > dashArray}) {
311236 final Path dest = Path ();
312237 for (final metric in source.computeMetrics ()) {
313238 double distance = 0.0 ;
0 commit comments