From 8c23c46db1872d8ff8709fd2bd9ff7561f529748 Mon Sep 17 00:00:00 2001 From: Caleb Hearon Date: Tue, 19 Nov 2024 21:47:26 -0500 Subject: [PATCH] ability to change the origin style (parent of ) 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 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). --- src/api.ts | 4 ++-- src/dom.ts | 6 +++--- src/style.ts | 22 ++++++++++++++++++++-- test/cascade.spec.js | 34 +++++++++++++++++----------------- test/font.spec.js | 4 ++-- 5 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/api.ts b/src/api.ts index 70a24a6..10092f1 100644 --- a/src/api.ts +++ b/src/api.ts @@ -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'; @@ -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().' diff --git a/src/dom.ts b/src/dom.ts index 32951f4..dc6a82a 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -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 { @@ -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; } @@ -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; diff --git a/src/style.ts b/src/style.ts index a8b3a5d..03786b1 100644 --- a/src/style.ts +++ b/src/style.ts @@ -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 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) { + originStyle = new Style({...initialPlainStyle, ...style}); +} type InheritedStyleDefinitions = {[K in keyof DeclaredStyleProperties]: boolean}; @@ -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); diff --git a/test/cascade.spec.js b/test/cascade.spec.js index 154ef5c..a853d47 100644 --- a/test/cascade.spec.js +++ b/test/cascade.spec.js @@ -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, @@ -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: '%'}, @@ -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); @@ -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', @@ -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, @@ -94,7 +94,7 @@ 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); @@ -102,7 +102,7 @@ describe('CSS Style', function () { 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); @@ -110,7 +110,7 @@ describe('CSS Style', function () { 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); @@ -118,7 +118,7 @@ describe('CSS Style', function () { 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); @@ -126,7 +126,7 @@ describe('CSS Style', function () { 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); @@ -134,7 +134,7 @@ describe('CSS Style', function () { 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}); @@ -142,20 +142,20 @@ describe('CSS Style', function () { 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); @@ -163,7 +163,7 @@ describe('CSS Style', function () { 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); diff --git a/test/font.spec.js b/test/font.spec.js index 96e1492..a6bb80d 100644 --- a/test/font.spec.js +++ b/test/font.spec.js @@ -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 () {