"""Two content repairs on page 2 of the Call for Papers, then a real text layer on both pages.

The CFP has no source file — it never had one — so page 2 is repaired in the pixels, the way the
earlier passes were, with the same fonts (Sora, Fraunces) and colours sampled from the page.

  1. "a short abstract of about 50 words" → "a short abstract of 50 words at most".
     The submission validator blocks at 51 words, so "about" was setting authors up to be
     refused at the portal. Body type resolves to Fraunces 35 / opsz 28 / weight 400: rendering
     the old sentence at that size reproduces its measured width to one pixel.

  2. The Key dates block gains the advanced registration deadline (5 June 2027) — the date that
     decides whether an accepted paper appears in the program, and the only one the website
     listed and the CFP did not. Five correct rows are left exactly where they are: everything
     from the Conference row down to the last paragraph slides one row pitch lower, into the
     194 px of white that sat before the footer band, and only the new row is drawn. All the
     original margins therefore survive untouched.

The two colours used for the new row are deliberately lighter than the sampled stroke cores:
freshly drawn glyphs are crisper than the JPEG-softened originals, so matching the core colour
would make the new row read darker than its neighbours. They are set so the mean ink density of
the new row matches the row above it (measured: 136 vs 138 for the label, 62 vs 63 for the date).

Every geometry constant below was measured off the page, and the script refuses to run if the
body type no longer reproduces the width of the sentence it is replacing.
"""

from PIL import Image, ImageDraw, ImageFont

PAGE2 = 'cfp_p2.jpg'
CREAM = (247, 246, 241)

# measured on the page
BODY_SIZE = 35                      # Fraunces, opsz 28, weight 400
LABEL_X, DATE_X = 139, 930
GREY = (117, 120, 120)              # regular key-date labels, lightened so the freshly
                                    # drawn row matches the ink density of the JPEG-softened ones
NAVY = (29, 40, 45)                 # dates, lightened for the same reason
BODY = None                         # sampled below

# Rows 1–4 (tops 1000 / 1048 / 1097 / 1146) do not move, so the 38 px under the gold rule is
# preserved. Everything from just above the Conference row down to the end of the Proceedings
# paragraph slides down one row pitch; the 194 px of white before the footer band absorbs it.
LOWER = (1185, 2000)                # Conference row → last paragraph line
PITCH = 49
NEW_ROW_TOP = 1195                  # the slot the Conference row vacates


def fraunces(size, weight=400):
    f = ImageFont.truetype('Fraunces.ttf', size)
    f.set_variation_by_axes([28, weight, 0, 0])
    return f


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


def sample_text_colour(px, y0, y1, x0, x1):
    """Core stroke colour: mean of the darkest quarter of the non-background pixels."""
    from collections import Counter
    c = Counter()
    for y in range(y0, y1 + 1):
        for x in range(x0, x1 + 1):
            p = px[x, y]
            if sum(p) / 3 < 200:
                c[p] += 1
    items = sorted(c.items(), key=lambda kv: sum(kv[0]) / 3)
    keep = items[:max(1, len(items) // 4)]
    tot = sum(n for _, n in keep)
    return tuple(sum(p[i] * n for p, n in keep) // tot for i in range(3))


def cap_top(draw, text, font):
    """Vertical offset from the drawing origin to the top of the inked box."""
    return draw.textbbox((0, 0), text, font=font, anchor='la')[1]


def main():
    im = Image.open(PAGE2).convert('RGB')
    px = im.load()
    d = ImageDraw.Draw(im)

    global BODY
    # lightened for the same reason as GREY/NAVY: the sampled core over-inks a fresh render
    core = sample_text_colour(px, 383, 408, 139, 1658)
    BODY = tuple(247 - round((247 - c) * 0.84) for c in core)

    # ---------------------------------------------------------------- 1. the abstract sentence
    old = ("and affiliations; a short abstract of about 50 words; motivation and context; "
           "principal methods")
    new = ("and affiliations; a short abstract of 50 words at most; motivation and context; "
           "principal methods")
    f = fraunces(BODY_SIZE)
    width_old = d.textlength(old, font=f)
    assert abs(width_old - 1519) <= 3, f'body type no longer matches: {width_old:.0f} vs 1519'

    d.rectangle((130, 378, 1700, 415), fill=CREAM)
    d.text((139, 383 - cap_top(d, old, f)), new, font=f, fill=BODY)

    # ---------------------------------------------------------------- 2. the sixth key date
    lower = im.crop((0, LOWER[0], im.width, LOWER[1]))
    d.rectangle((0, LOWER[0], im.width, LOWER[1] + PITCH), fill=CREAM)
    im.paste(lower, (0, LOWER[0] + PITCH))

    d = ImageDraw.Draw(im)
    lab, dat = 'Advanced registration until', '5 June 2027'
    fl, fd = fraunces(BODY_SIZE - 1), sora(30, 700)
    d.text((LABEL_X, NEW_ROW_TOP - cap_top(d, lab, fl)), lab, font=fl, fill=GREY)
    d.text((DATE_X, NEW_ROW_TOP - cap_top(d, dat, fd)), dat, font=fd, fill=NAVY)

    im.save('cfp_p2_fixed.png')
    print('cfp_p2_fixed.png écrit — couleur du corps de texte', BODY)


if __name__ == '__main__':
    main()
