Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/cards/gist.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,16 @@ const renderGistCard = (gistData, options = {}) => {
theme,
});

const lineWidth = 59;
const lineWidth = 360; // px — card width (400) - x offset (25) - right margin (15)
const fontSize = 13; // px — must match the .description font-size
const linesLimit = 10;
const desc = parseEmojis(description || "No description provided");
const multiLineDescription = wrapTextMultiline(desc, lineWidth, linesLimit);
const multiLineDescription = wrapTextMultiline(
desc,
lineWidth,
linesLimit,
fontSize,
);
const descriptionLines = multiLineDescription.length;
const descriptionSvg = multiLineDescription
.map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
Expand Down
4 changes: 3 additions & 1 deletion src/cards/repo.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
import { repoCardLocales } from "../translations.js";

const ICON_SIZE = 16;
const DESCRIPTION_LINE_WIDTH = 59;
const DESCRIPTION_LINE_WIDTH = 360; // px — card width (400) - x offset (25) - right margin (15)
const DESCRIPTION_FONT_SIZE = 13; // px — must match the .description font-size
const DESCRIPTION_MAX_LINES = 3;

/**
Expand Down Expand Up @@ -91,6 +92,7 @@ const renderRepoCard = (repo, options = {}) => {
desc,
DESCRIPTION_LINE_WIDTH,
descriptionMaxLines,
DESCRIPTION_FONT_SIZE,
);
const descriptionLinesCount = description_lines_count
? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
Expand Down
28 changes: 26 additions & 2 deletions src/common/fmt.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import wrap from "word-wrap";
import { encodeHTML } from "./html.js";
import { measureText } from "./render.js";

/**
* Retrieves num with suffix k(thousands) precise to given decimal places.
Expand Down Expand Up @@ -55,12 +56,17 @@ const formatBytes = (bytes) => {
/**
* Split text over multiple lines based on the card width.
*
* When `fontSize` is provided, `width` is treated as a pixel budget and lines
* are broken using {@link measureText} for font-proportional accuracy.
* Otherwise `width` is a character count and the `word-wrap` library is used.
*
* @param {string} text Text to split.
* @param {number} width Line width in number of characters.
* @param {number} width Line width — pixels when `fontSize` is set, characters otherwise.
* @param {number} maxLines Maximum number of lines.
* @param {number} [fontSize] Font size in px. Enables pixel-width wrapping.
* @returns {string[]} Array of lines.
*/
const wrapTextMultiline = (text, width = 59, maxLines = 3) => {
const wrapTextMultiline = (text, width = 59, maxLines = 3, fontSize) => {
const fullWidthComma = ",";
const encoded = encodeHTML(text);
const isChinese = encoded.includes(fullWidthComma);
Expand All @@ -69,6 +75,24 @@ const wrapTextMultiline = (text, width = 59, maxLines = 3) => {

if (isChinese) {
wrapped = encoded.split(fullWidthComma); // Chinese full punctuation
} else if (fontSize) {
// Pixel-width wrapping: measure raw text so HTML entities (e.g. &#8212;
// for —) are counted as single rendered glyphs, not multiple characters.
const words = text.split(/\s+/).filter(Boolean);
let currentLine = "";

for (const word of words) {
const testLine = currentLine ? `${currentLine} ${word}` : word;
if (measureText(testLine, fontSize) > width && currentLine) {
wrapped.push(encodeHTML(currentLine));
currentLine = word;
} else {
currentLine = testLine;
}
}
if (currentLine) {
wrapped.push(encodeHTML(currentLine));
}
} else {
wrapped = wrap(encoded, {
width,
Expand Down
23 changes: 3 additions & 20 deletions src/common/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ const renderError = ({
};

/**
* Retrieve text length.
* Retrieve text length based on Segoe UI font.
*
* @see https://stackoverflow.com/a/48172630/10629172
* @param {string} str String to measure.
Expand All @@ -197,27 +197,10 @@ const renderError = ({
const measureText = (str, fontSize = 10) => {
// prettier-ignore
const widths = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0.2796875, 0.2765625,
0.3546875, 0.5546875, 0.5546875, 0.8890625, 0.665625, 0.190625,
0.3328125, 0.3328125, 0.3890625, 0.5828125, 0.2765625, 0.3328125,
0.2765625, 0.3015625, 0.5546875, 0.5546875, 0.5546875, 0.5546875,
0.5546875, 0.5546875, 0.5546875, 0.5546875, 0.5546875, 0.5546875,
0.2765625, 0.2765625, 0.584375, 0.5828125, 0.584375, 0.5546875,
1.0140625, 0.665625, 0.665625, 0.721875, 0.721875, 0.665625,
0.609375, 0.7765625, 0.721875, 0.2765625, 0.5, 0.665625,
0.5546875, 0.8328125, 0.721875, 0.7765625, 0.665625, 0.7765625,
0.721875, 0.665625, 0.609375, 0.721875, 0.665625, 0.94375,
0.665625, 0.665625, 0.609375, 0.2765625, 0.3546875, 0.2765625,
0.4765625, 0.5546875, 0.3328125, 0.5546875, 0.5546875, 0.5,
0.5546875, 0.5546875, 0.2765625, 0.5546875, 0.5546875, 0.221875,
0.240625, 0.5, 0.221875, 0.8328125, 0.5546875, 0.5546875,
0.5546875, 0.5546875, 0.3328125, 0.5, 0.2765625, 0.5546875,
0.5, 0.721875, 0.5, 0.5, 0.5, 0.3546875, 0.259375, 0.353125, 0.5890625,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2733333110809326,0.28499999046325686,0.39166667461395266,0.5900000095367431,0.5383333206176758,0.8183333396911621,0.8,0.22999999523162842,0.30166666507720946,0.30166666507720946,0.41666665077209475,0.6833333492279052,0.21666667461395264,0.4,0.21666667461395264,0.3900000095367432,0.5383333206176758,0.5383333206176758,0.5383333206176758,0.5383333206176758,0.5383333206176758,0.5383333206176758,0.5383333206176758,0.5383333206176758,0.5383333206176758,0.5383333206176758,0.21666667461395264,0.21666667461395264,0.6833333492279052,0.6833333492279052,0.6833333492279052,0.4483333110809326,0.9550000190734863,0.6449999809265137,0.5733333110809327,0.6183333396911621,0.7016666889190674,0.5066666603088379,0.48833332061767576,0.6866666793823242,0.7099999904632568,0.26666667461395266,0.35666666030883787,0.5800000190734863,0.4699999809265137,0.8983333587646485,0.7483333110809326,0.753333330154419,0.5599999904632569,0.753333330154419,0.5983333110809326,0.5316666603088379,0.5233333110809326,0.6866666793823242,0.621666669845581,0.9333333015441895,0.5900000095367431,0.553333330154419,0.5699999809265137,0.30166666507720946,0.37833333015441895,0.30166666507720946,0.6833333492279052,0.41500000953674315,0.26833333969116213,0.5083333492279053,0.5883333206176757,0.4616666793823242,0.5883333206176757,0.5233333110809326,0.3133333444595337,0.5883333206176757,0.5666666507720948,0.24166667461395264,0.24166667461395264,0.49666666984558105,0.24166667461395264,0.8616666793823242,0.5666666507720948,0.5866666793823242,0.5883333206176757,0.5883333206176757,0.3483333349227905,0.425,0.33833334445953367,0.5666666507720948,0.4783333301544189,0.7233333110809326,0.45833334922790525,0.4833333492279053,0.45166668891906736,0.30166666507720946,0.24000000953674316,0.30166666507720946,0.6833333492279052
];

const avg = 0.5279276315789471;
const avg = 0.5131403493881227;
return (
str
.split("")
Expand Down
2 changes: 1 addition & 1 deletion tests/renderGistCard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe("test renderGistCard", () => {

expect(
document.getElementsByClassName("description")[0].children[1].textContent,
).toBe("English-language pangram—a sentence that contains all");
).toBe("English-language pangram—a sentence that contains all of the");
});

it("should not trim description if it is short", () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/renderRepoCard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe("Test renderRepoCard", () => {

expect(
document.getElementsByClassName("description")[0].children[1].textContent,
).toBe("English-language pangram—a sentence that contains all");
).toBe("English-language pangram—a sentence that contains all of the");

// Should not trim
document.body.innerHTML = renderRepoCard({
Expand Down
6 changes: 3 additions & 3 deletions tests/renderStatsCard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe("Test renderStatsCard", () => {
});
expect(document.querySelector("svg")).toHaveAttribute(
"width",
"305.81250000000006",
"299.9666657447815",
);

// Test minimum card width with rank and icons.
Expand All @@ -156,7 +156,7 @@ describe("Test renderStatsCard", () => {
});
expect(document.querySelector("svg")).toHaveAttribute(
"width",
"322.81250000000006",
"316.9666657447815",
);

// Test minimum card width with icons but without rank.
Expand Down Expand Up @@ -356,7 +356,7 @@ describe("Test renderStatsCard", () => {

expect(
document.body.getElementsByTagName("svg")[0].getAttribute("width"),
).toBe("305.81250000000006");
).toBe("299.9666657447815");
});

it("should auto resize if hide_rank is true & custom_title is set", () => {
Expand Down