Skip to content

Commit

Permalink
Fix scaling of SVG by removing relying on sharp and calculating a den…
Browse files Browse the repository at this point in the history
…sity option
  • Loading branch information
andy128k committed Feb 16, 2022
1 parent 2b21d58 commit 0c775de
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 54 deletions.
14 changes: 5 additions & 9 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import sharp from "sharp";
import { toIco } from "./ico.js";
import { FaviconImage } from "./index.js";
import { IconOptions } from "./config/defaults.js";
import { SvgTool } from "./svgtool.js";
import { svgDensity } from "./svgtool.js";

export type Dictionary<T> = { [key: string]: T };

Expand Down Expand Up @@ -124,12 +124,6 @@ export function relativeTo(
}

export class Images {
#svgtool: SvgTool;

constructor() {
this.#svgtool = new SvgTool();
}

bestSource(
sourceset: SourceImage[],
width: number,
Expand All @@ -153,8 +147,10 @@ export class Images {
pixelArt: boolean
): Promise<Buffer> {
if (source.metadata.format === "svg") {
const svgBuffer = await this.#svgtool.ensureSize(source, width, height);
return await sharp(svgBuffer)
const options = {
density: svgDensity(source.metadata, width, height),
};
return await sharp(source.data, options)
.resize({
width,
height,
Expand Down
65 changes: 20 additions & 45 deletions src/svgtool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import xml2js from "xml2js";
import { SourceImage } from "./helpers.js";
import sharp from "sharp";

// sharp renders the SVG in its source width and height with 72 DPI which can
// cause a blurry result in case the source SVG is defined in lower size than
Expand All @@ -11,50 +10,26 @@ import { SourceImage } from "./helpers.js";
// https://github.com/lovell/sharp/issues/729#issuecomment-284708688
//
// They suggest setting the image density to a "resized" density based on the
// target render size but this does not seem to work with favicons and may
// cause other errors with "unnecessarily high" image density values.
// target render size.
//
// For further information, see:
// Also, see:
// https://github.com/itgalaxy/favicons/issues/264
export class SvgTool {
async ensureSize(
svgSource: SourceImage,
width: number,
height: number
): Promise<Buffer> {
let svgWidth = svgSource.metadata.width;
let svgHeight = svgSource.metadata.height;

if (svgWidth >= width && svgHeight >= height) {
// If the base SVG is large enough, it does not need to be modified.
return svgSource.data;
} else if (width > height) {
svgHeight = Math.round(svgHeight * (width / svgWidth));
svgWidth = width;
} else {
// width <= height
svgWidth = Math.round(svgWidth * (height / svgHeight));
svgHeight = height;
}

// Modify the source SVG's width and height attributes for sharp to render
// it correctly.
return await this.resize(svgSource.data, svgWidth, svgHeight);
}

async resize(
svgFile: Buffer,
width: number,
height: number
): Promise<Buffer> {
const xmlDoc = await xml2js.parseStringPromise(svgFile);

xmlDoc.svg.$.width = width;
xmlDoc.svg.$.height = height;

const builder = new xml2js.Builder();
const modifiedSvg = builder.buildObject(xmlDoc);

return Buffer.from(modifiedSvg);
export function svgDensity(
metadata: sharp.Metadata,
width: number,
height: number
): number | undefined {
if (!metadata.width || !metadata.height) {
return undefined;
}
const currentDensity = metadata.density ?? 72;
return Math.min(
Math.max(
1,
currentDensity,
(currentDensity * width) / metadata.width,
(currentDensity * height) / metadata.height
),
100000
);
}

0 comments on commit 0c775de

Please sign in to comment.