@remotion/rounded-text-box 4.0.361
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/bundle.ts +15 -0
- package/dist/cjs/create-rounded-text-box.d.ts +19 -0
- package/dist/cjs/create-rounded-text-box.js +205 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +194 -0
- package/dist/cjs/layouts/fill-text-box.d.ts +10 -0
- package/dist/cjs/layouts/fill-text-box.js +63 -0
- package/dist/cjs/layouts/fit-text-on-n-lines.d.ts +19 -0
- package/dist/cjs/layouts/fit-text-on-n-lines.js +67 -0
- package/dist/cjs/layouts/fit-text.d.ts +14 -0
- package/dist/cjs/layouts/fit-text.js +24 -0
- package/dist/cjs/layouts/measure-text.d.ts +23 -0
- package/dist/cjs/layouts/measure-text.js +94 -0
- package/dist/esm/index.mjs +166 -0
- package/eslint.config.mjs +7 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @remotion/rounded-text-box
|
|
2
|
+
|
|
3
|
+
Create a TikTok-like multiline text box SVG path with rounded corners
|
|
4
|
+
|
|
5
|
+
[](https://npmcharts.com/compare/@remotion/rounded-text-box?minimal=true)
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @remotion/rounded-text-box --save-exact
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
When installing a Remotion package, make sure to align the version of all `remotion` and `@remotion/*` packages to the same version.
|
|
14
|
+
Remove the `^` character from the version number to use the exact version.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
See the [documentation](https://www.remotion.dev/docs/rounded-text-box) for more information.
|
package/bundle.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {buildPackage} from '../.monorepo/builder';
|
|
2
|
+
|
|
3
|
+
await buildPackage({
|
|
4
|
+
formats: {
|
|
5
|
+
cjs: 'build',
|
|
6
|
+
esm: 'build',
|
|
7
|
+
},
|
|
8
|
+
external: ['@remotion/layout-utils', '@remotion/paths'],
|
|
9
|
+
entrypoints: [
|
|
10
|
+
{
|
|
11
|
+
path: 'src/index.ts',
|
|
12
|
+
target: 'node',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Dimensions } from '@remotion/layout-utils';
|
|
2
|
+
import type { BoundingBox, ReducedInstruction } from '@remotion/paths';
|
|
3
|
+
export type TextAlign = 'left' | 'center' | 'right';
|
|
4
|
+
export type CreateRoundedTextBoxProps = {
|
|
5
|
+
textMeasurements: Dimensions[];
|
|
6
|
+
textAlign: TextAlign;
|
|
7
|
+
horizontalPadding: number;
|
|
8
|
+
borderRadius: number;
|
|
9
|
+
};
|
|
10
|
+
export type CreateRoundedTextBoxResult = {
|
|
11
|
+
d: string;
|
|
12
|
+
boundingBox: BoundingBox;
|
|
13
|
+
instructions: ReducedInstruction[];
|
|
14
|
+
};
|
|
15
|
+
export declare const createRoundedTextBox: ({ textMeasurements, textAlign, horizontalPadding, borderRadius: unclampedMaxCornerRadius, }: CreateRoundedTextBoxProps) => {
|
|
16
|
+
d: string;
|
|
17
|
+
boundingBox: BoundingBox;
|
|
18
|
+
instructions: ReducedInstruction[];
|
|
19
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createRoundedTextBox = void 0;
|
|
4
|
+
const paths_1 = require("@remotion/paths");
|
|
5
|
+
const clamp = (val, min, max) => {
|
|
6
|
+
return Math.min(Math.max(val, min), max);
|
|
7
|
+
};
|
|
8
|
+
const createRoundedTextBox = ({ textMeasurements, textAlign, horizontalPadding, borderRadius: unclampedMaxCornerRadius, }) => {
|
|
9
|
+
const instructions = [];
|
|
10
|
+
let maxWidth = 0;
|
|
11
|
+
for (const cornerRounding of textMeasurements) {
|
|
12
|
+
maxWidth = Math.max(maxWidth, cornerRounding.width + horizontalPadding * 2);
|
|
13
|
+
}
|
|
14
|
+
let yOffset = 0;
|
|
15
|
+
for (let i = 0; i < textMeasurements.length; i++) {
|
|
16
|
+
const previousLine = textMeasurements[i - 1];
|
|
17
|
+
const currentLine = textMeasurements[i];
|
|
18
|
+
const nextLine = textMeasurements[i + 1];
|
|
19
|
+
let xOffset = 0;
|
|
20
|
+
if (textAlign === 'center') {
|
|
21
|
+
xOffset = (maxWidth - (currentLine.width + horizontalPadding * 2)) / 2;
|
|
22
|
+
}
|
|
23
|
+
else if (textAlign === 'right') {
|
|
24
|
+
xOffset = maxWidth - (currentLine.width + horizontalPadding * 2);
|
|
25
|
+
}
|
|
26
|
+
const maxCornerRadius = clamp(unclampedMaxCornerRadius, 0, currentLine.height / 2);
|
|
27
|
+
if (i === 0) {
|
|
28
|
+
instructions.push({
|
|
29
|
+
type: 'M',
|
|
30
|
+
x: xOffset + maxCornerRadius,
|
|
31
|
+
y: yOffset,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const topRightCornerRadius = clamp(previousLine
|
|
35
|
+
? textAlign === 'right'
|
|
36
|
+
? 0
|
|
37
|
+
: textAlign === 'left'
|
|
38
|
+
? (previousLine.width - currentLine.width) / 2
|
|
39
|
+
: (previousLine.width - currentLine.width) / 4
|
|
40
|
+
: -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
41
|
+
// Top Right Corner
|
|
42
|
+
if (topRightCornerRadius !== 0) {
|
|
43
|
+
instructions.push({
|
|
44
|
+
type: 'L',
|
|
45
|
+
x: xOffset +
|
|
46
|
+
currentLine.width +
|
|
47
|
+
horizontalPadding * 2 +
|
|
48
|
+
topRightCornerRadius,
|
|
49
|
+
y: yOffset,
|
|
50
|
+
});
|
|
51
|
+
// Arc for rounded corner (top right)
|
|
52
|
+
instructions.push({
|
|
53
|
+
type: 'A',
|
|
54
|
+
rx: Math.abs(topRightCornerRadius),
|
|
55
|
+
ry: Math.abs(topRightCornerRadius),
|
|
56
|
+
xAxisRotation: 0,
|
|
57
|
+
largeArcFlag: false,
|
|
58
|
+
sweepFlag: topRightCornerRadius < 0,
|
|
59
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
60
|
+
y: yOffset + Math.abs(topRightCornerRadius),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
instructions.push({
|
|
65
|
+
type: 'L',
|
|
66
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
67
|
+
y: yOffset,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const bottomRightCornerRadius = clamp(nextLine
|
|
71
|
+
? textAlign === 'right'
|
|
72
|
+
? 0
|
|
73
|
+
: textAlign === 'left'
|
|
74
|
+
? (nextLine.width - currentLine.width) / 2
|
|
75
|
+
: (nextLine.width - currentLine.width) / 4
|
|
76
|
+
: -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
77
|
+
// Bottom Right Corner
|
|
78
|
+
if (bottomRightCornerRadius !== 0) {
|
|
79
|
+
instructions.push({
|
|
80
|
+
type: 'L',
|
|
81
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
82
|
+
y: yOffset + currentLine.height - Math.abs(bottomRightCornerRadius),
|
|
83
|
+
});
|
|
84
|
+
// Arc for rounded corner (bottom right)
|
|
85
|
+
instructions.push({
|
|
86
|
+
type: 'A',
|
|
87
|
+
rx: Math.abs(bottomRightCornerRadius),
|
|
88
|
+
ry: Math.abs(bottomRightCornerRadius),
|
|
89
|
+
xAxisRotation: 0,
|
|
90
|
+
largeArcFlag: false,
|
|
91
|
+
sweepFlag: bottomRightCornerRadius < 0,
|
|
92
|
+
x: xOffset +
|
|
93
|
+
currentLine.width +
|
|
94
|
+
horizontalPadding * 2 +
|
|
95
|
+
bottomRightCornerRadius,
|
|
96
|
+
y: yOffset + currentLine.height,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
instructions.push({
|
|
101
|
+
type: 'L',
|
|
102
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
103
|
+
y: yOffset + currentLine.height,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
yOffset += currentLine.height;
|
|
107
|
+
}
|
|
108
|
+
for (let i = textMeasurements.length - 1; i >= 0; i--) {
|
|
109
|
+
const cornerRounding = textMeasurements[i];
|
|
110
|
+
const prevCornerRounding = textMeasurements[i + 1];
|
|
111
|
+
const nextCornerRounding = textMeasurements[i - 1];
|
|
112
|
+
let xOffset = 0;
|
|
113
|
+
if (textAlign === 'center') {
|
|
114
|
+
xOffset = (maxWidth - (cornerRounding.width + horizontalPadding * 2)) / 2;
|
|
115
|
+
}
|
|
116
|
+
else if (textAlign === 'right') {
|
|
117
|
+
xOffset = maxWidth - (cornerRounding.width + horizontalPadding * 2);
|
|
118
|
+
}
|
|
119
|
+
const bottomLeftWidthDifference = prevCornerRounding
|
|
120
|
+
? prevCornerRounding.width - cornerRounding.width
|
|
121
|
+
: -Infinity;
|
|
122
|
+
const maxCornerRadius = clamp(unclampedMaxCornerRadius, 0, cornerRounding.height / 2);
|
|
123
|
+
const bottomLeftCornerRadius = clamp(prevCornerRounding
|
|
124
|
+
? textAlign === 'left'
|
|
125
|
+
? 0
|
|
126
|
+
: textAlign === 'right'
|
|
127
|
+
? bottomLeftWidthDifference / 2
|
|
128
|
+
: bottomLeftWidthDifference / 4
|
|
129
|
+
: -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
130
|
+
// Bottom Left Corner
|
|
131
|
+
if (bottomLeftCornerRadius !== 0) {
|
|
132
|
+
instructions.push({
|
|
133
|
+
type: 'L',
|
|
134
|
+
x: xOffset - bottomLeftCornerRadius,
|
|
135
|
+
y: yOffset,
|
|
136
|
+
});
|
|
137
|
+
// Arc for rounded corner (bottom left)
|
|
138
|
+
instructions.push({
|
|
139
|
+
type: 'A',
|
|
140
|
+
rx: Math.abs(bottomLeftCornerRadius),
|
|
141
|
+
ry: Math.abs(bottomLeftCornerRadius),
|
|
142
|
+
xAxisRotation: 0,
|
|
143
|
+
largeArcFlag: false,
|
|
144
|
+
sweepFlag: bottomLeftCornerRadius < 0,
|
|
145
|
+
x: xOffset,
|
|
146
|
+
y: yOffset - Math.abs(bottomLeftCornerRadius),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
instructions.push({
|
|
151
|
+
type: 'L',
|
|
152
|
+
x: xOffset,
|
|
153
|
+
y: yOffset,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const topLeftWidthDifference = nextCornerRounding
|
|
157
|
+
? nextCornerRounding.width - cornerRounding.width
|
|
158
|
+
: -Infinity;
|
|
159
|
+
const topLeftCornerRadius = clamp(nextCornerRounding
|
|
160
|
+
? textAlign === 'left'
|
|
161
|
+
? 0
|
|
162
|
+
: textAlign === 'right'
|
|
163
|
+
? topLeftWidthDifference / 2
|
|
164
|
+
: topLeftWidthDifference / 4
|
|
165
|
+
: -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
166
|
+
// Top Left Corner
|
|
167
|
+
if (topLeftCornerRadius !== 0) {
|
|
168
|
+
instructions.push({
|
|
169
|
+
type: 'L',
|
|
170
|
+
x: xOffset,
|
|
171
|
+
y: yOffset - cornerRounding.height + Math.abs(topLeftCornerRadius),
|
|
172
|
+
});
|
|
173
|
+
// Arc for rounded corner (top left)
|
|
174
|
+
instructions.push({
|
|
175
|
+
type: 'A',
|
|
176
|
+
rx: Math.abs(topLeftCornerRadius),
|
|
177
|
+
ry: Math.abs(topLeftCornerRadius),
|
|
178
|
+
xAxisRotation: 0,
|
|
179
|
+
largeArcFlag: false,
|
|
180
|
+
sweepFlag: topLeftCornerRadius < 0,
|
|
181
|
+
x: xOffset - topLeftCornerRadius,
|
|
182
|
+
y: yOffset - cornerRounding.height,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
instructions.push({
|
|
187
|
+
type: 'L',
|
|
188
|
+
x: xOffset,
|
|
189
|
+
y: yOffset - cornerRounding.height,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
yOffset -= cornerRounding.height;
|
|
193
|
+
}
|
|
194
|
+
instructions.push({
|
|
195
|
+
type: 'Z',
|
|
196
|
+
});
|
|
197
|
+
const reduced = (0, paths_1.reduceInstructions)(instructions);
|
|
198
|
+
const boundingBox = paths_1.PathInternals.getBoundingBoxFromInstructions(reduced);
|
|
199
|
+
return {
|
|
200
|
+
d: (0, paths_1.serializeInstructions)(reduced),
|
|
201
|
+
boundingBox,
|
|
202
|
+
instructions: reduced,
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
exports.createRoundedTextBox = createRoundedTextBox;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createRoundedTextBox, type CreateRoundedTextBoxProps, type CreateRoundedTextBoxResult, type TextAlign, } from './create-rounded-text-box';
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
6
|
+
var __toCommonJS = (from) => {
|
|
7
|
+
var entry = __moduleCache.get(from), desc;
|
|
8
|
+
if (entry)
|
|
9
|
+
return entry;
|
|
10
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
12
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
13
|
+
get: () => from[key],
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
}));
|
|
16
|
+
__moduleCache.set(from, entry);
|
|
17
|
+
return entry;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var exports_src = {};
|
|
31
|
+
__export(exports_src, {
|
|
32
|
+
createRoundedTextBox: () => createRoundedTextBox
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(exports_src);
|
|
35
|
+
|
|
36
|
+
// src/create-rounded-text-box.ts
|
|
37
|
+
var import_paths = require("@remotion/paths");
|
|
38
|
+
var clamp = (val, min, max) => {
|
|
39
|
+
return Math.min(Math.max(val, min), max);
|
|
40
|
+
};
|
|
41
|
+
var createRoundedTextBox = ({
|
|
42
|
+
textMeasurements,
|
|
43
|
+
textAlign,
|
|
44
|
+
horizontalPadding,
|
|
45
|
+
borderRadius: unclampedMaxCornerRadius
|
|
46
|
+
}) => {
|
|
47
|
+
const instructions = [];
|
|
48
|
+
let maxWidth = 0;
|
|
49
|
+
for (const cornerRounding of textMeasurements) {
|
|
50
|
+
maxWidth = Math.max(maxWidth, cornerRounding.width + horizontalPadding * 2);
|
|
51
|
+
}
|
|
52
|
+
let yOffset = 0;
|
|
53
|
+
for (let i = 0;i < textMeasurements.length; i++) {
|
|
54
|
+
const previousLine = textMeasurements[i - 1];
|
|
55
|
+
const currentLine = textMeasurements[i];
|
|
56
|
+
const nextLine = textMeasurements[i + 1];
|
|
57
|
+
let xOffset = 0;
|
|
58
|
+
if (textAlign === "center") {
|
|
59
|
+
xOffset = (maxWidth - (currentLine.width + horizontalPadding * 2)) / 2;
|
|
60
|
+
} else if (textAlign === "right") {
|
|
61
|
+
xOffset = maxWidth - (currentLine.width + horizontalPadding * 2);
|
|
62
|
+
}
|
|
63
|
+
const maxCornerRadius = clamp(unclampedMaxCornerRadius, 0, currentLine.height / 2);
|
|
64
|
+
if (i === 0) {
|
|
65
|
+
instructions.push({
|
|
66
|
+
type: "M",
|
|
67
|
+
x: xOffset + maxCornerRadius,
|
|
68
|
+
y: yOffset
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const topRightCornerRadius = clamp(previousLine ? textAlign === "right" ? 0 : textAlign === "left" ? (previousLine.width - currentLine.width) / 2 : (previousLine.width - currentLine.width) / 4 : -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
72
|
+
if (topRightCornerRadius !== 0) {
|
|
73
|
+
instructions.push({
|
|
74
|
+
type: "L",
|
|
75
|
+
x: xOffset + currentLine.width + horizontalPadding * 2 + topRightCornerRadius,
|
|
76
|
+
y: yOffset
|
|
77
|
+
});
|
|
78
|
+
instructions.push({
|
|
79
|
+
type: "A",
|
|
80
|
+
rx: Math.abs(topRightCornerRadius),
|
|
81
|
+
ry: Math.abs(topRightCornerRadius),
|
|
82
|
+
xAxisRotation: 0,
|
|
83
|
+
largeArcFlag: false,
|
|
84
|
+
sweepFlag: topRightCornerRadius < 0,
|
|
85
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
86
|
+
y: yOffset + Math.abs(topRightCornerRadius)
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
instructions.push({
|
|
90
|
+
type: "L",
|
|
91
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
92
|
+
y: yOffset
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const bottomRightCornerRadius = clamp(nextLine ? textAlign === "right" ? 0 : textAlign === "left" ? (nextLine.width - currentLine.width) / 2 : (nextLine.width - currentLine.width) / 4 : -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
96
|
+
if (bottomRightCornerRadius !== 0) {
|
|
97
|
+
instructions.push({
|
|
98
|
+
type: "L",
|
|
99
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
100
|
+
y: yOffset + currentLine.height - Math.abs(bottomRightCornerRadius)
|
|
101
|
+
});
|
|
102
|
+
instructions.push({
|
|
103
|
+
type: "A",
|
|
104
|
+
rx: Math.abs(bottomRightCornerRadius),
|
|
105
|
+
ry: Math.abs(bottomRightCornerRadius),
|
|
106
|
+
xAxisRotation: 0,
|
|
107
|
+
largeArcFlag: false,
|
|
108
|
+
sweepFlag: bottomRightCornerRadius < 0,
|
|
109
|
+
x: xOffset + currentLine.width + horizontalPadding * 2 + bottomRightCornerRadius,
|
|
110
|
+
y: yOffset + currentLine.height
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
instructions.push({
|
|
114
|
+
type: "L",
|
|
115
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
116
|
+
y: yOffset + currentLine.height
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
yOffset += currentLine.height;
|
|
120
|
+
}
|
|
121
|
+
for (let i = textMeasurements.length - 1;i >= 0; i--) {
|
|
122
|
+
const cornerRounding = textMeasurements[i];
|
|
123
|
+
const prevCornerRounding = textMeasurements[i + 1];
|
|
124
|
+
const nextCornerRounding = textMeasurements[i - 1];
|
|
125
|
+
let xOffset = 0;
|
|
126
|
+
if (textAlign === "center") {
|
|
127
|
+
xOffset = (maxWidth - (cornerRounding.width + horizontalPadding * 2)) / 2;
|
|
128
|
+
} else if (textAlign === "right") {
|
|
129
|
+
xOffset = maxWidth - (cornerRounding.width + horizontalPadding * 2);
|
|
130
|
+
}
|
|
131
|
+
const bottomLeftWidthDifference = prevCornerRounding ? prevCornerRounding.width - cornerRounding.width : -Infinity;
|
|
132
|
+
const maxCornerRadius = clamp(unclampedMaxCornerRadius, 0, cornerRounding.height / 2);
|
|
133
|
+
const bottomLeftCornerRadius = clamp(prevCornerRounding ? textAlign === "left" ? 0 : textAlign === "right" ? bottomLeftWidthDifference / 2 : bottomLeftWidthDifference / 4 : -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
134
|
+
if (bottomLeftCornerRadius !== 0) {
|
|
135
|
+
instructions.push({
|
|
136
|
+
type: "L",
|
|
137
|
+
x: xOffset - bottomLeftCornerRadius,
|
|
138
|
+
y: yOffset
|
|
139
|
+
});
|
|
140
|
+
instructions.push({
|
|
141
|
+
type: "A",
|
|
142
|
+
rx: Math.abs(bottomLeftCornerRadius),
|
|
143
|
+
ry: Math.abs(bottomLeftCornerRadius),
|
|
144
|
+
xAxisRotation: 0,
|
|
145
|
+
largeArcFlag: false,
|
|
146
|
+
sweepFlag: bottomLeftCornerRadius < 0,
|
|
147
|
+
x: xOffset,
|
|
148
|
+
y: yOffset - Math.abs(bottomLeftCornerRadius)
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
instructions.push({
|
|
152
|
+
type: "L",
|
|
153
|
+
x: xOffset,
|
|
154
|
+
y: yOffset
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const topLeftWidthDifference = nextCornerRounding ? nextCornerRounding.width - cornerRounding.width : -Infinity;
|
|
158
|
+
const topLeftCornerRadius = clamp(nextCornerRounding ? textAlign === "left" ? 0 : textAlign === "right" ? topLeftWidthDifference / 2 : topLeftWidthDifference / 4 : -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
159
|
+
if (topLeftCornerRadius !== 0) {
|
|
160
|
+
instructions.push({
|
|
161
|
+
type: "L",
|
|
162
|
+
x: xOffset,
|
|
163
|
+
y: yOffset - cornerRounding.height + Math.abs(topLeftCornerRadius)
|
|
164
|
+
});
|
|
165
|
+
instructions.push({
|
|
166
|
+
type: "A",
|
|
167
|
+
rx: Math.abs(topLeftCornerRadius),
|
|
168
|
+
ry: Math.abs(topLeftCornerRadius),
|
|
169
|
+
xAxisRotation: 0,
|
|
170
|
+
largeArcFlag: false,
|
|
171
|
+
sweepFlag: topLeftCornerRadius < 0,
|
|
172
|
+
x: xOffset - topLeftCornerRadius,
|
|
173
|
+
y: yOffset - cornerRounding.height
|
|
174
|
+
});
|
|
175
|
+
} else {
|
|
176
|
+
instructions.push({
|
|
177
|
+
type: "L",
|
|
178
|
+
x: xOffset,
|
|
179
|
+
y: yOffset - cornerRounding.height
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
yOffset -= cornerRounding.height;
|
|
183
|
+
}
|
|
184
|
+
instructions.push({
|
|
185
|
+
type: "Z"
|
|
186
|
+
});
|
|
187
|
+
const reduced = import_paths.reduceInstructions(instructions);
|
|
188
|
+
const boundingBox = import_paths.PathInternals.getBoundingBoxFromInstructions(reduced);
|
|
189
|
+
return {
|
|
190
|
+
d: import_paths.serializeInstructions(reduced),
|
|
191
|
+
boundingBox,
|
|
192
|
+
instructions: reduced
|
|
193
|
+
};
|
|
194
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Word } from './measure-text';
|
|
2
|
+
export declare const fillTextBox: ({ maxBoxWidth, maxLines, }: {
|
|
3
|
+
maxBoxWidth: number;
|
|
4
|
+
maxLines: number;
|
|
5
|
+
}) => {
|
|
6
|
+
add: ({ text, fontFamily, fontWeight, fontSize, letterSpacing, fontVariantNumeric, validateFontIsLoaded, textTransform, additionalStyles, }: Word) => {
|
|
7
|
+
exceedsBox: boolean;
|
|
8
|
+
newLine: boolean;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fillTextBox = void 0;
|
|
4
|
+
const measure_text_1 = require("./measure-text");
|
|
5
|
+
const fillTextBox = ({ maxBoxWidth, maxLines, }) => {
|
|
6
|
+
const lines = new Array(maxLines).fill(0).map(() => []);
|
|
7
|
+
return {
|
|
8
|
+
add: ({ text, fontFamily, fontWeight, fontSize, letterSpacing, fontVariantNumeric, validateFontIsLoaded, textTransform, additionalStyles, }) => {
|
|
9
|
+
const lastLineIndex = lines.reduceRight((acc, curr, index) => {
|
|
10
|
+
if (acc === -1 && curr.length > 0) {
|
|
11
|
+
return index;
|
|
12
|
+
}
|
|
13
|
+
return acc;
|
|
14
|
+
}, -1);
|
|
15
|
+
const currentlyAt = lastLineIndex === -1 ? 0 : lastLineIndex;
|
|
16
|
+
const lineToUse = lines[currentlyAt];
|
|
17
|
+
const lineWithWord = [
|
|
18
|
+
...lineToUse,
|
|
19
|
+
{
|
|
20
|
+
text,
|
|
21
|
+
fontFamily,
|
|
22
|
+
fontWeight,
|
|
23
|
+
fontSize,
|
|
24
|
+
letterSpacing,
|
|
25
|
+
fontVariantNumeric,
|
|
26
|
+
validateFontIsLoaded,
|
|
27
|
+
textTransform,
|
|
28
|
+
additionalStyles,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
const widths = lineWithWord.map((w) => (0, measure_text_1.measureText)(w).width);
|
|
32
|
+
const lineWidthWithWordAdded = widths.reduce((a, b) => a + b, 0);
|
|
33
|
+
if (Math.ceil(lineWidthWithWordAdded) < maxBoxWidth) {
|
|
34
|
+
lines[currentlyAt].push({
|
|
35
|
+
text: lines[currentlyAt].length === 0 ? text.trimStart() : text,
|
|
36
|
+
fontFamily,
|
|
37
|
+
fontWeight,
|
|
38
|
+
fontSize,
|
|
39
|
+
letterSpacing,
|
|
40
|
+
textTransform,
|
|
41
|
+
fontVariantNumeric,
|
|
42
|
+
});
|
|
43
|
+
return { exceedsBox: false, newLine: false };
|
|
44
|
+
}
|
|
45
|
+
if (currentlyAt === maxLines - 1) {
|
|
46
|
+
return { exceedsBox: true, newLine: false };
|
|
47
|
+
}
|
|
48
|
+
lines[currentlyAt + 1] = [
|
|
49
|
+
{
|
|
50
|
+
text: text.trimStart(),
|
|
51
|
+
fontFamily,
|
|
52
|
+
fontWeight,
|
|
53
|
+
fontSize,
|
|
54
|
+
letterSpacing,
|
|
55
|
+
textTransform,
|
|
56
|
+
fontVariantNumeric,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
return { exceedsBox: false, newLine: true };
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
exports.fillTextBox = fillTextBox;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TextTransform } from './measure-text';
|
|
2
|
+
type FitTextOnNLinesProps = {
|
|
3
|
+
text: string;
|
|
4
|
+
maxLines: number;
|
|
5
|
+
maxBoxWidth: number;
|
|
6
|
+
fontFamily: string;
|
|
7
|
+
fontWeight?: number | string;
|
|
8
|
+
letterSpacing?: string;
|
|
9
|
+
fontVariantNumeric?: string;
|
|
10
|
+
validateFontIsLoaded?: boolean;
|
|
11
|
+
textTransform?: TextTransform;
|
|
12
|
+
additionalStyles?: Record<string, string>;
|
|
13
|
+
maxFontSize?: number;
|
|
14
|
+
};
|
|
15
|
+
export declare const fitTextOnNLines: ({ text, maxLines, maxBoxWidth, fontFamily, fontWeight, letterSpacing, fontVariantNumeric, validateFontIsLoaded, textTransform, additionalStyles, maxFontSize, }: FitTextOnNLinesProps) => {
|
|
16
|
+
fontSize: number;
|
|
17
|
+
lines: string[];
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fitTextOnNLines = void 0;
|
|
4
|
+
const fill_text_box_1 = require("./fill-text-box");
|
|
5
|
+
const PRECISION = 100;
|
|
6
|
+
const fitTextOnNLines = ({ text, maxLines, maxBoxWidth, fontFamily, fontWeight, letterSpacing, fontVariantNumeric, validateFontIsLoaded, textTransform, additionalStyles, maxFontSize, }) => {
|
|
7
|
+
// Fixed max font size since we are using binary search a
|
|
8
|
+
const minFontSize = 0.1;
|
|
9
|
+
// Binary search to find the optimal font size
|
|
10
|
+
let left = Math.floor(minFontSize * PRECISION);
|
|
11
|
+
let right = Math.floor((maxFontSize !== null && maxFontSize !== void 0 ? maxFontSize : 2000) * PRECISION);
|
|
12
|
+
let optimalFontSize = minFontSize;
|
|
13
|
+
let optimalLines = [];
|
|
14
|
+
while (left <= right) {
|
|
15
|
+
const mid = Math.floor((left + right) / 2);
|
|
16
|
+
const fontSize = mid / PRECISION;
|
|
17
|
+
// Create a text box with current font size
|
|
18
|
+
const textBox = (0, fill_text_box_1.fillTextBox)({
|
|
19
|
+
maxBoxWidth,
|
|
20
|
+
maxLines,
|
|
21
|
+
});
|
|
22
|
+
// Split text into words and try to fit them
|
|
23
|
+
const words = text.split(' ');
|
|
24
|
+
let exceedsBox = false;
|
|
25
|
+
let currentLine = 0;
|
|
26
|
+
const lines = [''];
|
|
27
|
+
for (const word of words) {
|
|
28
|
+
const result = textBox.add({
|
|
29
|
+
text: lines[currentLine].length === 0 ? word : ' ' + word,
|
|
30
|
+
fontFamily,
|
|
31
|
+
fontWeight,
|
|
32
|
+
fontSize,
|
|
33
|
+
letterSpacing,
|
|
34
|
+
fontVariantNumeric,
|
|
35
|
+
validateFontIsLoaded,
|
|
36
|
+
textTransform,
|
|
37
|
+
additionalStyles,
|
|
38
|
+
});
|
|
39
|
+
if (result.exceedsBox) {
|
|
40
|
+
exceedsBox = true;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
if (result.newLine) {
|
|
44
|
+
lines.push('');
|
|
45
|
+
currentLine++;
|
|
46
|
+
}
|
|
47
|
+
lines[currentLine] += word + ' ';
|
|
48
|
+
}
|
|
49
|
+
// If text fits within the box and number of lines
|
|
50
|
+
if (!exceedsBox && currentLine < maxLines) {
|
|
51
|
+
optimalFontSize = fontSize;
|
|
52
|
+
optimalLines = lines;
|
|
53
|
+
left = mid + 1;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
right = mid - 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < optimalLines.length; i++) {
|
|
60
|
+
optimalLines[i] = optimalLines[i].trimEnd();
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
fontSize: optimalFontSize,
|
|
64
|
+
lines: optimalLines,
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
exports.fitTextOnNLines = fitTextOnNLines;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ModifyableCSSProperties, TextTransform } from '../layouts/measure-text';
|
|
2
|
+
export declare const fitText: ({ text, withinWidth, fontFamily, fontVariantNumeric, fontWeight, letterSpacing, validateFontIsLoaded, additionalStyles, textTransform, }: {
|
|
3
|
+
text: string;
|
|
4
|
+
withinWidth: number;
|
|
5
|
+
fontFamily: string;
|
|
6
|
+
fontWeight?: number | string;
|
|
7
|
+
letterSpacing?: string;
|
|
8
|
+
fontVariantNumeric?: string;
|
|
9
|
+
validateFontIsLoaded?: boolean;
|
|
10
|
+
textTransform?: TextTransform;
|
|
11
|
+
additionalStyles?: ModifyableCSSProperties;
|
|
12
|
+
}) => {
|
|
13
|
+
fontSize: number;
|
|
14
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fitText = void 0;
|
|
4
|
+
const measure_text_1 = require("../layouts/measure-text");
|
|
5
|
+
const sampleSize = 100;
|
|
6
|
+
/*
|
|
7
|
+
* @description Calculates the font size needed to fit text into a specified width container.
|
|
8
|
+
* @see [Documentation](https://remotion.dev/docs/layout-utils/fit-text)
|
|
9
|
+
*/
|
|
10
|
+
const fitText = ({ text, withinWidth, fontFamily, fontVariantNumeric, fontWeight, letterSpacing, validateFontIsLoaded, additionalStyles, textTransform, }) => {
|
|
11
|
+
const estimate = (0, measure_text_1.measureText)({
|
|
12
|
+
text,
|
|
13
|
+
fontFamily,
|
|
14
|
+
fontSize: sampleSize,
|
|
15
|
+
fontWeight,
|
|
16
|
+
fontVariantNumeric,
|
|
17
|
+
letterSpacing,
|
|
18
|
+
validateFontIsLoaded,
|
|
19
|
+
textTransform,
|
|
20
|
+
additionalStyles,
|
|
21
|
+
});
|
|
22
|
+
return { fontSize: (withinWidth / estimate.width) * sampleSize };
|
|
23
|
+
};
|
|
24
|
+
exports.fitText = fitText;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type Dimensions = {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
};
|
|
5
|
+
export type ModifyableCSSProperties<T = Partial<CSSStyleDeclaration>> = {
|
|
6
|
+
[P in keyof T as P extends 'length' ? never : P extends keyof CSSPropertiesOnWord ? never : T[P] extends string | number ? P : never]: T[P];
|
|
7
|
+
};
|
|
8
|
+
export type TextTransform = '-moz-initial' | 'inherit' | 'initial' | 'revert' | 'revert-layer' | 'unset' | 'none' | 'capitalize' | 'full-size-kana' | 'full-width' | 'lowercase' | 'uppercase';
|
|
9
|
+
type CSSPropertiesOnWord = {
|
|
10
|
+
fontFamily: string;
|
|
11
|
+
fontSize: number | string;
|
|
12
|
+
fontWeight?: number | string;
|
|
13
|
+
letterSpacing?: string;
|
|
14
|
+
fontVariantNumeric?: string;
|
|
15
|
+
textTransform?: TextTransform;
|
|
16
|
+
};
|
|
17
|
+
export type Word = {
|
|
18
|
+
text: string;
|
|
19
|
+
validateFontIsLoaded?: boolean;
|
|
20
|
+
additionalStyles?: ModifyableCSSProperties;
|
|
21
|
+
} & CSSPropertiesOnWord;
|
|
22
|
+
export declare const measureText: ({ text, fontFamily, fontSize, fontWeight, letterSpacing, fontVariantNumeric, validateFontIsLoaded, additionalStyles, textTransform, }: Word) => Dimensions;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.measureText = void 0;
|
|
4
|
+
const wordCache = new Map();
|
|
5
|
+
const takeMeasurement = ({ text, fontFamily, fontSize, fontWeight, letterSpacing, fontVariantNumeric, additionalStyles, textTransform, }) => {
|
|
6
|
+
if (typeof document === 'undefined') {
|
|
7
|
+
throw new Error('measureText() can only be called in a browser.');
|
|
8
|
+
}
|
|
9
|
+
const node = document.createElement('span');
|
|
10
|
+
if (fontFamily) {
|
|
11
|
+
node.style.fontFamily = fontFamily;
|
|
12
|
+
}
|
|
13
|
+
node.style.display = 'inline-block';
|
|
14
|
+
node.style.position = 'absolute';
|
|
15
|
+
node.style.top = `-10000px`;
|
|
16
|
+
node.style.whiteSpace = 'pre';
|
|
17
|
+
node.style.fontSize =
|
|
18
|
+
typeof fontSize === 'string' ? fontSize : `${fontSize}px`;
|
|
19
|
+
if (additionalStyles) {
|
|
20
|
+
for (const key of Object.keys(additionalStyles)) {
|
|
21
|
+
node.style[key] = additionalStyles[key];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (fontWeight) {
|
|
25
|
+
node.style.fontWeight = fontWeight.toString();
|
|
26
|
+
}
|
|
27
|
+
if (letterSpacing) {
|
|
28
|
+
node.style.letterSpacing = letterSpacing;
|
|
29
|
+
}
|
|
30
|
+
if (fontVariantNumeric) {
|
|
31
|
+
node.style.fontVariantNumeric = fontVariantNumeric;
|
|
32
|
+
}
|
|
33
|
+
if (textTransform) {
|
|
34
|
+
node.style.textTransform = textTransform;
|
|
35
|
+
}
|
|
36
|
+
node.innerText = text;
|
|
37
|
+
document.body.appendChild(node);
|
|
38
|
+
const computedFontFamily = window.getComputedStyle(node).fontFamily;
|
|
39
|
+
const boundingBox = node.getBoundingClientRect();
|
|
40
|
+
document.body.removeChild(node);
|
|
41
|
+
return {
|
|
42
|
+
boundingBox,
|
|
43
|
+
computedFontFamily,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/*
|
|
47
|
+
* @description Calculates the width and height of specified text to be used for layout calculations. Only works in the browser, not in Node.js or Bun.
|
|
48
|
+
* @see [Documentation](https://remotion.dev/docs/layout-utils/measure-text)
|
|
49
|
+
*/
|
|
50
|
+
const measureText = ({ text, fontFamily, fontSize, fontWeight, letterSpacing, fontVariantNumeric, validateFontIsLoaded, additionalStyles, textTransform, }) => {
|
|
51
|
+
const key = `${text}-${fontFamily}-${fontWeight}-${fontSize}-${letterSpacing}-${textTransform}-${JSON.stringify(additionalStyles)}`;
|
|
52
|
+
if (wordCache.has(key)) {
|
|
53
|
+
return wordCache.get(key);
|
|
54
|
+
}
|
|
55
|
+
const { boundingBox, computedFontFamily } = takeMeasurement({
|
|
56
|
+
fontFamily,
|
|
57
|
+
fontSize,
|
|
58
|
+
text,
|
|
59
|
+
fontVariantNumeric,
|
|
60
|
+
fontWeight,
|
|
61
|
+
letterSpacing,
|
|
62
|
+
additionalStyles,
|
|
63
|
+
textTransform,
|
|
64
|
+
});
|
|
65
|
+
if (validateFontIsLoaded && text.trim().length > 0) {
|
|
66
|
+
const { boundingBox: boundingBoxOfFallbackFont, computedFontFamily: computedFallback, } = takeMeasurement({
|
|
67
|
+
fontFamily: null,
|
|
68
|
+
fontSize,
|
|
69
|
+
text,
|
|
70
|
+
fontVariantNumeric,
|
|
71
|
+
fontWeight,
|
|
72
|
+
letterSpacing,
|
|
73
|
+
additionalStyles,
|
|
74
|
+
textTransform,
|
|
75
|
+
});
|
|
76
|
+
const sameAsFallbackFont = boundingBox.height === boundingBoxOfFallbackFont.height &&
|
|
77
|
+
boundingBox.width === boundingBoxOfFallbackFont.width;
|
|
78
|
+
// Ensure there are at least 4 unique characters, with just a few, there is more likely to be a false positive
|
|
79
|
+
if (sameAsFallbackFont &&
|
|
80
|
+
computedFallback !== computedFontFamily &&
|
|
81
|
+
new Set(text).size > 4) {
|
|
82
|
+
const err = [
|
|
83
|
+
`Called measureText() with "fontFamily": ${JSON.stringify(fontFamily)} but it looks like the font is not loaded at the time of calling.`,
|
|
84
|
+
`A measurement with the fallback font ${computedFallback} was taken and had the same dimensions, indicating that the browser used the fallback font.`,
|
|
85
|
+
'See https://remotion.dev/docs/layout-utils/best-practices for best practices.',
|
|
86
|
+
];
|
|
87
|
+
throw new Error(err.join('\n'));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const result = { height: boundingBox.height, width: boundingBox.width };
|
|
91
|
+
wordCache.set(key, result);
|
|
92
|
+
return result;
|
|
93
|
+
};
|
|
94
|
+
exports.measureText = measureText;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/create-rounded-text-box.ts
|
|
2
|
+
import {
|
|
3
|
+
PathInternals,
|
|
4
|
+
reduceInstructions,
|
|
5
|
+
serializeInstructions
|
|
6
|
+
} from "@remotion/paths";
|
|
7
|
+
var clamp = (val, min, max) => {
|
|
8
|
+
return Math.min(Math.max(val, min), max);
|
|
9
|
+
};
|
|
10
|
+
var createRoundedTextBox = ({
|
|
11
|
+
textMeasurements,
|
|
12
|
+
textAlign,
|
|
13
|
+
horizontalPadding,
|
|
14
|
+
borderRadius: unclampedMaxCornerRadius
|
|
15
|
+
}) => {
|
|
16
|
+
const instructions = [];
|
|
17
|
+
let maxWidth = 0;
|
|
18
|
+
for (const cornerRounding of textMeasurements) {
|
|
19
|
+
maxWidth = Math.max(maxWidth, cornerRounding.width + horizontalPadding * 2);
|
|
20
|
+
}
|
|
21
|
+
let yOffset = 0;
|
|
22
|
+
for (let i = 0;i < textMeasurements.length; i++) {
|
|
23
|
+
const previousLine = textMeasurements[i - 1];
|
|
24
|
+
const currentLine = textMeasurements[i];
|
|
25
|
+
const nextLine = textMeasurements[i + 1];
|
|
26
|
+
let xOffset = 0;
|
|
27
|
+
if (textAlign === "center") {
|
|
28
|
+
xOffset = (maxWidth - (currentLine.width + horizontalPadding * 2)) / 2;
|
|
29
|
+
} else if (textAlign === "right") {
|
|
30
|
+
xOffset = maxWidth - (currentLine.width + horizontalPadding * 2);
|
|
31
|
+
}
|
|
32
|
+
const maxCornerRadius = clamp(unclampedMaxCornerRadius, 0, currentLine.height / 2);
|
|
33
|
+
if (i === 0) {
|
|
34
|
+
instructions.push({
|
|
35
|
+
type: "M",
|
|
36
|
+
x: xOffset + maxCornerRadius,
|
|
37
|
+
y: yOffset
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const topRightCornerRadius = clamp(previousLine ? textAlign === "right" ? 0 : textAlign === "left" ? (previousLine.width - currentLine.width) / 2 : (previousLine.width - currentLine.width) / 4 : -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
41
|
+
if (topRightCornerRadius !== 0) {
|
|
42
|
+
instructions.push({
|
|
43
|
+
type: "L",
|
|
44
|
+
x: xOffset + currentLine.width + horizontalPadding * 2 + topRightCornerRadius,
|
|
45
|
+
y: yOffset
|
|
46
|
+
});
|
|
47
|
+
instructions.push({
|
|
48
|
+
type: "A",
|
|
49
|
+
rx: Math.abs(topRightCornerRadius),
|
|
50
|
+
ry: Math.abs(topRightCornerRadius),
|
|
51
|
+
xAxisRotation: 0,
|
|
52
|
+
largeArcFlag: false,
|
|
53
|
+
sweepFlag: topRightCornerRadius < 0,
|
|
54
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
55
|
+
y: yOffset + Math.abs(topRightCornerRadius)
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
instructions.push({
|
|
59
|
+
type: "L",
|
|
60
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
61
|
+
y: yOffset
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const bottomRightCornerRadius = clamp(nextLine ? textAlign === "right" ? 0 : textAlign === "left" ? (nextLine.width - currentLine.width) / 2 : (nextLine.width - currentLine.width) / 4 : -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
65
|
+
if (bottomRightCornerRadius !== 0) {
|
|
66
|
+
instructions.push({
|
|
67
|
+
type: "L",
|
|
68
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
69
|
+
y: yOffset + currentLine.height - Math.abs(bottomRightCornerRadius)
|
|
70
|
+
});
|
|
71
|
+
instructions.push({
|
|
72
|
+
type: "A",
|
|
73
|
+
rx: Math.abs(bottomRightCornerRadius),
|
|
74
|
+
ry: Math.abs(bottomRightCornerRadius),
|
|
75
|
+
xAxisRotation: 0,
|
|
76
|
+
largeArcFlag: false,
|
|
77
|
+
sweepFlag: bottomRightCornerRadius < 0,
|
|
78
|
+
x: xOffset + currentLine.width + horizontalPadding * 2 + bottomRightCornerRadius,
|
|
79
|
+
y: yOffset + currentLine.height
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
instructions.push({
|
|
83
|
+
type: "L",
|
|
84
|
+
x: xOffset + currentLine.width + horizontalPadding * 2,
|
|
85
|
+
y: yOffset + currentLine.height
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
yOffset += currentLine.height;
|
|
89
|
+
}
|
|
90
|
+
for (let i = textMeasurements.length - 1;i >= 0; i--) {
|
|
91
|
+
const cornerRounding = textMeasurements[i];
|
|
92
|
+
const prevCornerRounding = textMeasurements[i + 1];
|
|
93
|
+
const nextCornerRounding = textMeasurements[i - 1];
|
|
94
|
+
let xOffset = 0;
|
|
95
|
+
if (textAlign === "center") {
|
|
96
|
+
xOffset = (maxWidth - (cornerRounding.width + horizontalPadding * 2)) / 2;
|
|
97
|
+
} else if (textAlign === "right") {
|
|
98
|
+
xOffset = maxWidth - (cornerRounding.width + horizontalPadding * 2);
|
|
99
|
+
}
|
|
100
|
+
const bottomLeftWidthDifference = prevCornerRounding ? prevCornerRounding.width - cornerRounding.width : -Infinity;
|
|
101
|
+
const maxCornerRadius = clamp(unclampedMaxCornerRadius, 0, cornerRounding.height / 2);
|
|
102
|
+
const bottomLeftCornerRadius = clamp(prevCornerRounding ? textAlign === "left" ? 0 : textAlign === "right" ? bottomLeftWidthDifference / 2 : bottomLeftWidthDifference / 4 : -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
103
|
+
if (bottomLeftCornerRadius !== 0) {
|
|
104
|
+
instructions.push({
|
|
105
|
+
type: "L",
|
|
106
|
+
x: xOffset - bottomLeftCornerRadius,
|
|
107
|
+
y: yOffset
|
|
108
|
+
});
|
|
109
|
+
instructions.push({
|
|
110
|
+
type: "A",
|
|
111
|
+
rx: Math.abs(bottomLeftCornerRadius),
|
|
112
|
+
ry: Math.abs(bottomLeftCornerRadius),
|
|
113
|
+
xAxisRotation: 0,
|
|
114
|
+
largeArcFlag: false,
|
|
115
|
+
sweepFlag: bottomLeftCornerRadius < 0,
|
|
116
|
+
x: xOffset,
|
|
117
|
+
y: yOffset - Math.abs(bottomLeftCornerRadius)
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
instructions.push({
|
|
121
|
+
type: "L",
|
|
122
|
+
x: xOffset,
|
|
123
|
+
y: yOffset
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const topLeftWidthDifference = nextCornerRounding ? nextCornerRounding.width - cornerRounding.width : -Infinity;
|
|
127
|
+
const topLeftCornerRadius = clamp(nextCornerRounding ? textAlign === "left" ? 0 : textAlign === "right" ? topLeftWidthDifference / 2 : topLeftWidthDifference / 4 : -Infinity, -maxCornerRadius, maxCornerRadius);
|
|
128
|
+
if (topLeftCornerRadius !== 0) {
|
|
129
|
+
instructions.push({
|
|
130
|
+
type: "L",
|
|
131
|
+
x: xOffset,
|
|
132
|
+
y: yOffset - cornerRounding.height + Math.abs(topLeftCornerRadius)
|
|
133
|
+
});
|
|
134
|
+
instructions.push({
|
|
135
|
+
type: "A",
|
|
136
|
+
rx: Math.abs(topLeftCornerRadius),
|
|
137
|
+
ry: Math.abs(topLeftCornerRadius),
|
|
138
|
+
xAxisRotation: 0,
|
|
139
|
+
largeArcFlag: false,
|
|
140
|
+
sweepFlag: topLeftCornerRadius < 0,
|
|
141
|
+
x: xOffset - topLeftCornerRadius,
|
|
142
|
+
y: yOffset - cornerRounding.height
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
instructions.push({
|
|
146
|
+
type: "L",
|
|
147
|
+
x: xOffset,
|
|
148
|
+
y: yOffset - cornerRounding.height
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
yOffset -= cornerRounding.height;
|
|
152
|
+
}
|
|
153
|
+
instructions.push({
|
|
154
|
+
type: "Z"
|
|
155
|
+
});
|
|
156
|
+
const reduced = reduceInstructions(instructions);
|
|
157
|
+
const boundingBox = PathInternals.getBoundingBoxFromInstructions(reduced);
|
|
158
|
+
return {
|
|
159
|
+
d: serializeInstructions(reduced),
|
|
160
|
+
boundingBox,
|
|
161
|
+
instructions: reduced
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
export {
|
|
165
|
+
createRoundedTextBox
|
|
166
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"repository": {
|
|
3
|
+
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/rounded-text-box"
|
|
4
|
+
},
|
|
5
|
+
"name": "@remotion/rounded-text-box",
|
|
6
|
+
"version": "4.0.361",
|
|
7
|
+
"description": "Create a TikTok-like multiline text box SVG path with rounded corners",
|
|
8
|
+
"main": "dist/cjs/index.js",
|
|
9
|
+
"types": "dist/cjs/index.d.ts",
|
|
10
|
+
"module": "dist/esm/index.mjs",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"scripts": {
|
|
13
|
+
"formatting": "prettier --experimental-cli src --check",
|
|
14
|
+
"lint": "eslint src",
|
|
15
|
+
"make": "tsc -d && bun --env-file=../.env.bundle bundle.ts"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/cjs/index.d.ts",
|
|
21
|
+
"require": "./dist/cjs/index.js",
|
|
22
|
+
"module": "./dist/esm/index.mjs",
|
|
23
|
+
"import": "./dist/esm/index.mjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@remotion/layout-utils": "4.0.361",
|
|
28
|
+
"@remotion/paths": "4.0.361"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@remotion/eslint-config-internal": "4.0.361",
|
|
32
|
+
"eslint": "9.19.0"
|
|
33
|
+
},
|
|
34
|
+
"author": "Jonny Burger <jonny@remotion.dev>",
|
|
35
|
+
"maintainers": [
|
|
36
|
+
"Jonny Burger <jonny@remotion.dev>"
|
|
37
|
+
],
|
|
38
|
+
"contributors": [],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/remotion-dev/remotion/issues"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"remotion",
|
|
45
|
+
"svg"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://www.remotion.dev/docs/rounded-text-box"
|
|
51
|
+
}
|