source_utils_logoLayout.bs

' How big ItemDetails draws its logo, from the image's size alone.
'
' Pure on purpose: no node access, no m, no globals. The same arithmetic places the logo
' when its image loads AND predicts where it will be before the image arrives (from the
' original size the server reports), so the two can never disagree.

' The size the server sends for an image request bounded by maxWidth x maxHeight: scaled
' down to fit, aspect kept, never scaled up. Measured 2026-09-22 against Jellyfin 12.1:
' the served size is the scaled size ROUNDED to the nearest pixel (15 of 15 logos;
' truncating was a pixel short on 6 of them).
' @param {integer} width - the original image's width
' @param {integer} height - the original image's height
' @param {integer} maxWidth - the request's maxWidth
' @param {integer} maxHeight - the request's maxHeight
' @return {object} { width, height }, or invalid when either original side is unknown (0)
function serverFitSize(width as integer, height as integer, maxWidth as integer, maxHeight as integer) as object
  if width <= 0 or height <= 0 then return invalid
  scale = 1.0
  if maxWidth / width < scale then scale = maxWidth / width
  if maxHeight / height < scale then scale = maxHeight / height
  return { width: int(width * scale + 0.5), height: int(height * scale + 0.5) }
end function

' The size the logo is drawn at, from the size of the bitmap that loads.
'
' A small logo is scaled up to limits.minHeight, keeping aspect, unless that would make it
' wider than limits.maxWidth: maxWidth is a hard layout bound, minHeight only a target.
' When capHeight is set, a tall image is then scaled down to limits.maxHeight so it stays
' clear of the text rows above it.
' @param {integer} width - the bitmap's width
' @param {integer} height - the bitmap's height
' @param {object} limits - { minHeight, maxWidth, maxHeight }
' @param {boolean} capHeight - whether limits.maxHeight applies
' @return {object} { width, height }, or invalid when either side is unknown (0)
function logoDisplaySize(width as integer, height as integer, limits as object, capHeight as boolean) as object
  if width <= 0 or height <= 0 then return invalid

  scaleToMinHeight = limits.minHeight / height
  if height >= limits.minHeight and width <= limits.maxWidth
    displayWidth = width
    displayHeight = height
  else if int(width * scaleToMinHeight) <= limits.maxWidth
    displayWidth = int(width * scaleToMinHeight)
    displayHeight = limits.minHeight
  else
    displayWidth = limits.maxWidth
    displayHeight = int(height * limits.maxWidth / width)
  end if

  if capHeight and displayHeight > limits.maxHeight
    displayWidth = int(displayWidth * limits.maxHeight / displayHeight)
    displayHeight = limits.maxHeight
  end if

  return { width: displayWidth, height: displayHeight }
end function