logo-soup 0.1.0 → 0.2.1

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
@@ -10,7 +10,7 @@ logo-soup analyzes SVG/PNG images with `sharp`, detects their content bounding b
10
10
  ## Installation
11
11
 
12
12
  ```bash
13
- pnpm add logo-soup
13
+ pnpm add -D logo-soup
14
14
 
15
15
  # Or run directly
16
16
  npx logo-soup ./logos
@@ -22,19 +22,64 @@ npx logo-soup ./logos
22
22
  logo-soup ./public/logos -o logo-metrics.json
23
23
  ```
24
24
 
25
- Output JSON keys are filenames only (e.g. `"logo.svg"`), so consumers can prepend their own base path.
25
+ Output keys are filenames only (e.g. `"logo.svg"`), so you can prepend your own base path.
26
26
 
27
27
  ```
28
28
  logo-soup <dir> [options]
29
29
 
30
30
  Options:
31
31
  --output, -o Output JSON file path (default: "logo-metrics.json")
32
- --base-size Base size for normalization in px (default: 48)
32
+ --base-size Base size for normalization in px (default: 64)
33
33
  --scale-factor Aspect ratio normalization 0-1 (default: 0.5)
34
34
  --density-factor Density compensation 0-1 (default: 0.5)
35
35
  --extensions, -e Comma-separated file extensions (default: "svg,png")
36
36
  ```
37
37
 
38
+ ## Usage
39
+
40
+ Running the CLI produces a JSON file mapping each filename to its normalized dimensions:
41
+
42
+ ```json
43
+ {
44
+ "acme-wordmark.svg": { "width": 143, "height": 28, "offsetX": 0, "offsetY": 0.5 },
45
+ "globex-icon.svg": { "width": 64, "height": 64, "offsetX": 0, "offsetY": 0 },
46
+ "initech-monogram.svg": { "width": 52, "height": 79, "offsetX": -0.4, "offsetY": 0 }
47
+ }
48
+ ```
49
+
50
+ `width` and `height` are pixel dimensions normalized so every logo feels the same visual size – use them directly as `width`/`height` attributes. `offsetX`/`offsetY` are optional sub-pixel corrections (see [Visual Center Offsets](#visual-center-offsets)).
51
+
52
+ ### Logo Strip
53
+
54
+ The most common use case: a "trusted by" row or partner logo strip. Apply `width` and `height` directly – the normalization ensures all logos feel balanced side by side.
55
+
56
+ ```html
57
+ <div class="flex flex-wrap items-center justify-center gap-8">
58
+ <img src="/logos/acme-wordmark.svg" width="143" height="28" alt="Acme">
59
+ <img src="/logos/globex-icon.svg" width="64" height="64" alt="Globex">
60
+ <img src="/logos/initech-monogram.svg" width="52" height="79" alt="Initech">
61
+ </div>
62
+ ```
63
+
64
+ > [!TIP]
65
+ > The default `baseSize` is 64px, so a square logo renders at 64×64px. For a different base, pass `--base-size <n>` to the CLI (or `{ baseSize }` to `normalize()`), or scale uniformly with CSS – the proportions stay correct either way.
66
+
67
+ ### Visual Center Offsets
68
+
69
+ Some logos have visual weight that doesn't match their geometric center – a play button or an arrow, for instance. `offsetX`/`offsetY` correct for this by nudging the logo toward its optical center.
70
+
71
+ These offsets are typically small (< 2px) and `width`/`height` alone handle most of the balancing. For pixel-perfect alignment, apply them as CSS transforms:
72
+
73
+ ```html
74
+ <img src="/logos/acme-wordmark.svg" width="143" height="28" style="transform: translate(0px, 0.5px)">
75
+ ```
76
+
77
+ For horizontal strips where only vertical alignment matters, you can apply just the Y offset:
78
+
79
+ ```html
80
+ <img src="/logos/acme-wordmark.svg" width="143" height="28" style="transform: translateY(0.5px)">
81
+ ```
82
+
38
83
  ## Programmatic API
39
84
 
40
85
  ```ts
@@ -43,7 +88,7 @@ import { analyze, analyzeDirectory, normalize } from 'logo-soup'
43
88
  // Single file
44
89
  const metrics = await analyze('./logo.svg')
45
90
  if (metrics) {
46
- const dimensions = normalize(metrics, { baseSize: 48 })
91
+ const dimensions = normalize(metrics)
47
92
  console.log(dimensions) // { width, height, offsetX, offsetY }
48
93
  }
49
94
 
@@ -104,7 +149,7 @@ Converts raw metrics into display dimensions using aspect ratio normalization wi
104
149
  function normalize(metrics: Metrics, options?: NormalizeOptions): NormalizedDimensions
105
150
 
106
151
  interface NormalizeOptions {
107
- /** Base size in pixels (default: 48) */
152
+ /** Base size in pixels (default: 64) */
108
153
  baseSize?: number
109
154
  /** Aspect ratio normalization factor, 0–1 (default: 0.5) */
110
155
  scaleFactor?: number
package/dist/cli.mjs CHANGED
@@ -1,13 +1,14 @@
1
- import { c as DENSITY_FACTOR, d as SCALE_FACTOR, i as BASE_SIZE, o as DEFAULT_EXTENSIONS, r as analyzeDirectory, t as normalize } from "./normalize-BN3StFfP.mjs";
1
+ import { c as DENSITY_FACTOR, d as SCALE_FACTOR, i as BASE_SIZE, o as DEFAULT_EXTENSIONS, r as analyzeDirectory, t as normalize } from "./normalize-B5apzI38.mjs";
2
2
  import * as fsp from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import process from "node:process";
5
5
  import { defineCommand, runMain } from "citty";
6
6
  import { consola } from "consola";
7
+ import { colors } from "consola/utils";
7
8
 
8
9
  //#region package.json
9
10
  var name = "logo-soup";
10
- var version = "0.1.0";
11
+ var version = "0.2.1";
11
12
  var description = "Normalize logo dimensions for visual balance";
12
13
 
13
14
  //#endregion
@@ -62,10 +63,9 @@ const command = defineCommand({
62
63
  const baseSize = parseNumericArg(args["base-size"], "base-size", BASE_SIZE);
63
64
  const scaleFactor = parseNumericArg(args["scale-factor"], "scale-factor", SCALE_FACTOR);
64
65
  const densityFactor = parseNumericArg(args["density-factor"], "density-factor", DENSITY_FACTOR);
65
- const extensions = args.extensions ? args.extensions.split(",").map((ext) => ext.trim().toLowerCase()) : DEFAULT_EXTENSIONS;
66
- consola.start(`Analyzing logos in ${dirPath}`);
67
- const metricsMap = await analyzeDirectory(dirPath, { extensions });
66
+ const metricsMap = await analyzeDirectory(dirPath, { extensions: args.extensions ? args.extensions.split(",").map((ext) => ext.trim().toLowerCase()) : DEFAULT_EXTENSIONS });
68
67
  const results = {};
68
+ const entries = [];
69
69
  for (const [file, metrics] of metricsMap) {
70
70
  const dimensions = normalize(metrics, {
71
71
  baseSize,
@@ -73,22 +73,35 @@ const command = defineCommand({
73
73
  densityFactor
74
74
  });
75
75
  results[file] = dimensions;
76
- consola.log(` ${file} → ${dimensions.width}×${dimensions.height}px`);
76
+ entries.push([file, dimensions]);
77
+ }
78
+ console.log();
79
+ console.log(`${colors.cyan("●")} ${colors.bold(name)} ${colors.dim(`v${version}`)}`);
80
+ console.log();
81
+ const maxEntryLength = Math.max(...entries.map(([entry]) => entry.length));
82
+ const total = entries.length;
83
+ for (const [i, [file, dimensions]] of entries.entries()) {
84
+ const branch = i === total - 1 ? "└─" : "├─";
85
+ const dimStr = `${dimensions.width}${colors.dim("×")}${dimensions.height}`;
86
+ const padding = " ".repeat(maxEntryLength - file.length + 2);
87
+ console.log(` ${colors.dim(branch)} ${colors.cyan(file)}${padding}${dimStr}`);
77
88
  }
78
89
  const outputPath = path.resolve(args.output);
79
90
  await fsp.mkdir(path.dirname(outputPath), { recursive: true });
80
91
  await fsp.writeFile(outputPath, `${JSON.stringify(results, null, 2)}\n`);
81
- consola.success(`Wrote ${Object.keys(results).length} entries to ${outputPath}`);
92
+ const relativeOutput = path.relative(process.cwd(), outputPath);
93
+ console.log();
94
+ consola.success(`Wrote ${colors.bold(String(total))} entries to ${colors.cyan(relativeOutput)}`);
82
95
  }
83
96
  });
84
97
  function parseNumericArg(value, name, fallback) {
85
- if (value === void 0) return fallback;
86
- const parsed = Number(value);
87
- if (Number.isNaN(parsed)) {
98
+ if (value == null) return fallback;
99
+ const parsedNumber = Number(value);
100
+ if (Number.isNaN(parsedNumber)) {
88
101
  consola.error(`Invalid value for --${name}: "${value}" (expected a number)`);
89
102
  process.exit(1);
90
103
  }
91
- return parsed;
104
+ return parsedNumber;
92
105
  }
93
106
  runMain(command);
94
107
 
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as CONTRAST_THRESHOLD, c as DENSITY_FACTOR, d as SCALE_FACTOR, i as BASE_SIZE, l as REFERENCE_DENSITY, n as analyze, o as DEFAULT_EXTENSIONS, r as analyzeDirectory, s as DENSITY_DAMPENING, t as normalize, u as SAMPLE_MAX_SIZE } from "./normalize-BN3StFfP.mjs";
1
+ import { a as CONTRAST_THRESHOLD, c as DENSITY_FACTOR, d as SCALE_FACTOR, i as BASE_SIZE, l as REFERENCE_DENSITY, n as analyze, o as DEFAULT_EXTENSIONS, r as analyzeDirectory, s as DENSITY_DAMPENING, t as normalize, u as SAMPLE_MAX_SIZE } from "./normalize-B5apzI38.mjs";
2
2
 
3
3
  export { BASE_SIZE, CONTRAST_THRESHOLD, DEFAULT_EXTENSIONS, DENSITY_DAMPENING, DENSITY_FACTOR, REFERENCE_DENSITY, SAMPLE_MAX_SIZE, SCALE_FACTOR, analyze, analyzeDirectory, normalize };
@@ -8,7 +8,7 @@ const SAMPLE_MAX_SIZE = 200;
8
8
  /** Minimum contrast threshold to consider a pixel as content. */
9
9
  const CONTRAST_THRESHOLD = 10;
10
10
  /** Base size in pixels for the normalized output. */
11
- const BASE_SIZE = 48;
11
+ const BASE_SIZE = 64;
12
12
  /** Aspect ratio normalization factor (0–1). */
13
13
  const SCALE_FACTOR = .5;
14
14
  /** Density compensation factor (0–1). */
@@ -38,10 +38,7 @@ async function analyze(filePath, options = {}) {
38
38
  }
39
39
  async function analyzeDirectory(dirPath, options = {}) {
40
40
  const { extensions = DEFAULT_EXTENSIONS, ...analyzeOptions } = options;
41
- const files = (await fsp.readdir(dirPath)).filter((fileName) => {
42
- const ext = path.extname(fileName).slice(1).toLowerCase();
43
- return extensions.includes(ext);
44
- });
41
+ const files = (await fsp.readdir(dirPath)).filter((entry) => extensions.includes(path.extname(entry).slice(1).toLowerCase()));
45
42
  const results = /* @__PURE__ */ new Map();
46
43
  for (const file of files) {
47
44
  const absolutePath = path.resolve(dirPath, file);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "logo-soup",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.1",
5
5
  "packageManager": "pnpm@10.30.0",
6
6
  "description": "Normalize logo dimensions for visual balance",
7
7
  "author": "Johann Schopplich <hello@johannschopplich.com>",