ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
Style.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for SDL3 and C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 *
5 * This software is provided 'as-is', without any express or implied
6 * warranty. In no event will the authors be held liable for any damages
7 * arising from the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software
15 * in a product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 */
23
24#include <assert.h>
25#include <stdlib.h>
26#include <string.h>
27
28#include <Objectively.h>
29
30#include "Style.h"
31
32#define _Class _Style
33
34#pragma mark - Object
35
39static void dealloc(Object *self) {
40
41 Style *this = (Style *) self;
42
43 release(this->attributes);
44 release(this->selectors);
45
46 super(Object, self, dealloc);
47}
48
52static String *description(const Object *self) {
53
54 const Style *this = (Style *) self;
55
56 return $((Array *) this->selectors, componentsJoinedByCharacters, ", ");
57}
58
62static int hash(const Object *self) {
63
64 Style *this = (Style *) self;
65
66 int hash = HASH_SEED;
67
68 hash = HashForObject(hash, this->attributes);
69 hash = HashForObject(hash, this->selectors);
70
71 return hash;
72}
73
77static bool isEqual(const Object *self, const Object *other) {
78
79 if (super(Object, self, isEqual, other)) {
80 return true;
81 }
82
83 if (other && $(other, isKindOfClass, _Style())) {
84
85 const Style *this = (Style *) self;
86 const Style *that = (Style *) other;
87
88 if ($((Object *) this->selectors, isEqual, (Object *) that->selectors)) {
89 return $((Object *) this->attributes, isEqual, (Object *) that->attributes);
90 }
91 }
92
93 return false;
94}
95
96#pragma mark - Style
97
105typedef struct {
106 const char *shorthand;
107 const char *longhands[4];
109
115 { "corner-cut", { "corner-cut-top-left", "corner-cut-top-right",
116 "corner-cut-bottom-right", "corner-cut-bottom-left" } },
117 { "max-size", { "max-width", "max-height" } },
118 { "min-size", { "min-width", "min-height" } },
119 { "padding", { "padding-top", "padding-right", "padding-bottom", "padding-left" } },
120};
121
125static size_t styleShorthandCount(const StyleShorthand *shorthand) {
126
127 size_t count = 0;
128 while (count < lengthof(shorthand->longhands) && shorthand->longhands[count]) {
129 count++;
130 }
131
132 return count;
133}
134
138static const StyleShorthand *styleShorthandForKey(const char *key) {
139
140 for (size_t i = 0; i < lengthof(styleShorthands); i++) {
141 if (strcmp(styleShorthands[i].shorthand, key) == 0) {
142 return &styleShorthands[i];
143 }
144 }
145
146 return NULL;
147}
148
156static bool addShorthandAttribute(Style *self, const StyleShorthand *shorthand, ident value) {
157
158 const size_t count = styleShorthandCount(shorthand);
159
160 if ($((Object *) value, isKindOfClass, _Number())) {
161 for (size_t i = 0; i < count; i++) {
162 $((Dictionary *) self->attributes, setObjectForKeyPath, value, shorthand->longhands[i]);
163 }
164 return true;
165 }
166
167 if ($((Object *) value, isKindOfClass, _Array())) {
168 const Array *array = value;
169 if (array->count == count) {
170 for (size_t i = 0; i < count; i++) {
171 ident number = $(array, objectAtIndex, i);
172 $((Dictionary *) self->attributes, setObjectForKeyPath, number, shorthand->longhands[i]);
173 }
174 return true;
175 }
176 }
177
178 return false;
179}
180
184static void setAttribute(Style *self, const char *key, ident value) {
185
186 const StyleShorthand *shorthand = styleShorthandForKey(key);
187 if (shorthand == NULL || !addShorthandAttribute(self, shorthand, value)) {
188 $((Dictionary *) self->attributes, setObjectForKeyPath, value, key);
189 }
190}
191
196static void addAttribute(Style *self, const char *attr, ident value) {
197
198 assert(attr);
199 assert(value);
200
201 char *key = strtrim(attr);
202 assert(key);
203
204 setAttribute(self, key, value);
205
206 free(key);
207}
208
212static void addAttributes_enumerate(const Dictionary *dictionary, ident obj, ident key, ident data) {
213
214 setAttribute((Style *) data, ((String *) key)->chars, obj);
215}
216
221static void addAttributes(Style *self, const Dictionary *attributes) {
222
223 assert(attributes);
224
225 $(attributes, enumerateObjectsAndKeys, addAttributes_enumerate, self);
226}
227
232static void addBoolAttribute(Style *self, const char *attr, bool value) {
233 $(self, addAttribute, attr, $$(Boole, valueof, value));
234}
235
240static void addCharactersAttribute(Style *self, const char *attr, const char *value) {
241
242 String *string = $$(String, stringWithCharacters, value);
243
244 $(self, addAttribute, attr, string);
245
246 release(string);
247}
248
253static void addColorAttribute(Style *self, const char *attr, const SDL_Color *color) {
254
255 Number *r = $$(Number, numberWithValue, color->r);
256 Number *g = $$(Number, numberWithValue, color->g);
257 Number *b = $$(Number, numberWithValue, color->b);
258 Number *a = $$(Number, numberWithValue, color->a);
259
260 Array *array = $$(Array, arrayWithObjects, r, g, b, a, NULL);
261
262 $(self, addAttribute, attr, array);
263
264 release(array);
265 release(r);
266 release(g);
267 release(b);
268 release(a);
269}
270
275static void addDoubleAttribute(Style *self, const char *attr, double value) {
276
277 Number *number = $$(Number, numberWithValue, value);
278
279 $(self, addAttribute, attr, number);
280
281 release(number);
282}
283
288static void addEnumAttribute(Style *self, const char *attr, const EnumName *names, int value) {
289
290 String *string = nameof(names, value);
291
292 $(self, addAttribute, attr, string);
293
294 release(string);
295}
296
301static void addFloatAttribute(Style *self, const char *attr, float value) {
302 $(self, addDoubleAttribute, attr, value);
303}
304
309static void addIntegerAttribute(Style *self, const char *attr, int value) {
310 $(self, addDoubleAttribute, attr, value);
311}
312
317static void addPointAttribute(Style *self, const char *attr, const SDL_Point *value) {
318
319 Number *x = $$(Number, numberWithValue, value->x);
320 Number *y = $$(Number, numberWithValue, value->y);
321
322 Array *array = $$(Array, arrayWithObjects, x, y, NULL);
323
324 $(self, addAttribute, attr, array);
325
326 release(array);
327 release(x);
328 release(y);
329}
330
335static void addRectangleAttribute(Style *self, const char *attr, const SDL_Rect *value) {
336
337 Number *x = $$(Number, numberWithValue, value->x);
338 Number *y = $$(Number, numberWithValue, value->y);
339 Number *w = $$(Number, numberWithValue, value->w);
340 Number *h = $$(Number, numberWithValue, value->h);
341
342 Array *array = $$(Array, arrayWithObjects, x, y, w, h, NULL);
343
344 $(self, addAttribute, attr, array);
345
346 release(array);
347 release(x);
348 release(y);
349 release(w);
350 release(h);
351}
352
357static void addSelector(Style *self, Selector *selector) {
358
359 assert(selector);
360
361 $((Array *) self->selectors, addObject, selector);
362}
363
368static void addSizeAttribute(Style *self, const char *attr, const SDL_Size *value) {
369
370 Number *w = $$(Number, numberWithValue, value->w);
371 Number *h = $$(Number, numberWithValue, value->h);
372
373 Array *array = $$(Array, arrayWithObjects, w, h, NULL);
374
375 $(self, addAttribute, attr, array);
376
377 release(array);
378 release(w);
379 release(h);
380}
381
386static ident attributeValue(const Style *self, const char *attr) {
387 return $((Dictionary *) self->attributes, objectForKeyPath, attr);
388}
389
394static Style *initWithAttributes(Style *self, const Dictionary *attributes) {
395
396 self = $(self, initWithRules, NULL);
397 if (self) {
398 if (attributes) {
399 $(self, addAttributes, attributes);
400 }
401 }
402
403 return self;
404}
405
410static Style *initWithRules(Style *self, const char *rules) {
411
412 self = (Style *) super(Object, self, init);
413 if (self) {
414
415 self->selectors = $$(Selector, parse, rules);
416 assert(self->selectors);
417
418 for (size_t i = 0; i < self->selectors->count; i++) {
419 ((Selector *) self->selectors->elements[i])->style = self;
420 }
421
422 self->attributes = (Dictionary *) $$(Dictionary, dictionaryWithCapacity, 4);
423 assert(self->attributes);
424 }
425
426 return self;
427}
428
433static bool isComputedEqual(const Style *self, const Style *other) {
434
435 assert(other);
436
437 if (self->selectors->count == other->selectors->count) {
438
439 for (size_t i = 0; i < self->selectors->count; i++) {
440
441 const Selector *this = $(self->selectors, objectAtIndex, i);
442 const Selector *that = $(other->selectors, objectAtIndex, i);
443
444 if (strcmp(this->rule, that->rule)) {
445 return false;
446 }
447 }
448
449 return true;
450 }
451
452 return false;
453}
454
458static ident parseValue(String *string) {
459
460 ident value = NULL;
461
462 assert(string);
463
464 String *trimmed = $(string, trimmedString);
465 assert(trimmed);
466
467 StringReader *reader = $(alloc(StringReader), initWithString, trimmed);
468 assert(reader);
469
470 const Unicode *charset = L", \n\t";
471 Unicode stop;
472
473 String *token = $(reader, readToken, charset, &stop);
474 if (token) {
475
476 NumberFormatter *formatter = $(alloc(NumberFormatter), initWithFormat, NULL);
477 assert(formatter);
478
479 Number *number = $(formatter, numberFromString, token);
480
481 if (stop == READER_EOF) {
482
483 if (number) {
484 value = retain(number);
485 } else if (strcmp("true", token->chars) == 0) {
486 value = retain($$(Boole, True));
487 } else if (strcmp("false", token->chars) == 0) {
488 value = retain($$(Boole, False));
489 } else {
490 value = $((Object *) token, copy);
491 }
492
493 } else if (number) {
494 value = $$(Array, arrayWithCapacity, 4);
495
496 while (token) {
497
498 Object *obj = parseValue(token);
499 $((Array *) value, addObject, obj);
500 release(obj);
501 release(token);
502
503 token = $(reader, readToken, charset, NULL);
504 }
505 } else {
506 value = retain(trimmed);
507 }
508
509 release(number);
510 release(formatter);
511 release(token);
512 }
513
514 release(reader);
515 release(trimmed);
516
517 return value;
518}
519
527static char *stripComments(const char *css) {
528
529 const size_t len = strlen(css);
530
531 char *out = calloc(1, len + 1);
532 assert(out);
533
534 size_t w = 0;
535 for (size_t r = 0; r < len; ) {
536 if (css[r] == '/' && css[r + 1] == '*') {
537 r += 2;
538 while (r < len && !(css[r] == '*' && css[r + 1] == '/')) {
539 r++;
540 }
541 r = (r < len) ? r + 2 : len;
542 out[w++] = ' ';
543 } else {
544 out[w++] = css[r++];
545 }
546 }
547
548 out[w] = '\0';
549 return out;
550}
551
556static Array *parse(const char *css) {
557
558 Array *styles = $$(Array, array);
559 assert(styles);
560
561 if (css) {
562
563 char *stripped = stripComments(css);
564
565 StringReader *reader = $(alloc(StringReader), initWithCharacters, stripped);
566 assert(reader);
567
568 Style *style = NULL;
569 char *attr = NULL;
570
571 while (true) {
572
573 const Unicode *charset = style ? L"{:;}" : L"{}";
574 Unicode stop;
575
576 String *token = $(reader, readToken, charset, &stop);
577 if (token) {
578 switch (stop) {
579 case '{':
580 style = $(alloc(Style), initWithRules, token->chars);
581 assert(style);
582 break;
583 case ':':
584 if (style) {
585 attr = strtrim(token->chars);
586 assert(attr);
587 }
588 break;
589 case ';':
590 if (style && attr) {
591
592 ident value = parseValue(token);
593 $(style, addAttribute, attr, value);
594
595 free(attr);
596 attr = NULL;
597
598 release(value);
599 }
600 break;
601 case '}':
602 if (style) {
603 $(styles, addObject, style);
604 style = release(style);
605 }
606 break;
607 }
608
609 release(token);
610 } else {
611 break;
612 }
613 }
614
615 release(reader);
616 free(stripped);
617 }
618
619 return (Array *) styles;
620}
621
626static void removeAttribute(Style *self, const char *attr) {
627 $((Dictionary *) self->attributes, removeObjectForKeyPath, attr);
628}
629
634static void removeAllAttributes(Style *self) {
635 $((Dictionary *) self->attributes, removeAllObjects);
636}
637
638#pragma mark - Class lifecycle
639
643static void initialize(Class *clazz) {
644
645 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
646 ((ObjectInterface *) clazz->interface)->description = description;
647 ((ObjectInterface *) clazz->interface)->hash = hash;
648 ((ObjectInterface *) clazz->interface)->isEqual = isEqual;
649
650 ((StyleInterface *) clazz->interface)->addAttribute = addAttribute;
651 ((StyleInterface *) clazz->interface)->addAttributes = addAttributes;
652 ((StyleInterface *) clazz->interface)->addBoolAttribute = addBoolAttribute;
653 ((StyleInterface *) clazz->interface)->addCharactersAttribute = addCharactersAttribute;
654 ((StyleInterface *) clazz->interface)->addColorAttribute = addColorAttribute;
655 ((StyleInterface *) clazz->interface)->addDoubleAttribute = addDoubleAttribute;
656 ((StyleInterface *) clazz->interface)->addEnumAttribute = addEnumAttribute;
657 ((StyleInterface *) clazz->interface)->addFloatAttribute = addFloatAttribute;
658 ((StyleInterface *) clazz->interface)->addIntegerAttribute = addIntegerAttribute;
659 ((StyleInterface *) clazz->interface)->addPointAttribute = addPointAttribute;
660 ((StyleInterface *) clazz->interface)->addRectangleAttribute = addRectangleAttribute;
661 ((StyleInterface *) clazz->interface)->addSelector = addSelector;
662 ((StyleInterface *) clazz->interface)->addSizeAttribute = addSizeAttribute;
663 ((StyleInterface *) clazz->interface)->attributeValue = attributeValue;
664 ((StyleInterface *) clazz->interface)->initWithAttributes = initWithAttributes;
665 ((StyleInterface *) clazz->interface)->initWithRules = initWithRules;
666 ((StyleInterface *) clazz->interface)->isComputedEqual = isComputedEqual;
667 ((StyleInterface *) clazz->interface)->parse = parse;
668 ((StyleInterface *) clazz->interface)->removeAllAttributes = removeAllAttributes;
669 ((StyleInterface *) clazz->interface)->removeAttribute = removeAttribute;
670}
671
676Class *_Style(void) {
677 static Class *clazz;
678 static Once once;
679
680 do_once(&once, {
681 clazz = _initialize(&(const ClassDef) {
682 .name = "Style",
683 .superclass = _Object(),
684 .instanceSize = sizeof(Style),
685 .interfaceSize = sizeof(StyleInterface),
686 .initialize = initialize,
687 });
688 });
689
690 return clazz;
691}
692
693#undef _Class
static View * init(View *self)
Definition Box.c:67
static void addAttribute(Style *self, const char *attr, ident value)
Definition Style.c:196
static void addDoubleAttribute(Style *self, const char *attr, double value)
Definition Style.c:275
static const StyleShorthand styleShorthands[]
Definition Style.c:114
static bool isComputedEqual(const Style *self, const Style *other)
Definition Style.c:433
static void addAttributes_enumerate(const Dictionary *dictionary, ident obj, ident key, ident data)
DictionaryEnumerator for addAttributes.
Definition Style.c:212
static void addIntegerAttribute(Style *self, const char *attr, int value)
Definition Style.c:309
static void addEnumAttribute(Style *self, const char *attr, const EnumName *names, int value)
Definition Style.c:288
static const StyleShorthand * styleShorthandForKey(const char *key)
Definition Style.c:138
static bool addShorthandAttribute(Style *self, const StyleShorthand *shorthand, ident value)
Stores value as the longhands of shorthand, if it can be split into them.
Definition Style.c:156
static bool isEqual(const Object *self, const Object *other)
Definition Style.c:77
static Style * initWithRules(Style *self, const char *rules)
Definition Style.c:410
static char * stripComments(const char *css)
Returns a newly allocated copy of css with all / * ... * / comments removed. The tokenizer below spli...
Definition Style.c:527
static Array * parse(const char *css)
Definition Style.c:556
static String * description(const Object *self)
Definition Style.c:52
static void addCharactersAttribute(Style *self, const char *attr, const char *value)
Definition Style.c:240
static void removeAttribute(Style *self, const char *attr)
Definition Style.c:626
static void addSizeAttribute(Style *self, const char *attr, const SDL_Size *value)
Definition Style.c:368
static void removeAllAttributes(Style *self)
Definition Style.c:634
static void addColorAttribute(Style *self, const char *attr, const SDL_Color *color)
Definition Style.c:253
static Style * initWithAttributes(Style *self, const Dictionary *attributes)
Definition Style.c:394
static ident attributeValue(const Style *self, const char *attr)
Definition Style.c:386
static void addRectangleAttribute(Style *self, const char *attr, const SDL_Rect *value)
Definition Style.c:335
static void dealloc(Object *self)
Definition Style.c:39
static void addSelector(Style *self, Selector *selector)
Definition Style.c:357
Class * _Style(void)
Definition Style.c:676
static void addFloatAttribute(Style *self, const char *attr, float value)
Definition Style.c:301
static void initialize(Class *clazz)
Definition Style.c:643
static void setAttribute(Style *self, const char *key, ident value)
Stores value under key, expanding it into longhands if key is a shorthand.
Definition Style.c:184
static ident parseValue(String *string)
Definition Style.c:458
static void addBoolAttribute(Style *self, const char *attr, bool value)
Definition Style.c:232
static size_t styleShorthandCount(const StyleShorthand *shorthand)
Definition Style.c:125
static void addPointAttribute(Style *self, const char *attr, const SDL_Point *value)
Definition Style.c:317
static void addAttributes(Style *self, const Dictionary *attributes)
Definition Style.c:221
static int hash(const Object *self)
Definition Style.c:62
The Style type.
static Stylesheet * initWithString(Stylesheet *self, const String *string)
Definition Stylesheet.c:195
static Stylesheet * initWithCharacters(Stylesheet *self, const char *chars)
Definition Stylesheet.c:124
static Warning * initWithFormat(Warning *self, WarningType type, const char *fmt,...)
Definition Warning.c:50
Selectors are comprised of one or more SelectorSequences.
Definition Selector.h:49
char * rule
The rule, as provided by the user.
Definition Selector.h:70
The Style type.
Definition Style.h:43
Array * selectors
The Selectors.
Definition Style.h:64
Style * initWithRules(Style *self, const char *rules)
Initializes this Style with the given CSS selector rules.
Definition Style.c:410
bool isComputedEqual(const Style *self, const Style *other)
Performs a fast, rule-based comparison of this Style to the given Style.
Definition Style.c:433
Dictionary * attributes
Definition Style.h:59
A shorthand attribute, and the longhands it stands for.
Definition Style.c:105
const char * shorthand
Definition Style.c:106
const char * longhands[4]
Definition Style.c:107