diff --git a/app/index.html b/app/index.html index 59f0255..86650cb 100644 --- a/app/index.html +++ b/app/index.html @@ -955,6 +955,7 @@ async function generatePDF() { state.items.forEach(item => { // Item header needSpace(lh * 6); // need room for at least header + one line + pg.drawLine({start:{x:M.left, y:y+3}, end:{x:M.left+W, y:y+3}, thickness:0.75, color:accent}); pg.drawText('ITEM / PROJECT / TRAVEL', {x:M.left, y, size:sz, font:fontBold, color:accent}); const subStr = `Subtotal: ${baseCur} ${fmtAmt(item._subtotal)}`; const subW = fontBold.widthOfTextAtSize(subStr, sz); @@ -967,9 +968,26 @@ async function generatePDF() { item.lines.forEach((ln, li) => { needSpace(lh * 8); - // Thin grey rule above each vendor line + // Spacing between consecutive expense lines + if (!justBroke && li > 0) y -= 6; + + // Pre-calculate content height so zebra stripe rect can be drawn before text + const stripeH = (() => { + let h = (lh + 2) * 2; // vendor + description rows + if (ln.hasReceipt && ln.receipts.length > 0) h += lh * ln.receipts.length; + else if (!ln.hasReceipt) h += lh * Math.max(1, wrapText(ln.noReceiptExplanation||'–', fontBody, szSm, W*0.6).length); + else h += lh; + h += lh * Math.max(1, (ln.programs||[]).length); + return h + 4; + })(); + + // Zebra stripe: odd-indexed lines get a light grey background + if (li % 2 === 1) { + pg.drawRectangle({x:M.left, y:y-stripeH, width:W, height:stripeH+lh+2, color:rgb(0.95,0.95,0.95)}); + } + + // Thin grey rule above vendor text (drawn on top of stripe) if (!justBroke) { - if (li > 0) y -= 6; pg.drawLine({start:{x:M.left, y:y+lh+1}, end:{x:M.left+W, y:y+lh+1}, thickness:0.3, color:lineCol}); }