@altano/satori-fit-text 1.0.1 → 1.0.2

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 CHANGED
@@ -45,10 +45,16 @@ console.log(largestUsableFontSize);
45
45
 
46
46
  ## Example Uses
47
47
 
48
- - [React Component (sandbox)](https://codesandbox.io/s/altano-satori-fit-text-browser-example-j2gvk6)
49
- - [Next.js Open Graph Card - Simple (codesandbox)](https://codesandbox.io/p/sandbox/altano-satori-fit-text-next-js-demo-sv4flc)
48
+ ### Code Sandboxes
49
+
50
+ - [Playground with controls](https://codesandbox.io/p/sandbox/github/altano/npm-packages/tree/main/examples/satori-fit-text-playground?file=%2Fpackage.json%3A1%2C1)
51
+ - [React Component](https://codesandbox.io/s/altano-satori-fit-text-browser-example-j2gvk6)
52
+ - [Node.js CLI App](https://codesandbox.io/p/sandbox/github/altano/npm-packages/tree/main/examples/satori-fit-text-node-cli?file=%2Fpackage.json%3A1%2C1)
53
+ - [Next.js Open Graph Card - Simple](https://codesandbox.io/p/sandbox/altano-satori-fit-text-next-js-demo-sv4flc)
54
+
55
+ ### Code
56
+
50
57
  - [Next.js Open Graph Card - Complex](https://github.com/altano/alan.norbauer.com/blob/main/src/components/opengraph/image.tsx#L29-L35)
51
- - [Node.js CLI App (codesandbox)](https://codesandbox.io/p/sandbox/github/altano/npm-packages/tree/main/examples/satori-fit-text-node-cli?file=%2Fpackage.json%3A1%2C1)
52
58
 
53
59
  ## Implementation
54
60
 
@@ -1,23 +1,23 @@
1
1
  import log from "../log.js";
2
2
  import { TextMeasurer } from "./TextMeasurer.js";
3
3
  class BrowserTextMeasurer extends TextMeasurer {
4
- #getSvgElement(svgText) {
5
- const div = document.createElement("div");
6
- div.style.position = "fixed";
7
- div.innerHTML = svgText;
8
- const svg = div.firstElementChild;
4
+ #createSvgElement(svgXML) {
5
+ const wrapper = document.createElement("div");
6
+ wrapper.innerHTML = svgXML;
7
+ const svg = wrapper.firstElementChild;
9
8
  if (svg == null || !(svg instanceof SVGSVGElement)) {
10
9
  throw new Error(`Child should be svg element`);
11
10
  }
12
11
  return svg;
13
12
  }
14
13
  async getDimensions(fontSize) {
15
- const svgText = await this.generateSvg(fontSize);
16
- const svg = this.#getSvgElement(svgText);
14
+ const svgXML = await this.createSvgXmlString(fontSize);
15
+ const svg = this.#createSvgElement(svgXML);
17
16
  try {
18
17
  document.body.appendChild(svg);
19
18
  const dimensions = svg.getBBox();
20
- const { width, height } = dimensions;
19
+ const width = dimensions.width + Math.min(dimensions.x, 0);
20
+ const height = dimensions.height + Math.min(dimensions.y, 0);
21
21
  log({
22
22
  fontSize,
23
23
  width: `${width}px`,
@@ -25,7 +25,7 @@ class BrowserTextMeasurer extends TextMeasurer {
25
25
  widthFits: `${width}px ${width <= this.maxWidth ? "DOES" : "does NOT"} fit in ${this.maxWidth}`,
26
26
  heightFits: `${height}px ${height <= this.maxHeight ? "DOES" : "does NOT"} fit in ${this.maxHeight}`
27
27
  });
28
- return dimensions;
28
+ return { width, height };
29
29
  } finally {
30
30
  svg.parentElement?.removeChild?.(svg);
31
31
  }
@@ -14,7 +14,8 @@ class HeadlessTextMeasurer extends TextMeasurer {
14
14
  async getDimensions(fontSize) {
15
15
  await this.#setFontSize(fontSize);
16
16
  const dimensions = this.#canvas.bbox();
17
- const { width, height } = dimensions;
17
+ const width = dimensions.width + Math.min(dimensions.x, 0);
18
+ const height = dimensions.height + Math.min(dimensions.y, 0);
18
19
  log({
19
20
  fontSize,
20
21
  width: `${width}px`,
@@ -22,12 +23,12 @@ class HeadlessTextMeasurer extends TextMeasurer {
22
23
  widthFits: `${width}px ${width <= this.maxWidth ? "DOES" : "does NOT"} fit in ${this.maxWidth}`,
23
24
  heightFits: `${height}px ${height <= this.maxHeight ? "DOES" : "does NOT"} fit in ${this.maxHeight}`
24
25
  });
25
- return dimensions;
26
+ return { width, height };
26
27
  }
27
28
  async #setFontSize(fontSize) {
28
- const svgText = await this.generateSvg(fontSize);
29
+ const svgXML = await this.createSvgXmlString(fontSize);
29
30
  this.#canvas.clear();
30
- this.#canvas.svg(svgText);
31
+ this.#canvas.svg(svgXML);
31
32
  }
32
33
  }
33
34
  export {
@@ -9,6 +9,6 @@ export declare abstract class TextMeasurer {
9
9
  constructor(text: string, font: Font, maxWidth: number, maxHeight: number, lineHeight: number | "normal");
10
10
  abstract getDimensions(fontSize: number): Promise<Dimensions>;
11
11
  doesSizeFit(fontSize: number): Promise<boolean>;
12
- protected generateSvg(fontSize: number): Promise<string>;
12
+ protected createSvgXmlString(fontSize: number): Promise<string>;
13
13
  }
14
14
  //# sourceMappingURL=TextMeasurer.d.ts.map
@@ -13,38 +13,27 @@ class TextMeasurer {
13
13
  const { width, height } = await this.getDimensions(fontSize);
14
14
  return width <= this.maxWidth && height <= this.maxHeight;
15
15
  }
16
- async generateSvg(fontSize) {
16
+ async createSvgXmlString(fontSize) {
17
17
  const node = {
18
18
  type: "div",
19
+ key: "satori-fit-text-measure-node",
19
20
  props: {
20
21
  style: {
21
- all: "initial",
22
+ // Static styles
23
+ background: "white",
24
+ color: "black",
22
25
  display: "flex",
23
- flexDirection: "column",
24
- width: "100%",
25
- height: "100%",
26
+ flex: 1,
27
+ boxSizing: "border-box",
26
28
  margin: 0,
27
- padding: 0
29
+ padding: 0,
30
+ // Dynamic styles based on input
31
+ fontSize,
32
+ fontFamily: this.font.name,
33
+ maxWidth: this.maxWidth,
34
+ lineHeight: this.lineHeight
28
35
  },
29
- children: [
30
- {
31
- type: "div",
32
- props: {
33
- style: {
34
- // Static styles
35
- margin: 0,
36
- padding: 0,
37
- boxSizing: "border-box",
38
- // Dynamic styles based on input
39
- fontSize,
40
- fontFamily: this.font.name,
41
- maxWidth: this.maxWidth,
42
- lineHeight: this.lineHeight
43
- },
44
- children: this.text
45
- }
46
- }
47
- ]
36
+ children: this.text
48
37
  }
49
38
  };
50
39
  return satori(node, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@altano/satori-fit-text",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Fit text to a bounding box in Node or the browser",
5
5
  "keywords": [
6
6
  "fit-text",
@@ -53,9 +53,8 @@
53
53
  "jest-image-snapshot": "^6.4.0",
54
54
  "prettier": "^3.5.3",
55
55
  "react": "^19.1.0",
56
- "react-dom": "^19.1.0",
57
56
  "typescript": "^5.8.2",
58
- "vite": "^6.2.4",
57
+ "vite": "^6.2.5",
59
58
  "vitest": "^3.1.1",
60
59
  "@altano/assets": "0.0.0",
61
60
  "@altano/build-config": "0.0.0",
@@ -68,7 +67,7 @@
68
67
  "scripts": {
69
68
  "build": "tsup --config build-config/tsup.config.node.ts",
70
69
  "build:types": "tsc --project tsconfig.declarations.json",
71
- "check:exports": "attw --pack . --profile esm-only",
70
+ "check:exports": "attw --pack . --profile esm-only --quiet",
72
71
  "check:types:src": "tsc --noEmit",
73
72
  "check:types:test": "tsc --noEmit --project ./tests/tsconfig.json",
74
73
  "check:unused-dependencies": "depcheck",
@@ -80,7 +79,7 @@
80
79
  "lint:fix": "TIMING=1 pnpm lint --fix",
81
80
  "lint:timing": "TIMING=1 pnpm lint",
82
81
  "test:e2e": "playwright test",
83
- "test:e2e:server": "vite --strictPort --port 3717 ./tests/e2e",
82
+ "test:e2e:server": "pnpm --dir ../../examples/satori-fit-text-playground start",
84
83
  "test:e2e:ui": "playwright test --ui",
85
84
  "test:unit": "vitest --run",
86
85
  "test:unit:benchmark": "vitest --run bench",