PatchworkOS  966e257
A non-POSIX operating system.
Loading...
Searching...
No Matches
drawable.c
Go to the documentation of this file.
1#include "internal.h"
2
3#include <math.h>
4#include <stdlib.h>
5#include <string.h>
6
7void draw_rect(drawable_t* draw, const rect_t* rect, pixel_t pixel)
8{
9 if (draw == NULL || rect == NULL)
10 {
11 return;
12 }
13
14 rect_t fitRect = *rect;
15 RECT_FIT(&fitRect, &draw->contentRect);
16
17 for (int64_t y = fitRect.top; y < fitRect.bottom; y++)
18 {
19 memset32(&draw->buffer[fitRect.left + y * draw->stride], pixel, RECT_WIDTH(&fitRect));
20 }
21
22 draw_invalidate(draw, &fitRect);
23}
24
25typedef struct edge
26{
27 int64_t yMin; // scanline where edge starts
28 int64_t yMax; // scanline where edge ends
29 double x; // current x intersection
30 double invSlope; // dx/dy for incrementing x
31} edge_t;
32
33static inline int edge_compare(const void* a, const void* b)
34{
35 edge_t* edgeA = *(edge_t**)a;
36 edge_t* edgeB = *(edge_t**)b;
37 if (edgeA->x < edgeB->x)
38 {
39 return -1;
40 }
41 else if (edgeA->x > edgeB->x)
42 {
43 return 1;
44 }
45 return 0;
46}
47
48void draw_polygon(drawable_t* draw, const point_t* points, uint64_t pointCount, pixel_t pixel)
49{
50 if (draw == NULL || points == NULL || pointCount < 3)
51 {
52 return;
53 }
54
55 int64_t minY = INT64_MAX;
56 int64_t maxY = INT64_MIN;
57 int64_t minX = INT64_MAX;
58 int64_t maxX = INT64_MIN;
59
60 for (uint64_t i = 0; i < pointCount; i++)
61 {
62 minY = MIN(minY, points[i].y);
63 maxY = MAX(maxY, points[i].y);
64 minX = MIN(minX, points[i].x);
65 maxX = MAX(maxX, points[i].x);
66 }
67
68 edge_t edges[pointCount];
69 uint64_t edgeCount = 0;
70 for (uint64_t i = 0; i < pointCount; i++)
71 {
72 point_t p1 = points[i];
73 point_t p2 = points[(i + 1) % pointCount];
74 if (p1.y == p2.y)
75 {
76 continue;
77 }
78 edge_t* edge = &edges[edgeCount++];
79 if (p1.y < p2.y)
80 {
81 edge->yMin = p1.y;
82 edge->yMax = p2.y;
83 edge->x = p1.x;
84 edge->invSlope = (double)(p2.x - p1.x) / (double)(p2.y - p1.y);
85 }
86 else
87 {
88 edge->yMin = p2.y;
89 edge->yMax = p1.y;
90 edge->x = p2.x;
91 edge->invSlope = (double)(p1.x - p2.x) / (double)(p1.y - p2.y);
92 }
93 }
94
95 const double samples[4][2] = {{0.125, 0.375}, {0.375, 0.125}, {0.625, 0.875}, {0.875, 0.625}};
96 const int sampleCount = 4;
97
98 edge_t* activeEdges[pointCount];
99 uint64_t activeEdgeCount = 0;
100
101 for (int64_t y = minY; y <= maxY; y++)
102 {
103 for (uint64_t i = 0; i < edgeCount; i++)
104 {
105 if (edges[i].yMin == y)
106 {
107 activeEdges[activeEdgeCount++] = &edges[i];
108 }
109 }
110
111 for (uint64_t i = 0; i < activeEdgeCount; i++)
112 {
113 if (activeEdges[i]->yMax <= y)
114 {
115 activeEdges[i] = activeEdges[--activeEdgeCount];
116 i--;
117 }
118 }
119
120 if (activeEdgeCount < 2)
121 {
122 continue;
123 }
124
125 qsort(activeEdges, activeEdgeCount, sizeof(edge_t*), edge_compare);
126
127 int64_t scanMinX = (int64_t)(activeEdges[0]->x);
128 int64_t scanMaxX = (int64_t)(activeEdges[activeEdgeCount - 1]->x);
129
130 scanMinX = MAX(scanMinX - 1, draw->contentRect.left);
131 scanMaxX = MIN(scanMaxX + 1, draw->contentRect.right - 1);
132
133 if (y < draw->contentRect.top || y >= draw->contentRect.bottom)
134 {
135 goto update_edges;
136 }
137
138 for (int64_t x = scanMinX; x <= scanMaxX; x++)
139 {
140 int samplesInside = 0;
141
142 for (int s = 0; s < sampleCount; s++)
143 {
144 double sampleX = x + samples[s][0];
145 double sampleY = y + samples[s][1];
146
147 if (polygon_contains(sampleX, sampleY, points, pointCount))
148 {
149 samplesInside++;
150 }
151 }
152
153 if (samplesInside == 0)
154 {
155 continue;
156 }
157 else if (samplesInside == sampleCount)
158 {
159 draw->buffer[x + y * draw->stride] = pixel;
160 }
161 else
162 {
163 uint8_t originalAlpha = PIXEL_ALPHA(pixel);
164 uint8_t newAlpha = (originalAlpha * samplesInside) / sampleCount;
165
166 pixel_t aaPixel = PIXEL_ARGB(newAlpha, PIXEL_RED(pixel), PIXEL_GREEN(pixel), PIXEL_BLUE(pixel));
167 PIXEL_BLEND(&draw->buffer[x + y * draw->stride], &aaPixel);
168 }
169 }
170
171update_edges:
172 for (uint64_t i = 0; i < activeEdgeCount; i++)
173 {
174 activeEdges[i]->x += activeEdges[i]->invSlope;
175 }
176 }
177}
178
179void draw_line(drawable_t* draw, const point_t* start, const point_t* end, pixel_t pixel, uint32_t thickness)
180{
181 if (draw == NULL || start == NULL || end == NULL || thickness == 0)
182 {
183 return;
184 }
185
186 point_t fitStart = {CLAMP(start->x, draw->contentRect.left, draw->contentRect.right - 1),
187 CLAMP(start->y, draw->contentRect.top, draw->contentRect.bottom - 1)};
188 point_t fitEnd = {CLAMP(end->x, draw->contentRect.left, draw->contentRect.right - 1),
189 CLAMP(end->y, draw->contentRect.top, draw->contentRect.bottom - 1)};
190
191 point_t points[4];
192 double angle = atan2((double)(fitEnd.y - fitStart.y), (double)(fitEnd.x - fitStart.x));
193 double halfThickness = (double)thickness / 2.0;
194 double sinAngle = sin(angle + M_PI_2) * halfThickness;
195 double cosAngle = cos(angle + M_PI_2) * halfThickness;
196 points[0] = (point_t){.x = (int64_t)(fitStart.x + cosAngle), .y = (int64_t)(fitStart.y + sinAngle)};
197 points[1] = (point_t){.x = (int64_t)(fitStart.x - cosAngle), .y = (int64_t)(fitStart.y - sinAngle)};
198 points[2] = (point_t){.x = (int64_t)(fitEnd.x - cosAngle), .y = (int64_t)(fitEnd.y - sinAngle)};
199 points[3] = (point_t){.x = (int64_t)(fitEnd.x + cosAngle), .y = (int64_t)(fitEnd.y + sinAngle)};
200
201 draw_polygon(draw, points, 4, pixel);
202}
203
204void draw_frame(drawable_t* draw, const rect_t* rect, uint64_t width, pixel_t foreground, pixel_t background)
205{
206 if (draw == NULL || rect == NULL || width == 0)
207 {
208 return;
209 }
210
211 rect_t fitRect = *rect;
212 RECT_FIT(&fitRect, &draw->contentRect);
213
214 rect_t leftRect = (rect_t){
215 .left = fitRect.left,
216 .top = fitRect.top,
217 .right = fitRect.left + width,
218 .bottom = fitRect.bottom - width,
219 };
220 draw_rect(draw, &leftRect, foreground);
221
222 rect_t topRect = (rect_t){
223 .left = fitRect.left + width,
224 .top = fitRect.top,
225 .right = fitRect.right - width,
226 .bottom = fitRect.top + width,
227 };
228 draw_rect(draw, &topRect, foreground);
229
230 rect_t rightRect = (rect_t){
231 .left = fitRect.right - width,
232 .top = fitRect.top + width,
233 .right = fitRect.right,
234 .bottom = fitRect.bottom,
235 };
236 draw_rect(draw, &rightRect, background);
237
238 rect_t bottomRect = (rect_t){
239 .left = fitRect.left + width,
240 .top = fitRect.bottom - width,
241 .right = fitRect.right - width,
242 .bottom = fitRect.bottom,
243 };
244 draw_rect(draw, &bottomRect, background);
245
246 for (uint64_t y = 0; y < width; y++)
247 {
248 for (uint64_t x = 0; x < width; x++)
249 {
250 pixel_t color = x + y < width - 1 ? foreground : background;
251 draw->buffer[(fitRect.right - width + x) + (fitRect.top + y) * draw->stride] = color;
252 draw->buffer[(fitRect.left + x) + (fitRect.bottom - width + y) * draw->stride] = color;
253 }
254 }
255
256 draw_invalidate(draw, &fitRect);
257}
258
259void draw_dashed_outline(drawable_t* draw, const rect_t* rect, pixel_t pixel, uint32_t length, int32_t width)
260{
261 if (draw == NULL || rect == NULL || length == 0 || width <= 0)
262 {
263 return;
264 }
265
266 rect_t fitRect = *rect;
267 RECT_FIT(&fitRect, &draw->contentRect);
268
269 if (RECT_WIDTH(&fitRect) <= 0 || RECT_HEIGHT(&fitRect) <= 0)
270 {
271 return;
272 }
273
274 uint32_t totalLength = length * 2;
275
276 for (int32_t w = 0; w < width; w++)
277 {
278 if (fitRect.top + w >= draw->contentRect.top && fitRect.top + w < draw->contentRect.bottom)
279 {
280 int64_t y = fitRect.top + w;
281 for (int64_t x = fitRect.left; x < fitRect.right; x++)
282 {
283 uint32_t inPattern = (x - fitRect.left) % totalLength;
284 if (inPattern < length)
285 {
286 draw->buffer[x + y * draw->stride] = pixel;
287 }
288 }
289 }
290
291 if (fitRect.bottom - 1 - w >= draw->contentRect.top && fitRect.bottom - 1 - w < draw->contentRect.bottom &&
292 RECT_HEIGHT(&fitRect) > 1 && fitRect.bottom - 1 - w > fitRect.top + w)
293 {
294 int64_t y = fitRect.bottom - 1 - w;
295 for (int64_t x = fitRect.left; x < fitRect.right; x++)
296 {
297 uint32_t inPattern = (x - fitRect.left) % totalLength;
298 if (inPattern < length)
299 {
300 draw->buffer[x + y * draw->stride] = pixel;
301 }
302 }
303 }
304 }
305
306 for (int32_t w = 0; w < width; w++)
307 {
308 if (fitRect.left + w >= draw->contentRect.left && fitRect.left + w < draw->contentRect.right)
309 {
310 int64_t x = fitRect.left + w;
311 for (int64_t y = fitRect.top + width; y < fitRect.bottom - width; y++)
312 {
313 uint32_t inPattern = (y - fitRect.top - width) % totalLength;
314 if (inPattern < length)
315 {
316 draw->buffer[x + y * draw->stride] = pixel;
317 }
318 }
319 }
320
321 if (fitRect.right - 1 - w >= draw->contentRect.left && fitRect.right - 1 - w < draw->contentRect.right &&
322 RECT_WIDTH(&fitRect) > 1 && fitRect.right - 1 - w > fitRect.left + w)
323 {
324 int64_t x = fitRect.right - 1 - w;
325 for (int64_t y = fitRect.top + width; y < fitRect.bottom - width; y++)
326 {
327 uint32_t inPattern = (y - fitRect.top - width) % totalLength;
328 if (inPattern < length)
329 {
330 draw->buffer[x + y * draw->stride] = pixel;
331 }
332 }
333 }
334 }
335
336 draw_invalidate(draw, &fitRect);
337}
338
339void draw_bezel(drawable_t* draw, const rect_t* rect, uint64_t width, pixel_t pixel)
340{
341 if (draw == NULL || rect == NULL || width == 0)
342 {
343 return;
344 }
345
346 rect_t fitRect = *rect;
347 RECT_FIT(&fitRect, &draw->contentRect);
348
349 rect_t leftRect = (rect_t){
350 .left = fitRect.left,
351 .top = fitRect.top + width - width / 2,
352 .right = fitRect.left + width,
353 .bottom = fitRect.bottom - width + width / 2,
354 };
355 draw_rect(draw, &leftRect, pixel);
356
357 rect_t topRect = (rect_t){
358 .left = fitRect.left + width - width / 2,
359 .top = fitRect.top,
360 .right = fitRect.right - width + width / 2,
361 .bottom = fitRect.top + width,
362 };
363 draw_rect(draw, &topRect, pixel);
364
365 rect_t rightRect = (rect_t){
366 .left = fitRect.right - width,
367 .top = fitRect.top + width - width / 2,
368 .right = fitRect.right,
369 .bottom = fitRect.bottom - width + width / 2,
370 };
371 draw_rect(draw, &rightRect, pixel);
372
373 rect_t bottomRect = (rect_t){
374 .left = fitRect.left + width - width / 2,
375 .top = fitRect.bottom - width,
376 .right = fitRect.right - width + width / 2,
377 .bottom = fitRect.bottom,
378 };
379 draw_rect(draw, &bottomRect, pixel);
380}
381
382void draw_gradient(drawable_t* draw, const rect_t* rect, pixel_t start, pixel_t end, direction_t direction,
383 bool shouldAddNoise)
384{
385 if (draw == NULL || rect == NULL)
386 {
387 return;
388 }
389
390 rect_t fitRect = *rect;
391 RECT_FIT(&fitRect, &draw->contentRect);
392
393 int64_t width = RECT_WIDTH(&fitRect);
394 int64_t height = RECT_HEIGHT(&fitRect);
395
396 for (int64_t y = fitRect.top; y < fitRect.bottom; y++)
397 {
398 for (int64_t x = fitRect.left; x < fitRect.right; x++)
399 {
400 int32_t factorNum;
401 int32_t factorDenom;
402
403 switch (direction)
404 {
406 {
407 factorNum = (y - fitRect.top);
408 factorDenom = height;
409 }
410 break;
412 {
413 factorNum = (x - fitRect.left);
414 factorDenom = width;
415 }
416 break;
418 {
419 factorNum = (x - fitRect.left) + (y - fitRect.top);
420 factorDenom = width + height;
421 }
422 break;
423 default:
424 {
425 factorNum = 0;
426 factorDenom = 1;
427 }
428 break;
429 }
430
431 int32_t deltaRed = PIXEL_RED(end) - PIXEL_RED(start);
432 int32_t deltaGreen = PIXEL_GREEN(end) - PIXEL_GREEN(start);
433 int32_t deltaBlue = PIXEL_BLUE(end) - PIXEL_BLUE(start);
434
435 int32_t red = PIXEL_RED(start) + ((factorNum * deltaRed) / factorDenom);
436 int32_t green = PIXEL_GREEN(start) + ((factorNum * deltaGreen) / factorDenom);
437 int32_t blue = PIXEL_BLUE(start) + ((factorNum * deltaBlue) / factorDenom);
438
439 if (shouldAddNoise)
440 {
441 int32_t noiseRed = (rand() % 5) - 2;
442 int32_t noiseGreen = (rand() % 5) - 2;
443 int32_t noiseBlue = (rand() % 5) - 2;
444
445 red += noiseRed;
446 green += noiseGreen;
447 blue += noiseBlue;
448
449 red = CLAMP(0, 255, red);
450 green = CLAMP(0, 255, green);
451 blue = CLAMP(0, 255, blue);
452 }
453
454 pixel_t pixel = PIXEL_ARGB(255, red, green, blue);
455 draw->buffer[x + y * draw->stride] = pixel;
456 }
457 }
458
459 draw_invalidate(draw, &fitRect);
460}
461
462void draw_transfer(drawable_t* dest, drawable_t* src, const rect_t* destRect, const point_t* srcPoint)
463{
464 if (dest == NULL || src == NULL || destRect == NULL || srcPoint == NULL)
465 {
466 return;
467 }
468
469 int64_t width = RECT_WIDTH(destRect);
470 int64_t height = RECT_HEIGHT(destRect);
471
472 if (width <= 0 || height <= 0)
473 {
474 return;
475 }
476 if (srcPoint->x < 0 || srcPoint->y < 0 || srcPoint->x + width > RECT_WIDTH(&src->contentRect) ||
477 srcPoint->y + height > RECT_HEIGHT(&src->contentRect))
478 {
479 return;
480 }
481 if (destRect->left < 0 || destRect->top < 0 || destRect->left + width > RECT_WIDTH(&dest->contentRect) ||
482 destRect->top + height > RECT_HEIGHT(&dest->contentRect))
483 {
484 return;
485 }
486
487 if (dest == src)
488 {
489 for (int64_t y = 0; y < height; y++)
490 {
491 memmove(&dest->buffer[destRect->left + (y + destRect->top) * dest->stride],
492 &src->buffer[srcPoint->x + (y + srcPoint->y) * src->stride], width * sizeof(pixel_t));
493 }
494 }
495 else
496 {
497 for (int64_t y = 0; y < height; y++)
498 {
499 memcpy(&dest->buffer[destRect->left + (y + destRect->top) * dest->stride],
500 &src->buffer[srcPoint->x + (y + srcPoint->y) * src->stride], width * sizeof(pixel_t));
501 }
502 }
503
504 draw_invalidate(dest, destRect);
505}
506
507void draw_transfer_blend(drawable_t* dest, drawable_t* src, const rect_t* destRect, const point_t* srcPoint)
508{
509 if (dest == NULL || src == NULL || destRect == NULL || srcPoint == NULL)
510 {
511 return;
512 }
513
514 int64_t width = RECT_WIDTH(destRect);
515 int64_t height = RECT_HEIGHT(destRect);
516
517 if (width <= 0 || height <= 0)
518 {
519 return;
520 }
521 if (srcPoint->x < 0 || srcPoint->y < 0 || srcPoint->x + width > RECT_WIDTH(&src->contentRect) ||
522 srcPoint->y + height > RECT_HEIGHT(&src->contentRect))
523 {
524 return;
525 }
526 if (destRect->left < 0 || destRect->top < 0 || destRect->left + width > RECT_WIDTH(&dest->contentRect) ||
527 destRect->top + height > RECT_HEIGHT(&dest->contentRect))
528 {
529 return;
530 }
531
532 for (int64_t y = 0; y < height; y++)
533 {
534 for (int64_t x = 0; x < width; x++)
535 {
536 pixel_t srcPixel = src->buffer[(srcPoint->x + x) + (srcPoint->y + y) * src->stride];
537 pixel_t* destPixel = &dest->buffer[(destRect->left + x) + (destRect->top + y) * dest->stride];
538 PIXEL_BLEND(destPixel, &srcPixel);
539 }
540 }
541
542 draw_invalidate(dest, destRect);
543}
544
545void draw_image(drawable_t* draw, image_t* image, const rect_t* destRect, const point_t* srcPoint)
546{
547 draw_transfer(draw, image_draw(image), destRect, srcPoint);
548}
549
550void draw_image_blend(drawable_t* draw, image_t* image, const rect_t* destRect, const point_t* srcPoint)
551{
552 draw_transfer_blend(draw, image_draw(image), destRect, srcPoint);
553}
554
555static void draw_grf_char(drawable_t* draw, const font_t* font, const point_t* point, uint8_t chr, pixel_t pixel)
556{
557 uint32_t offset = font->grf.glyphOffsets[chr];
558 if (offset == GRF_NONE)
559 {
560 return;
561 }
562 grf_glyph_t* glyph = (grf_glyph_t*)(&font->grf.buffer[offset]);
563
564 int32_t baselineY = point->y + font->grf.ascender;
565
566 for (uint16_t y = 0; y < glyph->height; y++)
567 {
568 for (uint16_t x = 0; x < glyph->width; x++)
569 {
570 uint8_t gray = glyph->buffer[y * glyph->width + x];
571
572 if (gray > 0)
573 {
574 int32_t targetX = point->x + glyph->bearingX + x;
575 int32_t targetY = baselineY - glyph->bearingY + y;
576
577 if (targetX < 0 || targetY < 0 || targetX >= RECT_WIDTH(&draw->contentRect) ||
578 targetY >= RECT_HEIGHT(&draw->contentRect))
579 {
580 continue;
581 }
582
583 pixel_t output = PIXEL_ARGB(gray, PIXEL_RED(pixel), PIXEL_GREEN(pixel), PIXEL_BLUE(pixel));
584 PIXEL_BLEND(&draw->buffer[targetX + targetY * draw->stride], &output);
585 }
586 }
587 }
588}
589
590void draw_string(drawable_t* draw, const font_t* font, const point_t* point, pixel_t pixel, const char* string,
591 uint64_t length)
592{
593 if (draw == NULL || string == NULL || point == NULL || length == 0)
594 {
595 return;
596 }
597
598 if (font == NULL)
599 {
600 font = font_default(draw->disp);
601 }
602
603 uint64_t width = font_width(font, string, length);
604 int32_t visualTextHeight = font->grf.ascender - font->grf.descender;
605 rect_t textArea = RECT_INIT_DIM(point->x, point->y, width, visualTextHeight);
606
607 point_t pos = *point;
608 for (uint64_t i = 0; i < length; i++)
609 {
610 uint32_t offset = font->grf.glyphOffsets[(uint8_t)string[i]];
611 if (offset == GRF_NONE)
612 {
613 continue;
614 }
615 grf_glyph_t* glyph = (grf_glyph_t*)(&font->grf.buffer[offset]);
616
617 draw_grf_char(draw, font, &pos, string[i], pixel);
618 pos.x += glyph->advanceX;
619 if (i != length - 1)
620 {
621 pos.x += font_kerning_offset(font, string[i], string[i + 1]);
622 }
623 }
624
625 draw_invalidate(draw, &textArea);
626}
627
628static void draw_calculate_aligned_text_pos(const rect_t* rect, const font_t* font, const char* string, uint64_t length,
629 align_t xAlign, align_t yAlign, point_t* aligned)
630{
631 int64_t width = font_width(font, string, length);
632 int32_t visualTextHeight = font->grf.ascender - font->grf.descender;
633
634 switch (xAlign)
635 {
636 case ALIGN_MIN:
637 aligned->x = rect->left;
638 break;
639 case ALIGN_CENTER:
640 aligned->x = MAX(rect->left + (RECT_WIDTH(rect) / 2) - (width / 2), rect->left);
641 break;
642 case ALIGN_MAX:
643 aligned->x = MAX(rect->left + RECT_WIDTH(rect) - width, rect->left);
644 break;
645 default:
646 aligned->x = rect->left;
647 break;
648 }
649
650 switch (yAlign)
651 {
652 case ALIGN_MIN:
653 aligned->y = rect->top;
654 break;
655 case ALIGN_CENTER:
656 aligned->y = rect->top + (RECT_HEIGHT(rect) / 2) - (visualTextHeight / 2);
657 break;
658 case ALIGN_MAX:
659 aligned->y = rect->top + RECT_HEIGHT(rect) - visualTextHeight;
660 break;
661 default:
662 aligned->y = rect->top;
663 break;
664 }
665}
666
667void draw_text(drawable_t* draw, const rect_t* rect, const font_t* font, align_t xAlign, align_t yAlign, pixel_t pixel,
668 const char* text)
669{
670 if (draw == NULL || rect == NULL || text == NULL || *text == '\0')
671 {
672 return;
673 }
674
675 if (font == NULL)
676 {
677 font = font_default(draw->disp);
678 }
679
680 uint64_t maxWidth = RECT_WIDTH(rect);
681 uint64_t originalTextLen = strlen(text);
682 uint64_t textWidth = font_width(font, text, originalTextLen);
683
684 if (textWidth <= maxWidth)
685 {
686 point_t startPoint;
687 draw_calculate_aligned_text_pos(rect, font, text, originalTextLen, xAlign, yAlign, &startPoint);
688 draw_string(draw, font, &startPoint, pixel, text, originalTextLen);
689 }
690 else
691 {
692 const char* ellipsis = "...";
693 uint64_t ellipsisWidth = font_width(font, ellipsis, 3);
694
695 const char* drawText = text;
696 uint64_t drawTextLen = 0;
697 const char* drawEllipsis = NULL;
698
699 if (ellipsisWidth <= maxWidth)
700 {
701 uint64_t currentContentWidth = 0;
702 uint64_t fittedOriginalLen = 0;
703
704 for (uint64_t i = 0; i < originalTextLen; ++i)
705 {
706 uint64_t charWidth = font_width(font, text + i, 1);
707 if (i < originalTextLen - 1)
708 {
709 charWidth += font_kerning_offset(font, text[i], text[i + 1]);
710 }
711
712 if (currentContentWidth + charWidth + ellipsisWidth <= maxWidth)
713 {
714 currentContentWidth += charWidth;
715 fittedOriginalLen++;
716 }
717 else
718 {
719 break;
720 }
721 }
722
723 drawText = text;
724 drawTextLen = fittedOriginalLen;
725 drawEllipsis = ellipsis;
726 }
727 else
728 {
729 uint64_t currentContentWidth = 0;
730 uint64_t fittedEllipsisLen = 0;
731
732 for (uint64_t i = 0; i < 3; ++i)
733 {
734 uint64_t charWidth = font_width(font, ellipsis + i, 1);
735 if (i < 2)
736 {
737 charWidth += font_kerning_offset(font, ellipsis[i], ellipsis[i + 1]);
738 }
739
740 if (currentContentWidth + charWidth <= maxWidth)
741 {
742 currentContentWidth += charWidth;
743 fittedEllipsisLen++;
744 }
745 else
746 {
747 break;
748 }
749 }
750
751 drawText = ellipsis;
752 drawTextLen = fittedEllipsisLen;
753 drawEllipsis = NULL;
754 }
755
756 int64_t totalWidth;
757 if (drawEllipsis != NULL)
758 {
759 totalWidth = font_width(font, drawText, drawTextLen) + font_width(font, drawEllipsis, 3);
760 }
761 else
762 {
763 totalWidth = font_width(font, drawText, drawTextLen);
764 }
765
766 int32_t visualTextHeight = font->grf.ascender - font->grf.descender;
767 point_t startPoint;
768
769 switch (xAlign)
770 {
771 case ALIGN_MIN:
772 startPoint.x = rect->left;
773 break;
774 case ALIGN_CENTER:
775 startPoint.x = MAX(rect->left + (RECT_WIDTH(rect) / 2) - (totalWidth / 2), rect->left);
776 break;
777 case ALIGN_MAX:
778 startPoint.x = MAX(rect->left + RECT_WIDTH(rect) - totalWidth, rect->left);
779 break;
780 default:
781 startPoint.x = rect->left;
782 break;
783 }
784
785 switch (yAlign)
786 {
787 case ALIGN_MIN:
788 startPoint.y = rect->top;
789 break;
790 case ALIGN_CENTER:
791 startPoint.y = rect->top + (RECT_HEIGHT(rect) / 2) - (visualTextHeight / 2);
792 break;
793 case ALIGN_MAX:
794 startPoint.y = rect->top + RECT_HEIGHT(rect) - visualTextHeight;
795 break;
796 default:
797 startPoint.y = rect->top;
798 break;
799 }
800
801 draw_string(draw, font, &startPoint, pixel, drawText, drawTextLen);
802
803 if (drawEllipsis != NULL)
804 {
805 point_t ellipsisStartPoint = {.x = startPoint.x + font_width(font, drawText, drawTextLen),
806 .y = startPoint.y};
807 draw_string(draw, font, &ellipsisStartPoint, pixel, drawEllipsis, 3);
808 }
809 }
810}
811
812void draw_text_multiline(drawable_t* draw, const rect_t* rect, const font_t* font, align_t xAlign, align_t yAlign,
813 pixel_t pixel, const char* text)
814{
815 if (draw == NULL || rect == NULL || text == NULL || *text == '\0')
816 {
817 return;
818 }
819
820 if (font == NULL)
821 {
822 font = font_default(draw->disp);
823 }
824
825 rect_t fitRect = *rect;
826 RECT_FIT(&fitRect, &draw->contentRect);
827
828 int32_t lineHeight = font->grf.ascender - font->grf.descender;
829 int32_t maxWidth = RECT_WIDTH(&fitRect);
830
831 uint64_t lineCount = 0;
832 const char* textPtr = text;
833
834 while (*textPtr != '\0')
835 {
836 const char* lineStart = textPtr;
837 const char* lineEnd = textPtr;
838 const char* lastSpace = NULL;
839 int32_t currentWidth = 0;
840
841 while (*textPtr != '\0' && *textPtr != '\n')
842 {
843 if (*textPtr == ' ')
844 {
845 lastSpace = textPtr;
846 }
847
848 uint32_t offset = font->grf.glyphOffsets[(uint8_t)*textPtr];
849 if (offset != GRF_NONE)
850 {
851 grf_glyph_t* glyph = (grf_glyph_t*)(&font->grf.buffer[offset]);
852 currentWidth += glyph->advanceX;
853 if (*(textPtr + 1) != '\0' && *(textPtr + 1) != '\n')
854 {
855 currentWidth += font_kerning_offset(font, *textPtr, *(textPtr + 1));
856 }
857
858 if (currentWidth > maxWidth && lastSpace != NULL)
859 {
860 lineEnd = lastSpace;
861 break;
862 }
863 }
864
865 textPtr++;
866 }
867
868 if (*textPtr == '\n' || *textPtr == '\0')
869 {
870 lineEnd = textPtr;
871 }
872 else if (lastSpace == NULL)
873 {
874 lineEnd = textPtr;
875 }
876
877 lineCount++;
878
879 if (*textPtr == '\n')
880 {
881 textPtr++;
882 }
883 else if (*textPtr != '\0')
884 {
885 textPtr = lineEnd + 1;
886 }
887 }
888
889 int32_t totalTextHeight = lineCount * lineHeight;
890
891 int32_t startY;
892 switch (yAlign)
893 {
894 case ALIGN_MIN:
895 startY = fitRect.top;
896 break;
897 case ALIGN_CENTER:
898 startY = fitRect.top + (RECT_HEIGHT(&fitRect) / 2) - (totalTextHeight / 2);
899 break;
900 case ALIGN_MAX:
901 startY = fitRect.top + RECT_HEIGHT(&fitRect) - totalTextHeight;
902 break;
903 default:
904 startY = fitRect.top;
905 break;
906 }
907
908 textPtr = text;
909 int32_t currentY = startY;
910
911 while (*textPtr != '\0' && currentY + lineHeight <= fitRect.bottom)
912 {
913 const char* lineStart = textPtr;
914 const char* lineEnd = textPtr;
915 const char* lastSpace = NULL;
916 int32_t currentWidth = 0;
917
918 while (*textPtr != '\0' && *textPtr != '\n')
919 {
920 if (*textPtr == ' ')
921 {
922 lastSpace = textPtr;
923 }
924
925 uint32_t offset = font->grf.glyphOffsets[(uint8_t)*textPtr];
926 if (offset != GRF_NONE)
927 {
928 grf_glyph_t* glyph = (grf_glyph_t*)(&font->grf.buffer[offset]);
929 currentWidth += glyph->advanceX;
930 if (*(textPtr + 1) != '\0' && *(textPtr + 1) != '\n')
931 {
932 currentWidth += font_kerning_offset(font, *textPtr, *(textPtr + 1));
933 }
934
935 if (currentWidth > maxWidth && lastSpace != NULL)
936 {
937 lineEnd = lastSpace;
938 break;
939 }
940 }
941
942 textPtr++;
943 }
944
945 if (*textPtr == '\n' || *textPtr == '\0')
946 {
947 lineEnd = textPtr;
948 }
949 else if (lastSpace == NULL)
950 {
951 lineEnd = textPtr;
952 }
953
954 int64_t lineLength = lineEnd - lineStart;
955 int64_t lineWidth = font_width(font, lineStart, lineLength);
956
957 int32_t lineX;
958 switch (xAlign)
959 {
960 case ALIGN_MIN:
961 lineX = fitRect.left;
962 break;
963 case ALIGN_CENTER:
964 lineX = MAX(fitRect.left + (RECT_WIDTH(&fitRect) / 2) - (lineWidth / 2), fitRect.left);
965 break;
966 case ALIGN_MAX:
967 lineX = MAX(fitRect.left + RECT_WIDTH(&fitRect) - lineWidth, fitRect.left);
968 break;
969 default:
970 lineX = fitRect.left;
971 break;
972 }
973
974 point_t linePos = {.x = lineX, .y = currentY};
975 draw_string(draw, font, &linePos, pixel, lineStart, lineLength);
976
977 currentY += lineHeight;
978
979 if (*textPtr == '\n')
980 {
981 textPtr++;
982 }
983 else if (*textPtr != '\0')
984 {
985 textPtr = lineEnd + 1;
986 }
987 }
988
989 draw_invalidate(draw, &fitRect);
990}
991
992void draw_ridge(drawable_t* draw, const rect_t* rect, uint64_t width, pixel_t foreground, pixel_t background)
993{
994 if (draw == NULL || rect == NULL || width == 0)
995 {
996 return;
997 }
998
999 draw_frame(draw, rect, width / 2, background, foreground);
1000
1001 rect_t innerRect = *rect;
1002 RECT_SHRINK(&innerRect, width / 2);
1003 draw_frame(draw, &innerRect, width / 2, foreground, background);
1004}
1005
1006void draw_separator(drawable_t* draw, const rect_t* rect, pixel_t highlight, pixel_t shadow, direction_t direction)
1007{
1008 if (draw == NULL || rect == NULL)
1009 {
1010 return;
1011 }
1012
1013 rect_t fitRect = *rect;
1014 RECT_FIT(&fitRect, &draw->contentRect);
1015
1016 switch (direction)
1017 {
1018 case DIRECTION_VERTICAL:
1019 {
1020 int64_t width = RECT_WIDTH(&fitRect);
1021
1022 rect_t leftRect = {.left = fitRect.left,
1023 .top = fitRect.top,
1024 .right = fitRect.left + width / 2,
1025 .bottom = fitRect.bottom};
1026 rect_t rightRect = {.left = fitRect.left + width / 2,
1027 .top = fitRect.top,
1028 .right = fitRect.right,
1029 .bottom = fitRect.bottom};
1030
1031 draw_rect(draw, &leftRect, highlight);
1032 draw_rect(draw, &rightRect, shadow);
1033 }
1034 break;
1036 {
1037 int64_t height = RECT_HEIGHT(&fitRect);
1038
1039 rect_t topRect = {.left = fitRect.left,
1040 .top = fitRect.top,
1041 .right = fitRect.right,
1042 .bottom = fitRect.top + height / 2};
1043 rect_t bottomRect = {.left = fitRect.left,
1044 .top = fitRect.top + height / 2,
1045 .right = fitRect.right,
1046 .bottom = fitRect.bottom};
1047
1048 draw_rect(draw, &topRect, highlight);
1049 draw_rect(draw, &bottomRect, shadow);
1050 }
1051 break;
1052 default:
1053 {
1054 }
1055 }
1056
1057 draw_invalidate(draw, &fitRect);
1058}
1059
1060void draw_invalidate(drawable_t* draw, const rect_t* rect)
1061{
1062 if (rect == NULL)
1063 {
1064 draw->invalidRect = draw->contentRect;
1065 return;
1066 }
1067
1068 if (RECT_AREA(&draw->invalidRect) == 0)
1069 {
1070 draw->invalidRect = *rect;
1071 }
1072 else
1073 {
1074 RECT_EXPAND_TO_CONTAIN(&draw->invalidRect, rect);
1075 }
1076}
static void draw_grf_char(drawable_t *draw, const font_t *font, const point_t *point, uint8_t chr, pixel_t pixel)
Definition drawable.c:555
static void draw_calculate_aligned_text_pos(const rect_t *rect, const font_t *font, const char *string, uint64_t length, align_t xAlign, align_t yAlign, point_t *aligned)
Definition drawable.c:628
static int edge_compare(const void *a, const void *b)
Definition drawable.c:33
uint64_t font_width(const font_t *font, const char *string, uint64_t length)
Definition font.c:130
int16_t font_kerning_offset(const font_t *font, char firstChar, char secondChar)
Definition font.c:99
font_t * font_default(display_t *disp)
Definition font.c:7
#define GRF_NONE
Definition grf.h:25
void draw_text_multiline(drawable_t *draw, const rect_t *rect, const font_t *font, align_t xAlign, align_t yAlign, pixel_t pixel, const char *text)
Draw multiline text to a drawable.
Definition drawable.c:812
void draw_line(drawable_t *draw, const point_t *start, const point_t *end, pixel_t pixel, uint32_t thickness)
Draw a line between two points.
Definition drawable.c:179
align_t
Alignment type.
Definition drawable.h:48
void draw_transfer_blend(drawable_t *dest, drawable_t *src, const rect_t *destRect, const point_t *srcPoint)
Transfer pixels from one drawable to another with alpha blending.
Definition drawable.c:507
void draw_polygon(drawable_t *draw, const point_t *points, uint64_t pointCount, pixel_t pixel)
Draw a filled polygon.
Definition drawable.c:48
void draw_gradient(drawable_t *draw, const rect_t *rect, pixel_t start, pixel_t end, direction_t direction, bool shouldAddNoise)
Draw a gradient filled rectangle.
Definition drawable.c:382
void draw_string(drawable_t *draw, const font_t *font, const point_t *point, pixel_t pixel, const char *string, uint64_t length)
Draw a string.
Definition drawable.c:590
void draw_image_blend(drawable_t *draw, image_t *image, const rect_t *destRect, const point_t *srcPoint)
Draw an image with alpha blending.
Definition drawable.c:550
void draw_rect(drawable_t *draw, const rect_t *rect, pixel_t pixel)
Draw a filled rectangle.
Definition drawable.c:7
void draw_ridge(drawable_t *draw, const rect_t *rect, uint64_t width, pixel_t foreground, pixel_t background)
Draw a ridge effect.
Definition drawable.c:992
void draw_image(drawable_t *draw, image_t *image, const rect_t *destRect, const point_t *srcPoint)
Draw an image,.
Definition drawable.c:545
void draw_separator(drawable_t *draw, const rect_t *rect, pixel_t highlight, pixel_t shadow, direction_t direction)
Draw a separator line.
Definition drawable.c:1006
void draw_transfer(drawable_t *dest, drawable_t *src, const rect_t *destRect, const point_t *srcPoint)
Transfer pixels from one drawable to another.
Definition drawable.c:462
void draw_text(drawable_t *draw, const rect_t *rect, const font_t *font, align_t xAlign, align_t yAlign, pixel_t pixel, const char *text)
Draw text to a drawable.
Definition drawable.c:667
void draw_dashed_outline(drawable_t *draw, const rect_t *rect, pixel_t pixel, uint32_t length, int32_t width)
Draw a dashed outline just inside the given rectangle.
Definition drawable.c:259
direction_t
Direction type.
Definition drawable.h:59
void draw_bezel(drawable_t *draw, const rect_t *rect, uint64_t width, pixel_t pixel)
Draw a filled border bezel just inside the given rectangle.
Definition drawable.c:339
void draw_invalidate(drawable_t *draw, const rect_t *rect)
Invalidate a rectangle in the drawable.
Definition drawable.c:1060
void draw_frame(drawable_t *draw, const rect_t *rect, uint64_t width, pixel_t foreground, pixel_t background)
Draw a skeuomorphic frame.
Definition drawable.c:204
@ ALIGN_MIN
Definition drawable.h:51
@ ALIGN_CENTER
Definition drawable.h:49
@ ALIGN_MAX
Definition drawable.h:50
@ DIRECTION_DIAGONAL
Definition drawable.h:62
@ DIRECTION_HORIZONTAL
Definition drawable.h:61
@ DIRECTION_VERTICAL
Definition drawable.h:60
bool polygon_contains(double px, double py, const point_t *points, uint64_t pointCount)
Check if a point is inside a polygon.
Definition polygon.c:27
#define CLAMP(x, low, high)
Definition math.h:17
#define MIN(x, y)
Definition math.h:16
#define MAX(x, y)
Definition math.h:15
#define NULL
Pointer error value.
Definition NULL.h:23
drawable_t * image_draw(image_t *image)
Definition image.c:84
double atan2(double y, double x)
Definition atan2.c:4
double cos(double x)
Definition cos.c:6
#define M_PI_2
Definition math.h:12
double sin(double x)
Definition sin.c:6
#define PIXEL_BLEND(dest, src)
Definition pixel.h:20
#define PIXEL_GREEN(pixel)
Definition pixel.h:15
#define PIXEL_RED(pixel)
Definition pixel.h:14
uint32_t pixel_t
Definition pixel.h:11
#define PIXEL_BLUE(pixel)
Definition pixel.h:16
#define PIXEL_ARGB(a, r, g, b)
Definition pixel.h:18
#define PIXEL_ALPHA(pixel)
Definition pixel.h:13
int64_t x
Definition main.c:152
int64_t y
Definition main.c:153
static void start()
Definition main.c:542
static image_t * image
Definition main.c:5
#define RECT_SHRINK(rect, margin)
Definition rect.h:81
#define RECT_FIT(rect, parent)
Definition rect.h:73
#define RECT_AREA(rect)
Definition rect.h:40
#define RECT_INIT_DIM(x, y, width, height)
Definition rect.h:32
#define RECT_HEIGHT(rect)
Definition rect.h:39
#define RECT_WIDTH(rect)
Definition rect.h:38
#define RECT_EXPAND_TO_CONTAIN(rect, other)
Definition rect.h:44
__UINT32_TYPE__ uint32_t
Definition stdint.h:15
__INT32_TYPE__ int32_t
Definition stdint.h:14
__UINT64_TYPE__ uint64_t
Definition stdint.h:17
__UINT8_TYPE__ uint8_t
Definition stdint.h:11
__UINT16_TYPE__ uint16_t
Definition stdint.h:13
#define INT64_MIN
Definition stdint.h:72
__INT64_TYPE__ int64_t
Definition stdint.h:16
#define INT64_MAX
Definition stdint.h:73
_PUBLIC int rand(void)
Definition rand.c:5
_PUBLIC void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *))
Definition qsort.c:47
_PUBLIC void * memmove(void *_RESTRICT s1, const void *_RESTRICT s2, size_t n)
_PUBLIC void * memset32(void *s, __UINT32_TYPE__ c, size_t n)
Definition memset32.c:4
_PUBLIC void * memcpy(void *_RESTRICT s1, const void *_RESTRICT s2, size_t n)
Definition memcpy.c:61
_PUBLIC size_t strlen(const char *s)
Definition strlen.c:3
Drawable structure.
Definition drawable.h:35
pixel_t * buffer
Definition drawable.h:38
uint32_t stride
Definition drawable.h:37
rect_t contentRect
Definition drawable.h:39
display_t * disp
Definition drawable.h:36
rect_t invalidRect
Definition drawable.h:40
double invSlope
Definition drawable.c:30
double x
Definition drawable.c:29
int64_t yMax
Definition drawable.c:28
int64_t yMin
Definition drawable.c:27
grf_t grf
Definition internal.h:19
uint16_t height
Definition grf.h:49
uint8_t buffer[]
Definition grf.h:50
int16_t bearingY
Definition grf.h:45
uint16_t width
Definition grf.h:48
int16_t advanceX
Definition grf.h:46
int16_t bearingX
Definition grf.h:44
int16_t ascender
Definition grf.h:31
uint8_t buffer[]
Definition grf.h:38
uint32_t glyphOffsets[256]
Definition grf.h:34
int16_t descender
Definition grf.h:32
int64_t y
Definition point.h:14
int64_t x
Definition point.h:13
Definition rect.h:13
int32_t bottom
Definition rect.h:17
int32_t top
Definition rect.h:15
int32_t right
Definition rect.h:16
int32_t left
Definition rect.h:14