diff --git a/Applications/Internet/Cyberia.app/Cyberia.HC b/Applications/Internet/Cyberia.app/Cyberia.HC index a1f5076..81093b0 100644 --- a/Applications/Internet/Cyberia.app/Cyberia.HC +++ b/Applications/Internet/Cyberia.app/Cyberia.HC @@ -407,6 +407,7 @@ U0 @cyberia_navigate(Bool refresh = FALSE) renderer->link_pointer = Compositor.theme.pointer.link; renderer->link_callback = &@cyberia_link_clicked; renderer->form_submit_callback = &@cyberia_form_submit_clicked; + renderer->image_load_callback = &@reflow_node_list; renderer->widgets_base = widgets_base->next; renderer->status_widget = status1; renderer->background_widget = background1; @@ -576,7 +577,7 @@ U0 @cyberia_win_repaint(Window*) hanbagabtn1->x = win->width - hanbagabtn1->width - 9; addressbar1->width = hanbagabtn1->x - addressbar1->x - 3; - background1->width = win->width; + background1->width = win->width - 9; background1->height = win->height - 84; vscroll1->x = win->width; @@ -614,6 +615,7 @@ U0 @cyberia_win_repaint(Window*) vscroll1->max = browser->renderer->calculated_page_height; vscroll1->length = (vscroll1->height - 31) / MaxI64(2, ToI64(browser->renderer->calculated_page_height / background1->height)); vscroll1->x = win->width - 25; + background1->width -= vscroll1->width; } } diff --git a/Applications/Internet/Cyberia.app/Resources/Default.css b/Applications/Internet/Cyberia.app/Resources/Default.css index e75a590..ec26de1 100644 --- a/Applications/Internet/Cyberia.app/Resources/Default.css +++ b/Applications/Internet/Cyberia.app/Resources/Default.css @@ -11,45 +11,83 @@ a { text-decoration: underline } -b, strong { +b, +strong { font-weight: bold } +html, +body { + display: block; +} + +body { + margin: 8px; +} + +blockquote { + margin-left: 40px; + margin-right: 40px; +} + +button { + display: inline-block; +} + +center { + display: block; + text-align: center; +} + code { font-family: monospace } -em, i { +em, +i { font-style: italic } h1 { - font-size: 2em; - font-weight: bold + margin-top: 0.67em; + margin-bottom: 0.67em; + font-size: 2.00em; + font-weight: bold; } h2 { - font-size: 1.5em; - font-weight: bold + margin-top: 0.83em; + margin-bottom: 0.83em; + font-size: 1.50em; + font-weight: bold; } h3 { + margin-top: 1.00em; + margin-bottom: 1.00em; font-size: 1.17em; - font-weight: bold + font-weight: bold; } h4 { - font-weight: bold + margin-top: 1.33em; + margin-bottom: 1.33em; + font-size: 1.00em; + font-weight: bold; } h5 { + margin-top: 1.67em; + margin-bottom: 1.67em; font-size: 0.83em; - font-weight: bold + font-weight: bold; } h6 { + margin-top: 2.33em; + margin-bottom: 2.33em; font-size: 0.67em; - font-weight: bold + font-weight: bold; } mark { @@ -57,7 +95,18 @@ mark { color: black } +ol, +ul { + padding-left: 24px; +} + +p { + margin-top: 1em; + margin-bottom: 1em; +} + table { + display: block; text-align: left; } diff --git a/Applications/Internet/Cyberia.app/Resources/Default.css-mod b/Applications/Internet/Cyberia.app/Resources/Default.css-mod new file mode 100644 index 0000000..2a6df61 --- /dev/null +++ b/Applications/Internet/Cyberia.app/Resources/Default.css-mod @@ -0,0 +1,1061 @@ +Document { + background-color: #ffffff; + color: #000000; + font-family: "serif"; + font-size: 16px; + font-weight: normal; +} + +body { + margin: 8px; +} + +/* 15.3.1 Hidden elements + * https://html.spec.whatwg.org/multipage/rendering.html#hidden-elements + */ + +area, +base, +basefont, +datalist, +head, +link, +meta, +noembed, +noframes, +param, +rp, +script, +style, +template, +title { + display: none; +} + +[hidden]:not([hidden=until-found i]) { + display: none; +} + +[hidden=until-found i]:not(embed) { + content-visibility: hidden; +} + +embed[hidden] { + display: inline; + height: 0; + width: 0; +} + +input[type=hidden i] { + display: none !important; +} + +@media (scripting) { + noscript { + display: none !important; + } +} + +/* 15.3.2 The page + * https://html.spec.whatwg.org/multipage/rendering.html#the-page + */ + +html, +body { + display: block; +} + +/* 15.3.3 Flow content + * https://html.spec.whatwg.org/multipage/rendering.html#flow-content-3 + */ +address, +blockquote, +center, +dialog, +div, +figure, +figcaption, +footer, +form, +header, +hr, +legend, +listing, +main, +p, +plaintext, +pre, +search, +xmp { + display: block; +} + +blockquote, +figure, +listing, +p, +plaintext, +pre, +xmp { + margin-top: 1em; + margin-bottom: 1em; +} + +blockquote, +figure { + margin-left: 40px; + margin-right: 40px; +} + +address { + font-style: italic; +} + +listing, +plaintext, +pre, +xmp { + font-family: monospace; + white-space: pre; +} + +dialog:not([open]) { + display: none; +} + +dialog { + position: absolute; + inset-inline-start: 0; + inset-inline-end: 0; + width: fit-content; + height: fit-content; + margin: auto; + border: solid; + padding: 1em; + background-color: Canvas; + color: CanvasText; +} + +dialog:modal { + position: fixed; + overflow: auto; + inset-block: 0; + max-width: calc(100% - 6px - 2em); + max-height: calc(100% - 6px - 2em); +} + +dialog::backdrop { + background: rgba(0, 0, 0, 0.1); +} + +[popover]:not(:popover-open):not(dialog[open]) { + display: none; +} + +dialog:popover-open { + display: block; +} + +[popover] { + position: fixed; + inset: 0; + width: fit-content; + height: fit-content; + margin: auto; + border: solid; + padding: 0.25em; + overflow: auto; + color: CanvasText; + background-color: Canvas; +} + +:popover-open::backdrop { + position: fixed; + inset: 0; + pointer-events: none !important; + background-color: transparent; +} + +/* 4.2. The ::backdrop Pseudo-Element + * https://drafts.csswg.org/css-position-4/#backdrop + */ +::backdrop { + position: fixed; + inset: 0; +} + +slot { + display: contents; +} + +/* 15.3.4 Phrasing content + * https://html.spec.whatwg.org/multipage/rendering.html#phrasing-content-3 + */ + +cite, +dfn, +em, +i, +var { + font-style: italic; +} + +b, +strong { + font-weight: bolder; +} + +code, +kbd, +samp, +tt { + font-family: monospace; +} + +big { + font-size: larger; +} + +small { + font-size: smaller; +} + +sub { + vertical-align: sub; +} + +sup { + vertical-align: super; +} + +sub, +sup { + line-height: normal; + font-size: smaller; +} + +ruby { + display: ruby; +} + +rt { + display: ruby-text; +} + +:link { + color: LinkText; +} + +:visited { + color: VisitedText; +} + +:link:active, +:visited:active { + color: ActiveText; +} + +:link, +:visited { + text-decoration: underline; + cursor: pointer; +} + +:focus-visible { + outline: auto; +} + +mark { + background: Mark; + color: MarkText; +} + +abbr[title], +acronym[title] { + text-decoration: dotted underline; +} + +ins, +u { + text-decoration: underline; +} + +del, +s, +strike { + text-decoration: line-through; +} + +q::before { + content: open-quote; +} + +q::after { + content: close-quote; +} + +/* + NOTE: This isn't a real property and value. See https://github.com/whatwg/html/issues/2291 + br { + display-outside: newline; + } + */ + +/* this also has bidi implications */ +nobr { + white-space: nowrap; +} + +/* + NOTE: This isn't a real property and value. See https://github.com/whatwg/html/issues/2291 + wbr { + display-outside: break-opportunity; + } + */ + +/* this also has bidi implications */ +nobr wbr { + white-space: normal; +} + +/* 15.3.5 Bidirectional text + * https://html.spec.whatwg.org/multipage/rendering.html#bidi-rendering + */ + +[dir]:dir(ltr), +bdi:dir(ltr), +input[type=tel i]:dir(ltr) { + direction: ltr; +} + +[dir]:dir(rtl), +bdi:dir(rtl) { + direction: rtl; +} + +address, +blockquote, +center, +div, +figure, +figcaption, +footer, +form, +header, +hr, +legend, +listing, +main, +p, +plaintext, +pre, +summary, +xmp, +article, +aside, +h1, +h2, +h3, +h4, +h5, +h6, +hgroup, +nav, +search, +section, +table, +caption, +colgroup, +col, +thead, +tbody, +tfoot, +tr, +td, +th, +dir, +dd, +dl, +dt, +menu, +ol, +ul, +li, +bdi, +output, +[dir=ltr i], +[dir=rtl i], +[dir=auto i] { + unicode-bidi: isolate; +} + +bdo, +bdo[dir] { + unicode-bidi: isolate-override; +} + +input[dir=auto i]:is([type=search i], [type=tel i], [type=url i], + [type=email i]), +textarea[dir=auto i], +pre[dir=auto i] { + unicode-bidi: plaintext; +} + +/* 15.3.6 Sections and headings + * https://html.spec.whatwg.org/multipage/rendering.html#sections-and-headings + */ + +article, +aside, +h1, +h2, +h3, +h4, +h5, +h6, +hgroup, +nav, +section { + display: block; +} + +h1 { + margin-top: 0.67em; + margin-bottom: 0.67em; + font-size: 2.00em; + font-weight: bold; +} + +h2 { + margin-top: 0.83em; + margin-bottom: 0.83em; + font-size: 1.50em; + font-weight: bold; +} + +h3 { + margin-top: 1.00em; + margin-bottom: 1.00em; + font-size: 1.17em; + font-weight: bold; +} + +h4 { + margin-top: 1.33em; + margin-bottom: 1.33em; + font-size: 1.00em; + font-weight: bold; +} + +h5 { + margin-top: 1.67em; + margin-bottom: 1.67em; + font-size: 0.83em; + font-weight: bold; +} + +h6 { + margin-top: 2.33em; + margin-bottom: 2.33em; + font-size: 0.67em; + font-weight: bold; +} + +:is(article, aside, nav, section) h1 { + margin-top: 0.83em; + margin-bottom: 0.83em; + font-size: 1.50em; +} + +:is(article, aside, nav, section) :is(article, aside, nav, section) h1 { + margin-top: 1.00em; + margin-bottom: 1.00em; + font-size: 1.17em; +} + +:is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) h1 { + margin-top: 1.33em; + margin-bottom: 1.33em; + font-size: 1.00em; +} + +:is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) h1 { + margin-top: 1.67em; + margin-bottom: 1.67em; + font-size: 0.83em; +} + +:is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) :is(article, aside, nav, section) h1 { + margin-top: 2.33em; + margin-bottom: 2.33em; + font-size: 0.67em; +} + +/* 15.3.7 Lists + * https://html.spec.whatwg.org/multipage/rendering.html#lists + */ +dir, +dd, +dl, +dt, +menu, +ol, +ul { + display: block; +} + +li { + display: list-item; + text-align: match-parent; +} + +dir, +dl, +menu, +ol, +ul { + margin-block-start: 1em; + margin-block-end: 1em; +} + +:is(dir, dl, menu, ol, ul) :is(dir, dl, menu, ol, ul) { + margin-block-start: 0; + margin-block-end: 0; +} + +dd { + margin-inline-start: 40px; +} + +dir, +menu, +ol, +ul { + padding-inline-start: 40px; +} + +ol, +ul, +menu { + counter-reset: list-item; +} + +ol { + list-style-type: decimal; +} + +dir, +menu, +ul { + list-style-type: disc; +} + +:is(dir, menu, ol, ul) :is(dir, menu, ul) { + list-style-type: circle; +} + +:is(dir, menu, ol, ul) :is(dir, menu, ol, ul) :is(dir, menu, ul) { + list-style-type: square; +} + +/* 15.3.8 Tables + * https://html.spec.whatwg.org/multipage/rendering.html#tables-2 + */ + +table { + display: table; +} + +caption { + display: table-caption; +} + +colgroup, +colgroup[hidden] { + display: table-column-group; +} + +col, +col[hidden] { + display: table-column; +} + +thead, +thead[hidden] { + display: table-header-group; +} + +tbody, +tbody[hidden] { + display: table-row-group; +} + +tfoot, +tfoot[hidden] { + display: table-footer-group; +} + +tr, +tr[hidden] { + display: table-row; +} + +td, +th { + display: table-cell; +} + +colgroup[hidden], +col[hidden], +thead[hidden], +tbody[hidden], +tfoot[hidden], +tr[hidden] { + visibility: collapse; +} + +table { + box-sizing: border-box; + border-spacing: 2px; + border-collapse: separate; + text-indent: initial; +} + +td, +th { + padding: 1px; +} + +th { + font-weight: bold; + /* + The text-align property for table headings is non-standard, but all + existing user-agents seem to render them centered by default. + + See: + - https://trac.webkit.org/browser/trunk/Source/WebCore/css/html.css?rev=295625#L272 + - https://searchfox.org/mozilla-central/rev/0b55b868c17835942d40ca3fedfca8057481207b/layout/style/res/html.css#473 + */ + text-align: center; +} + +caption { + text-align: center; +} + +thead, +tbody, +tfoot, +table>tr { + vertical-align: middle; +} + +tr, +td, +th { + vertical-align: inherit; +} + +thead, +tbody, +tfoot, +tr { + border-color: inherit; +} + +table[rules=none i], +table[rules=groups i], +table[rules=rows i], +table[rules=cols i], +table[rules=all i], +table[frame=void i], +table[frame=above i], +table[frame=below i], +table[frame=hsides i], +table[frame=lhs i], +table[frame=rhs i], +table[frame=vsides i], +table[frame=box i], +table[frame=border i], +table[rules=none i]>tr>td, +table[rules=none i]>tr>th, +table[rules=groups i]>tr>td, +table[rules=groups i]>tr>th, +table[rules=rows i]>tr>td, +table[rules=rows i]>tr>th, +table[rules=cols i]>tr>td, +table[rules=cols i]>tr>th, +table[rules=all i]>tr>td, +table[rules=all i]>tr>th, +table[rules=none i]>thead>tr>td, +table[rules=none i]>thead>tr>th, +table[rules=groups i]>thead>tr>td, +table[rules=groups i]>thead>tr>th, +table[rules=rows i]>thead>tr>td, +table[rules=rows i]>thead>tr>th, +table[rules=cols i]>thead>tr>td, +table[rules=cols i]>thead>tr>th, +table[rules=all i]>thead>tr>td, +table[rules=all i]>thead>tr>th, +table[rules=none i]>tbody>tr>td, +table[rules=none i]>tbody>tr>th, +table[rules=groups i]>tbody>tr>td, +table[rules=groups i]>tbody>tr>th, +table[rules=rows i]>tbody>tr>td, +table[rules=rows i]>tbody>tr>th, +table[rules=cols i]>tbody>tr>td, +table[rules=cols i]>tbody>tr>th, +table[rules=all i]>tbody>tr>td, +table[rules=all i]>tbody>tr>th, +table[rules=none i]>tfoot>tr>td, +table[rules=none i]>tfoot>tr>th, +table[rules=groups i]>tfoot>tr>td, +table[rules=groups i]>tfoot>tr>th, +table[rules=rows i]>tfoot>tr>td, +table[rules=rows i]>tfoot>tr>th, +table[rules=cols i]>tfoot>tr>td, +table[rules=cols i]>tfoot>tr>th, +table[rules=all i]>tfoot>tr>td, +table[rules=all i]>tfoot>tr>th { + border-color: black; +} + +/* 15.3.10 Form controls + * https://html.spec.whatwg.org/multipage/rendering.html#form-controls + */ + +input, +select, +button, +textarea { + letter-spacing: initial; + word-spacing: initial; + line-height: initial; + text-transform: initial; + text-indent: initial; + text-shadow: initial; + appearance: auto; +} + +input, +select, +textarea { + text-align: initial; +} + +input:is([type=reset i], [type=button i], [type=submit i]), +button { + text-align: center; +} + +input, +button { + display: inline-block; +} + +input[type=hidden i], +input[type=file i], +input[type=image i] { + appearance: none; +} + +input:is([type=radio i], [type=checkbox i], [type=reset i], [type=button i], + [type=submit i], [type=color i], [type=search i]), +select, +button { + box-sizing: border-box; +} + +textarea { + white-space: pre-wrap; +} + +/* 15.3.11 The hr element + * https://html.spec.whatwg.org/multipage/rendering.html#the-hr-element-2 + */ + +hr { + color: gray; + border-style: inset; + border-width: 1px; + margin-block-start: 0.5em; + margin-inline-end: auto; + margin-block-end: 0.5em; + margin-inline-start: auto; + overflow: hidden; +} + +/* 15.3.12 The fieldset and legend elements + * https://html.spec.whatwg.org/multipage/rendering.html#the-fieldset-and-legend-elements + */ + +fieldset { + display: block; + margin-inline-start: 2px; + margin-inline-end: 2px; + border: groove 2px ThreeDFace; + padding-block-start: 0.35em; + padding-inline-end: 0.75em; + padding-block-end: 0.625em; + padding-inline-start: 0.75em; + min-inline-size: min-content; +} + +legend { + padding-left: 2px; + padding-right: 2px; +} + +legend[align=left i] { + justify-self: left; +} + +legend[align=center i] { + justify-self: center; +} + +legend[align=right i] { + justify-self: right; +} + +/* 15.4.1 Embedded content + * https://html.spec.whatwg.org/multipage/rendering.html#embedded-content-rendering-rules + */ + +iframe { + border: 2px inset; +} + +video { + object-fit: contain; +} + +/* 15.4.3 Attributes for embedded content and images + * https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images + */ + +embed[align=left i], +iframe[align=left i], +img[align=left i], +input[type=image i][align=left i], +object[align=left i] { + float: left; +} + +embed[align=right i], +iframe[align=right i], +img[align=right i], +input[type=image i][align=right i], +object[align=right i] { + float: right; +} + +embed[align=top i], +iframe[align=top i], +img[align=top i], +input[type=image i][align=top i], +object[align=top i] { + vertical-align: top; +} + +embed[align=baseline i], +iframe[align=baseline i], +img[align=baseline i], +input[type=image i][align=baseline i], +object[align=baseline i] { + vertical-align: baseline; +} + +embed[align=texttop i], +iframe[align=texttop i], +img[align=texttop i], +input[type=image i][align=texttop i], +object[align=texttop i] { + vertical-align: text-top; +} + +embed[align=absmiddle i], +iframe[align=absmiddle i], +img[align=absmiddle i], +input[type=image i][align=absmiddle i], +object[align=absmiddle i], +embed[align=abscenter i], +iframe[align=abscenter i], +img[align=abscenter i], +input[type=image i][align=abscenter i], +object[align=abscenter i] { + vertical-align: middle; +} + +embed[align=bottom i], +iframe[align=bottom i], +img[align=bottom i], +input[type=image i][align=bottom i], +object[align=bottom i] { + vertical-align: bottom; +} + +/* 15.5.5 The details and summary elements + * https://html.spec.whatwg.org/multipage/rendering.html#the-details-and-summary-elements + */ + +details, +summary { + display: block; +} + +details>summary:first-of-type { + display: list-item; + counter-increment: list-item 0; + list-style: disclosure-closed inside; +} + +details[open]>summary:first-of-type { + list-style-type: disclosure-open; +} + +/* 15.5.12 The marquee element + * https://html.spec.whatwg.org/multipage/rendering.html#the-marquee-element-2 + */ + +marquee { + display: inline-block; + text-align: initial; + overflow: hidden !important; +} + +/* 15.5.13 The meter element + * https://html.spec.whatwg.org/multipage/rendering.html#the-meter-element-2 + */ + +meter { + appearance: auto; +} + +/* 15.5.14 The progress element + * https://html.spec.whatwg.org/multipage/rendering.html#the-progress-element-2 + */ + +progress { + appearance: auto; +} + +/* https://www.w3.org/TR/mediaqueries-5/#descdef-media-inverted-colors + */ +@media (inverted-colors) { + + img:not(picture>img), + picture, + video { + filter: invert(100%); + } +} + +/* https://github.com/whatwg/html/pull/9546 + */ +input[type=checkbox][switch] { + /* FIXME: Workaround until we can properly style dark switches */ + color-scheme: light; + appearance: none; + height: 1em; + width: 1.8em; + vertical-align: middle; + border-radius: 1em; + position: relative; + overflow: hidden; + border-color: transparent; + background-color: ButtonFace; +} + +input[type=checkbox][switch]::before { + content: ''; + position: absolute; + height: 0; + width: 0; + border: .46em solid Field; + border-radius: 100%; + top: 0; + bottom: 0; + left: 0; + margin: auto; +} + +input[type=checkbox][switch]:checked::before { + left: calc(100% - .87em); +} + +input[type=checkbox][switch]:checked { + background-color: AccentColor; +} + +/* https://drafts.csswg.org/css-ui/#propdef-user-select */ +button, +meter, +progress, +select { + user-select: none; +} + +/* https://drafts.csswg.org/css-view-transitions-1/#ua-styles */ +:root { + view-transition-name: root; +} + +:root::view-transition { + position: fixed; + inset: 0; +} + +:root::view-transition-group(*) { + position: absolute; + top: 0; + left: 0; + + animation-duration: 0.25s; + animation-fill-mode: both; +} + +:root::view-transition-image-pair(*) { + position: absolute; + inset: 0; + + animation-duration: inherit; + animation-fill-mode: inherit; + animation-delay: inherit; +} + +:root::view-transition-old(*), +:root::view-transition-new(*) { + position: absolute; + inset-block-start: 0; + inline-size: 100%; + block-size: auto; + + animation-duration: inherit; + animation-fill-mode: inherit; + animation-delay: inherit; +} + +/* Default cross-fade transition */ +@keyframes -ua-view-transition-fade-out { + to { + opacity: 0; + } +} + +@keyframes -ua-view-transition-fade-in { + from { + opacity: 0; + } +} + +/* Keyframes for blending when there are 2 images */ +@keyframes -ua-mix-blend-mode-plus-lighter { + from { + mix-blend-mode: plus-lighter + } + + to { + mix-blend-mode: plus-lighter + } +} \ No newline at end of file diff --git a/System/Libraries/Html/Reflow.HC b/System/Libraries/Html/Reflow.HC new file mode 100644 index 0000000..4706080 --- /dev/null +++ b/System/Libraries/Html/Reflow.HC @@ -0,0 +1,637 @@ +// FIXME: *** Handle margin collapsing *** + +Bool reflow_debug = FALSE; + +U0 @reflow_position_widget(Widget* widget, HtmlRenderer* renderer) +{ + widget->x = renderer->reflow.bounds.x1 + renderer->reflow.inline.x; + widget->y = renderer->reflow.bounds.y1 + renderer->reflow.inline.y + renderer->scroll_y; +} + +U0 @reflow_break_line(HtmlRenderer* renderer) +{ + renderer->reflow.inline.x = 0; + renderer->reflow.inline.y += renderer->reflow.inline.max_line_height; + renderer->reflow.inline.max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; +} + +U0 @reflow_break_line_if_not_enough_horizontal_space(Widget* widget, HtmlRenderer* renderer) +{ + I32 combined_x = renderer->reflow.bounds.x1 + renderer->reflow.inline.x; + if (combined_x > renderer->reflow.bounds.x2 - widget->width) { + @reflow_break_line(renderer); + } +} + +U0 @reflow_break_line_if_inline_x_is_positive(HtmlRenderer* renderer) +{ + if (renderer->reflow.inline.x > 0) { + @reflow_break_line(renderer); + } +} + +U0 @reflow_resolve_css_side(@html_dom_node* node, @css_side* parent_resolved_side, @css_side* resolved_side, @css_side* side) +{ + switch (side->type) { + case CSS_DISTANCE_PIXELS: + MemCpy(resolved_side, side, sizeof(@css_side)); + break; + case CSS_DISTANCE_EM: + resolved_side->value = ToI64(side->value * node->fontSize); + resolved_side->type = CSS_DISTANCE_PIXELS; + break; + case CSS_DISTANCE_PERCENT: + resolved_side->value = ToI64(parent_resolved_side->value * (100 / side->value)); + resolved_side->type = CSS_DISTANCE_PIXELS; + break; + default: + break; + } +} + +U0 @reflow_resolve_dynamic_margins(@html_dom_node* node) +{ + @reflow_resolve_css_side(node, &node->parentNode->resolvedMargin.top, &node->resolvedMargin.top, &node->margin.top); + @reflow_resolve_css_side(node, &node->parentNode->resolvedMargin.right, &node->resolvedMargin.right, &node->margin.right); + @reflow_resolve_css_side(node, &node->parentNode->resolvedMargin.bottom, &node->resolvedMargin.bottom, &node->margin.bottom); + @reflow_resolve_css_side(node, &node->parentNode->resolvedMargin.left, &node->resolvedMargin.left, &node->margin.left); +} + +U0 @reflow_resolve_dynamic_borders(@html_dom_node* node) +{ + @reflow_resolve_css_side(node, &node->parentNode->resolvedBorder.top, &node->resolvedBorder.top, &node->border.top); + @reflow_resolve_css_side(node, &node->parentNode->resolvedBorder.right, &node->resolvedBorder.right, &node->border.right); + @reflow_resolve_css_side(node, &node->parentNode->resolvedBorder.bottom, &node->resolvedBorder.bottom, &node->border.bottom); + @reflow_resolve_css_side(node, &node->parentNode->resolvedBorder.left, &node->resolvedBorder.left, &node->border.left); +} + +U0 @reflow_resolve_dynamic_padding(@html_dom_node* node) +{ + @reflow_resolve_css_side(node, &node->parentNode->resolvedPadding.top, &node->resolvedPadding.top, &node->padding.top); + @reflow_resolve_css_side(node, &node->parentNode->resolvedPadding.right, &node->resolvedPadding.right, &node->padding.right); + @reflow_resolve_css_side(node, &node->parentNode->resolvedPadding.bottom, &node->resolvedPadding.bottom, &node->padding.bottom); + @reflow_resolve_css_side(node, &node->parentNode->resolvedPadding.left, &node->resolvedPadding.left, &node->padding.left); +} + +I64 @reflow_resolve_css_distance(F64 parent_distance, F64 distance, I64 type) +{ + switch (type) { + case CSS_DISTANCE_PIXELS: + return ToI64(distance); + case CSS_DISTANCE_EM: + return ToI64(distance * RENDERER_DEFAULT_MAX_LINE_HEIGHT); + case CSS_DISTANCE_PERCENT: + return ToI64(parent_distance * (100 / distance)); + default: + return 0; + } +} + +U0 @reflow_resolve_dynamic_width_and_height(@html_dom_node* node) +{ + node->resolvedWidth = @reflow_resolve_css_distance(node->parentNode->width, node->width, node->widthDistanceType); + node->resolvedHeight = @reflow_resolve_css_distance(node->parentNode->height, node->height, node->heightDistanceType); +} + +U0 @reflow_resolve_dimensions_for_widget(Widget* widget) +{ + @html_dom_node* node = widget->data; + + // FIXME: Handle dynamic width/height for images + if (!StrICmp(node->tagName, "img") && (!widget->width || !widget->height)) { + if (widget->type == WIDGET_TYPE_CONTEXT2D && widget(Context2DWidget*)->ctx) { + node->width = widget(Context2DWidget*)->ctx->width; + node->widthDistanceType = CSS_DISTANCE_PIXELS; + node->height = widget(Context2DWidget*)->ctx->height; + node->heightDistanceType = CSS_DISTANCE_PIXELS; + widget->width = node->resolvedWidth = node->width; + widget->height = node->resolvedHeight = node->height; + return; + } + } + + // set a random bgcolor for the widget, so we can tell what is what during debugging + if (reflow_debug) { + switch (widget->type) { + case WIDGET_TYPE_BORDERED_RECT: + widget(BorderedRectWidget*)->color = Color(RandU16 & 0xff, RandU16 & 0xff, RandU16 & 0xff); + break; + case WIDGET_TYPE_RECT: + widget(RectWidget*)->color = Color(RandU16 & 0xff, RandU16 & 0xff, RandU16 & 0xff); + break; + default: + break; + } + } + + @reflow_resolve_dynamic_width_and_height(node); + @reflow_resolve_dynamic_margins(node); + @reflow_resolve_dynamic_borders(node); + @reflow_resolve_dynamic_padding(node); +} + +U0 @reflow_push(HtmlRenderer* renderer) +{ + if (reflow_debug) { + "push: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } + ++renderer->reflow_index; + MemCpy(&renderer->reflow_stack[renderer->reflow_index], &renderer->reflow, sizeof(@renderer_reflow)); +} + +U0 @reflow_pop(HtmlRenderer* renderer) +{ + if (reflow_debug) { + "pop: before: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } + MemCpy(&renderer->reflow, &renderer->reflow_stack[renderer->reflow_index], sizeof(@renderer_reflow)); + --renderer->reflow_index; + if (reflow_debug) { + "pop: after: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } +} + +U0 @reflow_set_initial_values(HtmlRenderer* renderer) +{ + renderer->reflow.bounds.x1 = 0; + renderer->reflow.bounds.x2 = renderer->background_widget->width; + renderer->reflow.bounds.y1 = 0; + renderer->reflow.bounds.y2 = renderer->win->height; + renderer->reflow.inline.x = 0; + renderer->reflow.inline.y = 0; + renderer->reflow.inline.max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; + renderer->reflow.parent = NULL; + renderer->reflow_index = -1; +} + +U0 @reflow_update_vertical_scrollbar(HtmlRenderer* renderer) +{ + // FIXME: this is glitchy and wrong + VerticalScrollBarWidget* vscroll = renderer->vertical_scroll_widget; + I32 origin_y = renderer->background_widget->y; + I32 offset_y = origin_y; + I32 minimum_vscroll_value = 0; + if (vscroll && vscroll->max && vscroll->value) { + minimum_vscroll_value = ToI64((vscroll->max * 1.0) / (vscroll->height * 1.0) * (16 * 1.0)); + offset_y -= vscroll->value - minimum_vscroll_value; + } + renderer->scroll_y = offset_y; +} + +Bool @reflow_widget_has_closing_tag(Widget* widget) +{ + if (!widget) + return FALSE; + return (widget->width == -0xbeef && widget->height == -0xcafe); +} + +U0 @reflow_apply_margins_to_bounds(Widget* widget, HtmlRenderer* renderer) +{ + @html_dom_node* node = widget->data; + + I32 auto_width = 0; + I32 border_left = 0; + I32 border_right = 0; + + if (node->margin.left.type == CSS_DISTANCE_AUTO && node->margin.right.type == CSS_DISTANCE_AUTO) { + switch (widget->type) { + case WIDGET_TYPE_BORDERED_RECT: + border_left = widget(BorderedRectWidget*)->left.size; + border_right = widget(BorderedRectWidget*)->right.size; + break; + default: + break; + } + auto_width = ((renderer->reflow.bounds.x2 - renderer->reflow.bounds.x1) / 2) - ((border_left + widget->width + border_right) / 2); + renderer->reflow.bounds.x1 += auto_width; + renderer->reflow.bounds.x2 -= auto_width; + } + + switch (node->resolvedMargin.left.type) { + case CSS_DISTANCE_UNDEFINED: + break; + case CSS_DISTANCE_PIXELS: + default: + renderer->reflow.bounds.x1 += ToI64(node->resolvedMargin.left.value); + break; + } + + switch (node->resolvedMargin.right.type) { + case CSS_DISTANCE_UNDEFINED: + break; + case CSS_DISTANCE_PIXELS: + default: + renderer->reflow.bounds.x2 -= ToI64(node->resolvedMargin.right.value); + break; + } + + switch (node->resolvedMargin.top.type) { + case CSS_DISTANCE_UNDEFINED: + break; + case CSS_DISTANCE_PIXELS: + default: + renderer->reflow.bounds.y1 += ToI64(node->resolvedMargin.top.value); + break; + } + + switch (node->resolvedMargin.bottom.type) { + case CSS_DISTANCE_UNDEFINED: + break; + case CSS_DISTANCE_PIXELS: + default: + renderer->reflow.bounds.y2 -= ToI64(node->resolvedMargin.bottom.value); + break; + } +} + +U0 @reflow_apply_border_and_padding_to_bounds(Widget* widget, HtmlRenderer* renderer) +{ + @html_dom_node* node = widget->data; + renderer->reflow.bounds.x1 += ToI64(node->resolvedBorder.left.value) + ToI64(node->resolvedPadding.left.value); + renderer->reflow.bounds.x2 -= ToI64(node->resolvedBorder.right.value) + ToI64(node->resolvedPadding.right.value); + renderer->reflow.bounds.y1 += ToI64(node->resolvedBorder.top.value) + ToI64(node->resolvedPadding.top.value); + renderer->reflow.bounds.y2 -= ToI64(node->resolvedBorder.bottom.value) + ToI64(node->resolvedPadding.bottom.value); +} + +U0 @reflow_reset_inline_position(HtmlRenderer* renderer) +{ + renderer->reflow.inline.x = 0; + renderer->reflow.inline.y = 0; +} + +U0 @reflow_set_widget_dimensions(Widget* widget, HtmlRenderer* renderer) +{ + @html_dom_node* node = widget->data; + + if (node->widthDistanceType == CSS_DISTANCE_UNDEFINED) { + switch (node->display) { + case CSS_DISPLAY_BLOCK: + if (!(!StrICmp(node->tagName, "html") || !StrICmp(node->tagName, "body"))) { + node->resolvedWidth = renderer->reflow.bounds.x2 - renderer->reflow.bounds.x1; + } + break; + default: + break; + } + } + + if (node->heightDistanceType == CSS_DISTANCE_UNDEFINED) { + switch (node->display) { + case CSS_DISPLAY_BLOCK: + @reflow_break_line_if_inline_x_is_positive(renderer); + break; + default: + break; + } + if (reflow_debug) { + "inline_y is: %d\n", renderer->reflow.inline.y; + } + node->resolvedHeight = renderer->reflow.inline.y; + } + + widget->width = ToI64(node->resolvedPadding.left.value) + node->resolvedWidth + ToI64(node->resolvedPadding.right.value); + widget->height = ToI64(node->resolvedPadding.top.value) + node->resolvedHeight + ToI64(node->resolvedPadding.bottom.value); +} + +U0 @reflow_set_widget_as_parent_widget(Widget* widget, HtmlRenderer* renderer) +{ + renderer->reflow.parent = widget; +} + +U0 @reflow_set_parent_widget_dimensions(HtmlRenderer* renderer) +{ + @reflow_set_widget_dimensions(renderer->reflow.parent, renderer); +} + +U0 @reflow_apply_inline_x_to_bounds(HtmlRenderer* renderer) +{ + renderer->reflow.bounds.x1 += renderer->reflow.inline.x; +} + +U0 @reflow_apply_inline_y_to_bounds(HtmlRenderer* renderer) +{ + renderer->reflow.bounds.y1 += renderer->reflow.inline.y; +} + +U0 @reflow_apply_inline_to_bounds(HtmlRenderer* renderer) +{ + @reflow_apply_inline_x_to_bounds(renderer); + @reflow_apply_inline_y_to_bounds(renderer); +} + +U0 @reflow_apply_widget_height_to_inline(Widget* widget, HtmlRenderer* renderer) +{ + @html_dom_node* node = widget->data; + + I32 height = widget->height; + height += ToI64(node->resolvedMargin.top.value) + ToI64(node->resolvedMargin.bottom.value); + height += ToI64(node->resolvedBorder.top.value) + ToI64(node->resolvedBorder.bottom.value); + + renderer->reflow.inline.y += height; + if (reflow_debug) { + "margins top/bottom: %d,%d\n", ToI64(node->resolvedMargin.top.value), ToI64(node->resolvedMargin.bottom.value); + "borders top/bottom: %d,%d\n", ToI64(node->resolvedBorder.top.value), ToI64(node->resolvedBorder.bottom.value); + "widget->height: %d\n", widget->height; + "Added height: %d\n", height; + + "current: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } +} + +U0 @reflow_apply_widget_width_to_inline(Widget* widget, HtmlRenderer* renderer) +{ + @html_dom_node* node = widget->data; + + I32 width = widget->height; + width += ToI64(node->resolvedMargin.left.value) + ToI64(node->resolvedMargin.right.value); + width += ToI64(node->resolvedBorder.left.value) + ToI64(node->resolvedBorder.right.value); + + renderer->reflow.inline.x += width; + if (reflow_debug) { + "margins left/right: %d,%d\n", ToI64(node->resolvedMargin.left.value), ToI64(node->resolvedMargin.right.value); + "borders left/right: %d,%d\n", ToI64(node->resolvedBorder.left.value), ToI64(node->resolvedBorder.right.value); + "widget->width: %d\n", widget->width; + "Added width: %d\n", width; + + "current: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } +} + +U0 @reflow_apply_width_and_height_to_bounds(Widget* widget, HtmlRenderer* renderer) +{ + @html_dom_node* node = widget->data; + if (node->resolvedWidth) { + renderer->reflow.bounds.x2 = renderer->reflow.bounds.x1 + node->resolvedWidth; + } + if (node->resolvedHeight) { + renderer->reflow.bounds.y2 = renderer->reflow.bounds.y1 + node->resolvedHeight; + } +} + +U0 @reflow_begin_block(Widget* widget, HtmlRenderer* renderer) +{ + @html_dom_node* node = widget->data; + if (reflow_debug) { + "begin block: <%s>\n", node->tagName; + } + @reflow_break_line_if_inline_x_is_positive(renderer); + @reflow_push(renderer); + + @reflow_apply_inline_y_to_bounds(renderer); + @reflow_reset_inline_position(renderer); + + if (reflow_debug) { + "apply inline y and reset: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } + + @reflow_set_widget_as_parent_widget(widget, renderer); + @reflow_apply_margins_to_bounds(widget, renderer); + @reflow_position_widget(widget, renderer); + @reflow_apply_border_and_padding_to_bounds(widget, renderer); + @reflow_apply_width_and_height_to_bounds(widget, renderer); +} + +U0 @reflow_end_block(Widget* widget, HtmlRenderer* renderer) +{ + Widget* parent = renderer->reflow.parent; + @html_dom_node* node = parent->data; + if (reflow_debug) { + "end block: \n", node->tagName; + } + @reflow_set_parent_widget_dimensions(renderer); + @reflow_pop(renderer); + @reflow_apply_widget_height_to_inline(parent, renderer); + + if (reflow_debug) { + "apply widget height to inline: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } +} + +U0 @reflow_begin_inline_block(Widget* widget, HtmlRenderer* renderer) +{ + // @reflow_begin_block(widget, renderer); + + @html_dom_node* node = widget->data; + if (reflow_debug) { + "begin inline-block: <%s>\n", node->tagName; + } + @reflow_push(renderer); + + @reflow_apply_inline_to_bounds(renderer); + @reflow_reset_inline_position(renderer); + + if (reflow_debug) { + "apply inline and reset: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } + + @reflow_set_widget_as_parent_widget(widget, renderer); + @reflow_apply_margins_to_bounds(widget, renderer); + @reflow_position_widget(widget, renderer); + @reflow_apply_border_and_padding_to_bounds(widget, renderer); + @reflow_apply_width_and_height_to_bounds(widget, renderer); +} + +U0 @reflow_end_inline_block(Widget* widget, HtmlRenderer* renderer) +{ + // @reflow_end_block(widget, renderer); + + Widget* parent = renderer->reflow.parent; + @html_dom_node* node = parent->data; + if (reflow_debug) { + "end inline-block: \n", node->tagName; + } + @reflow_set_parent_widget_dimensions(renderer); + @reflow_pop(renderer); + @reflow_apply_widget_width_to_inline(parent, renderer); + + if (reflow_debug) { + "apply widget width to inline: bounds: %d/%d/%d/%d, inline: %d/%d, max_height: %d\n", + renderer->reflow.bounds.x1, renderer->reflow.bounds.y1, + renderer->reflow.bounds.x2, renderer->reflow.bounds.y2, + renderer->reflow.inline.x, renderer->reflow.inline.y, + renderer->reflow.inline.max_line_height; + } +} + +U0 @reflow_break_line(HtmlRenderer* renderer) +{ + renderer->reflow.inline.x = 0; + renderer->reflow.inline.y += renderer->reflow.inline.max_line_height; + renderer->reflow.inline.max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; +} + +U0 @reflow_break_line_if_not_enough_horizontal_space(Widget* widget, HtmlRenderer* renderer) +{ + I32 resolved_inline_x = renderer->reflow.bounds.x1 + renderer->reflow.inline.x; + if (resolved_inline_x > renderer->reflow.bounds.x2 - widget->width) { + @reflow_break_line(renderer); + } +} + +Bool @reflow_stop_calculating_offset(Widget* widget) +{ + @html_dom_node* node = widget->data; + return (!node || node->display == CSS_DISPLAY_BLOCK || !StrICmp(node->tagName, "br") || @reflow_widget_has_closing_tag(widget)); +} + +I32 @reflow_calculate_text_align_offset(@window_widgets_list* wl, HtmlRenderer* renderer) +{ + I32 line_break_width = renderer->reflow.bounds.x2 - renderer->reflow.bounds.x1; + I32 offset = wl->widget->width; + wl = wl->next; + while (wl && wl->widget && offset + wl->widget->width < line_break_width) { + if (@reflow_stop_calculating_offset(wl->widget)) { + wl = NULL; + } else { + offset += wl->widget->width; + wl = wl->next; + } + } + return offset; +} + +U0 @reflow_handle_inline_text_align(@window_widgets_list* wl, @html_dom_node* node, HtmlRenderer* renderer) +{ + if (!node->textAlign) + return; + + if (!renderer->reflow.inline.x) { + I32 offset_x = @reflow_calculate_text_align_offset(wl, renderer); + switch (node->textAlign) { + case CSS_TEXT_ALIGN_CENTER: + renderer->reflow.inline.x = (((renderer->reflow.bounds.x2 - renderer->reflow.bounds.x1) / 2) - (offset_x / 2)); + break; + case CSS_TEXT_ALIGN_RIGHT: + renderer->reflow.inline.x = (renderer->reflow.bounds.x2 - renderer->reflow.bounds.x1) - offset_x; + break; + default: + break; + } + } +} + +U0 @reflow_inline(@window_widgets_list* wl, Widget* widget, HtmlRenderer* renderer) +{ + @html_dom_node* node = widget->data; + if (!StrICmp(node->tagName, "br")) { + @reflow_break_line(renderer); + return; + } + @reflow_handle_inline_text_align(wl, node, renderer); + @reflow_break_line_if_not_enough_horizontal_space(widget, renderer); + @reflow_position_widget(widget, renderer); + + renderer->reflow.inline.x += widget->width; + renderer->reflow.inline.max_line_height = MaxI64(widget->height, renderer->reflow.inline.max_line_height); +} + +U0 @reflow_set_hyperlink_if_needed(Widget* widget, HtmlRenderer* renderer) +{ + if (widget->callback.clicked) + return; + @html_dom_node* node = widget->data; + if (@self_or_ancestor_matches_tag_name(node, "a")) { + widget->pointer = renderer->link_pointer; + Gui.Widget.SetCallback(widget, "clicked", renderer->link_callback); + } +} + +U0 @reflow_node_list(HtmlRenderer* renderer) +{ + if (!renderer || !renderer->win || !renderer->widgets_base) + return; + + //"Reflow begin\n"; + + Widget* widget = NULL; + @html_dom_node* node = NULL; + + @window_widgets_list* wl = renderer->widgets_base->next; + + @reflow_update_vertical_scrollbar(renderer); + @reflow_set_initial_values(renderer); + @reflow_push(renderer); + + while (wl) { + if (!wl->widget || !wl->widget->data) { + goto reflow_next_wl; + } + + widget = wl->widget; + node = widget->data; + + if (!@reflow_widget_has_closing_tag(widget)) { + @reflow_resolve_dimensions_for_widget(widget); + @reflow_set_hyperlink_if_needed(widget, renderer); + } + + switch (node->display) { + case CSS_DISPLAY_BLOCK: + if (@reflow_widget_has_closing_tag(widget)) { + @reflow_end_block(widget, renderer); + } else { + @reflow_begin_block(widget, renderer); + } + goto reflow_next_wl; + case CSS_DISPLAY_INLINE_BLOCK: + if (@reflow_widget_has_closing_tag(widget)) { + @reflow_end_inline_block(widget, renderer); + } else { + @reflow_begin_inline_block(widget, renderer); + } + goto reflow_next_wl; + default: + @reflow_inline(wl, widget, renderer); + break; + } + + reflow_next_wl: + wl = wl->next; + } + + wl = renderer->widgets_base->next; + while (wl) { + if (wl->widget && wl->widget->data) { + node = wl->widget->data; + if (!StrICmp(node->tagName, "body")) { + renderer->calculated_page_height = node->resolvedHeight; + break; + } + } + wl = wl->next; + } + + //"Reflow end"; +} diff --git a/System/Libraries/Html/Renderer.HC b/System/Libraries/Html/Renderer.HC index 3d33f7b..27dd85b 100644 --- a/System/Libraries/Html/Renderer.HC +++ b/System/Libraries/Html/Renderer.HC @@ -53,11 +53,25 @@ class @html_lazyload_image @html_lazyload_image* next; }; -class @renderer_reflow_state +class @renderer_reflow_bounds { I32 x1; I32 x2; I32 y1; + I32 y2; +} + +class @renderer_reflow_inline +{ + I32 x; + I32 y; + I64 max_line_height; +} + +class @renderer_reflow +{ + @renderer_reflow_bounds bounds; + @renderer_reflow_inline inline; Widget* parent; }; @@ -85,16 +99,20 @@ class @html_renderer Window* win; @window_widgets_list* widgets_base; @window_widgets_list* images; + I64 inline_x; + I64 inline_y; I64 render_x; I64 render_y; + I64 scroll_y; I64 indent; - I64 max_line_height; I64 calculated_page_height; Context2D* link_pointer; U64 link_callback; U64 form_submit_callback; - @renderer_reflow_state state[256]; - I64 state_index; + U64 (*image_load_callback)(U64); + @renderer_reflow reflow_stack[128]; + @renderer_reflow reflow; + I64 reflow_index; }; #define HtmlRenderer @html_renderer @@ -723,6 +741,11 @@ Bool @apply_css_properties_to_node(@html_dom_node* node, JsonObject* properties) @set_css_distance(values->@(0), &node->width, &node->widthDistanceType); } + // FIXME: Handle max-width correctly + if (!StrICmp(key->name, "max-width")) { + @set_css_distance(values->@(0), &node->width, &node->widthDistanceType); + } + if (!StrICmp(key->name, "height")) { @set_css_distance(values->@(0), &node->height, &node->heightDistanceType); } @@ -747,11 +770,11 @@ Bool @apply_css_properties_to_node(@html_dom_node* node, JsonObject* properties) } } - if (!StrICmp(key->name, "line-height") && !StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { - StrCpy(node_tmpnum_buf, values->@(0)); - node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; - node->fontSize = ToI64((Str2I64(node_tmpnum_buf) / 3) * 2); - } + // if (!StrICmp(key->name, "line-height") && !StrICmp(values->@(0) + StrLen(values->@(0)) - 2, "px")) { + // StrCpy(node_tmpnum_buf, values->@(0)); + // node_tmpnum_buf[StrLen(node_tmpnum_buf) - 2] = NULL; + // node->fontSize = ToI64((Str2I64(node_tmpnum_buf) / 3) * 2); + // } if (!StrICmp(key->name, "font-size")) { StrCpy(node_tmpnum_buf, values->@(0)); @@ -1581,7 +1604,9 @@ U0 @render_node_text(@html_dom_node* node, HtmlRenderer* renderer) fragment_widget->ctx->line(fragment_widget->ctx->width - 1, 0, fragment_widget->ctx->width - 1, fragment_widget->ctx->height - 1, fragment_bounding_box_color); } - fragment_widget->width = fragment_widget->ctx->width; + // FIXME: We use node->resolvedMargin.right.value as a dumb hack to add spacing between + // list item numbers/bullets and adjacent text fragments, for now. + fragment_widget->width = fragment_widget->ctx->width + node->resolvedMargin.right.value; fragment_widget->height = fragment_widget->ctx->height; fragment_widget->fast_copy = TRUE; } @@ -1647,6 +1672,8 @@ U0 @handle_tag_specific_functions(@html_dom_node* node, HtmlRenderer* renderer) } if (!StrICmp(node->tagName, "li")) { + // FIXME: We use node->resolvedMargin.right.value as a dumb hack to add spacing between + // list item numbers/bullets and adjacent text fragments, for now. @html_dom_node* prepend_text_node = NULL; if (@self_or_ancestor_matches_tag_name(node, "ol")) { I64 ordered_list_index = @self_or_ancestor_matches_tag_name(node, "ol")->attributes->@("orderedListIndex") + 1; @@ -1655,11 +1682,13 @@ U0 @handle_tag_specific_functions(@html_dom_node* node, HtmlRenderer* renderer) prepend_text_node->parentNode = node; prepend_text_node->text = CAlloc(16, renderer->task); StrPrint(prepend_text_node->text, "%d.", ordered_list_index); + prepend_text_node->resolvedMargin.right.value = 8; node->children->prepend(prepend_text_node); } else if (@self_or_ancestor_matches_tag_name(node, "ul")) { prepend_text_node = @create_new_node("InternalTextNode", renderer->task); prepend_text_node->parentNode = node; prepend_text_node->text = StrNew("\xe2\x80\xa2", renderer->task); + prepend_text_node->resolvedMargin.right.value = 8; node->children->prepend(prepend_text_node); } } @@ -1693,14 +1722,15 @@ U0 @render_internal_text_node(@html_dom_node* node, HtmlRenderer* renderer) U0 @set_bordered_rect_from_resolved_node(@html_dom_node* node, BorderedRectWidget* widget) { - if (!node || !widget || !node->width || !node->height) + if (!node || !widget) return; + if (node->widthDistanceType == CSS_DISTANCE_PIXELS) { + widget->width = node->width; + } + if (node->heightDistanceType == CSS_DISTANCE_PIXELS) { + widget->height = node->height; + } - if (node->widthDistanceType != CSS_DISTANCE_PIXELS || node->heightDistanceType != CSS_DISTANCE_PIXELS) - return; - - widget->width = node->width; - widget->height = node->height; widget->color = node->backgroundColor; if (node->border.top.value) { @@ -1814,187 +1844,6 @@ render_node_return: --renderer->indent; } -U0 @reflow_push_state(HtmlRenderer* renderer, I32 x1, I32 x2, I32 y1, Widget* parent) -{ - ++renderer->state_index; - @renderer_reflow_state* state = &renderer->state[renderer->state_index]; - state->x1 = x1; - state->x2 = x2; - state->y1 = y1; - state->parent = parent; -} - -U0 @reflow_pop_state(HtmlRenderer* renderer, I32* x1, I32* x2, I32* y1, U64* parent) -{ - @renderer_reflow_state* state = &renderer->state[renderer->state_index]; - if (x1) - *x1 = state->x1; - if (x2) - *x2 = state->x2; - if (y1) - *y1 = state->y1; - if (parent) - *parent = state->parent; - --renderer->state_index; -} - -Bool @reflow_widget_is_closing_tag(Widget* widget) -{ - if (!widget) - return FALSE; - return (widget->width == -0xbeef && widget->height == -0xcafe); -} - -I64 @reflow_cumulative_width(@window_widgets_list* wl_item, I64 line_break_width) -{ - @html_dom_node* node = NULL; - I64 width = wl_item->widget->width; - wl_item = wl_item->next; - while (wl_item && wl_item->widget && wl_item->widget->data && width + wl_item->widget->width < line_break_width) { - node = wl_item->widget->data; - if (node->display == CSS_DISPLAY_BLOCK || !StrICmp(node->tagName, "br") || @reflow_widget_is_closing_tag(wl_item->widget)) { - wl_item = NULL; - } else { - width += wl_item->widget->width; - wl_item = wl_item->next; - } - } - return width; -} - -U0 @reflow_node_list(HtmlRenderer* renderer) -{ - if (!renderer || !renderer->win || !renderer->widgets_base) - return; - - renderer->state_index = -1; - - I64 x1 = 0; - I64 y1 = 0; - I64 x2 = renderer->win->width; - I64 indent_x = 0; - Widget* parent = NULL; - Widget* widget = NULL; - - @reflow_push_state(renderer, x1, x2, y1, parent); - - renderer->render_x = x1; - renderer->render_y = y1; - renderer->max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; - - @html_dom_node* node = NULL; - @window_widgets_list* wl_item = renderer->widgets_base->next; - - VerticalScrollBarWidget* vscroll = renderer->vertical_scroll_widget; - I64 origin_y = renderer->background_widget->y; - I64 offset_y = origin_y; - - I64 minimum_vscroll_value = 0; - if (vscroll && vscroll->max && vscroll->value) { - minimum_vscroll_value = ToI64((vscroll->max * 1.0) / (vscroll->height * 1.0) * (16 * 1.0)); - offset_y -= vscroll->value - minimum_vscroll_value; - } - - while (wl_item) { - widget = wl_item->widget; - if (!widget) - goto reflow_next_wl_item; - - node = widget->data; - if (!node) - goto reflow_next_wl_item; - - switch (node->display) { - case CSS_DISPLAY_BLOCK: - case CSS_DISPLAY_INLINE_BLOCK: - if (@reflow_widget_is_closing_tag(widget)) { - if (node->display == CSS_DISPLAY_INLINE_BLOCK) - renderer->render_x = x2; - @reflow_pop_state(renderer, &x1, &x2, &y1, &parent); - if (node->display == CSS_DISPLAY_BLOCK) { - renderer->render_x = x1; - renderer->render_y += renderer->max_line_height; - renderer->max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; - while (renderer->render_y < y1) { - ++renderer->render_y; - } - } - } else { - widget->x = renderer->render_x; - widget->y = renderer->render_y + offset_y; - @reflow_push_state(renderer, x1, x2, renderer->render_y + widget->height, parent); - parent = widget; - if (node->display == CSS_DISPLAY_INLINE_BLOCK) { - x1 = renderer->render_x; - } - if (node->display == CSS_DISPLAY_BLOCK) { - if (renderer->render_x > x1) { - renderer->render_y += renderer->max_line_height; - } - renderer->render_x = x1; - renderer->max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; - } - if (widget->width > 0) { - x2 = widget->x + widget->width; - } - } - goto reflow_next_wl_item; - default: - break; - } - - if (!StrICmp(node->tagName, "br")) { - renderer->render_x = x1; - renderer->render_y += renderer->max_line_height; - renderer->max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; - goto reflow_next_wl_item; - } - - if (renderer->render_x == x1 && node->textAlign) { - indent_x = @reflow_cumulative_width(wl_item, x2 - x1); - switch (node->textAlign) { - case CSS_TEXT_ALIGN_CENTER: - renderer->render_x = ((x2 - x1) / 2) - (indent_x / 2); - break; - case CSS_TEXT_ALIGN_RIGHT: - renderer->render_x = -8 + x2 - indent_x; - break; - default: - // Invalid node->textAlign value; default to text-align: left; - break; - } - } - - if (renderer->render_x > x2 - widget->width) { - renderer->render_x = x1; - renderer->render_y += renderer->max_line_height; - renderer->max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; - } - - widget->x = renderer->render_x; - widget->y = renderer->render_y + offset_y; - - if (@self_or_ancestor_matches_tag_name(node, "a")) { - widget->pointer = renderer->link_pointer; - Gui.Widget.SetCallback(widget, "clicked", renderer->link_callback); - } - - renderer->render_x += widget->width; - renderer->max_line_height = Max(renderer->max_line_height, widget->height); - - reflow_next_wl_item: - wl_item = wl_item->next; - } - - if (renderer->render_x) { - renderer->render_x = x1; - renderer->render_y += renderer->max_line_height; - renderer->max_line_height = RENDERER_DEFAULT_MAX_LINE_HEIGHT; - } - - renderer->calculated_page_height = renderer->render_y - origin_y; -} - U0 @process_css_rules_from_external_stylesheet(HtmlRenderer* renderer, U8* str) { // download (or load from cache) and process stylesheet @@ -2285,7 +2134,9 @@ U0 @fetch_images_for_page(HtmlRenderer* renderer) widget->height = widget->ctx->height; } - @reflow_node_list(renderer); + if (renderer->image_load_callback) { + renderer->image_load_callback(renderer); + } @fetch_next_image : image_list_item = image_list_item->next; } diff --git a/System/MakeSystem.HC b/System/MakeSystem.HC index 69569a5..8b6a0dd 100644 --- a/System/MakeSystem.HC +++ b/System/MakeSystem.HC @@ -85,13 +85,13 @@ JsonObject* config = Json.Parse(FileRead("M:/Settings/config.json"), erythros_me #include "Libraries/Widget"; #include "Libraries/Theme"; - @http_init_tmp_and_cache_directories; #include "Libraries/Css/Tokenizer"; #include "Libraries/Html/Tokenizer"; #include "Libraries/Html/Renderer"; +#include "Libraries/Html/Reflow"; "}\n"; load_elf("M:/build/bin/net");