Skip to content

feat: dynamically size thumbnail to fill remaining space#162

Merged
TobiTenno merged 9 commits intoWFCD:mainfrom
SlayerOrnstein:feat-dynamic-thumb
Feb 23, 2026
Merged

feat: dynamically size thumbnail to fill remaining space#162
TobiTenno merged 9 commits intoWFCD:mainfrom
SlayerOrnstein:feat-dynamic-thumb

Conversation

@SlayerOrnstein
Copy link
Member

@SlayerOrnstein SlayerOrnstein commented Apr 6, 2025

What did you fix?

Noticed this is how it is done in-game plus it gets rid of some of the dead space for one liners


Reproduction steps


Evidence/screenshot/link to line

Considerations

  • Does this contain a new dependency? No
  • Does this introduce opinionated data formatting or manual data entry? No
  • Does this pr include updated data files in a separate commit that can be reverted for a clean code-only pr? Yes
  • Have I run the linter? Yes
  • Is is a bug fix, feature request, or enhancement? Enhancement

Summary by CodeRabbit

  • New Features

    • Dynamic layout: thumbnails and titles now reposition based on text size so images and text fit together.
    • Conditional description rendering with improved wrapping and centered alignment for multi-line descriptions.
    • Text-height calculation added to ensure consistent spacing between title, description, and image.
  • Refactor

    • Adjusted vertical positioning and padding for improved visual balance and layout consistency.
  • Documentation

    • README image links updated to local asset paths.

@coderabbitai
Copy link

coderabbitai bot commented Apr 6, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Background image rendering was refactored to use dynamic layout: horizontal padding increased, text block height is measured via a new textHeight util, thumbnail and title positions are computed from that height, and descriptions are wrapped and drawn to fit maxWidth.

Changes

Cohort / File(s) Summary
Drawing logic
src/drawers.ts
Increased horizantalPad 7→8. Reworked backgroundImage to compute maxWidth, call textHeight, track a drawing position, adjust thumbnail height/placement, draw mod name after thumbnail, and wrap/draw description lines conditionally (removed fixed-position name/description).
Text measurement util
src/utils.ts
Added textHeight(context: SKRSContext2D, maxWidth: number, title: string, lines?: string[]): number that measures title + wrapped lines and returns total text block height (includes bottom spacing).
Docs
README.md
Replaced external raw.githubusercontent image links with local relative asset paths under /assets/readme/....

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant BackgroundImage
    participant TextHeight
    participant Canvas

    Caller->>BackgroundImage: backgroundImage(props)
    Note right of BackgroundImage: compute maxWidth, prepare title/lines/thumb
    BackgroundImage->>TextHeight: textHeight(context, maxWidth, title, lines)
    TextHeight-->>BackgroundImage: modTextHeight
    BackgroundImage->>Canvas: draw background
    BackgroundImage->>Canvas: draw thumbnail at tracked position (using modTextHeight)
    BackgroundImage->>Canvas: draw mod name at computed position
    BackgroundImage->>Canvas: wrapText & draw description lines
    BackgroundImage-->>Caller: return final image
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • TobiTenno

Poem

I'm a rabbit, hopping through the stack,
Padding nudged to eight — no looking back.
Text now measures, wraps, and finds its place,
Thumbs tuck in neatly, titles fall in grace.
A tiny hop of code, with a cheerful face 🐇✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objective: making thumbnail sizing dynamic based on remaining space, which is the primary focus of the changes across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/utils.ts (1)

214-232: Consider parameterizing or synchronizing line spacing.
The function currently uses a hardcoded bottom line spacing of 15, while in other parts of the code (e.g., src/drawers.ts), there's a separate line spacing of 15 as well. It might be beneficial to unify these spacing values to avoid mismatches or to consider parameterizing them, so that future adjustments don’t require scattered changes.

src/drawers.ts (2)

17-17: Correct the spelling of “horizontalPad.”
Please consider renaming horizantalPad to horizontalPad for clarity and consistency.

-export const horizantalPad = 8;
+export const horizontalPad = 8;

167-187: Unify text spacing between measurement and drawing.
You’re adding manual line spacing of 15 here, but the total text height is derived elsewhere using ascent and descent metrics (plus a constant). Consider consolidating the spacing logic to keep the visual spacing consistent and reduce potential discrepancies between measured and rendered text spacing.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e660e3 and 2c2b645.

📒 Files selected for processing (2)
  • src/drawers.ts (2 hunks)
  • src/utils.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/drawers.ts (1)
src/utils.ts (4)
  • modDescription (121-138)
  • textHeight (215-232)
  • textColor (179-183)
  • wrapText (140-159)
🔇 Additional comments (2)
src/drawers.ts (2)

13-13: Looks good.
Importing textHeight is consistent with its usage. No concerns here.


151-155:

Details

❓ Verification inconclusive

Ensure large text doesn’t cause layout problems.
modTextHeight can potentially become very large for lengthy descriptions, which could shrink or overlap content unexpectedly. Double-check how large text is handled upstream to confirm that it won’t disrupt your layout.


🏁 Script executed:

#!/bin/bash
# Searching for calls to `backgroundImage` to see if there's a limit on description sizes
rg -A 3 $'backgroundImage'

Length of output: 801


Verify that extremely long mod descriptions are safely handled.
The calculation of modTextHeight in src/drawers.ts (lines 151–155) may yield very large values when processing lengthy descriptions, which risks shrinking or overlapping content in the layout. Our search for references to backgroundImage in both src/generator.ts and src/drawers.ts did not reveal any explicit limits or safeguards on the description’s size upstream. Please verify that the textHeight logic and any associated layout mechanisms (especially where backgroundImage is used) adequately constrain or adjust for oversized text to maintain a robust design.

@SlayerOrnstein SlayerOrnstein changed the title feat: thumbnail height is dynamically sized based on description height feat: dynamically size thumbnail to take up empty space Feb 23, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/drawers.ts (2)

4-13: ⚠️ Potential issue | 🔴 Critical

textHeight is missing from the imports — will fail to compile.

textHeight is called at line 153 but is not imported from './utils.js'.

🐛 Proposed fix
 import {
   fetchModPiece,
   fetchPolarity,
   flip,
   getTier,
   modDescription,
   modRarityMap,
   textColor,
+  textHeight,
   wrapText,
 } from './utils.js';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/drawers.ts` around lines 4 - 13, The file is missing the textHeight
import from ./utils.js which causes a compile error where textHeight is used
(around line where textHeight(...) is called); update the import list at the top
(the existing import that brings in fetchModPiece, fetchPolarity, flip, getTier,
modDescription, modRarityMap, textColor, wrapText) to also import textHeight
from './utils.js' so the symbol is available to the function that measures text
height.

216-240: ⚠️ Potential issue | 🔴 Critical

Old static rendering code not removed — root cause of all four CI failures and double-draws mod name + description.

The new dynamic rendering logic (lines 167–188) supersedes this block entirely. Leaving it in redeclares description (line 224), lines (line 227), and maxWidth (line 233) in the same function scope, causing all four biome lint errors. It also draws mod.name a second time at the fixed position canvas.height * 0.52 and re-renders the description starting at canvas.height * 0.56, producing overlapping artifacts.

🐛 Proposed fix — remove the stale block
-  context.fillStyle = textColor(tier);
-
-  context.textAlign = 'center';
-  context.textBaseline = 'middle';
-  context.font = '400 16px "Roboto"';
-
-  context.fillText(mod.name, canvas.width * 0.5, canvas.height * 0.52);
-
-  const description = modDescription(mod.description, mod.levelStats, rank ?? 0);
-  if (description && description.length > 0) {
-    const x = canvas.width * 0.5;
-    const lines = description.split('\n');
-
-    context.font = '12px "Roboto"';
-    let start = canvas.height * 0.56;
-
-    lines.forEach((line) => {
-      const maxWidth = background.width * 0.8;
-      const texts = wrapText(context, line, maxWidth);
-      texts.forEach((text) => {
-        context.fillText(text, x, start, maxWidth);
-        start += 15;
-      });
-    });
-  }
-
   return loadImage(await canvas.encode('png'));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/drawers.ts` around lines 216 - 240, This block duplicates the new dynamic
rendering and redeclares variables (description, lines, maxWidth) and causes
double-drawing of the mod name and description; remove the entire stale static
rendering block starting at the context.fillStyle = textColor(tier); ... through
the closing brace that handles description rendering so only the new dynamic
rendering (lines ~167–188) remains, ensuring you keep references to
modDescription(...) and wrapText(...) only in the new code path and do not
re-declare description/lines/maxWidth in the same function scope.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/drawers.ts`:
- Line 153: textHeight is being called before the canvas font is set so
measurements use the default font; fix by setting the description/body font on
the canvas context before computing modTextHeight (i.e., move or add the
context.font assignment used for descriptions so it runs prior to the call to
textHeight), ensuring the measured height matches the rendered description;
reference functions/variables: textHeight, modTextHeight, and the context.font
assignment that currently sets "400 16px Roboto" (adjust to the description font
used when rendering).

---

Outside diff comments:
In `@src/drawers.ts`:
- Around line 4-13: The file is missing the textHeight import from ./utils.js
which causes a compile error where textHeight is used (around line where
textHeight(...) is called); update the import list at the top (the existing
import that brings in fetchModPiece, fetchPolarity, flip, getTier,
modDescription, modRarityMap, textColor, wrapText) to also import textHeight
from './utils.js' so the symbol is available to the function that measures text
height.
- Around line 216-240: This block duplicates the new dynamic rendering and
redeclares variables (description, lines, maxWidth) and causes double-drawing of
the mod name and description; remove the entire stale static rendering block
starting at the context.fillStyle = textColor(tier); ... through the closing
brace that handles description rendering so only the new dynamic rendering
(lines ~167–188) remains, ensuring you keep references to modDescription(...)
and wrapText(...) only in the new code path and do not re-declare
description/lines/maxWidth in the same function scope.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6f634 and ab6c52c.

📒 Files selected for processing (2)
  • src/drawers.ts
  • src/utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils.ts

@SlayerOrnstein SlayerOrnstein changed the title feat: dynamically size thumbnail to take up empty space feat: dynamically size thumbnail to take up remaining space Feb 23, 2026
@SlayerOrnstein SlayerOrnstein changed the title feat: dynamically size thumbnail to take up remaining space feat: dynamically size thumbnail to fill remaining space Feb 23, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/drawers.ts (1)

151-154: ⚠️ Potential issue | 🟠 Major

textHeight still called before context.font is set — measurements use the default "10px sans-serif".

context.font is first assigned at line 171 ('400 16px "Roboto"') and 178 ('12px "Roboto"'). All measureText calls inside textHeight therefore operate on the default font, underestimating modTextHeight and causing thumbHeight to be larger than expected, which can push description text behind the thumbnail.

🔧 Minimal fix — set body font before the call
+ context.font = '12px "Roboto"';
  const modTextHeight = textHeight(context, maxWidth, mod.name, lines);

For a fully accurate title measurement, textHeight in src/utils.ts could accept a separate titleFont parameter and set it on the context before measuring the title, but that is a broader change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/drawers.ts` around lines 151 - 154, The measured mod text height is
computed before the canvas font is set, so textHeight(context, ...) uses the
default font and underestimates heights; fix by ensuring context.font is set to
the intended body/title font before calling textHeight (or update textHeight to
accept a font parameter and set context.font inside it) — locate the call to
textHeight in this file (modTextHeight = textHeight(context, maxWidth, mod.name,
lines)) and either set context.font = '400 16px "Roboto"' (or the appropriate
font used later) immediately before that call or change textHeight signature to
accept a titleFont and set context.font there prior to measurements.
🧹 Nitpick comments (2)
src/drawers.ts (2)

179-188: lineSpacing = 15 in the rendering loop doesn't match textHeight's per-line metric.

textHeight (src/utils.ts:194) accumulates actualBoundingBoxAscent + actualBoundingBoxDescent per wrapped line (≈ 11–13 px for 12 px Roboto), while the rendering loop below advances start by a fixed lineSpacing = 15. The mismatch grows with line count: for an N-line description the measured space is roughly N×12 + 15 px, but the rendered space is N×15 px. The two happen to meet near 5 lines, so for typical short Warframe mod descriptions this leaves only a small visual gap rather than an overlap. Aligning the per-line accumulation in textHeight to use the same lineSpacing constant would make the two functions consistent.

♻️ Option — use a shared lineSpacing constant

In src/utils.ts:

+ export const LINE_SPACING = 15;

  export const textHeight = (context: SKRSContext2D, maxWidth: number, title: string, lines?: string[]): number => {
-   const bottomLineSpacing = 15;
+   const bottomLineSpacing = LINE_SPACING;
    const titleMetrics = context.measureText(title);
    let height = titleMetrics.actualBoundingBoxAscent + titleMetrics.actualBoundingBoxDescent;
    if (lines) {
      lines.forEach((line) => {
        const text = wrapText(context, line, maxWidth);
        text.forEach((t) => {
-         const metrics = context.measureText(t);
-         height += metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
+         height += LINE_SPACING;
        });
      });
    }
    return height + bottomLineSpacing;
  };

In src/drawers.ts:

+ import { ..., LINE_SPACING, ... } from './utils.js';
  ...
- const lineSpacing = 15;
+ const lineSpacing = LINE_SPACING;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/drawers.ts` around lines 179 - 188, The rendering loop in drawText uses a
hardcoded lineSpacing = 15 which diverges from the per-line metric calculated by
textHeight (function textHeight in src/utils.ts); make the two consistent by
extracting a shared lineSpacing constant (or exported function/constant) and use
it in both drawers.ts (where start is advanced after context.fillText in the
wrapText loop) and in textHeight so per-line accumulation matches the actual
render step; update references to the variable in the wrapText usage and
textHeight implementation to import/use that shared constant.

151-160: maxWidth and thumbWidth use different formulas — only coincidentally equal at W = 160.

maxWidth = background.width * 0.9 while thumbWidth = canvas.width - horizantalPad * 2. They diverge for any background image width other than 160 px, so text wrapping width and thumbnail width won't match for other frame sizes.

♻️ Unify the formulas
- const maxWidth = background.width * 0.9;
+ const maxWidth = canvas.width - horizantalPad * 2;
  const description = modDescription(mod.description, mod.levelStats, rank ?? 0);
  const lines = description?.split('\n');
  const modTextHeight = textHeight(context, maxWidth, mod.name, lines);

  let position = canvas.height * 0.17;
  if (mod.imageName || image) {
    const thumb = await loadImage(image ?? `https://cdn.warframestat.us/img/${mod.imageName}`);
-   const thumbWidth = canvas.width - horizantalPad * 2;
+   const thumbWidth = maxWidth;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/drawers.ts` around lines 151 - 160, The text wrapping width and thumbnail
width are calculated differently which causes mismatch; make them use the same
formula by computing a single maxWidth based on the rendered canvas frame and
horizontal padding (e.g., replace maxWidth = background.width * 0.9 with
maxWidth = canvas.width - horizantalPad * 2) and then set thumbWidth = maxWidth
(remove the separate canvas.width expression). Update uses of maxWidth (e.g., in
textHeight and when sizing the thumb before loadImage) so both text wrapping and
thumbnail sizing reference the same variable (maxWidth/thumbWidth) — changes
affect maxWidth, thumbWidth, textHeight(context, ...), and the image sizing
logic around loadImage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/drawers.ts`:
- Around line 151-154: The measured mod text height is computed before the
canvas font is set, so textHeight(context, ...) uses the default font and
underestimates heights; fix by ensuring context.font is set to the intended
body/title font before calling textHeight (or update textHeight to accept a font
parameter and set context.font inside it) — locate the call to textHeight in
this file (modTextHeight = textHeight(context, maxWidth, mod.name, lines)) and
either set context.font = '400 16px "Roboto"' (or the appropriate font used
later) immediately before that call or change textHeight signature to accept a
titleFont and set context.font there prior to measurements.

---

Nitpick comments:
In `@src/drawers.ts`:
- Around line 179-188: The rendering loop in drawText uses a hardcoded
lineSpacing = 15 which diverges from the per-line metric calculated by
textHeight (function textHeight in src/utils.ts); make the two consistent by
extracting a shared lineSpacing constant (or exported function/constant) and use
it in both drawers.ts (where start is advanced after context.fillText in the
wrapText loop) and in textHeight so per-line accumulation matches the actual
render step; update references to the variable in the wrapText usage and
textHeight implementation to import/use that shared constant.
- Around line 151-160: The text wrapping width and thumbnail width are
calculated differently which causes mismatch; make them use the same formula by
computing a single maxWidth based on the rendered canvas frame and horizontal
padding (e.g., replace maxWidth = background.width * 0.9 with maxWidth =
canvas.width - horizantalPad * 2) and then set thumbWidth = maxWidth (remove the
separate canvas.width expression). Update uses of maxWidth (e.g., in textHeight
and when sizing the thumb before loadImage) so both text wrapping and thumbnail
sizing reference the same variable (maxWidth/thumbWidth) — changes affect
maxWidth, thumbWidth, textHeight(context, ...), and the image sizing logic
around loadImage.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab6c52c and a5b1076.

⛔ Files ignored due to path filters (5)
  • assets/readme/Afterburn.png is excluded by !**/*.png
  • assets/readme/Archgun_Riven_Mod.png is excluded by !**/*.png
  • assets/readme/Primed_Flow.png is excluded by !**/*.png
  • assets/readme/Steel_Charge.png is excluded by !**/*.png
  • assets/readme/Vitality.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • README.md
  • src/drawers.ts
✅ Files skipped from review due to trivial changes (1)
  • README.md

@TobiTenno TobiTenno merged commit 491524c into WFCD:main Feb 23, 2026
2 checks passed
@SlayerOrnstein SlayerOrnstein deleted the feat-dynamic-thumb branch February 23, 2026 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants