diff --git a/README.md b/README.md index 1659455..70cc9b9 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,9 @@ Audio layers will be mixed together. If `cutFrom`/`cutTo` is set, the resulting - `fontPath` - See `defaults.layer.fontPath` - `text` - Title text to show, keep it short - `textColor` - default `#ffffff` -- `position` - Vertical position: `top`, `bottom` or `center` +- `position` - One of either: + - `top`, `bottom` or `center` - vertical position + - An object `{ x, y, originX = 'left', originY = 'top' }`, where `{ x: 0, y: 0 }` is the upper left corner of the screen, and `{ x: 1, y: 1 }` is the lower right corner. `originX` and `originY` are optional, and specify the position origin of the text object. #### Layer type 'subtitle' - `fontPath` - See `defaults.layer.fontPath` diff --git a/examples/subtitle.json5 b/examples/subtitle.json5 index b1d2207..3bd711b 100644 --- a/examples/subtitle.json5 +++ b/examples/subtitle.json5 @@ -9,5 +9,9 @@ { type: 'subtitle', text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.' }, { type: 'title', position: 'top', text: 'Subtitles' }, ] }, + { duration: 2, layers: [ + { type: 'fill-color' }, + { type: 'title', position: { x: 0, y: 1, originY: 'bottom' }, text: 'Custom position' }, + ] }, ], } \ No newline at end of file diff --git a/sources/fabricFrameSource.js b/sources/fabricFrameSource.js index 7466ed1..48e2de5 100644 --- a/sources/fabricFrameSource.js +++ b/sources/fabricFrameSource.js @@ -232,6 +232,32 @@ async function subtitleFrameSource({ width, height, params }) { return { onRender }; } +function getPositionProps({ position, width, height, objHeight }) { + let originY = 'center'; + let originX = 'center'; + let top = height / 2; + let left = width / 2; + + if (position === 'top') { + originY = 'top'; + top = height * objHeight; + } else if (position === 'bottom') { + originY = 'bottom'; + top = height; + } + + if (position.x != null) { + originX = position.originX || 'left'; + left = width * position.x; + } + if (position.y != null) { + originY = position.originY || 'top'; + top = height * position.y; + } + + return { originX, originY, top, left }; +} + async function titleFrameSource({ width, height, params }) { const { text, textColor = '#ffffff', fontFamily = 'sans-serif', position = 'center' } = params; @@ -252,22 +278,15 @@ async function titleFrameSource({ width, height, params }) { width: width * 0.8, }); + // We need the text as an image in order to scale it const textImage = await new Promise((r) => textBox.cloneAsImage(r)); - let originY = 'center'; - let top = height / 2; - if (position === 'top') { - originY = 'top'; - top = height * 0.05; - } else if (position === 'bottom') { - originY = 'bottom'; - top = height; - } + const { left, top, originX, originY } = getPositionProps({ position, width, height, objHeight: 0.05 }); textImage.set({ - originX: 'center', + originX, originY, - left: width / 2, + left, top, scaleX: scale, scaleY: scale,