Skip to content

Commit

Permalink
perf: precalculate used values on the Style
Browse files Browse the repository at this point in the history
diverging it from computed styles even more

For the zoom property, which multiplies every length by a zoom
factor, I tested out continuing on the current path, which involved
a function for each of the affected properties. It was
significantly slower (perf-2 got 10ms slower pretty consistently).

I decided it's better to pre-calculate used values immediately.
These aren't fully used values per the spec: for example, a
percentage `width` will still be a percentage `width`, but they
are reduced as much as they can be. Since the style tree is far
smaller than the document tree, this is affordable.

I tried a hundred re-parses of the document in the demo site and
Style only took around 17ms. The difference in practice for
hyperscript users will be even more negligible.
  • Loading branch information
chearon committed Oct 18, 2024
1 parent 68478a5 commit 30f2bc2
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 18 deletions.
5 changes: 2 additions & 3 deletions src/layout-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,12 +322,11 @@ export function getMetrics(style: Style, match: FaceMatch): InlineMetrics {
let metrics = metricsCache.get(style)?.get(match.face);
if (metrics) return metrics;
const fontSize = style.fontSize;
const cssLineHeight = style.getLineHeight();
// now do CSS2 §10.8.1
const {ascender, xHeight, descender, lineGap} = match.font.getMetrics('ltr'); // TODO vertical text
const toPx = 1 / match.face.upem * fontSize;
const pxHeight = (ascender - descender) * toPx;
const lineHeight = cssLineHeight === 'normal' ? pxHeight + lineGap * toPx : cssLineHeight;
const lineHeight = style.lineHeight === 'normal' ? pxHeight + lineGap * toPx : style.lineHeight;
const halfLeading = (lineHeight - pxHeight) / 2;
const ascenderPx = ascender * toPx;
const descenderPx = -descender * toPx;
Expand Down Expand Up @@ -1071,7 +1070,7 @@ function inlineBlockBaselineStep(parent: Inline, block: BlockContainer) {
}

if (typeof block.style.verticalAlign === 'object') {
const lineHeight = block.style.getLineHeight();
const lineHeight = block.style.lineHeight;
if (lineHeight === 'normal') {
// TODO: is there a better/faster way to do this? currently struts only
// exist if there is a paragraph, but I think spec is saying do this
Expand Down
36 changes: 23 additions & 13 deletions src/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ function percentGtZero(cssVal: number | {value: number, unit: '%'}) {
}

export class Style {
computed: ComputedStyle;

whiteSpace: WhiteSpace;
color: Color;
fontSize: number;
Expand All @@ -309,7 +311,7 @@ export class Style {
fontStyle: FontStyle;
fontStretch: FontStretch;
fontFamily: string[];
lineHeight: 'normal' | number | {value: number, unit: null};
lineHeight: 'normal' | number;
verticalAlign: VerticalAlign;
backgroundColor: Color;
backgroundClip: BackgroundClip;
Expand Down Expand Up @@ -353,7 +355,20 @@ export class Style {
overflowWrap: 'anywhere' | 'break-word' | 'normal';
overflow: 'visible' | 'hidden';

// This section reduces to used values as much as possible
// Be careful accessing off of "this" since these are called in the ctor

private usedLineHeight(style: ComputedStyle) {
if (typeof style.lineHeight === 'object') {
return style.lineHeight.value * this.fontSize;
} else {
return style.lineHeight;
}
}

constructor(style: ComputedStyle) {
this.computed = style;

this.whiteSpace = style.whiteSpace;
this.color = style.color;
this.fontSize = style.fontSize;
Expand All @@ -362,7 +377,7 @@ export class Style {
this.fontStyle = style.fontStyle;
this.fontStretch = style.fontStretch;
this.fontFamily = style.fontFamily;
this.lineHeight = style.lineHeight;
this.lineHeight = this.usedLineHeight(style);
this.verticalAlign = style.verticalAlign;
this.backgroundColor = style.backgroundColor;
this.backgroundClip = style.backgroundClip;
Expand Down Expand Up @@ -407,11 +422,6 @@ export class Style {
this.overflow = style.overflow;
}

getLineHeight() {
if (typeof this.lineHeight === 'object') return this.lineHeight.value * this.fontSize;
return this.lineHeight;
}

getTextAlign() {
if (this.textAlign === 'start') {
if (this.direction === 'ltr') {
Expand Down Expand Up @@ -802,7 +812,7 @@ function defaultifyStyle(parentStyle: Style, style: CascadedStyle) {
for (const _ in initialPlainStyle) {
const p = _ as keyof typeof initialPlainStyle;
if (style[p] === inherited || !(p in style) && inheritedStyle[p]) {
ret[p] = parentStyle[p];
ret[p] = parentStyle.computed[p];
} else if (style[p] === initial || !(p in style) && !inheritedStyle[p]) {
ret[p] = initialPlainStyle[p];
} else {
Expand All @@ -819,9 +829,9 @@ function computeStyle(parentStyle: Style, style: SpecifiedStyle) {
// Compute fontSize first since em values depend on it
if (typeof style.fontSize === 'object') {
if (style.fontSize.unit === '%') {
computed.fontSize = parentStyle.fontSize * style.fontSize.value / 100;
computed.fontSize = parentStyle.computed.fontSize * style.fontSize.value / 100;
} else {
computed.fontSize = parentStyle.fontSize * style.fontSize.value;
computed.fontSize = parentStyle.computed.fontSize * style.fontSize.value;
}
} else {
computed.fontSize = style.fontSize;
Expand All @@ -847,9 +857,9 @@ function computeStyle(parentStyle: Style, style: SpecifiedStyle) {
// https://www.w3.org/TR/css-fonts-4/#relative-weights
if (style.fontWeight === 'bolder' || style.fontWeight === 'lighter') {
const bolder = style.fontWeight === 'bolder';
const pWeight = parentStyle.fontWeight;
const pWeight = parentStyle.computed.fontWeight;
if (pWeight < 100) {
computed.fontWeight = bolder ? 400 : parentStyle.fontWeight;
computed.fontWeight = bolder ? 400 : parentStyle.computed.fontWeight;
} else if (pWeight >= 100 && pWeight < 350) {
computed.fontWeight = bolder ? 400 : 100;
} else if (pWeight >= 350 && pWeight < 550) {
Expand All @@ -859,7 +869,7 @@ function computeStyle(parentStyle: Style, style: SpecifiedStyle) {
} else if (pWeight >= 750 && pWeight < 900) {
computed.fontWeight = bolder ? 900 : 700;
} else {
computed.fontWeight = bolder ? parentStyle.fontWeight : 700;
computed.fontWeight = bolder ? parentStyle.computed.fontWeight : 700;
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('Hyperscript API', function () {
const h1 = dom(h('div', {style: {fontSize: 99}}, 'abc'));
expect(h1.children[0].style.fontSize).to.equal(99);
const h2 = dom(h('div', {style: {lineHeight: {value: 123, unit: null}}}, [h('div')]));
expect(h2.children[0].style.lineHeight).to.deep.equal({value: 123, unit: null});
expect(h2.children[0].style.lineHeight).to.deep.equal(123 * 16);
});

it('uses the html element', function () {
Expand Down
2 changes: 1 addition & 1 deletion test/cascade.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ describe('CSS Style', function () {
it('computes unitless line-height', function () {
const parentComputed = createStyle(initialStyle, {fontSize: 10});
const childComputed = createStyle(parentComputed, {lineHeight: {value: 2, unit: null}});
expect(childComputed.lineHeight).to.deep.equal({value: 2, unit: null});
expect(childComputed.lineHeight).to.deep.equal(20);
});

it('computes line-height as a percentage', function () {
Expand Down

0 comments on commit 30f2bc2

Please sign in to comment.