Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server rendering #18

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion js/lib-node/batch-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { setTimeout } from 'timers/promises';
export class BatchState {
constructor(fps, opts) {
this.fps = fps || 30;
this.videoTimeOffset = opts?.videoTimeOffset || 0;
this.currentFrame = 0;
this.initialStateApplied = false;

Expand All @@ -20,7 +21,7 @@ export class BatchState {
}

getVideoTime() {
return this.currentFrame / this.fps;
return this.videoTimeOffset + this.currentFrame / this.fps;
}

applyNextWebVttCueIfNeeded() {
Expand Down
12 changes: 10 additions & 2 deletions js/src/comp-backing-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,8 @@ class TextNode extends StyledNodeBase {

if (oldProps.text !== newProps.text) return true;

if (!deepEqual(oldProps.spans, newProps.spans)) return true;

return false;
}

Expand All @@ -869,15 +871,21 @@ class TextNode extends StyledNodeBase {

this.text = newProps.text || '';

if (this.text.length < 1) {
this.spans = Array.isArray(newProps.spans) ? newProps.spans : null;

if (this.text.length < 1 && !this.spans) {
this.attrStringDesc = null;
} else {
if (!this.container) {
console.error('** container missing for text node %s', this.uuid);
return;
}
if (this.spans) {
this.joinedSpansText = this.spans.map((span) => span.string).join(' ');
}

this.attrStringDesc = makeAttributedStringDesc(
this.text,
this.spans || [{ string: this.text }],
this.style || {},
this.container.viewportSize,
this.container.pixelsPerGridUnit
Expand Down
5 changes: 4 additions & 1 deletion js/src/loader-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ export function makeVCSRootContainer(
console.error(msg);
}

static getDerivedStateFromError(_error) {
static getDerivedStateFromError(error) {
if (errorCb) {
errorCb(error);
}
return { hasError: true };
}

Expand Down
34 changes: 26 additions & 8 deletions js/src/react/components/Text.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import * as React from 'react';

export function Text(props) {
const text = Array.isArray(props.children)
? props.children.join(' ')
: props.children || '';

// fontSize is mandatory, so add a default if not present
let style = props.style || {};
if (!style.fontSize_gu && !style.fontSize_px && !style.fontSize_vh) {
Expand All @@ -14,14 +10,36 @@ export function Text(props) {
};
}

// can't use JSX in VCS core because it needs to run on Node without transpiling
return React.createElement('label', {
const intrinsicProps = {
id: props.id,
layout: props.layout,
style,
transform: props.transform || {},
clip: props.clip || false,
blend: props.blend || {},
text,
});
};

if (Array.isArray(props.children)) {
// an array of spans.
// these can be either plain strings or tuples containing a string and its style override, e.g.:
// ["Hello world", {textColor: 'yellow'}]
intrinsicProps.spans = props.children.map((c) => {
let string = '';
let styleOverride = {};
if (Array.isArray(c)) {
const n = c.length;
if (n > 0) string = '' + c[0];
if (n > 1 && typeof c[1] === 'object') styleOverride = c[1];
} else {
string = '' + c;
}
return { string, style: styleOverride };
});
} else {
// a single text string
intrinsicProps.text = props.children || '';
}

// can't use JSX in VCS core because it needs to run on Node without transpiling
return React.createElement('label', intrinsicProps);
}
27 changes: 20 additions & 7 deletions js/src/render/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ function recurseRenderNode(
let strokeW_px;
let srcDrawable;
let scaleMode = node.scaleMode;
let textContent = node.text;
let textContent = node.text || node.joinedSpansText;
let textStyle = node.style;

let warningOutText;
Expand Down Expand Up @@ -475,7 +475,7 @@ function recurseRenderNode(
warningFrame.y += 2;
const lines = warningOutText.split('\n');
for (const line of lines) {
drawStyledText(ctx, line, warningStyle, warningFrame, comp);
drawStyledText(ctx, line, {}, warningStyle, warningFrame, comp);
warningFrame.y += 20;
}
}
Expand Down Expand Up @@ -554,6 +554,10 @@ function drawStyledTextLayoutBlocks(
) {
let { x, y } = frame;

let emojiOffset = Number.isFinite(fontMetrics?.baseline)
? fontMetrics.baseline
: 0;

for (const paragraphLinesArr of blocks) {
for (const lineDesc of paragraphLinesArr) {
const { box, string, runs } = lineDesc;
Expand All @@ -571,10 +575,20 @@ function drawStyledTextLayoutBlocks(
h: box.height,
};

const runStyle = {
...style,
};
const attrs = run.attributes;
if (attrs.color) runStyle.textColor = attrs.color;
if (attrs.fontSize) {
runStyle.fontSize_gu = null;
runStyle.fontSize_vh = null;
runStyle.fontSize_px = attrs.fontSize;
}

// unicode object substitution indicates emojis.
// we assume it's at position 0 because embedEmojis() creates such runs
if (chunk.indexOf(String.fromCharCode(0xfffc)) === 0) {
const attrs = run.attributes;
const emoji = attrs?.attachment?.emoji;
if (!emoji) {
console.warn(
Expand All @@ -587,13 +601,13 @@ function drawStyledTextLayoutBlocks(
ctx,
emoji,
Math.round(textFrame.x),
Math.round(textFrame.y + yOffset + (fontMetrics?.baseline || 0)),
Math.round(textFrame.y + yOffset + emojiOffset),
width,
height
);
}
} else {
drawStyledText(ctx, chunk, fontMetrics, style, textFrame, comp);
drawStyledText(ctx, chunk, fontMetrics, runStyle, textFrame, comp);
}

//console.log('run %s - ', chunk, run);
Expand Down Expand Up @@ -663,7 +677,7 @@ function drawStyledText(ctx, text, fontMetrics, style, frame, comp) {
}

let fontBaseline = fontMetrics?.baseline;
if (fontBaseline == null) {
if (!Number.isFinite(fontBaseline)) {
fontBaseline = fontSize_px * 0.8;
}

Expand All @@ -682,7 +696,6 @@ function drawStyledText(ctx, text, fontMetrics, style, frame, comp) {
ctx.strokeText(text, textX, textY);
}
}

ctx.fillText(text, textX, textY);
}

Expand Down
36 changes: 29 additions & 7 deletions js/src/text/attributed-string.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function computeStyleAttributes(styledObj, viewport, pxPerGu) {
let textAlign = styledObj.textAlign || 'left';

let color, opacity, shadow;
color = Array.isArray(styledObj.color) ? styledObj.color : [];
color = styledObj.textColor;
opacity = styledObj.opacity;
shadow = styledObj.shadow || {};

Expand All @@ -74,7 +74,7 @@ function computeStyleAttributes(styledObj, viewport, pxPerGu) {
// returned 'fragments' array is the pieces needed to construct a Fontkit AttributedString object.
// we're returning also the font object, since our attributed strings are currently single-style
// (no inline fonts in fragments, yet).
export function makeAttributedStringDesc(string, styledObj, viewport, pxPerGu) {
export function makeAttributedStringDesc(spans, styledObj, viewport, pxPerGu) {
let fragments = [];

const {
Expand All @@ -84,6 +84,7 @@ export function makeAttributedStringDesc(string, styledObj, viewport, pxPerGu) {
fontWeight,
fontStyle,
textAlign,
color,
} = computeStyleAttributes(styledObj, viewport, pxPerGu);

let font;
Expand All @@ -108,17 +109,33 @@ export function makeAttributedStringDesc(string, styledObj, viewport, pxPerGu) {
fontSize: size_px,
lineHeight: lineHeight_px,
align: textAlign,
color,
};
/*
other attributes available include:
- paragraphSpacing
- indent
*/

fragments.push({
string,
attributes,
});
for (const span of spans) {
const { string, style: overrideStyle } = span;
let spanAttrs = attributes;
if (overrideStyle) {
const overrideAttrs = computeStyleAttributes(
{ ...styledObj, ...overrideStyle },
viewport,
pxPerGu
);
spanAttrs = { ...attributes };
if (overrideAttrs.color) spanAttrs.color = overrideAttrs.color;
if (overrideAttrs.size_px) spanAttrs.fontSize = overrideAttrs.size_px;
// TODO: add more supported attributes
}
fragments.push({
string,
attributes: spanAttrs,
});
}

fragments = embedEmojis(fragments);

Expand Down Expand Up @@ -149,9 +166,14 @@ function calculateBaseline(font, fontSize) {
}

const upm = font.head.unitsPerEm;

const ascender = hheaTable.ascent; // this value seems more correct than 'os2Table.typoAscender'
//const descender = os2Table.typoDescender;
const capHeight = os2Table.capHeight;

const capHeight = Number.isFinite(os2Table.capHeight)
? os2Table.capHeight
: ascender * 0.8; // some old fonts may not have this value, so take a guess if we must

const lineGap = hheaTable.lineGap || 0;

const scale = fontSize / upm;
Expand Down
29 changes: 29 additions & 0 deletions js/src/text/standard-fonts.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,18 @@ const Teko = [

const NotoColorEmoji = [{ fileName: 'NotoColorEmoji-Regular.ttf' }];

// -- fonts added for Bolt Foundry ---

const BebasNeue = [{ fileName: 'BebasNeue-Bold.ttf', fontWeight: 700 }];

const Futura = [{ fileName: 'Futura-Bold.ttf', fontWeight: 700 }];

const KC = [{ fileName: 'KCIllHand-Regular.ttf', fontWeight: 400 }];

const Lovelo = [{ fileName: 'Lovelo-Bold.otf', fontWeight: 700 }];

// ---

export const defaultFontFamilyName = 'Roboto';
export const emojiFontFamilyName = 'NotoColorEmoji';

Expand Down Expand Up @@ -208,4 +220,21 @@ export const standardFontFamilies = [
family: emojiFontFamilyName,
variants: NotoColorEmoji,
},
// --- added for Bolt Foundry ---
{
family: 'BebasNeue',
variants: BebasNeue,
},
{
family: 'Futura',
variants: Futura,
},
{
family: 'KCIllHand',
variants: KC,
},
{
family: 'Lovelo',
variants: Lovelo,
},
];
Loading