"""The META 2027 Sponsorship & Exhibition prospectus.

Written from scratch in the META 2027 visual language, as a real vector PDF: the text is text,
so it is searchable, indexable, readable by a screen reader, and a sponsor can copy a price or
an address straight out of it. The 2026 prospectus was a 543 KB PDF whose tables came out of
extraction mangled; this one weighs a fraction of that and extracts cleanly.

WHERE THE CONTENT COMES FROM — nothing here is invented:

  prices, packages, add-ons   META2026_Sponsorship.pdf, carried over unchanged on instruction
                              ("on peut garder les mêmes prix")
  venue, dates, ISSN          the META 2027 site (attend.html, footer)
  audience figures            the META 2027 site: 17 editions, 40+ countries (partners.html),
                              933 contributions at META 2026 (proceedings.html)

Three defects in the 2026 source are corrected rather than copied:

  1. the surfaces were corrupted ("63 m", "43 m", "33 m"); the comparison table on its page 3
     gives the true values — 18 m², 12/9 m², 9 m² — and those are used here;
  2. its ✓/✗ marks extracted as "3" and "7"; written here as words, which the brand
     font carries and a screen reader can read;
  3. it gave two different materials deadlines, 1 May on page 3 and 15 May on page 6. A single
     date is used, and it is declared once in DEADLINES below.

DEADLINES ARE PROPOSALS. The 2026 early-bird sat 135 days before the conference; the dates
below sit 134 days before, the same position. They are the one thing in this document that is
not sourced — the chair confirms them before the prospectus circulates.
"""

import fitz

OUT = 'META2027_Sponsorship.pdf'
FONT_DIR = '../cfp/'                      # Sora.ttf and Fraunces.ttf live with the CFP tooling

W, H = 595, 842                           # A4
M = 56                                    # page margin

NAVY   = (0.043, 0.086, 0.133)
CREAM  = (0.969, 0.965, 0.945)
GOLD   = (0.847, 0.706, 0.416)
GOLDD  = (0.525, 0.408, 0.173)
INK    = (0.102, 0.145, 0.192)
MUTED  = (0.420, 0.471, 0.514)
LINE   = (0.812, 0.788, 0.741)
WHITE  = (1, 1, 1)

CONF   = 'META 2027'
SUB    = '17th International Conference on Metamaterials, Photonic Crystals and Plasmonics'
VENUE  = 'Niagara Falls Convention Centre'
ADDR   = '6815 Stanley Avenue, Niagara Falls, Ontario L2G 3Y9, Canada'
DATES  = '13–16 July 2027'
MAIL   = 'contact@metaconferences.org'
SITE   = 'metaconferences.org'
ISSN   = 'Proceedings ISSN 2429-1390 (Scopus-indexed)'

DEADLINES = {
    'earlybird': '1 March 2027',
    'materials': '1 May 2027',
    'cancel':    '1 May 2027',
}

TIERS = [('Platinum', '€10,000'), ('Gold', '€6,900'), ('Silver', '€3,900')]

# One row per line of comparison, Platinum / Gold / Silver. Kept to a single line per cell so the
# three tiers can be read across, which is the only thing a comparison table is for. An em dash
# means the tier does not include it — the 2026 source used a tick and a cross, which neither the
# brand font nor a screen reader handles, and which extracted as "3" and "7".
ROWS = [
    ('Exhibition space',      '18 m²',        '12 m² or 9 m²',  '9 m²'),
    ('Total passes',          '6',            '4',              '3'),
    ('   of which delegate',  '3',            '2',              '2'),
    ('   of which exhibitor', '3',            '2',              '1'),
    ('Conference dinner',     '3 tickets',    '2 tickets',      '1 ticket'),
    ('Industrial workshop',   '90 min',       '60 min',         '45 min'),
    ('Workshop slot priority', 'First choice', 'Second choice',  'Shared pool'),
    ('Program advertisement', 'Full page',  'Half page',      'Half page'),
    ('Prime positioning',     'Yes',          'Yes',            '—'),
    ('Reserved lunch seating', 'Yes',         'Yes, one day',   '—'),
]

ADDONS = [
    ('Conference dinner co-sponsor', '€1,900'),
    ('Best Talk Award co-sponsor', '€1,200'),
    ('Best Poster Award co-sponsor', '€1,200'),
    ('Program advertisement, full page', '€1,000'),
    ('Conference app banner advertisement', '€1,000'),
    ('Coffee break co-sponsor', '€900'),
    ('Additional industrial workshop slot (45 min)', '€900'),
    ('Conference helper T-shirts, co-branded', '€900'),
    ('Lanyards, co-branded', '€900'),
    ('Conference bag insert (single flyer)', '€900'),
    ('Additional exhibitor staff pass', '€250'),
    ('Extra exhibition space, per m² beyond the package', '€150'),
    ('Lanyards supplied by the sponsor', 'Free'),
    ('Meeting room', 'On request'),
]

# The four days of META 2027 — 13 July 2027 is a Tuesday, so the conference runs Tuesday to
# Friday, as META 2026 did. The grid below shows the shape of the week, not who has booked what:
# there are no bookings yet, and a printed prospectus cannot carry live availability.
DAYS = ['Tuesday 13 July', 'Wednesday 14 July', 'Thursday 15 July', 'Friday 16 July']
WINDOWS = ['Morning A', 'Morning B', 'Afternoon A', 'Afternoon B']

FIGURES = [('17', 'editions since 2008'), ('933', 'contributions at META 2026'),
           ('40+', 'countries represented'), ('4', 'days of science')]


# Measuring objects. fitz.get_text_length() only knows the Base-14 fonts, so every width in this
# document is measured against the actual embedded outlines — which is what makes the centring and
# the right-aligned prices land where they should.
FONTS = {'sora': fitz.Font(fontfile=FONT_DIR + 'Sora.ttf'),
         'fraunces': fitz.Font(fontfile=FONT_DIR + 'Fraunces.ttf')}


def width(s, font, size):
    return FONTS[font].text_length(s, fontsize=size)


class Doc:
    def __init__(self):
        self.doc = fitz.open()
        self.page = None

    def new(self, bg=CREAM):
        self.page = self.doc.new_page(width=W, height=H)
        self.page.draw_rect(fitz.Rect(0, 0, W, H), color=None, fill=bg)
        self.page.insert_font(fontname='sora', fontfile=FONT_DIR + 'Sora.ttf')
        self.page.insert_font(fontname='fraunces', fontfile=FONT_DIR + 'Fraunces.ttf')
        return self.page

    def t(self, x, y, s, font='sora', size=10, color=INK, align=0, box=None):
        """One line. align 0 left, 1 centre, 2 right — measured, not guessed.

        With box, x is the left edge of a column of that width and the text is placed inside it;
        without, x is the anchor itself.
        """
        w = width(s, font, size)
        if align == 1:
            x = x - w / 2 if box is None else x + (box - w) / 2
        elif align == 2:
            x = x - w if box is None else x + box - w
        self.page.insert_text((x, y), s, fontname=font, fontsize=size, color=color)
        return w

    def para(self, x, y, w, s, font='fraunces', size=10, color=INK, leading=1.45):
        r = fitz.Rect(x, y, x + w, H)
        used = self.page.insert_textbox(r, s, fontname=font, fontsize=size, color=color,
                                        lineheight=leading, align=0)
        if used < 0:
            raise SystemExit(f'texte trop long : {s[:60]}…')
        return H - used            # bottom of the block

    def rule(self, x, y, w, color=LINE, h=0.7):
        self.page.draw_rect(fitz.Rect(x, y, x + w, y + h), color=None, fill=color)

    def kicker(self, x, y, s, color=GOLDD):
        self.t(x, y, s.upper(), 'sora', 8.5, color)
        return y

    def h2(self, x, y, s, color=INK, size=21):
        self.t(x, y, s, 'sora', size, color)
        self.rule(x, y + 9, 46, GOLD, 2)
        return y

    def runner(self, n):
        """Navy strip carrying the running title and the folio."""
        self.page.draw_rect(fitz.Rect(0, 0, W, 52), color=None, fill=NAVY)
        self.t(M, 24, f'{CONF} — Sponsorship & Exhibition', 'sora', 9.5, WHITE)
        self.t(M, 38, f'{VENUE} · {DATES}', 'sora', 8, GOLD)
        self.t(W - M, 32, str(n), 'sora', 9.5, GOLD, align=2)

    def foot(self, s=None):
        self.rule(M, H - 58, W - 2 * M)
        self.t(M, H - 44, s or f'{SITE} · {MAIL}', 'fraunces', 8.5, MUTED)


def cover(d):
    p = d.new(NAVY)
    # A quiet vertical wash, drawn as bands rather than embedding an image. The bands overlap by a
    # third of their height: butted exactly, the seams show as hairlines at print resolution.
    for i in range(140):
        y = 300 + i * 3.9
        p.draw_rect(fitz.Rect(0, y, W, y + 5.2), color=None,
                    fill=(0.043 + i * 0.00028, 0.086 + i * 0.00030, 0.133 + i * 0.00034))
    d.t(M, 120, SUB.upper(), 'sora', 8.5, GOLD)
    d.t(M, 205, 'META', 'sora', 62, WHITE)
    d.t(M + width('META ', 'sora', 62), 205, '2027', 'sora', 62, GOLD)
    d.rule(M, 232, 92, GOLD, 2.4)
    d.t(M, 272, VENUE, 'fraunces', 15, WHITE)
    d.t(M, 294, 'Niagara Falls, Ontario, Canada', 'fraunces', 15, WHITE)
    d.t(M, 322, DATES.upper(), 'sora', 13, GOLD)

    box = fitz.Rect(M, 400, M + 268, 434)
    p.draw_rect(box, color=GOLD, fill=None, width=1.1, radius=0.35)
    d.t(M + 22, 422, 'SPONSORSHIP & EXHIBITION', 'sora', 11, GOLD)

    d.para(M, 468, W - 2 * M - 40,
           'META brings the international metamaterials, photonics and plasmonics community '
           'together for four days. This document sets out how an organization can take part — '
           'as an exhibitor, a workshop host, or a partner of the scientific program.',
           'fraunces', 12.5, (0.80, 0.84, 0.88), 1.6)

    d.rule(M, H - 108, W - 2 * M, (0.20, 0.26, 0.32))
    d.t(M, H - 84, f'{SITE}   ·   {MAIL}', 'sora', 9.5, WHITE)
    d.t(M, H - 66, ISSN, 'fraunces', 9, (0.62, 0.67, 0.72))


def audience(d):
    d.new(); d.runner(2)
    y = 108
    d.kicker(M, y, 'The community')
    d.h2(M, y + 30, 'Who you meet at META')
    y = d.para(M, y + 58, W - 2 * M,
               'META is one of the principal international meetings on metamaterials, '
               'nanophotonics, photonic crystals and plasmonics. It gathers the people who build '
               'this field — principal investigators and group leaders, senior R&D staff, device '
               'and test engineers, and the product managers who turn their work into instruments '
               'and components.', 'fraunces', 11, INK, 1.55)

    y += 34
    cw = (W - 2 * M) / 4
    for i, (n, lab) in enumerate(FIGURES):
        x = M + i * cw
        d.t(x, y + 26, n, 'sora', 25, GOLDD)
        d.para(x, y + 36, cw - 14, lab, 'fraunces', 9, MUTED, 1.35)
    y += 84
    d.rule(M, y, W - 2 * M)

    y += 40
    d.kicker(M, y, 'Scientific scope')
    y = d.para(M, y + 20, W - 2 * M,
               'Metamaterials and metasurfaces · flat optics and meta-optics · photonic crystals · '
               'plasmonics and nano-optics · quantum photonics · light–matter interactions · '
               'structured light · mmWave and THz · acoustic, mechanical and thermal metamaterials · '
               'inverse design and AI-driven photonics.', 'fraunces', 10.5, INK, 1.5)

    y += 30
    d.kicker(M, y, 'Program format')
    y = d.para(M, y + 20, W - 2 * M,
               'Plenary lectures and keynotes, special symposia, special sessions, tutorials, '
               'industrial workshops, an exhibition placed on the main circulation, and the '
               'conference dinner.', 'fraunces', 10.5, INK, 1.5)

    y += 30
    d.kicker(M, y, 'Venue')
    d.para(M, y + 20, W - 2 * M,
           f'{VENUE}, {ADDR} — in the Fallsview district, a short walk from the Falls. '
           'Sessions, catering and the exhibition share the same floor, so exhibitors sit on the '
           'path delegates take between talks.', 'fraunces', 10.5, INK, 1.5)
    d.foot()


def value(d):
    d.new(); d.runner(3)
    y = 108
    d.kicker(M, y, 'What sponsorship buys')
    d.h2(M, y + 30, 'Four days inside the field')
    y += 74

    cols = [
        ('For your organization', [
            ('Visibility that lasts', 'Your name sits in the program, on the site and on the '
                                      'floor for the whole conference, not for a single hour.'),
            ('Technical credibility', 'Your work is shown beside current research, to an audience '
                                      'that can judge it.'),
            ('A reading of the field', 'Four days of talks tell you where the science is going '
                                       'before it reaches the trade press.'),
        ]),
        ('For your team', [
            ('Qualified conversations', 'The people at the booth are the people who specify and '
                                        'buy instruments and components.'),
            ('Research partnerships', 'Joint projects with academic groups start at meetings like '
                                      'this one.'),
            ('Recruitment', 'Doctoral researchers and postdocs in your exact area, in one room.'),
        ]),
    ]
    cw = (W - 2 * M - 30) / 2
    deepest = y
    for i, (title, items) in enumerate(cols):
        x = M + i * (cw + 30)
        d.t(x, y, title, 'sora', 11.5, GOLDD)
        d.rule(x, y + 8, 30, GOLD, 1.6)
        yy = y + 34
        for h, body in items:
            d.t(x, yy, h, 'sora', 10, INK)
            yy = d.para(x, yy + 10, cw, body, 'fraunces', 9.5, MUTED, 1.5) + 22
        deepest = max(deepest, yy)

    y = deepest + 20                     # follow the taller column, don't jump to a fixed line
    d.rule(M, y, W - 2 * M)
    y += 34
    d.kicker(M, y, 'Included in every package')
    y += 20
    for line in [
        'Exhibition space on the main circulation, beside the session rooms and the coffee points.',
        'An industrial workshop slot — your own session, in the program, with full AV.',
        'Your logo and company profile on the conference website and in the printed program.',
        'Delegate and exhibitor passes, with lunches and coffee breaks.',
        'An advertisement in the program.',
    ]:
        d.t(M, y + 8, '—', 'sora', 9, GOLD)
        y = d.para(M + 16, y, W - 2 * M - 16, line, 'fraunces', 10.5, INK, 1.5) + 10
    d.t(M, y + 22, 'The three packages differ in scale, not in kind — the comparison is overleaf.',
        'fraunces', 10, MUTED)
    d.foot()


def packages(d):
    d.new(); d.runner(4)
    y = 108
    d.kicker(M, y, 'Packages')
    d.h2(M, y + 30, 'Three levels')
    y += 62

    LABW = 150                                  # the label column — without it the row names sit
    colw = (W - 2 * M - LABW) / 3               # inside Platinum and read as Platinum features
    for i, (name, price) in enumerate(TIERS):
        x = M + LABW + i * colw
        d.page.draw_rect(fitz.Rect(x + 3, y, x + colw - 3, y + 60),
                         color=None, fill=NAVY if i == 0 else (0.13, 0.18, 0.23))
        d.t(x + 3, y + 23, name.upper(), 'sora', 10.5, GOLD, align=1, box=colw - 6)
        d.t(x + 3, y + 48, price, 'sora', 17, WHITE, align=1, box=colw - 6)

    yy = y + 60
    rowh = 27
    for r, (label, *vals) in enumerate(ROWS):
        if r % 2 == 0:
            d.page.draw_rect(fitz.Rect(M, yy, W - M, yy + rowh),
                             color=None, fill=(0.937, 0.929, 0.906))
        sub = label.startswith('   ')
        d.t(M + (14 if sub else 4), yy + 18, label.strip(), 'sora',
            8 if sub else 8.8, MUTED if sub else GOLDD)
        for i, v in enumerate(vals):
            x = M + LABW + i * colw
            d.t(x + 3, yy + 18, v, 'fraunces', 10, INK, align=1, box=colw - 6)
        yy += rowh

    d.rule(M, yy + 1, W - 2 * M, (0.812, 0.788, 0.741), 1.2)

    yy += 34
    d.kicker(M, yy, 'What the space is, and is not')
    yy = d.para(M, yy + 18, W - 2 * M,
                'Exhibition spaces are supplied as empty floor area with basic tables and chairs. '
                'Booth walls, branding and any further furniture are brought by the sponsor. '
                'Additional space beyond the package allocation is €150 per m². '
                'Limited academic and start-up rates exist — write to us. All prices include VAT.',
                'fraunces', 10, INK, 1.5)

    band = fitz.Rect(M, yy + 26, W - M, yy + 112)
    d.page.draw_rect(band, color=None, fill=(0.980, 0.960, 0.910))
    d.page.draw_rect(fitz.Rect(M, yy + 26, M + 4, yy + 112), color=None, fill=GOLD)
    d.t(M + 24, yy + 54, f'Book by {DEADLINES["earlybird"]} — 20% off', 'sora', 12.5, GOLDD)
    d.para(M + 24, yy + 64, W - 2 * M - 48,
           'The early-bird discount applies to every package above. Workshop slots and exhibition '
           'positions are allocated by tier and by booking date, so booking early decides both '
           'what you pay and where you stand.', 'fraunces', 9.8, INK, 1.45)
    d.foot()


def addons(d):
    d.new(); d.runner(5)
    y = 108
    d.kicker(M, y, 'Add-ons')
    d.h2(M, y + 30, 'Beyond the package')
    y += 68

    colw = (W - 2 * M - 26) / 2
    half = (len(ADDONS) + 1) // 2
    for c in range(2):
        x = M + c * (colw + 26)
        yy = y
        for label, price in ADDONS[c * half:(c + 1) * half]:
            d.para(x, yy, colw - 62, label, 'fraunces', 9.5, INK, 1.35)
            d.t(x + colw, yy + 9, price, 'sora', 9.5, GOLDD, align=2)
            d.rule(x, yy + 22, colw, (0.878, 0.863, 0.831))
            yy += 31

    y += half * 31 + 22
    d.rule(M, y, W - 2 * M)
    y += 30
    d.kicker(M, y, 'Industrial workshops')
    y = d.para(M, y + 20, W - 2 * M,
               'Every package includes a workshop slot: your own session in the conference '
               'program, in a room with full AV. The week holds sixteen workshop windows, four '
               'on each of the four days. Platinum chooses first, then Gold, then Silver, and '
               'Silver slots are drawn from a shared pool. The add-on above buys a further slot, '
               'not the first one — two of them make 90 minutes.',
               'fraunces', 10, INK, 1.5) + 22

    # The grid shows the shape of the week. It carries no availability: nothing is booked yet, and
    # a printed document cannot tell a sponsor in March what is still free in June.
    cw = [132] + [(W - 2 * M - 132) / 4] * 4
    x = M
    for i, htxt in enumerate([''] + WINDOWS):
        d.t(x + 6, y, htxt, 'sora', 8, GOLDD)
        x += cw[i]
    y += 8
    d.rule(M, y, W - 2 * M, GOLD, 1.2)
    y += 6
    for day in DAYS:
        d.t(M + 6, y + 15, day, 'sora', 9, INK)
        for i in range(4):
            x = M + cw[0] + i * cw[1]
            d.page.draw_rect(fitz.Rect(x + 6, y + 9, x + cw[1] - 18, y + 15),
                             color=None, fill=(0.918, 0.886, 0.816))
        y += 25
        d.rule(M, y - 4, W - 2 * M, (0.878, 0.863, 0.831))
    d.t(M, y + 22, 'Sixteen workshop windows. Slots are confirmed on booking, in tier order.',
        'fraunces', 9, MUTED)
    d.foot()


def booking(d):
    d.new(); d.runner(6)
    y = 108
    d.kicker(M, y, 'Booking')
    d.h2(M, y + 30, 'How to take part')
    y += 70

    for label, body in [
        ('Early-bird — 20% off',
         f'Bookings confirmed and paid by {DEADLINES["earlybird"]} take a 20% discount on the '
         'package price. Workshop slots are limited and allocated by tier and booking date.'),
        ('Logo and company profile',
         f'Due by {DEADLINES["materials"]} to be certain of inclusion in the printed program, '
         'on the website and in the on-site signage.'),
        ('Invoicing',
         'Invoices are payable within 30 days of issue. Early-bird rates require payment by '
         f'{DEADLINES["earlybird"]}. All prices include VAT.'),
        ('Space allocation',
         'Locations are assigned by sponsorship tier and by booking date. Booking early is how '
         'you get the position you want.'),
        ('Cancellation',
         f'Written to {MAIL} before {DEADLINES["cancel"]}: full refund less a 4% fee for card '
         'payments, or less €30 for wire transfers.'),
        ('Something else',
         'Packages can be assembled to fit a specific objective. Tell us what you want to achieve '
         'and we will put a proposal together.'),
    ]:
        d.t(M, y, label, 'sora', 10.5, INK)
        y = d.para(M, y + 10, W - 2 * M, body, 'fraunces', 10, MUTED, 1.5) + 20

    box = fitz.Rect(M, y + 14, W - M, y + 128)
    d.page.draw_rect(box, color=None, fill=NAVY)
    d.t(M + 26, y + 48, 'Talk to us', 'sora', 15, WHITE)
    d.t(M + 26, y + 74, MAIL, 'sora', 12, GOLD)
    d.para(M + 26, y + 88, W - 2 * M - 52,
           'Sponsorship and exhibition inquiries are answered by the organizing team. '
           f'{SITE}', 'fraunces', 9.5, (0.72, 0.77, 0.81), 1.45)
    d.foot(f'{CONF} · {VENUE} · {DATES} · {MAIL}')


MUST = ['€10,000', '€6,900', '€3,900', '€150', '€250', '13–16 July 2027',
        'Niagara Falls Convention Centre', MAIL, '18 m²', '12 m² or 9 m²',
        DEADLINES['earlybird'], DEADLINES['materials']]
MUST_NOT = ['Dublin', 'Trinity College', '2026 —', '63 m', '43 m', '33 m']


def main():
    d = Doc()
    cover(d); audience(d); value(d); packages(d); addons(d); booking(d)
    d.doc.subset_fonts()
    d.doc.set_metadata({
        'title': f'{CONF} — Sponsorship & Exhibition Prospectus',
        'author': 'META Conferences',
        'subject': f'{SUB}. {VENUE}, Niagara Falls, Ontario, Canada, {DATES}.',
        'keywords': 'META 2027, sponsorship, exhibition, industrial workshop, metamaterials, '
                    'photonics, plasmonics, Niagara Falls',
    })
    d.doc.save(OUT, garbage=4, deflate=True, clean=True)
    d.doc.close()

    import os
    doc = fitz.open(OUT)
    text = ' '.join(' '.join(p.get_text().split()) for p in doc)
    missing = [s for s in MUST if s not in text]
    leaked = [s for s in MUST_NOT if s in text]
    print(f'{OUT} — {len(doc)} pages, {os.path.getsize(OUT)/1024:.0f} Ko, '
          f'{len(text)} caractères extractibles')
    print('  manquant :', missing or 'rien')
    print('  résidu 2026 :', leaked or 'rien')
    if missing or leaked:
        raise SystemExit('vérification échouée')
    print('  vérification OK')


if __name__ == '__main__':
    main()
