Skip to content

Commit

Permalink
ability to change the origin style (parent of <html>)
Browse files Browse the repository at this point in the history
I want to invisibly set the zoom to the devicePixelRatio in the
demo site, but still allow people to see the effect of zoom on
<html> as they would in a normal browser.

I don't think this will be useful for other purposes, and it
effectively clears the style cache, so not documenting it.

---

I've been thinking about how to optimize the use of the zoom
property for devicePixelRatio for more than a month now. The way
the API is now, you have to regenerate both a DOM and a layout
tree if you want to change it. This could be bad for CellEngine
since it can retain tens of thousands of layouts.

I seriously considered having an element's style list be mutable.
I mostly figured out how Firefox tracks style damage, restyle
roots, lazy frame construction, etc. Avoiding recreating the DOM
and box tree when you want to change styles would lower GC
pressure and reduce CPU time. However, the CPU doesn't get off
that easily since layout is far more expensive than DOM and box
generation combined. It would also be a big API change because
I would have to move generate and style calc into layout.

Ultimatley I decided this isn't a problem to fix here because:
1. Server-side doesn't benefit from mutable DOM, and that's the
   primary focus for dropflow
2. CellEngine could use zoom: 1 for measurement and, when DPR
   changes, only regenerate in-viewport layouts (mark the rest
   dirty). Also worth noting is that CellEngine doesn't even need
   zoom since it only renders text. Who knows if it ever will.
3. CellEngine should probably not be retaining that many layouts.
   It would be better to page them in and out, only creating all
   of them when needed (measuring).
  • Loading branch information
chearon committed Nov 20, 2024
1 parent 5c9c18e commit 8c23c46
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {HTMLElement, TextNode} from './dom.js';
import {DeclaredStyle, initialStyle, computeElementStyle} from './style.js';
import {DeclaredStyle, getOriginStyle, computeElementStyle} from './style.js';
import {registerFont, unregisterFont, getFontUrls, RegisterFontOptions} from './text-font.js';
import {generateBlockContainer, layoutBlockBox, BlockFormattingContext, BlockContainer} from './layout-flow.js';
import HtmlPaintBackend from './paint-html.js';
Expand All @@ -18,7 +18,7 @@ export {createDeclaredStyle as style} from './style.js';
export {registerFont, unregisterFont};

export function generate(rootElement: HTMLElement): BlockContainer {
if (rootElement.style === initialStyle) {
if (rootElement.style === getOriginStyle()) {
throw new Error(
'To use the hyperscript API, pass the element tree to dom() and use ' +
'the return value as the argument to generate().'
Expand Down
6 changes: 3 additions & 3 deletions src/dom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Box} from './layout-box.js';
import {loggableText} from './util.js';
import {Style, DeclaredStyle, initialStyle, EMPTY_STYLE} from './style.js';
import {Style, DeclaredStyle, getOriginStyle, EMPTY_STYLE} from './style.js';
import {query, queryAll, Adapter} from './style-query.js';

export class TextNode {
Expand All @@ -11,7 +11,7 @@ export class TextNode {

constructor(id: string, text: string, parent: HTMLElement | null = null) {
this.id = id;
this.style = initialStyle;
this.style = getOriginStyle();
this.text = text;
this.parent = parent;
}
Expand Down Expand Up @@ -40,7 +40,7 @@ export class HTMLElement {
) {
this.id = id;
this.tagName = tagName;
this.style = initialStyle;
this.style = getOriginStyle();
this.declaredStyle = declaredStyle;
this.parent = parent;
this.attrs = attrs;
Expand Down
22 changes: 20 additions & 2 deletions src/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,25 @@ const initialPlainStyle: ComputedStyle = Object.freeze({
overflow: 'visible'
});

export const initialStyle = new Style(initialPlainStyle);
let originStyle = new Style(initialPlainStyle);

export function getOriginStyle() {
return originStyle;
}

/**
* Set the style that the <html> style inherits from
*
* Be careful calling this. It makes the inheritance style cache useless for any
* styles created after calling it. Using it incorrectly can hurt performance.
*
* Currently the only legitimately known usage is to set the zoom to a desired
* CSS-to-device pixel density (devicePixelRatio). As such, it should only be
* called when devicePixelRatio actually changes.
*/
export function setOriginStyle(style: Partial<ComputedStyle>) {
originStyle = new Style({...initialPlainStyle, ...style});
}

type InheritedStyleDefinitions = {[K in keyof DeclaredStyleProperties]: boolean};

Expand Down Expand Up @@ -964,7 +982,7 @@ export function computeElementStyle(el: HTMLElement | TextNode) {
el.style = createStyle(el.parent!.style, EMPTY_STYLE);
} else {
const styles = el.getDeclaredStyles();
const parentStyle = el.parent ? el.parent.style : initialStyle;
const parentStyle = el.parent ? el.parent.style : originStyle;
const uaDeclaredStyle = uaDeclaredStyles[el.tagName];
if (uaDeclaredStyle) styles.push(uaDeclaredStyle);
if (!el.parent) styles.push(rootDeclaredStyle);
Expand Down
34 changes: 17 additions & 17 deletions test/cascade.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//@ts-check

import {createStyle, createDeclaredStyle, initialStyle, inherited, initial} from '../src/style.js';
import {createStyle, createDeclaredStyle, getOriginStyle, inherited, initial} from '../src/style.js';
import {BlockContainer} from '../src/layout-flow.js';
import {BoxArea} from '../src/layout-box.js';
import {expect} from 'chai';

describe('CSS Style', function () {
it('calculates used value for border width', function () {
const style = createStyle(initialStyle, createDeclaredStyle({
const style = createStyle(getOriginStyle(), createDeclaredStyle({
borderTopWidth: 1,
borderTopStyle: 'none',
borderRightWidth: 1,
Expand All @@ -28,7 +28,7 @@ describe('CSS Style', function () {
});

it('calculates used values for percentages', function () {
const style = createStyle(initialStyle, createDeclaredStyle({
const style = createStyle(getOriginStyle(), createDeclaredStyle({
paddingTop: {value: 50, unit: '%'},
paddingRight: {value: 50, unit: '%'},
paddingBottom: {value: 50, unit: '%'},
Expand All @@ -42,7 +42,7 @@ describe('CSS Style', function () {
}));

const documentElement = new BlockContainer(
createStyle(initialStyle, createDeclaredStyle({width: 100, height: 200})), [], 0
createStyle(getOriginStyle(), createDeclaredStyle({width: 100, height: 200})), [], 0
);
const box = new BlockContainer(style, [], 0);
box.containingBlock = new BoxArea(documentElement, 0, 0, 100, 200);
Expand All @@ -60,7 +60,7 @@ describe('CSS Style', function () {
});

it('normalizes border-box to content-box', function () {
const style = createStyle(initialStyle, createDeclaredStyle({
const style = createStyle(getOriginStyle(), createDeclaredStyle({
width: 100,
borderLeftWidth: 10,
borderLeftStyle: 'solid',
Expand All @@ -77,7 +77,7 @@ describe('CSS Style', function () {
});

it('normalizes padding-box to content-box', function () {
const style = createStyle(initialStyle, createDeclaredStyle({
const style = createStyle(getOriginStyle(), createDeclaredStyle({
width: 100,
borderLeftWidth: 10,
borderRightWidth: 10,
Expand All @@ -94,76 +94,76 @@ describe('CSS Style', function () {

it('computes unitless line-height', function () {
const parentDeclared = createDeclaredStyle({fontSize: 10});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({lineHeight: {value: 2, unit: null}});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.lineHeight).to.deep.equal(20);
});

it('computes line-height as a percentage', function () {
const parentDeclared = createDeclaredStyle({fontSize: 50});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({lineHeight: {value: 50, unit: '%'}});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.lineHeight).to.equal(25);
});

it('computes font-size as a percentage', function () {
const parentDeclared = createDeclaredStyle({fontSize: 50});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({fontSize: {value: 50, unit: '%'}});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.fontSize).to.equal(25);
});

it('computes font-weight: bolder', function () {
const parentDeclared = createDeclaredStyle({fontWeight: 400});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({fontWeight: 'bolder'});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.fontWeight).to.equal(700);
});

it('computes font-weight: lighter', function () {
const parentDeclared = createDeclaredStyle({fontWeight: 400});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({fontWeight: 'lighter'});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.fontWeight).to.equal(100);
});

it('supports the inherit keyword', function () {
const parentDeclared = createDeclaredStyle({backgroundColor: {r: 200, g: 200, b: 200, a: 1}});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({backgroundColor: inherited});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.backgroundColor).to.deep.equal({r: 200, g: 200, b: 200, a: 1});
});

it('supports the initial keyword', function () {
const parentDeclared = createDeclaredStyle({color: {r: 200, g: 200, b: 200, a: 1}});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({color: initial});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.color).to.deep.equal(initialStyle.color);
expect(childComputed.color).to.deep.equal(getOriginStyle().color);
});

it('defaultifies correctly if the style has a zero', function () {
const style = createDeclaredStyle({width: 0});
expect(createStyle(initialStyle, style).width).to.equal(0);
expect(createStyle(getOriginStyle(), style).width).to.equal(0);
});

it('resolves em on the element itself', function () {
const parentDeclared = createDeclaredStyle({fontSize: 16});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({fontSize: 64, marginTop: {value: 1, unit: 'em'}});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.marginTop).to.equal(64);
});

it('resolves em on the parent when font-size is used', function () {
const parentDeclared = createDeclaredStyle({fontSize: 16});
const parentComputed = createStyle(initialStyle, parentDeclared);
const parentComputed = createStyle(getOriginStyle(), parentDeclared);
const childDeclared = createDeclaredStyle({fontSize: {value: 2, unit: 'em'}});
const childComputed = createStyle(parentComputed, childDeclared);
expect(childComputed.fontSize).to.equal(32);
Expand Down
4 changes: 2 additions & 2 deletions test/font.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import {expect} from 'chai';
import * as oflo from '../src/api-with-parse.js';
import {registerFontAsset, unregisterFontAsset} from '../assets/register.js';
import {getCascade} from '../src/text-font.js';
import {initialStyle, createStyle, createDeclaredStyle} from '../src/style.js';
import {getOriginStyle, createStyle, createDeclaredStyle} from '../src/style.js';

/** @param {import("../src/style.js").DeclaredStyleProperties} style */
function style(style) {
return createStyle(initialStyle, createDeclaredStyle(style));
return createStyle(getOriginStyle(), createDeclaredStyle(style));
}

describe('Font Registration and Matching', function () {
Expand Down

0 comments on commit 8c23c46

Please sign in to comment.