function autoFitText(element, text, maxFontSize = 10, minFontSize = 3.5, isMultiline = false, useEllipsis = false) {
if (!element) return;
element.textContent = text;
// Add flex styling for consistent single-line alignment as in original script
if (!isMultiline) {
element.style.display = 'flex';
element.style.alignItems = 'center';
element.style.lineHeight = '1';
element.style.whiteSpace = 'nowrap';
} else {
element.style.whiteSpace = 'normal';
element.style.wordBreak = 'break-word';
}
element.style.fontSize = maxFontSize + 'pt';
element.style.overflow = 'hidden';
if (useEllipsis) {
element.style.textOverflow = 'ellipsis';
element.classList.add('dynamic-text');
return;
}
element.style.textOverflow = 'clip';
// Temporarily force flex-start so scrollWidth measures properly (bypasses right-align overflow bugs)
const originalJustify = element.style.justifyContent;
if (!isMultiline) {
element.style.justifyContent = 'flex-start';
}
let fontSize = maxFontSize;
while (fontSize >= minFontSize) {
if (element.scrollWidth <= element.clientWidth + 1 && element.scrollHeight <= element.clientHeight + 1) {
break;
}
fontSize -= 0.25;
element.style.fontSize = fontSize + 'pt';
}
// Restore original justification
if (!isMultiline) {
element.style.justifyContent = originalJustify;
}
element.classList.add('dynamic-text');
}
function updatePreview(data) {
if (!data) return;
// Apply label font settings before measuring (affects autoFitText width calculations)
const labelFont = document.getElementById('label-font-select')?.value || 'Arial';
const labelBold = document.getElementById('label-bold-toggle')?.checked ? 'bold' : 'normal';
const labelItalic = document.getElementById('label-italic-toggle')?.checked ? 'italic' : 'normal';
['.label-preview', '.label-preview-large', '.label-preview-xlarge'].forEach(sel => {
const el = document.querySelector(sel);
if (el) { el.style.fontFamily = labelFont; el.style.fontWeight = labelBold; el.style.fontStyle = labelItalic; }
});
// Un-hide parent containers to allow element dimensions to be calculated correctly
const hiddenParents = [];
const elementsToCheck = [document.querySelector('.label-preview'), document.querySelector('.label-preview-large'), document.querySelector('.label-preview-xlarge')];
elementsToCheck.forEach(el => {
let current = el;
while (current && current !== document.body) {
const style = window.getComputedStyle(current);
if (style.display === 'none') {
hiddenParents.push({
el: current,
display: current.style.display,
visibility: current.style.visibility,
position: current.style.position
});
current.style.display = 'block';
current.style.visibility = 'hidden';
current.style.position = 'absolute';
}
current = current.parentElement;
}
});
// Per-field style overrides from settings
const lfs = window.labelFieldStyles || {};
const applyFieldStyle = (selector, fieldKey) => {
const s = lfs[fieldKey] || {};
[selector, selector + '-large', selector + '-xlarge'].forEach(sel => {
const el = document.querySelector(sel);
if (!el) return;
if (s.b) el.style.fontWeight = 'bold';
else el.style.fontWeight = '';
if (s.i) el.style.fontStyle = 'italic';
else el.style.fontStyle = '';
});
};
['artist','title','genre','style','format','price','condition','comment','notes'].forEach(f => applyFieldStyle('.print-' + f, f));
applyFieldStyle('.print-bottom-info', 'info');
const updateAllSizes = (selector, text, maxFontSize = 10, minFontSize = 3.5, isMultiline = false, fieldKey = null) => {
const elSmall = document.querySelector(selector);
const elLarge = document.querySelector(selector + '-large');
const elXLarge = document.querySelector(selector + '-xlarge');
const useEllipsis = fieldKey ? !!(lfs[fieldKey] && lfs[fieldKey].e) : false;
autoFitText(elSmall, text, maxFontSize, minFontSize, isMultiline, useEllipsis);
autoFitText(elLarge, text, maxFontSize + 1.5, minFontSize, isMultiline, useEllipsis);
autoFitText(elXLarge, text, maxFontSize + 3, minFontSize, isMultiline, useEllipsis);
};
updateAllSizes('.print-artist', data.artist || '', 8.5, 3.5, false, 'artist');
updateAllSizes('.print-title', data.title || '', 8.5, 3.5, false, 'title');
updateAllSizes('.print-genre', data.genre || '', 8.5, 3.5, false, 'genre');
// Shorten 'Progressive' to 'Prog' for label preview
const styleText = (data.style || '').replace(/Progressive/g, 'Prog');
updateAllSizes('.print-style', styleText, 8.5, 3.5, false, 'style');
let formatText;
if (typeof data.format === 'string' && data.format.trim()) {
formatText = data.format;
} else {
const format = (data.formats || [])[0] || {};
const formatDesc = format.descriptions ? format.descriptions.join(' ') : '';
formatText = `${data.format_quantity || 1}x ${format.name || ''} ${formatDesc}`.trim();
}
// Use smaller base for size logic: description uses 6pt / 8pt max usually
updateAllSizes('.print-description', formatText, 6.5, 3.5, true, 'format');
updateAllSizes('.print-comment', data.comment || '', 7, 3.5, false, 'comment');
updateAllSizes('.print-notes', data.notes || '', 6, 3.5, false, 'notes');
['', '-large', '-xlarge'].forEach(suffix => {
const bottomInfoDiv = document.querySelector(`.print-bottom-info${suffix}`);
if (bottomInfoDiv) {
const yearElement = bottomInfoDiv.querySelector('.year');
const countryElement = bottomInfoDiv.querySelector('.country');
const labelElement = bottomInfoDiv.querySelector('.label');
const hasYear = data.year && data.year !== 0;
// Toggle no-year class to hide year span and its separator
bottomInfoDiv.classList.toggle('no-year', !hasYear);
if (yearElement) {
yearElement.textContent = hasYear ? data.year.toString() : '';
}
if (countryElement && labelElement) {
const countryText = data.country || '';
// Remove ' Records' from label text for print preview only
const labelText = (data.label || '').replace(/ Records$/, '');
countryElement.textContent = countryText;
labelElement.textContent = labelText;
// Dynamically fit the entire row by shrinking only the label
bottomInfoDiv.style.whiteSpace = 'nowrap';
bottomInfoDiv.style.overflow = 'hidden';
let maxFont = suffix === '-xlarge' ? 9 : (suffix ? 8 : 7);
let labelFontSize = maxFont;
countryElement.style.fontSize = maxFont + 'pt';
labelElement.style.fontSize = labelFontSize + 'pt';
while (labelFontSize >= 3) {
if (bottomInfoDiv.scrollWidth <= bottomInfoDiv.clientWidth + 1) {
break;
}
labelFontSize -= 0.25;
labelElement.style.fontSize = labelFontSize + 'pt';
}
}
}
});
['', '-large', '-xlarge'].forEach(suffix => {
const conditionElement = document.querySelector(`.print-condition${suffix}`);
if (conditionElement) {
const mediaMatch = data.mediaCondition?.match(/\((.*?)\)/);
const sleeveMatch = data.sleeveCondition?.match(/\((.*?)\)/);
const mediaCondition = mediaMatch ? mediaMatch[1] : data.mediaCondition;
const sleeveCondition = sleeveMatch ? sleeveMatch[1] : data.sleeveCondition;
const condText = `${mediaCondition} | ${sleeveCondition}`;
const condS = lfs['condition'] || {};
if (condS.b) conditionElement.style.fontWeight = 'bold'; else conditionElement.style.fontWeight = '';
if (condS.i) conditionElement.style.fontStyle = 'italic'; else conditionElement.style.fontStyle = '';
autoFitText(conditionElement, condText, suffix === '-xlarge' ? 8.5 : (suffix ? 7.5 : 6), 3.5, false, !!condS.e);
}
});
['', '-large', '-xlarge'].forEach(suffix => {
const priceElement = document.querySelector(`.print-price${suffix}`);
if (priceElement && data.price) {
const priceText = String(data.price).startsWith('$') ? data.price : `$${data.price}`;
const priceS = lfs['price'] || {};
if (priceS.b) priceElement.style.fontWeight = 'bold'; else priceElement.style.fontWeight = '';
if (priceS.i) priceElement.style.fontStyle = 'italic'; else priceElement.style.fontStyle = '';
autoFitText(priceElement, priceText, suffix === '-xlarge' ? 15 : (suffix ? 13 : 10), 5, false, !!priceS.e);
}
});
['', '-large', '-xlarge'].forEach(suffix => {
const barcodeElement = document.querySelector(`.print-barcode${suffix}`);
if (barcodeElement) {
let qrData = data.sku != null ? data.sku : data.id;
// Labels always show the bare integer SKU — strip -R and -A suffixes unconditionally
if (typeof qrData === 'string') qrData = qrData.replace(/-[AR]$/i, '');
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${qrData}`;
barcodeElement.innerHTML = ``;
}
});
// Restore hidden parents
for (let i = hiddenParents.length - 1; i >= 0; i--) {
const p = hiddenParents[i];
p.el.style.display = p.display;
p.el.style.visibility = p.visibility;
p.el.style.position = p.position;
}
}
function displayReleaseInfo(data) {
const releaseDetailsContainer = document.getElementById('releaseDetails');
if (!releaseDetailsContainer) return;
const fields = [
{ key: 'artist', value: data.artist },
{ key: 'title', value: data.title },
{ key: 'genre', value: data.genre },
{ key: 'style', value: data.style },
{ key: 'label', value: (data.label || '').replace(/ Records$/, '') },
{ key: 'year', value: data.year === 0 ? '' : data.year },
{ key: 'country', value: data.country || 'N/A' },
{ key: 'format', value: `${data.format_quantity || 1} x ${data.formats.map(f => `${f.name} (${f.descriptions?.join(', ') || ''})`).join(', ')}` },
{ key: 'catno', value: data.labels ? data.labels[0].catno : 'N/A' },
{ key: 'releaseId', value: data.id },
{ key: 'comment', value: data.comment },
{ key: 'appleId', value: data.appleId || 'N/A' },
{ key: 'collectionSku', value: data.collectionSku || '' },
];
fields.forEach(field => {
const fieldElement = releaseDetailsContainer.querySelector(`.info-field[data-field="${field.key}"] .value`);
if (fieldElement) {
fieldElement.textContent = field.value;
}
});
const marketInfoContainer = document.querySelector('.market-info');
if (marketInfoContainer) {
marketInfoContainer.innerHTML = `