NetSurf
layout_flex.c
Go to the documentation of this file.
1/*
2 * Copyright 2022 Michael Drake <tlsa@netsurf-browser.org>
3 *
4 * This file is part of NetSurf, http://www.netsurf-browser.org/
5 *
6 * NetSurf is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * NetSurf is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/**
20 * \file
21 * HTML layout implementation: display: flex.
22 *
23 * Layout is carried out in two stages:
24 *
25 * 1. + calculation of minimum / maximum box widths, and
26 * + determination of whether block level boxes will have >zero height
27 *
28 * 2. + layout (position and dimensions)
29 *
30 * In most cases the functions for the two stages are a corresponding pair
31 * layout_minmax_X() and layout_X().
32 */
33
34#include <string.h>
35
36#include "utils/log.h"
37#include "utils/utils.h"
38
39#include "html/box.h"
40#include "html/html.h"
41#include "html/private.h"
42#include "html/box_inspect.h"
44
45/**
46 * Flex item data
47 */
49 enum css_flex_basis_e basis;
50 css_fixed basis_length;
51 css_unit basis_unit;
52 struct box *box;
53
54 css_fixed shrink;
55 css_fixed grow;
56
61
65 size_t line;
66
67 bool freeze;
70};
71
72/**
73 * Flex line data
74 */
78
81
82 int pos;
83
84 size_t first;
85 size_t count;
86 size_t frozen;
87};
88
89/**
90 * Flex layout context
91 */
92struct flex_ctx {
94 const struct box *flex;
95 const css_unit_ctx *unit_len_ctx;
96
99
102
105 enum css_flex_wrap_e wrap;
106
107 struct flex_items {
108 size_t count;
111
112 struct flex_lines {
113 size_t count;
114 size_t alloc;
117};
118
119/**
120 * Destroy a flex layout context
121 *
122 * \param[in] ctx Flex layout context
123 */
124static void layout_flex_ctx__destroy(struct flex_ctx *ctx)
125{
126 if (ctx != NULL) {
127 free(ctx->item.data);
128 free(ctx->line.data);
129 free(ctx);
130 }
131}
132
133/**
134 * Create a flex layout context
135 *
136 * \param[in] content HTML content containing flex box
137 * \param[in] flex Box to create layout context for
138 * \return flex layout context or NULL on error
139 */
142 const struct box *flex)
143{
144 struct flex_ctx *ctx;
145
146 ctx = calloc(1, sizeof(*ctx));
147 if (ctx == NULL) {
148 return NULL;
149 }
150 ctx->line.alloc = 1;
151
153 ctx->item.data = calloc(ctx->item.count, sizeof(*ctx->item.data));
154 if (ctx->item.data == NULL) {
156 return NULL;
157 }
158
159 ctx->line.alloc = 1;
160 ctx->line.data = calloc(ctx->line.alloc, sizeof(*ctx->line.data));
161 if (ctx->line.data == NULL) {
163 return NULL;
164 }
165
166 ctx->flex = flex;
167 ctx->content = content;
168 ctx->unit_len_ctx = &content->unit_len_ctx;
169
170 ctx->wrap = css_computed_flex_wrap(flex->style);
173
174 return ctx;
175}
176
177/**
178 * Find box side representing the start of flex container in main direction.
179 *
180 * \param[in] ctx Flex layout context.
181 * \return the start side.
182 */
184 const struct flex_ctx *ctx)
185{
186 if (ctx->horizontal) {
187 return (ctx->main_reversed) ? RIGHT : LEFT;
188 } else {
189 return (ctx->main_reversed) ? BOTTOM : TOP;
190 }
191}
192
193/**
194 * Find box side representing the end of flex container in main direction.
195 *
196 * \param[in] ctx Flex layout context.
197 * \return the end side.
198 */
200 const struct flex_ctx *ctx)
201{
202 if (ctx->horizontal) {
203 return (ctx->main_reversed) ? LEFT : RIGHT;
204 } else {
205 return (ctx->main_reversed) ? TOP : BOTTOM;
206 }
207}
208
209/**
210 * Perform layout on a flex item
211 *
212 * \param[in] ctx Flex layout context
213 * \param[in] item Item to lay out
214 * \param[in] available_width Available width for item in pixels
215 * \return true on success false on failure
216 */
218 const struct flex_ctx *ctx,
219 const struct flex_item_data *item,
220 int available_width)
221{
222 bool success;
223 struct box *b = item->box;
224
225 switch (b->type) {
226 case BOX_BLOCK:
227 success = layout_block_context(b, -1, ctx->content);
228 break;
229 case BOX_TABLE:
230 b->float_container = b->parent;
231 success = layout_table(b, available_width, ctx->content);
232 b->float_container = NULL;
233 break;
234 case BOX_FLEX:
235 b->float_container = b->parent;
236 success = layout_flex(b, available_width, ctx->content);
237 b->float_container = NULL;
238 break;
239 default:
240 assert(0 && "Bad flex item back type");
241 success = false;
242 break;
243 }
244
245 if (!success) {
246 NSLOG(flex, ERROR, "box %p: layout failed", b);
247 }
248
249 return success;
250}
251
252/**
253 * Calculate an item's base and target main sizes.
254 *
255 * \param[in] ctx Flex layout context
256 * \param[in] item Item to get sizes of
257 * \param[in] available_width Available width in pixels
258 * \return true on success false on failure
259 */
261 const struct flex_ctx *ctx,
262 struct flex_item_data *item,
263 int available_width)
264{
265 struct box *b = item->box;
266 int content_min_width = b->min_width;
267 int content_max_width = b->max_width;
268 int delta_outer_main = lh__delta_outer_main(ctx->flex, b);
269
270 NSLOG(flex, DEEPDEBUG, "box %p: delta_outer_main: %i",
271 b, delta_outer_main);
272
273 if (item->basis == CSS_FLEX_BASIS_SET) {
274 if (item->basis_unit == CSS_UNIT_PCT) {
276 item->basis_length,
277 available_width);
278 } else {
279 item->base_size = FIXTOINT(css_unit_len2device_px(
280 b->style, ctx->unit_len_ctx,
281 item->basis_length,
282 item->basis_unit));
283 }
284
285 } else if (item->basis == CSS_FLEX_BASIS_AUTO) {
286 item->base_size = ctx->horizontal ? b->width : b->height;
287 } else {
288 item->base_size = AUTO;
289 }
290
291 if (ctx->horizontal == false) {
292 if (b->width == AUTO) {
293 b->width = min(max(content_min_width, available_width),
294 content_max_width);
296 }
297
298 if (!layout_flex_item(ctx, item, b->width)) {
299 return false;
300 }
301 }
302
303 if (item->base_size == AUTO) {
304 if (ctx->horizontal == false) {
305 item->base_size = b->height;
306 } else {
307 item->base_size = content_max_width - delta_outer_main;
308 }
309 }
310
311 item->base_size += delta_outer_main;
312
313 if (ctx->horizontal) {
314 item->base_size = min(item->base_size, available_width);
315 item->base_size = max(item->base_size, content_min_width);
316 }
317
318 item->target_main_size = item->base_size;
319 item->main_size = item->base_size;
320
321 if (item->max_main > 0 &&
322 item->main_size > item->max_main + delta_outer_main) {
323 item->main_size = item->max_main + delta_outer_main;
324 }
325
326 if (item->main_size < item->min_main + delta_outer_main) {
327 item->main_size = item->min_main + delta_outer_main;
328 }
329
330 NSLOG(flex, DEEPDEBUG, "flex-item box: %p: base_size: %i, main_size %i",
331 b, item->base_size, item->main_size);
332
333 return true;
334}
335
336/**
337 * Fill out all item's data in a flex container.
338 *
339 * \param[in] ctx Flex layout context
340 * \param[in] flex Flex box
341 * \param[in] available_width Available width in pixels
342 */
344 const struct flex_ctx *ctx,
345 const struct box *flex,
346 int available_width)
347{
348 size_t i = 0;
349 bool horizontal = ctx->horizontal;
350
351 for (struct box *b = flex->children; b != NULL; b = b->next) {
352 struct flex_item_data *item = &ctx->item.data[i++];
353
354 b->float_container = b->parent;
355 layout_find_dimensions(ctx->unit_len_ctx, available_width, -1,
356 b, b->style, &b->width, &b->height,
357 horizontal ? &item->max_main : &item->max_cross,
358 horizontal ? &item->min_main : &item->min_cross,
359 horizontal ? &item->max_cross : &item->max_main,
360 horizontal ? &item->min_cross : &item->min_main,
361 b->margin, b->padding, b->border);
362 b->float_container = NULL;
363
364 NSLOG(flex, DEEPDEBUG, "flex-item box: %p: width: %i",
365 b, b->width);
366
367 item->box = b;
368 item->basis = css_computed_flex_basis(b->style,
369 &item->basis_length, &item->basis_unit);
370
371 css_computed_flex_shrink(b->style, &item->shrink);
372 css_computed_flex_grow(b->style, &item->grow);
373
374 layout_flex__base_and_main_sizes(ctx, item, available_width);
375 }
376}
377
378/**
379 * Ensure context's lines array has a free space
380 *
381 * \param[in] ctx Flex layout context
382 * \return true on success false on out of memory
383 */
385{
386 struct flex_line_data *temp;
387 size_t line_alloc = ctx->line.alloc * 2;
388
389 if (ctx->line.alloc > ctx->line.count) {
390 return true;
391 }
392
393 temp = realloc(ctx->line.data, sizeof(*ctx->line.data) * line_alloc);
394 if (temp == NULL) {
395 return false;
396 }
397 ctx->line.data = temp;
398
399 memset(ctx->line.data + ctx->line.alloc, 0,
400 sizeof(*ctx->line.data) * (line_alloc - ctx->line.alloc));
401 ctx->line.alloc = line_alloc;
402
403 return true;
404}
405
406/**
407 * Assigns flex items to the line and returns the line
408 *
409 * \param[in] ctx Flex layout context
410 * \param[in] item_index Index to first item to assign to this line
411 * \return Pointer to the new line, or NULL on error.
412 */
414 size_t item_index)
415{
416 enum box_side start_side = layout_flex__main_start_side(ctx);
417 enum box_side end_side = layout_flex__main_end_side(ctx);
418 struct flex_line_data *line;
419 int used_main = 0;
420
422 return NULL;
423 }
424
425 line = &ctx->line.data[ctx->line.count];
426 line->first = item_index;
427
428 NSLOG(flex, DEEPDEBUG, "flex container %p: available main: %i",
429 ctx->flex, ctx->available_main);
430
431 while (item_index < ctx->item.count) {
432 struct flex_item_data *item = &ctx->item.data[item_index];
433 struct box *b = item->box;
434 int pos_main;
435
436 pos_main = ctx->horizontal ?
437 item->main_size :
438 b->height + lh__delta_outer_main(ctx->flex, b);
439
440 if (ctx->wrap == CSS_FLEX_WRAP_NOWRAP ||
441 pos_main + used_main <= ctx->available_main ||
442 lh__box_is_absolute(item->box) ||
443 ctx->available_main == AUTO ||
444 line->count == 0 ||
445 pos_main == 0) {
446 if (lh__box_is_absolute(item->box) == false) {
447 line->main_size += item->main_size;
448 used_main += pos_main;
449
450 if (b->margin[start_side] == AUTO) {
451 line->main_auto_margin_count++;
452 }
453 if (b->margin[end_side] == AUTO) {
454 line->main_auto_margin_count++;
455 }
456 }
457 item->line = ctx->line.count;
458 line->count++;
459 item_index++;
460 } else {
461 break;
462 }
463 }
464
465 if (line->count > 0) {
466 ctx->line.count++;
467 } else {
468 NSLOG(layout, ERROR, "Failed to fit any flex items");
469 }
470
471 return line;
472}
473
474/**
475 * Freeze an item on a line
476 *
477 * \param[in] line Line to containing item
478 * \param[in] item Item to freeze
479 */
480static inline void layout_flex__item_freeze(
481 struct flex_line_data *line,
482 struct flex_item_data *item)
483{
484 item->freeze = true;
485 line->frozen++;
486
487 if (!lh__box_is_absolute(item->box)){
488 line->used_main_size += item->target_main_size;
489 }
490
491 NSLOG(flex, DEEPDEBUG, "flex-item box: %p: "
492 "Frozen at target_main_size: %i",
493 item->box, item->target_main_size);
494}
495
496/**
497 * Calculate remaining free space and unfrozen item factor sum
498 *
499 * \param[in] ctx Flex layout context
500 * \param[in] line Line to calculate free space on
501 * \param[out] unfrozen_factor_sum Returns sum of unfrozen item's flex factors
502 * \param[in] initial_free_main Initial free space in main direction
503 * \param[in] available_main Available space in main direction
504 * \param[in] grow Whether to grow or shrink
505 * return remaining free space on line
506 */
508 struct flex_ctx *ctx,
509 struct flex_line_data *line,
510 css_fixed *unfrozen_factor_sum,
511 int initial_free_main,
512 int available_main,
513 bool grow)
514{
515 int remaining_free_main = available_main;
516 size_t item_count = line->first + line->count;
517
518 *unfrozen_factor_sum = 0;
519
520 for (size_t i = line->first; i < item_count; i++) {
521 struct flex_item_data *item = &ctx->item.data[i];
522
523 if (item->freeze) {
524 remaining_free_main -= item->target_main_size;
525 } else {
526 remaining_free_main -= item->base_size;
527
528 *unfrozen_factor_sum += grow ?
529 item->grow : item->shrink;
530 }
531 }
532
533 if (*unfrozen_factor_sum < F_1) {
534 int free_space = FIXTOINT(FMUL(INTTOFIX(initial_free_main),
535 *unfrozen_factor_sum));
536
537 if (free_space < remaining_free_main) {
538 remaining_free_main = free_space;
539 }
540 }
541
542 NSLOG(flex, DEEPDEBUG, "Remaining free space: %i",
543 remaining_free_main);
544
545 return remaining_free_main;
546}
547
548/**
549 * Clamp flex item target main size and get min/max violations
550 *
551 * \param[in] ctx Flex layout context
552 * \param[in] line Line to align items on
553 * return total violation in pixels
554 */
556 struct flex_ctx *ctx,
557 struct flex_line_data *line)
558{
559
560 int total_violation = 0;
561 size_t item_count = line->first + line->count;
562
563 for (size_t i = line->first; i < item_count; i++) {
564 struct flex_item_data *item = &ctx->item.data[i];
566
567 NSLOG(flex, DEEPDEBUG, "item %p: target_main_size: %i",
568 item->box, target_main_size);
569
570 if (item->freeze) {
571 continue;
572 }
573
574 if (item->max_main > 0 &&
575 target_main_size > item->max_main) {
577 item->max_violation = true;
578 NSLOG(flex, DEEPDEBUG, "Violation: max_main: %i",
579 item->max_main);
580 }
581
582 if (target_main_size < item->min_main) {
584 item->min_violation = true;
585 NSLOG(flex, DEEPDEBUG, "Violation: min_main: %i",
586 item->min_main);
587 }
588
589 if (target_main_size < item->box->min_width) {
591 item->min_violation = true;
592 NSLOG(flex, DEEPDEBUG, "Violation: box min_width: %i",
593 item->box->min_width);
594 }
595
596 if (target_main_size < 0) {
598 item->min_violation = true;
599 NSLOG(flex, DEEPDEBUG, "Violation: less than 0");
600 }
601
602 total_violation += target_main_size - item->target_main_size;
604 }
605
606 NSLOG(flex, DEEPDEBUG, "Total violation: %i", total_violation);
607
608 return total_violation;
609}
610
611/**
612 * Distribute remaining free space proportional to the flex factors.
613 *
614 * Remaining free space may be negative.
615 *
616 * \param[in] ctx Flex layout context
617 * \param[in] line Line to distribute free space on
618 * \param[in] unfrozen_factor_sum Sum of unfrozen item's flex factors
619 * \param[in] remaining_free_main Remaining free space in main direction
620 * \param[in] grow Whether to grow or shrink
621 */
623 struct flex_ctx *ctx,
624 struct flex_line_data *line,
625 css_fixed unfrozen_factor_sum,
626 int remaining_free_main,
627 bool grow)
628{
629 size_t item_count = line->first + line->count;
630
631 if (grow) {
632 css_fixed remainder = 0;
633 for (size_t i = line->first; i < item_count; i++) {
634 struct flex_item_data *item = &ctx->item.data[i];
635 css_fixed result;
636 css_fixed ratio;
637
638 if (item->freeze) {
639 continue;
640 }
641
642 ratio = FDIV(item->grow, unfrozen_factor_sum);
643 result = FMUL(INTTOFIX(remaining_free_main), ratio) +
644 remainder;
645
646 item->target_main_size = item->base_size +
647 FIXTOINT(result);
648 remainder = FIXFRAC(result);
649 }
650 } else {
651 css_fixed scaled_shrink_factor_sum = 0;
652 css_fixed remainder = 0;
653
654 for (size_t i = line->first; i < item_count; i++) {
655 struct flex_item_data *item = &ctx->item.data[i];
656 css_fixed scaled_shrink_factor;
657
658 if (item->freeze) {
659 continue;
660 }
661
662 scaled_shrink_factor = FMUL(
663 item->shrink,
664 INTTOFIX(item->base_size));
665 scaled_shrink_factor_sum += scaled_shrink_factor;
666 }
667
668 for (size_t i = line->first; i < item_count; i++) {
669 struct flex_item_data *item = &ctx->item.data[i];
670 css_fixed scaled_shrink_factor;
671 css_fixed result;
672 css_fixed ratio;
673
674 if (item->freeze) {
675 continue;
676 } else if (scaled_shrink_factor_sum == 0) {
677 item->target_main_size = item->main_size;
679 continue;
680 }
681
682 scaled_shrink_factor = FMUL(
683 item->shrink,
684 INTTOFIX(item->base_size));
685 ratio = FDIV(scaled_shrink_factor,
686 scaled_shrink_factor_sum);
687 result = FMUL(INTTOFIX(abs(remaining_free_main)),
688 ratio) + remainder;
689
690 item->target_main_size = item->base_size -
691 FIXTOINT(result);
692 remainder = FIXFRAC(result);
693 }
694 }
695}
696
697/**
698 * Resolve flexible item lengths along a line.
699 *
700 * See 9.7 of Tests CSS Flexible Box Layout Module Level 1.
701 *
702 * \param[in] ctx Flex layout context
703 * \param[in] line Line to resolve
704 * \return true on success, false on failure.
705 */
707 struct flex_ctx *ctx,
708 struct flex_line_data *line)
709{
710 size_t item_count = line->first + line->count;
711 int available_main = ctx->available_main;
712 int initial_free_main;
713 bool grow;
714
715 if (available_main == AUTO) {
716 available_main = INT_MAX;
717 }
718
719 grow = (line->main_size < available_main);
720 initial_free_main = available_main;
721
722 NSLOG(flex, DEEPDEBUG, "box %p: line %zu: first: %zu, count: %zu",
723 ctx->flex, line - ctx->line.data,
724 line->first, line->count);
725 NSLOG(flex, DEEPDEBUG, "Line main_size: %i, available_main: %i",
726 line->main_size, available_main);
727
728 for (size_t i = line->first; i < item_count; i++) {
729 struct flex_item_data *item = &ctx->item.data[i];
730
731 /* 3. Size inflexible items */
732 if (grow) {
733 if (item->grow == 0 ||
734 item->base_size > item->main_size) {
735 item->target_main_size = item->main_size;
737 }
738 } else {
739 if (item->shrink == 0 ||
740 item->base_size < item->main_size) {
741 item->target_main_size = item->main_size;
743 }
744 }
745
746 /* 4. Calculate initial free space */
747 if (item->freeze) {
748 initial_free_main -= item->target_main_size;
749 } else {
750 initial_free_main -= item->base_size;
751 }
752 }
753
754 /* 5. Loop */
755 while (line->frozen < line->count) {
756 css_fixed unfrozen_factor_sum;
757 int remaining_free_main;
758 int total_violation;
759
760 NSLOG(flex, DEEPDEBUG, "flex-container: %p: Resolver pass",
761 ctx->flex);
762
763 /* b */
764 remaining_free_main = layout_flex__remaining_free_main(ctx,
765 line, &unfrozen_factor_sum, initial_free_main,
766 available_main, grow);
767
768 /* c */
769 if (remaining_free_main != 0) {
771 line, unfrozen_factor_sum,
772 remaining_free_main, grow);
773 }
774
775 /* d */
776 total_violation = layout_flex__get_min_max_violations(
777 ctx, line);
778
779 /* e */
780 for (size_t i = line->first; i < item_count; i++) {
781 struct flex_item_data *item = &ctx->item.data[i];
782
783 if (item->freeze) {
784 continue;
785 }
786
787 if (total_violation == 0 ||
788 (total_violation > 0 && item->min_violation) ||
789 (total_violation < 0 && item->max_violation)) {
791 }
792 }
793 }
794
795 return true;
796}
797
798/**
799 * Position items along a line
800 *
801 * \param[in] ctx Flex layout context
802 * \param[in] line Line to resolve
803 * \return true on success, false on failure.
804 */
806 struct flex_ctx *ctx,
807 struct flex_line_data *line)
808{
809 int main_pos = ctx->flex->padding[layout_flex__main_start_side(ctx)];
810 int post_multiplier = ctx->main_reversed ? 0 : 1;
811 int pre_multiplier = ctx->main_reversed ? -1 : 0;
812 size_t item_count = line->first + line->count;
813 int extra_remainder = 0;
814 int extra = 0;
815
816 if (ctx->main_reversed) {
817 main_pos = lh__box_size_main(ctx->horizontal, ctx->flex) -
818 main_pos;
819 }
820
821 if (ctx->available_main != AUTO &&
823 ctx->available_main > line->used_main_size) {
824 if (line->main_auto_margin_count > 0) {
825 extra = ctx->available_main - line->used_main_size;
826
827 extra_remainder = extra % line->main_auto_margin_count;
828 extra /= line->main_auto_margin_count;
829 }
830 }
831
832 for (size_t i = line->first; i < item_count; i++) {
833 enum box_side main_end = ctx->horizontal ? RIGHT : BOTTOM;
834 enum box_side main_start = ctx->horizontal ? LEFT : TOP;
835 struct flex_item_data *item = &ctx->item.data[i];
836 struct box *b = item->box;
837 int extra_total = 0;
838 int extra_post = 0;
839 int extra_pre = 0;
840 int box_size_main;
841 int *box_pos_main;
842
843 if (ctx->horizontal) {
844 b->width = item->target_main_size -
846
847 if (!layout_flex_item(ctx, item, b->width)) {
848 return false;
849 }
850 }
851
852 box_size_main = lh__box_size_main(ctx->horizontal, b);
853 box_pos_main = ctx->horizontal ? &b->x : &b->y;
854
855 if (!lh__box_is_absolute(b)) {
856 if (b->margin[main_start] == AUTO) {
857 extra_pre = extra + extra_remainder;
858 }
859 if (b->margin[main_end] == AUTO) {
860 extra_post = extra + extra_remainder;
861 }
862 extra_total = extra_pre + extra_post;
863
864 main_pos += pre_multiplier *
865 (extra_total + box_size_main +
866 lh__delta_outer_main(ctx->flex, b));
867 }
868
869 *box_pos_main = main_pos + lh__non_auto_margin(b, main_start) +
870 extra_pre + b->border[main_start].width;
871
872 if (!lh__box_is_absolute(b)) {
873 int cross_size;
874 int box_size_cross = lh__box_size_cross(
875 ctx->horizontal, b);
876
877 main_pos += post_multiplier *
878 (extra_total + box_size_main +
879 lh__delta_outer_main(ctx->flex, b));
880
881 cross_size = box_size_cross + lh__delta_outer_cross(
882 ctx->flex, b);
883 if (line->cross_size < cross_size) {
884 line->cross_size = cross_size;
885 }
886 }
887 }
888
889 return true;
890}
891
892/**
893 * Collect items onto lines and place items along the lines
894 *
895 * \param[in] ctx Flex layout context
896 * \return true on success, false on failure.
897 */
899 struct flex_ctx *ctx)
900{
901 size_t pos = 0;
902
903 while (pos < ctx->item.count) {
904 struct flex_line_data *line;
905
907 if (line == NULL) {
908 return false;
909 }
910
911 pos += line->count;
912
913 NSLOG(flex, DEEPDEBUG, "flex-container: %p: "
914 "fitted: %zu (total: %zu/%zu)",
915 ctx->flex, line->count,
916 pos, ctx->item.count);
917
918 if (!layout_flex__resolve_line(ctx, line)) {
919 return false;
920 }
921
923 return false;
924 }
925
926 ctx->cross_size += line->cross_size;
927 if (ctx->main_size < line->main_size) {
928 ctx->main_size = line->main_size;
929 }
930 }
931
932 return true;
933}
934
935/**
936 * Align items on a line.
937 *
938 * \param[in] ctx Flex layout context
939 * \param[in] line Line to align items on
940 * \param[in] extra Extra line width in pixels
941 */
943 struct flex_line_data *line, int extra)
944{
945 enum box_side cross_start = ctx->horizontal ? TOP : LEFT;
946 size_t item_count = line->first + line->count;
947
948 for (size_t i = line->first; i < item_count; i++) {
949 struct flex_item_data *item = &ctx->item.data[i];
950 struct box *b = item->box;
951 int cross_free_space;
952 int *box_size_cross;
953 int *box_pos_cross;
954
955 box_pos_cross = ctx->horizontal ? &b->y : &b->x;
956 box_size_cross = lh__box_size_cross_ptr(ctx->horizontal, b);
957
958 cross_free_space = line->cross_size + extra - *box_size_cross -
960
961 switch (lh__box_align_self(ctx->flex, b)) {
962 default:
963 case CSS_ALIGN_SELF_STRETCH:
965 *box_size_cross += cross_free_space;
966
967 /* Relayout children for stretch. */
968 if (!layout_flex_item(ctx, item, b->width)) {
969 return;
970 }
971 }
973 case CSS_ALIGN_SELF_FLEX_START:
974 *box_pos_cross = ctx->flex->padding[cross_start] +
975 line->pos +
976 lh__non_auto_margin(b, cross_start) +
977 b->border[cross_start].width;
978 break;
979
980 case CSS_ALIGN_SELF_FLEX_END:
981 *box_pos_cross = ctx->flex->padding[cross_start] +
982 line->pos + cross_free_space +
983 lh__non_auto_margin(b, cross_start) +
984 b->border[cross_start].width;
985 break;
986
987 case CSS_ALIGN_SELF_BASELINE:
988 case CSS_ALIGN_SELF_CENTER:
989 *box_pos_cross = ctx->flex->padding[cross_start] +
990 line->pos + cross_free_space / 2 +
991 lh__non_auto_margin(b, cross_start) +
992 b->border[cross_start].width;
993 break;
994 }
995 }
996}
997
998/**
999 * Place the lines and align the items on the line.
1000 *
1001 * \param[in] ctx Flex layout context
1002 */
1003static void layout_flex__place_lines(struct flex_ctx *ctx)
1004{
1005 bool reversed = ctx->wrap == CSS_FLEX_WRAP_WRAP_REVERSE;
1006 int line_pos = reversed ? ctx->cross_size : 0;
1007 int post_multiplier = reversed ? 0 : 1;
1008 int pre_multiplier = reversed ? -1 : 0;
1009 int extra_remainder = 0;
1010 int extra = 0;
1011
1012 if (ctx->available_cross != AUTO &&
1013 ctx->available_cross > ctx->cross_size &&
1014 ctx->line.count > 0) {
1015 extra = ctx->available_cross - ctx->cross_size;
1016
1017 extra_remainder = extra % ctx->line.count;
1018 extra /= ctx->line.count;
1019 }
1020
1021 for (size_t i = 0; i < ctx->line.count; i++) {
1022 struct flex_line_data *line = &ctx->line.data[i];
1023
1024 line_pos += pre_multiplier * line->cross_size;
1025 line->pos = line_pos;
1026 line_pos += post_multiplier * line->cross_size +
1027 extra + extra_remainder;
1028
1030 extra + extra_remainder);
1031
1032 if (extra_remainder > 0) {
1033 extra_remainder--;
1034 }
1035 }
1036}
1037
1038/**
1039 * Layout a flex container.
1040 *
1041 * \param[in] flex table to layout
1042 * \param[in] available_width width of containing block
1043 * \param[in] content memory pool for any new boxes
1044 * \return true on success, false on memory exhaustion
1045 */
1046bool layout_flex(struct box *flex, int available_width,
1048{
1049 int max_height, min_height;
1050 struct flex_ctx *ctx;
1051 bool success = false;
1052
1054 if (ctx == NULL) {
1055 return false;
1056 }
1057
1058 NSLOG(flex, DEEPDEBUG, "box %p: %s, available_width %i, width: %i",
1059 flex, ctx->horizontal ? "horizontal" : "vertical",
1060 available_width, flex->width);
1061
1063 ctx->unit_len_ctx, available_width, -1,
1064 flex, flex->style, NULL, &flex->height,
1065 NULL, NULL, &max_height, &min_height,
1067
1068 available_width = min(available_width, flex->width);
1069
1070 if (ctx->horizontal) {
1071 ctx->available_main = available_width;
1072 ctx->available_cross = ctx->flex->height;
1073 } else {
1074 ctx->available_main = ctx->flex->height;
1075 ctx->available_cross = available_width;
1076 }
1077
1078 NSLOG(flex, DEEPDEBUG, "box %p: available_main: %i",
1079 flex, ctx->available_main);
1080 NSLOG(flex, DEEPDEBUG, "box %p: available_cross: %i",
1081 flex, ctx->available_cross);
1082
1083 layout_flex_ctx__populate_item_data(ctx, flex, available_width);
1084
1085 /* Place items onto lines. */
1087 if (!success) {
1088 goto cleanup;
1089 }
1090
1092
1093 if (flex->height == AUTO) {
1094 flex->height = ctx->horizontal ?
1095 ctx->cross_size :
1096 ctx->main_size;
1097 }
1098
1099 if (flex->height != AUTO) {
1100 if (max_height >= 0 && flex->height > max_height) {
1101 flex->height = max_height;
1102 }
1103 if (min_height > 0 && flex->height < min_height) {
1104 flex->height = min_height;
1105 }
1106 }
1107
1108 success = true;
1109
1110cleanup:
1112
1113 NSLOG(flex, DEEPDEBUG, "box %p: %s: w: %i, h: %i", flex,
1114 success ? "success" : "failure",
1115 flex->width, flex->height);
1116 return success;
1117}
STATIC char result[100]
Definition: arexx.c:77
Box interface.
#define UNKNOWN_WIDTH
Definition: box.h:45
@ BOX_BLOCK
Definition: box.h:56
@ BOX_TABLE
Definition: box.h:59
@ BOX_FLEX
Definition: box.h:70
box_side
Sides of a box.
Definition: box.h:98
@ TOP
Definition: box.h:98
@ BOTTOM
Definition: box.h:98
@ LEFT
Definition: box.h:98
@ RIGHT
Definition: box.h:98
HTML Box tree inspection interface.
static unsigned box_count_children(const struct box *b)
Definition: box_inspect.h:142
bool layout_block_context(struct box *block, int viewport_height, html_content *content)
Layout a block formatting context.
Definition: layout.c:3531
static struct gui_layout_table layout_table
Definition: font.c:156
Interface to text/html content handler.
static int layout_flex__get_min_max_violations(struct flex_ctx *ctx, struct flex_line_data *line)
Clamp flex item target main size and get min/max violations.
Definition: layout_flex.c:555
static void layout_flex__distribute_free_main(struct flex_ctx *ctx, struct flex_line_data *line, css_fixed unfrozen_factor_sum, int remaining_free_main, bool grow)
Distribute remaining free space proportional to the flex factors.
Definition: layout_flex.c:622
static bool layout_flex_item(const struct flex_ctx *ctx, const struct flex_item_data *item, int available_width)
Perform layout on a flex item.
Definition: layout_flex.c:217
static enum box_side layout_flex__main_start_side(const struct flex_ctx *ctx)
Find box side representing the start of flex container in main direction.
Definition: layout_flex.c:183
static bool layout_flex__place_line_items_main(struct flex_ctx *ctx, struct flex_line_data *line)
Position items along a line.
Definition: layout_flex.c:805
static void layout_flex_ctx__populate_item_data(const struct flex_ctx *ctx, const struct box *flex, int available_width)
Fill out all item's data in a flex container.
Definition: layout_flex.c:343
bool layout_flex(struct box *flex, int available_width, html_content *content)
Layout a flex container.
Definition: layout_flex.c:1046
static enum box_side layout_flex__main_end_side(const struct flex_ctx *ctx)
Find box side representing the end of flex container in main direction.
Definition: layout_flex.c:199
static bool layout_flex__resolve_line(struct flex_ctx *ctx, struct flex_line_data *line)
Resolve flexible item lengths along a line.
Definition: layout_flex.c:706
static void layout_flex__place_lines(struct flex_ctx *ctx)
Place the lines and align the items on the line.
Definition: layout_flex.c:1003
static bool layout_flex__collect_items_into_lines(struct flex_ctx *ctx)
Collect items onto lines and place items along the lines.
Definition: layout_flex.c:898
static struct flex_ctx * layout_flex_ctx__create(html_content *content, const struct box *flex)
Create a flex layout context.
Definition: layout_flex.c:140
static void layout_flex_ctx__destroy(struct flex_ctx *ctx)
Destroy a flex layout context.
Definition: layout_flex.c:124
static bool layout_flex__base_and_main_sizes(const struct flex_ctx *ctx, struct flex_item_data *item, int available_width)
Calculate an item's base and target main sizes.
Definition: layout_flex.c:260
static void layout_flex__item_freeze(struct flex_line_data *line, struct flex_item_data *item)
Freeze an item on a line.
Definition: layout_flex.c:480
static struct flex_line_data * layout_flex__build_line(struct flex_ctx *ctx, size_t item_index)
Assigns flex items to the line and returns the line.
Definition: layout_flex.c:413
static bool layout_flex_ctx__ensure_line(struct flex_ctx *ctx)
Ensure context's lines array has a free space.
Definition: layout_flex.c:384
static void layout_flex__place_line_items_cross(struct flex_ctx *ctx, struct flex_line_data *line, int extra)
Align items on a line.
Definition: layout_flex.c:942
static int layout_flex__remaining_free_main(struct flex_ctx *ctx, struct flex_line_data *line, css_fixed *unfrozen_factor_sum, int initial_free_main, int available_main, bool grow)
Calculate remaining free space and unfrozen item factor sum.
Definition: layout_flex.c:507
HTML layout private interface.
static bool lh__flex_direction_reversed(const struct box *flex)
#define FPCT_OF_INT_TOINT(a, b)
static bool lh__flex_main_is_horizontal(const struct box *flex)
#define AUTO
static int lh__delta_outer_main(const struct box *flex, const struct box *b)
static int * lh__box_size_cross_ptr(bool horizontal, struct box *b)
static bool lh__box_is_absolute(const struct box *b)
static int lh__box_size_main(bool horizontal, const struct box *b)
static int lh__box_size_cross(bool horizontal, const struct box *b)
static void layout_find_dimensions(const css_unit_ctx *unit_len_ctx, int available_width, int viewport_height, const struct box *box, const css_computed_style *style, int *width, int *height, int *max_width, int *min_width, int *max_height, int *min_height, int margin[4], int padding[4], struct box_border border[4])
Calculate width, height, and thickness of margins, paddings, and borders.
static enum css_align_self_e lh__box_align_self(const struct box *flex, const struct box *item)
static int lh__delta_outer_width(const struct box *b)
static int lh__delta_outer_cross(const struct box *flex, const struct box *b)
static int lh__non_auto_margin(const struct box *b, enum box_side side)
static bool lh__box_size_cross_is_auto(bool horizontal, struct box *b)
#define NSLOG(catname, level, logmsg, args...)
Definition: log.h:116
Private data for text/html content.
Interface to utility string handling.
int width
border-width (pixels)
Definition: box.h:107
Node in box tree.
Definition: box.h:177
struct box_border border[4]
Border: TOP, RIGHT, BOTTOM, LEFT.
Definition: box.h:327
int min_width
Width of box taking all line breaks (including margins etc).
Definition: box.h:343
int width
Width of content box (excluding padding etc.).
Definition: box.h:289
struct box * parent
Parent box, or NULL.
Definition: box.h:236
struct box * children
First child box, or NULL.
Definition: box.h:226
int height
Height of content box (excluding padding etc.).
Definition: box.h:293
struct box * float_container
If box is a float, points to box's containing block.
Definition: box.h:261
int margin[4]
Margin: TOP, RIGHT, BOTTOM, LEFT.
Definition: box.h:317
int max_width
Width that would be taken with no line breaks.
Definition: box.h:349
struct box * next
Next sibling box, or NULL.
Definition: box.h:216
box_type type
Type of box.
Definition: box.h:181
css_computed_style * style
Style for this box.
Definition: box.h:205
int padding[4]
Padding: TOP, RIGHT, BOTTOM, LEFT.
Definition: box.h:322
int x
Coordinate of left padding edge relative to parent box, or relative to ancestor that contains this bo...
Definition: box.h:280
int y
Coordinate of top padding edge, relative as for x.
Definition: box.h:284
Content which corresponds to a single URL.
struct flex_item_data * data
Definition: layout_flex.c:109
struct flex_line_data * data
Definition: layout_flex.c:115
Flex layout context.
Definition: layout_flex.c:92
struct flex_ctx::flex_lines line
int available_main
Definition: layout_flex.c:100
const struct box * flex
Definition: layout_flex.c:94
int available_cross
Definition: layout_flex.c:101
int cross_size
Definition: layout_flex.c:98
struct flex_ctx::flex_items item
html_content * content
Definition: layout_flex.c:93
const css_unit_ctx * unit_len_ctx
Definition: layout_flex.c:95
bool main_reversed
Definition: layout_flex.c:104
bool horizontal
Definition: layout_flex.c:103
enum css_flex_wrap_e wrap
Definition: layout_flex.c:105
int main_size
Definition: layout_flex.c:97
Flex item data.
Definition: layout_flex.c:48
enum css_flex_basis_e basis
Definition: layout_flex.c:49
css_fixed basis_length
Definition: layout_flex.c:50
bool min_violation
Definition: layout_flex.c:68
struct box * box
Definition: layout_flex.c:52
bool max_violation
Definition: layout_flex.c:69
css_fixed shrink
Definition: layout_flex.c:54
css_fixed grow
Definition: layout_flex.c:55
int target_main_size
Definition: layout_flex.c:62
css_unit basis_unit
Definition: layout_flex.c:51
Flex line data.
Definition: layout_flex.c:75
int main_auto_margin_count
Definition: layout_flex.c:80
Data specific to CONTENT_HTML.
Definition: private.h:93
Interface to a number of general purpose functionality.
#define fallthrough
switch fall through
Definition: utils.h:115
#define min(x, y)
Definition: utils.h:46
#define max(x, y)
Definition: utils.h:50
static nserror line(const struct redraw_context *ctx, const plot_style_t *style, const struct rect *line)
Plots a line.
Definition: plot.c:579