"""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 carried two contradictory materials deadlines, 1 May on page 3 and 15 May on page 6.
     There is now no materials deadline at all — see below.

NO MANUFACTURED URGENCY. On instruction of 22 August 2026, this edition carries no early-bird
discount, no sponsor booking deadline and no materials deadline. Where availability genuinely
constrains a sponsor, AVAILABILITY states it as a fact. The 2026 add-ons that were dropped —
conference dinner co-sponsor, coffee break co-sponsor, bag insert — are held out by MUST_NOT at
the end of this file, along with every deadline and discount word, so a future edit cannot quietly
reintroduce them.

Everything here is mirrored on sponsors.html. Change one and change the other: the page and the
PDF are the same offer, and a sponsor who compares them must not find two different prices.
"""

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)'

# NO DEADLINES. There is deliberately no early-bird date, no sponsor booking deadline and no
# materials deadline in this document: manufactured urgency has no place in a scientific
# conference's commercial offer, and any date printed here would bind the conference for a year.
# Where availability genuinely matters, AVAILABILITY below says so as a fact, not as a countdown.
AVAILABILITY = ('Workshop slots and prime exhibition locations are allocated '
                'subject to availability.')

# Ascending order — a company reads up from the entry package, not down from the most expensive.
TIERS = [('Silver', '€3,900'), ('Gold', '€6,900'), ('Platinum', '€10,000')]

# One row per line of comparison, Silver / Gold / Platinum. 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.
ROWS = [
    ('Exhibition space',       '9 m²',         '12 m²',          '18 m²'),
    ('Delegate passes',        '1',            '2',              '3'),
    ('Exhibitor passes',       '2',            '2',              '3'),
    ('Total passes',           '3',            '4',              '6'),
    ('Conference dinner',      '1 ticket',     '2 tickets',      '3 tickets'),
    ('Industrial workshop',    '45 min',       '60 min',         '90 min'),
    ('Workshop slot priority', 'Shared pool',  'Second choice',  'First choice'),
    ('Program advertisement',  'Half page',    'Half page',      'Full page'),
    ('Exhibition positioning', '—',            'Priority',       'Prime'),
]

# What the two kinds of pass are for. The 2026 wording implied exhibitors could not present a
# paper; that is wrong for this community, where the person on the stand is often the author.
PASSES = [
    ('Delegate Pass', 'Full conference access, including lunches and coffee breaks. May be '
                      'assigned to a company representative who is also presenting a paper.'),
    ('Exhibitor Pass', 'For exhibition staff and company representatives attending primarily in '
                       'support of the stand. Includes lunches and coffee breaks. It is not a '
                       'full scientific conference registration.'),
]
PASS_NOTE = ('Included Delegate Passes may be assigned to presenting authors. For sponsor teams '
             'bringing additional presenting authors, further Delegate registrations can be '
             'arranged with the organizers.')

ADDONS = [
    ('Additional 45-min industrial workshop', '€1,500'),
    ('Best Talk Award co-sponsor', '€1,500'),
    ('Best Poster Award co-sponsor', '€1,500'),
    ('Program full-page advertisement', '€1,000'),
    ('Conference app banner', '€1,000'),
    ('Lanyards, co-branded', '€1,000'),
    ('Conference helper T-shirts, co-branded', '€1,000'),
    ('Additional exhibitor staff pass', '€300'),
    ('Lanyards supplied by the sponsor', 'Free'),
    ('Meeting room', 'On request'),
]
ADDON_NOTE = ('An additional 45-minute industrial workshop slot is available for €1,500. This is '
              'in addition to the workshop time already included in the selected sponsorship '
              'package. Other sponsorship opportunities are available 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)


_SCRATCH = fitz.open()
_SP = _SCRATCH.new_page(width=W, height=H)
for _n, _f in (('sora', 'Sora.ttf'), ('fraunces', 'Fraunces.ttf')):
    _SP.insert_font(fontname=_n, fontfile=FONT_DIR + _f)


def para_height(w, s, font='fraunces', size=10, leading=1.45):
    """How tall a wrapped paragraph will be, measured by laying it out invisibly.

    Boxes drawn behind text have to be sized before the text exists, so the height has to be
    known in advance — guessing it is what pushed the pass note outside its band.
    """
    used = _SP.insert_textbox(fitz.Rect(0, 0, w, H), s, fontname=font, fontsize=size,
                              lineheight=leading, render_mode=3)
    return H - used


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} — Exhibits & Sponsorships', '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, 'EXHIBITS & SPONSORSHIPS', '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 += 54

    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 + 54),
                         color=None, fill=NAVY if i == len(TIERS) - 1 else (0.13, 0.18, 0.23))
        d.t(x + 3, y + 21, name.upper(), 'sora', 10.5, GOLD, align=1, box=colw - 6)
        d.t(x + 3, y + 45, price, 'sora', 16, WHITE, align=1, box=colw - 6)

    yy = y + 54
    rowh = 23
    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 + 15, 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 + 15, v, 'fraunces', 9.6, 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 += 30
    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. '
                'Prices include taxes.',
                'fraunces', 10, INK, 1.5)

    band_top = yy + 14
    inner = W - 2 * M - 48
    # size the band to what goes in it
    body_h = sum(14 + para_height(inner, b, 'fraunces', 9.5, 1.45) + 6 for _, b in PASSES)
    note_h = para_height(inner, PASS_NOTE, 'fraunces', 9.5, 1.45)
    band_h = 34 + body_h + 10 + note_h + 12
    if band_top + band_h > H - 70:      # must not run into the footer rule
        raise SystemExit(f'bandeau des badges trop bas : {band_top + band_h:.0f} > {H - 70}')

    d.page.draw_rect(fitz.Rect(M, band_top, W - M, band_top + band_h), color=None,
                     fill=(0.980, 0.960, 0.910))
    d.page.draw_rect(fitz.Rect(M, band_top, M + 4, band_top + band_h), color=None, fill=GOLD)
    d.t(M + 24, band_top + 26, 'What the two passes are', 'sora', 11.5, GOLDD)
    yy = band_top + 36
    for name, body in PASSES:
        d.t(M + 24, yy + 14, name, 'sora', 9.8, INK)
        yy = d.para(M + 24, yy + 20, inner, body, 'fraunces', 9.5, MUTED, 1.45) + 6
    d.para(M + 24, yy + 14, inner, PASS_NOTE, 'fraunces', 9.5, 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 + 10
    y = d.para(M, y, W - 2 * M, ADDON_NOTE, 'fraunces', 9.5, MUTED, 1.45) + 14
    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.t(M, y + 40, AVAILABILITY, '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 [
        ('Choose a package',
         'Tell us which package you want, and whether you want any of the add-ons. We confirm '
         'availability, then send an agreement and an invoice.'),
        ('Space and slot allocation',
         AVAILABILITY + ' Locations and workshop times are confirmed in writing when the '
         'agreement is signed — no position is held informally.'),
        ('Logo and company profile',
         'Send your logo and a short company profile once the agreement is signed. They go on the '
         'website, in the printed program and into the on-site signage.'),
        ('Invoicing',
         'An invoice is issued with the sponsorship agreement, and its payment terms are set out '
         'there. Prices include taxes.'),
        ('Cancellation',
         'Cancellation terms are set out in the sponsorship agreement.'),
        ('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', '€1,500', '€1,000', '€300',
        '13–16 July 2027', 'Niagara Falls Convention Centre', MAIL, '18 m²', '12 m²',
        'Delegate Pass', 'Exhibitor Pass', 'subject to availability',
        'available on request', '1 ticket', '2 tickets', '3 tickets',
        'lunches and coffee breaks', 'Exhibits & Sponsorships']
# No deadline, no discount, no urgency, and none of the dropped 2026 add-ons may come back.
MUST_NOT = ['Dublin', 'Trinity College', '63 m', '43 m', '33 m',
            'early-bird', 'Early-bird', 'Early bird', '20% off', 'discount',
            'deadline', 'Deadline', 'dinner co-sponsor', 'Coffee break', 'bag insert',
            '1 March 2027', '1 May 2027', 'organisation', 'programme', 'Programme',
            'payable within 30 days', '12 m² or 9 m²', 'Conference app visibility',
            'Extra exhibition space', 'Additional space:', 'per m²',
            'academic and start-up rates', 'prices include VAT']


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} — Exhibits & Sponsorships Prospectus',
        'author': 'META Conferences',
        'subject': f'{SUB}. {VENUE}, Niagara Falls, Ontario, Canada, {DATES}.',
        'keywords': 'META 2027, exhibits, 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()
