Skip to content

Instantly share code, notes, and snippets.

@TarVK
Last active April 25, 2025 03:17
Show Gist options
  • Save TarVK/9b81f9540754b676945ff23d1178501f to your computer and use it in GitHub Desktop.
Save TarVK/9b81f9540754b676945ff23d1178501f to your computer and use it in GitHub Desktop.
Obtain the font size for text to fit a given width or height, using binary search
/** A type for font info */
type IFontStyle = {
family: string;
style?: "normal" | "italic" | "oblique";
weight?:
| "normal"
| "bold"
| "bolder"
| "lighter"
| "100"
| "200"
| "300"
| "400"
| "500"
| "600"
| "700"
| "800"
| "900";
};
type IFont = IFontStyle & {
size?: number;
};
/**
* Retrieves the font object from an element
* @param element The element to retrieve the font fro;m
* @returns The font object
*/
const getFontFromElement = (element: HTMLElement): IFont => {
const style = window.getComputedStyle(element);
return {
family: style.fontFamily,
size: parseFloat(style.fontSize),
style: style.fontStyle as any,
weight: style.fontWeight as any
};
};
/**
* Retrieves the font as a string to be set on a canvas
* @param font The font object
* @returns The font string
*/
const getFontString = (font: IFont): string => {
return `${font.style || ""} ${font.weight || ""} ${font.size}px ${
font.family
}`;
};
/**
* Retrieves the font size given the max width and or height, text and font style
* @returns The font size
*/
const getFontSize = ({
width = Infinity,
height = Infinity,
text,
font
}: {
width?: number;
height?: number;
text: string;
font: IFontStyle;
}): number => {
let step = 256;
let size = step;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
ctx.font = getFontString(font);
while (step > 1) {
step /= 2;
ctx.font = getFontString({...font, size});
const measurement = ctx.measureText(text);
const msrHeight =
measurement.actualBoundingBoxAscent + measurement.actualBoundingBoxDescent;
size +=
(measurement.width < width && msrHeight < height ? (step == 1 ? 0 : 1) : -1) *
step; //(step==1?0:1) ensures that the last step doesn't enlarge which could have caused text to not fit anymore
}
return size;
};
@TarVK
Copy link
Author

TarVK commented Mar 15, 2020

And a hook for usage to fit a component's width in react

import {useRef, useState} from "react";
import {calculateFontSize, getFontFromElement} from "./calculateFontSize";

/**
 * Obtains the fontsize
 * @param text The text to fill the container with
 * @param maxHeight The max height the text may have
 * @returns The fontSize followed by a setter for the container
 */
export const useFillFontSize = (text: string, maxHeight: number = Infinity) => {
    const fontSize = useRef(-1);
    const prevText = useRef("");
    const [textContainer, setTextContainer] = useState(null as null | HTMLElement);
    if ((fontSize.current == -1 || prevText.current != text) && textContainer) {
        prevText.current = text;
        fontSize.current = calculateFontSize({
            width: textContainer.clientWidth,
            height: maxHeight,
            text,
            font: getFontFromElement(textContainer),
        });
    }
    return [fontSize.current, setTextContainer] as const;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment