"""Patch the Call for Papers PDF, whose text is baked into two full-page images.

No source file exists — not on the server, not on the Mac — so the corrections are made in the
pixels, with the same fonts the site and the PDF use (Sora and Fraunces, both fetched from Google
Fonts) and colours sampled from the page rather than guessed.

Three repairs:
  page 1  "Autumn 2026"  →  "1 September 2026"        (white Sora bold on navy)
  page 2  "Autumn 2026"  →  "1 September 2026"        (navy Sora bold on cream)
  page 2  "Special sessions & symposia" heading and its paragraph lose the symposium
          invitation — the six symposia are fixed; only special sessions are proposable.

Text boxes are found by colour thresholding inside a narrow band, never by hard-coded pixel
coordinates alone, so a slightly different render does not silently paint the wrong spot.
"""

from PIL import Image, ImageDraw, ImageFont

SCALE = 3  # renders are 3× the 595×841pt page


def sora(size, weight=700):
    f = ImageFont.truetype('Sora.ttf', size)
    f.set_variation_by_axes([weight])
    return f


def fraunces(size):
    f = ImageFont.truetype('Fraunces.ttf', size)
    # axes: opsz, wght, SOFT, WONK — body text: moderate optical size, regular weight
    f.set_variation_by_axes([28, 400, 0, 0])
    return f


def find_text_box(im, band, predicate):
    """Bounding box of pixels matching `predicate` within band=(x0,y0,x1,y1)."""
    x0, y0, x1, y1 = band
    px = im.load()
    xs, ys = [], []
    for y in range(y0, y1):
        for x in range(x0, x1):
            if predicate(px[x, y]):
                xs.append(x)
                ys.append(y)
    assert xs, 'aucun pixel de texte dans la bande %s' % (band,)
    return min(xs), min(ys), max(xs), max(ys)


def cap_height(box):
    return box[3] - box[1]


def font_for_cap(mk, target_cap, probe='X'):
    """Pick the font size whose capital height matches the original text's."""
    for size in range(10, 120):
        f = mk(size)
        bbox = f.getbbox(probe)
        if bbox[3] - bbox[1] >= target_cap:
            return f
    raise AssertionError


# ---------------------------------------------------------------- page 1
im1 = Image.open('hi-page1.png').convert('RGB')
d1 = ImageDraw.Draw(im1)

is_white = lambda p: p[0] > 225 and p[1] > 225 and p[2] > 225
# the value line sits under the grey "Call opens" label, left column of the footer strip
box = find_text_box(im1, (100, 2030, 760, 2120), is_white)
bg = im1.getpixel((box[0], box[1] - 18))          # navy sampled just above the text
pad = 6
d1.rectangle((box[0] - pad, box[1] - pad, box[2] + pad, box[3] + pad), fill=bg)
f = font_for_cap(lambda s: sora(s, 700), cap_height(box))
# draw aligned to the original left edge and baseline
d1.text((box[0], box[3]), '1 September 2026', font=f, fill=(255, 255, 255), anchor='ls')
print('page 1 : « Autumn 2026 » %s → « 1 September 2026 »' % (box,))

# ---------------------------------------------------------------- page 2 — key dates value
im2 = Image.open('hi-page2.png').convert('RGB')
d2 = ImageDraw.Draw(im2)

is_dark = lambda p: p[0] < 90 and p[1] < 90 and p[2] < 100
box = find_text_box(im2, (900, 975, 1350, 1040), is_dark)
bg = im2.getpixel((box[2] + 40, box[1] + 5))      # cream to the right of the value
pad = 6
d2.rectangle((box[0] - pad, box[1] - pad, box[2] + 260, box[3] + pad), fill=bg)
navy = im2.getpixel((950, 1060))                   # colour of the row below ("12 February 2027" is gold; use heading navy)
f = font_for_cap(lambda s: sora(s, 700), cap_height(box))
d2.text((box[0], box[3]), '1 September 2026', font=f, fill=(18, 34, 44), anchor='ls')
print('page 2 : valeur Key dates %s remplacée' % (box,))

# ---------------------------------------------------------------- page 2 — heading
box = find_text_box(im2, (120, 1530, 1100, 1610), is_dark)
# erase everything after "Special sessions": find the width of that prefix in the matched font
f_head = font_for_cap(lambda s: sora(s, 700), cap_height(box), probe='S')
bg = im2.getpixel((1200, box[1]))
keep_w = d2.textlength('Special sessions', font=f_head)
d2.rectangle((box[0] + keep_w - 2, box[1] - 8, box[2] + 10, box[3] + 14), fill=bg)
print('page 2 : titre tronqué après « Special sessions » (larg. conservée %d px)' % keep_w)

# ---------------------------------------------------------------- page 2 — paragraph
# the three serif lines under the heading
is_grey = lambda p: p[0] < 120 and p[1] < 120 and p[2] < 120
box = find_text_box(im2, (120, 1620, 1750, 1830), is_grey)
bg = im2.getpixel((1700, box[1] - 10))
d2.rectangle((box[0] - 4, box[1] - 8, 1760, box[3] + 10), fill=bg)

text = ('Researchers are invited to propose Special Sessions on focused or rapidly developing '
        'areas within the scope of META 2027. Proposals should identify the scientific theme, '
        'the organizers and prospective contributors.')
# line height measured from the original three lines
line_h = (box[3] - box[1]) / 3.0 * 1.02
f_body = font_for_cap(fraunces, 30, probe='R')     # ~ the original body size at this scale
# wrap to the original column width
words, lines, cur = text.split(), [], ''
maxw = 1620 - box[0]
for w in words:
    t = (cur + ' ' + w).strip()
    if d2.textlength(t, font=f_body) > maxw and cur:
        lines.append(cur)
        cur = w
    else:
        cur = t
lines.append(cur)
y = box[1] + 34
for line in lines:
    d2.text((box[0], y), line, font=f_body, fill=(58, 58, 56), anchor='ls')
    y += line_h
print('page 2 : paragraphe réécrit en %d ligne(s)' % len(lines))

im1.save('patched-1.png')
im2.save('patched-2.png')

# ---------------------------------------------------------------- rebuild the PDF
rgb1 = im1.convert('RGB')
rgb2 = im2.convert('RGB')
rgb1.save('META2027_Call_for_Papers.pdf', save_all=True, append_images=[rgb2],
          resolution=SCALE * 72, quality=82)
import os
print('PDF reconstruit : %d octets' % os.path.getsize('META2027_Call_for_Papers.pdf'))
