Please create a colorful handbook pdf from from list of vocabulary words mentioned in attached file ,write list of vocabulary words the list must be with respelling of individual vocabulary words in bracelet and it's meaning in English and later in Russian language for all individual vocabulary words and also add 3 daily life use of vocabulary as examples of sentences with it's Russian translation under English sentences. The vocabulary words should be bit more bigger and in colorful backgrounds so easy to differentiate. Please note you have to only use the vocabulary words given under title " Language notes" on different pages of attached file, don't choose other words from this attached file and please skip that last " language notes" on page 92 with model verbs.
cd /tmp/workspace/attachments/79eccc15-8a5f-48c4-9b95-0653e65d105a/ && python3 -c " import pdfplumber with pdfplumber.open('DOC-20261105-WA0001.pdf') as p: print(len(p.pages)) for i,pg in enumerate(p.pages): t=pg.extract_text() or '' print('=== PAGE',i+1, len(t)) if 'anguage notes' in t or 'ANGUAGE NOTES' in t: print(t) "
cd /tmp/workspace/attachments/79eccc15-8a5f-48c4-9b95-0653e65d105a/ && python3 -c " import pdfplumber with pdfplumber.open('DOC-20261105-WA0001.pdf') as p: for i in (1,4): print('=== PAGE',i+1) print(p.pages[i].extract_text()) "
ls /usr/share/fonts/truetype/ 2>/dev/null; fc-list | grep -iE "dejavu|noto|liberation|open" | head -20; mkdir -p /tmp/workspace/vocab-handbook
python3 -c "import matplotlib,os;d=os.path.join(matplotlib.get_data_path(),'fonts/ttf');print(d);print([f for f in os.listdir(d) if 'DejaVuSans' in f])"
~/vocab-handbook/build.py
import os, matplotlib
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.colors import HexColor, white
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
D = os.path.join(matplotlib.get_data_path(), 'fonts/ttf')
pdfmetrics.registerFont(TTFont('R', f'{D}/DejaVuSans.ttf'))
pdfmetrics.registerFont(TTFont('B', f'{D}/DejaVuSans-Bold.ttf'))
pdfmetrics.registerFont(TTFont('I', f'{D}/DejaVuSans-Oblique.ttf'))
pdfmetrics.registerFont(TTFont('BI', f'{D}/DejaVuSans-BoldOblique.ttf'))
pdfmetrics.registerFontFamily('R', normal='R', bold='B', italic='I', boldItalic='BI')
W, H = A4
# (word, respelling, pos, EN meaning, RU meaning, [(en, ru) x3], color, light)
WORDS = [
("twisting my words", "TWIS-ting my WURDZ", "phrase",
"deliberately misunderstanding or changing what I am saying",
"намеренно искажать мои слова; передёргивать то, что я сказал(а)",
[("I never said I hated your cooking. Stop twisting my words!",
"Я никогда не говорил, что ненавижу твою стряпню. Хватит искажать мои слова!"),
("She is twisting my words to make me look bad in front of the boss.",
"Она передёргивает мои слова, чтобы выставить меня в плохом свете перед начальником."),
("You're twisting my words. I only asked if we could eat out less often.",
"Ты искажаешь мои слова. Я лишь спросил, можем ли мы реже ужинать в ресторанах.")],
"#E53935", "#FFEBEE"),
("got our wires crossed", "GOT OW-er WY-erz KRAWST", "phrase",
"misunderstood each other",
"не поняли друг друга; произошла путаница",
[("I thought the meeting was at ten, but she said eleven. We must have got our wires crossed.",
"Я думал, что встреча в десять, а она сказала - в одиннадцать. Мы, видимо, не поняли друг друга."),
("Sorry I'm late. We got our wires crossed about where to meet.",
"Извини, что опоздал. Мы запутались, где нам встречаться."),
("My husband bought milk and so did I. We got our wires crossed again!",
"Мой муж купил молоко, и я тоже. Мы опять не договорились!")],
"#FB8C00", "#FFF3E0"),
("that came out all wrong", "THAT kaym OWT awl RAWNG", "phrase",
"used to apologize for saying something rude or inappropriate",
"используется, чтобы извиниться за неудачную или грубую фразу: я неправильно выразился(ась)",
[("I didn't mean you look tired. Sorry, that came out all wrong.",
"Я не хотел сказать, что ты выглядишь усталой. Прости, это вышло совсем не так."),
("Oh no, that came out all wrong! I love your new haircut.",
"Ой, нет, я совсем не то сказал! Мне очень нравится твоя новая стрижка."),
("I wanted to thank you for helping, but that came out all wrong.",
"Я хотел поблагодарить тебя за помощь, но получилось совсем неудачно.")],
"#F9A825", "#FFFDE7"),
("it's not the end of the world", "its NOT thuh END uv thuh WURLD", "phrase",
"whatever has upset you is not as important as you think",
"то, что тебя расстроило, не так важно, как тебе кажется; это не конец света",
[("You missed the bus? It's not the end of the world. Another one comes in ten minutes.",
"Ты опоздал на автобус? Это не конец света. Следующий придёт через десять минут."),
("I burnt the toast, but it's not the end of the world. I'll make some more.",
"Я сжёг тосты, но это не конец света. Я приготовлю ещё."),
("He failed his driving test, but it's not the end of the world. He can try again.",
"Он не сдал экзамен по вождению, но это не конец света. Он может попробовать снова.")],
"#43A047", "#E8F5E9"),
("no point crying over spilled milk", "NOH POYNT KRY-ing OH-ver SPILD MILK", "phrase",
"no reason to worry about things that have already happened and cannot be changed",
"нет смысла переживать из-за того, что уже случилось и чего не изменить; слезами горю не поможешь",
[("My phone fell in the water. Oh well, there's no point crying over spilled milk.",
"Мой телефон упал в воду. Ну что ж, слезами горю не поможешь."),
("I forgot to book the tickets, but there's no point crying over spilled milk. Let's find another way.",
"Я забыл заказать билеты, но нет смысла жалеть об этом. Давай найдём другой вариант."),
("You lost your keys yesterday. There's no point crying over spilled milk. Let's call a locksmith.",
"Ты вчера потерял ключи. Что было, то прошло. Давай вызовем слесаря.")],
"#00ACC1", "#E0F7FA"),
("we're getting nowhere here / we're just going round in circles",
"weer GET-ing NOH-wair HEER / weer JUST GOH-ing ROWND in SUR-kulz", "phrase",
"we are repeating the same things without solving anything",
"мы повторяем одно и то же и ни к чему не приходим; мы ходим по кругу",
[("We've talked about the holiday for an hour. We're getting nowhere here.",
"Мы говорим об отпуске уже час. Мы ни к чему не приходим."),
("My brother and I keep arguing about money. We're just going round in circles.",
"Мы с братом постоянно спорим о деньгах. Мы просто ходим по кругу."),
("Let's stop for today. We're going round in circles, and we're tired.",
"Давай на сегодня закончим. Мы ходим по кругу, и мы устали.")],
"#1E88E5", "#E3F2FD"),
("as it happens", "az it HAP-enz", "phrase",
"actually; as a matter of fact",
"вообще-то; кстати; как раз так получилось",
[("Do you like Italian food? As it happens, I'm going to an Italian restaurant tonight.",
"Ты любишь итальянскую кухню? Как раз так получилось, что сегодня вечером я иду в итальянский ресторан."),
("I can't talk long. As it happens, my train leaves in ten minutes.",
"Я не могу долго говорить. Вообще-то мой поезд отправляется через десять минут."),
("'Do you know a good dentist?' 'As it happens, my sister is one.'",
"«Ты знаешь хорошего стоматолога?» «Кстати, моя сестра как раз стоматолог».")],
"#5E35B1", "#EDE7F6"),
("get a grip of costs", "GET uh GRIP uv KOSTS", "verb + preposition + noun",
"to minimize costs; to take control of spending",
"сократить расходы; взять расходы под контроль",
[("We need to get a grip of costs because we spend too much on food.",
"Нам нужно взять расходы под контроль, потому что мы тратим слишком много на еду."),
("The manager wants to get a grip of costs before the end of the year.",
"Менеджер хочет сократить расходы до конца года."),
("To save for our holiday, we decided to get a grip of costs at home.",
"Чтобы накопить на отпуск, мы решили контролировать расходы дома.")],
"#D81B60", "#FCE4EC"),
("left, right and centre", "LEFT RYT and SEN-ter", "phrase",
"on every side; everywhere (in large amounts)",
"повсюду; во все стороны; направо и налево",
[("New cafés are opening left, right and centre in our neighbourhood.",
"В нашем районе повсюду открываются новые кафе."),
("He has been spending money left, right and centre since he got the new job.",
"Он тратит деньги направо и налево с тех пор, как получил новую работу."),
("At the sale, people were buying clothes left, right and centre.",
"На распродаже люди скупали одежду со всех сторон.")],
"#8E24AA", "#F3E5F5"),
("petulant", "PET-yuh-lunt", "adjective",
"childishly bad-tempered; sulky and easily annoyed",
"капризный; по-детски раздражительный; обидчивый",
[("My little brother gets petulant when he doesn't get his own way.",
"Мой младший брат становится капризным, когда всё идёт не по его."),
("Don't be so petulant. It's only a game.",
"Не будь таким капризным. Это всего лишь игра."),
("She gave a petulant answer and slammed the door.",
"Она капризно ответила и хлопнула дверью.")],
"#6D4C41", "#EFEBE9"),
]
SRC = "Source: Outcomes Advanced 3rd ed. Teacher's Book, Unit 6 'Conflict and resolution', Language notes (pp. 89 and 91)"
def para(c, text, x, y_top, w, size, font='R', color='#222222', leading=None, align=0):
st = ParagraphStyle('p', fontName=font, fontSize=size, leading=leading or size * 1.3,
textColor=HexColor(color), alignment=align)
p = Paragraph(text, st)
_, h = p.wrap(w, 1000)
p.drawOn(c, x, y_top - h)
return h
def height(text, w, size, font='R', leading=None):
st = ParagraphStyle('p', fontName=font, fontSize=size, leading=leading or size * 1.3)
return Paragraph(text, st).wrap(w, 1000)[1]
def rrect(c, x, y, w, h, r, fill, stroke=None, lw=1):
c.setFillColor(HexColor(fill))
if stroke:
c.setStrokeColor(HexColor(stroke)); c.setLineWidth(lw)
c.roundRect(x, y, w, h, r, fill=1, stroke=1 if stroke else 0)
def confetti(c, seed, colors):
import random
random.seed(seed)
for _ in range(40):
col = HexColor(random.choice(colors))
c.setFillColor(col)
x, y = random.uniform(0, W), random.uniform(0, H)
r = random.uniform(3, 9)
c.saveState()
c.setFillAlpha(0.18)
c.circle(x, y, r, fill=1, stroke=0)
c.restoreState()
ALLC = [w[6] for w in WORDS]
def cover(c):
c.setFillColor(HexColor('#FFF8E1')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 1, ALLC)
# rainbow bands
bw = W / len(ALLC)
for i, col in enumerate(ALLC):
c.setFillColor(HexColor(col)); c.rect(i * bw, H - 28, bw, 28, fill=1, stroke=0)
c.rect(i * bw, 0, bw, 28, fill=1, stroke=0)
rrect(c, 45, H - 420, W - 90, 300, 30, '#1E88E5')
rrect(c, 55, H - 410, W - 110, 280, 24, '#42A5F5')
c.setFillColor(white); c.setFont('B', 40)
c.drawCentredString(W / 2, H - 205, "Vocabulary")
c.drawCentredString(W / 2, H - 258, "Handbook")
c.setFont('B', 17)
c.drawCentredString(W / 2, H - 305, "Arguments and Discussions")
c.setFont('R', 14)
c.drawCentredString(W / 2, H - 335, "Словарь: споры и обсуждения")
c.setFont('R', 12)
c.drawCentredString(W / 2, H - 375, "English + respelling + Русский перевод + 3 examples")
# colored chips
y = H - 470
for i, wd in enumerate(WORDS):
col = wd[6]
cx = 60 if i % 2 == 0 else W / 2 + 5
cy = y - (i // 2) * 52
rrect(c, cx, cy - 36, W / 2 - 65, 42, 14, col)
c.setFillColor(white)
nm = wd[0] if len(wd[0]) < 28 else "we're getting nowhere here..."
fs = 12.5 if len(nm) < 24 else 10.5
c.setFont('B', fs)
c.drawCentredString(cx + (W / 2 - 65) / 2, cy - 20, nm)
c.setFillColor(HexColor('#555555')); c.setFont('R', 8.5)
c.drawCentredString(W / 2, 42, SRC)
c.showPage()
def contents(c):
c.setFillColor(HexColor('#F3F8FF')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 2, ALLC)
rrect(c, 40, H - 110, W - 80, 70, 20, '#5E35B1')
c.setFillColor(white); c.setFont('B', 26)
c.drawCentredString(W / 2, H - 85, "Word List / Список слов")
y = H - 140
for i, wd in enumerate(WORDS):
col, light = wd[6], wd[7]
h = 62
rrect(c, 40, y - h, W - 80, h, 14, light, col, 2)
rrect(c, 40, y - h, 52, h, 14, col)
c.setFillColor(white); c.setFont('B', 22)
c.drawCentredString(66, y - h / 2 - 8, str(i + 1))
nm = wd[0]
fs = 16 if len(nm) < 30 else 11.5
para(c, f"<b>{nm}</b>", 105, y - 8, W - 190, fs, 'B', col, leading=fs * 1.25)
hh = height(f"<b>{nm}</b>", W - 190, fs, 'B', fs * 1.25)
para(c, f"({wd[1]})", 105, y - 10 - hh, W - 190, 9.5, 'I', '#444444')
c.setFillColor(HexColor('#444444')); c.setFont('R', 10)
c.drawRightString(W - 52, y - h + 10, f"p. {i + 3}")
y -= h + 8
c.setFillColor(HexColor('#555555')); c.setFont('R', 8.5)
c.drawCentredString(W / 2, 28, SRC)
c.showPage()
def word_page(c, idx, wd):
word, resp, pos, en, ru, ex, col, light = wd
c.setFillColor(HexColor(light)); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 10 + idx, [col])
# banner
bx, bw = 30, W - 60
wsize = 38 if len(word) < 20 else (30 if len(word) < 30 else 25)
# word text height
wh = height(f"<b>{word}</b>", bw - 60, wsize, 'B', wsize * 1.2)
rh = height(f"({resp})", bw - 60, 16, 'B', 20)
banner_h = wh + rh + 70
top = H - 30
rrect(c, bx, top - banner_h, bw, banner_h, 28, col)
# number badge
c.setFillColor(white); c.circle(bx + 34, top - 34, 20, fill=1, stroke=0)
c.setFillColor(HexColor(col)); c.setFont('B', 20)
c.drawCentredString(bx + 34, top - 41, str(idx + 1))
# pos tag
c.setFillColor(white); c.setFont('I', 11)
c.drawRightString(bx + bw - 25, top - 38, pos)
y = top - 56
h1 = para(c, f"<b>{word}</b>", bx + 30, y, bw - 60, wsize, 'B', '#FFFFFF', wsize * 1.2, align=1)
y -= h1 + 6
rrect(c, bx + 30, y - rh - 8, bw - 60, rh + 10, 12, '#FFFFFF')
para(c, f"({resp})", bx + 40, y - 4, bw - 80, 16, 'B', col, 20, align=1)
y = top - banner_h - 18
def box(label_en, label_ru, text, y, font='R'):
th = height(text, bw - 50, 13.5, font, 18)
h = th + 42
rrect(c, bx, y - h, bw, h, 16, '#FFFFFF', col, 2.5)
rrect(c, bx + 14, y - 8 - 6, 0, 0, 0, '#FFFFFF')
c.setFillColor(HexColor(col)); c.roundRect(bx + 16, y - 24, 118, 18, 9, fill=1, stroke=0)
c.setFillColor(white); c.setFont('B', 10)
c.drawCentredString(bx + 75, y - 18, label_en)
para(c, text, bx + 25, y - 30, bw - 50, 13.5, font, '#222222', 18)
return y - h - 12
y = box("MEANING", "", en, y)
y = box("ЗНАЧЕНИЕ", "", ru, y)
# examples
c.setFillColor(HexColor(col)); c.setFont('B', 14)
c.drawString(bx + 4, y - 14, "Daily-life examples / Примеры из жизни")
y -= 24
for k, (e, r) in enumerate(ex):
eh = height(e, bw - 70, 12.5, 'B', 16)
rh2 = height(r, bw - 70, 11.5, 'R', 15)
h = eh + rh2 + 26
rrect(c, bx, y - h, bw, h, 14, '#FFFFFF', col, 1.5)
c.setFillColor(HexColor(col)); c.circle(bx + 24, y - h / 2, 14, fill=1, stroke=0)
c.setFillColor(white); c.setFont('B', 14)
c.drawCentredString(bx + 24, y - h / 2 - 5, str(k + 1))
para(c, e, bx + 50, y - 10, bw - 70, 12.5, 'B', '#222222', 16)
para(c, r, bx + 50, y - 14 - eh, bw - 70, 11.5, 'R', col, 15)
y -= h + 9
c.setFillColor(HexColor('#777777')); c.setFont('R', 8)
c.drawCentredString(W / 2, 20, f"Vocabulary Handbook | page {idx + 3}")
assert y > 30, (word, y)
c.showPage()
def review(c):
c.setFillColor(HexColor('#FFFDE7')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 99, ALLC)
rrect(c, 40, H - 110, W - 80, 70, 20, '#E53935')
c.setFillColor(white); c.setFont('B', 22)
c.drawCentredString(W / 2, H - 82, "Quick Review / Быстрое повторение")
y = H - 130
for i, wd in enumerate(WORDS):
col, light = wd[6], wd[7]
nm = wd[0]
ru = wd[4].split(';')[0]
fs = 13 if len(nm) < 30 else 9.5
th = height(f"<b>{nm}</b>", 200, fs, 'B', fs * 1.2)
rt = height(ru, 235, 10.5, 'R', 13.5)
h = max(th, rt) + 18
rrect(c, 40, y - h, W - 80, h, 12, col)
rrect(c, 250, y - h + 4, W - 80 - 214, h - 8, 9, '#FFFFFF')
para(c, f"<b>{nm}</b>", 52, y - 9, 190, fs, 'B', '#FFFFFF', fs * 1.2)
para(c, ru, 258, y - 9, 235, 10.5, 'R', col, 13.5)
y -= h + 7
c.setFillColor(HexColor('#555555')); c.setFont('R', 9)
c.drawCentredString(W / 2, 50, "Tip: say each example aloud, then cover the Russian line and translate it back.")
c.drawCentredString(W / 2, 36, "Совет: прочитайте пример вслух, закройте перевод и переведите его сами.")
c.showPage()
out = '/tmp/workspace/vocab-handbook/Vocabulary_Handbook_Arguments_EN_RU.pdf'
c = canvas.Canvas(out, pagesize=A4)
c.setTitle("Vocabulary Handbook: Arguments and Discussions (EN-RU)")
cover(c); contents(c)
for i, wd in enumerate(WORDS):
word_page(c, i, wd)
review(c)
c.save()
print(out)
cd /tmp/workspace/vocab-handbook && python3 build.py
ls -la /tmp/workspace/ /tmp/workspace/vocab-handbook/
cd /tmp/workspace/vocab-handbook && python3 build.py && python3 -c " import pypdf;r=pypdf.PdfReader('Vocabulary_Handbook_Arguments_EN_RU.pdf');print(len(r.pages))"
cd /tmp/workspace/vocab-handbook && python3 -c " try: import fitz d=fitz.open('Vocabulary_Handbook_Arguments_EN_RU.pdf') for i in (0,1,7,12): d[i].get_pixmap(dpi=60).save(f'p{i}.png') print('fitz ok') except Exception as e: print('fitz',e) import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Arguments_EN_RU.pdf') for i in (0,1,7,12): d[i].render(scale=0.9).to_pil().save(f'p{i}.png') print('pdfium ok') "; ls
cd /tmp/workspace/vocab-handbook && python3 - <<'E' s=open('build.py').read() # cover chips s=s.replace(''' nm = wd[0] if len(wd[0]) < 28 else "we're getting nowhere here..." fs = 12.5 if len(nm) < 24 else 10.5 c.setFont('B', fs)''',''' nm = wd[0] if len(wd[0]) < 40 else "getting nowhere / going round in circles" fs = 12.5 while pdfmetrics.stringWidth(nm, 'B', fs) > W / 2 - 85: fs -= 0.5 c.setFont('B', fs)''') # word sizes s=s.replace("wsize = 38 if len(word) < 20 else (30 if len(word) < 30 else 25)","wsize = 46 if len(word) < 14 else (40 if len(word) < 24 else (32 if len(word) < 40 else 27))") s=s.replace("th = height(text, bw - 50, 13.5, font, 18)","th = height(text, bw - 50, 15, font, 20)") s=s.replace("para(c, text, bx + 25, y - 30, bw - 50, 13.5, font, '#222222', 18)","para(c, text, bx + 25, y - 30, bw - 50, 15, font, '#222222', 20)") s=s.replace("height(e, bw - 70, 12.5, 'B', 16)","height(e, bw - 70, 14, 'B', 18)") s=s.replace("height(r, bw - 70, 11.5, 'R', 15)","height(r, bw - 70, 12.5, 'R', 16)") s=s.replace("para(c, e, bx + 50, y - 10, bw - 70, 12.5, 'B', '#222222', 16)","para(c, e, bx + 50, y - 10, bw - 70, 14, 'B', '#222222', 18)") s=s.replace("para(c, r, bx + 50, y - 14 - eh, bw - 70, 11.5, 'R', col, 15)","para(c, r, bx + 50, y - 14 - eh, bw - 70, 12.5, 'R', '#1a1a1a', 16)") s=s.replace("h = eh + rh2 + 26","h = eh + rh2 + 30") s=s.replace("rrect(c, bx, y - h, bw, h, 14, '#FFFFFF', col, 1.5)","rrect(c, bx, y - h, bw, h, 14, '#FFFFFF', col, 2)") # review russian dark s=s.replace("para(c, ru, 258, y - 9, 235, 10.5, 'R', col, 13.5)","para(c, ru, 258, y - 9, 235, 10.5, 'R', '#1a1a1a', 13.5)") open('build.py','w').write(s) E python3 build.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Arguments_EN_RU.pdf') for i in (0,2,6,7,10): d[i].render(scale=0.9).to_pil().save(f'p{i}.png') "
cd /tmp/workspace/vocab-handbook && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Arguments_EN_RU.pdf') d[1].render(scale=0.9).to_pil().save('c1.png')"
cd /tmp/workspace/vocab-handbook && sed -i 's/ h = 62$/ h = 57/; s/ y -= h + 8$/ y -= h + 6/; s/ y = H - 140$/ y = H - 128/' build.py && python3 build.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Arguments_EN_RU.pdf') d[1].render(scale=0.9).to_pil().save('c1.png')"
Vocabulary Handbook Arguments EN RU
PDF Document · PDF
Please recreate the same colorful pdf with address vocabulary words as follows , Briny , comical, perilous, idle vs ideal, profound.
~/vocab-handbook/build2.py
import re
old = open('/tmp/workspace/vocab-handbook/build.py').read()
head = old.split("# (word, respelling")[0] # imports + fonts
helpers = old[old.index("SRC ="):old.index("ALLC =")] # para/height/rrect/confetti
wp = old[old.index("def word_page"):old.index("def review")] # word page function
wp = wp.replace("def word_page(c, idx, wd):", "def word_page(c, idx, wd, pg):").replace("page {idx + 3}", "page {pg}")
exec(head + helpers.replace(
"SRC = \"Source: Outcomes Advanced 3rd ed. Teacher's Book, Unit 6 'Conflict and resolution', Language notes (pp. 89 and 91)\"",
"SRC = 'Vocabulary Handbook | English + respelling + Русский перевод'"))
exec(wp)
WORDS = [
("briny", "BRY-nee", "adjective",
"very salty, like sea water",
"очень солёный, как морская вода; солёный (рассольный)",
[("The sea air was briny, and I could taste salt on my lips.",
"Морской воздух был солёным, и я чувствовал соль на губах."),
("We ate briny olives with our bread at lunch.",
"На обед мы ели солёные оливки с хлебом."),
("Don't drink the briny water at the beach. It will make you thirstier.",
"Не пей солёную воду на пляже. От неё захочется пить ещё сильнее.")],
"#00ACC1", "#E0F7FA"),
("comical", "KOM-ih-kul", "adjective",
"funny in a way that makes people laugh, often because it looks silly",
"смешной, забавный; вызывающий смех (часто из-за нелепого вида)",
[("My cat's attempt to catch the laser dot was so comical that we all laughed.",
"Попытка моего кота поймать лазерную точку была такой смешной, что мы все засмеялись."),
("He made a comical face to cheer up the baby.",
"Он скорчил забавную рожицу, чтобы развеселить малыша."),
("It looked comical when the tall man sat on the tiny child's chair.",
"Выглядело комично, когда высокий мужчина сел на крошечный детский стульчик.")],
"#FB8C00", "#FFF3E0"),
("perilous", "PER-ih-lus", "adjective",
"full of danger or risk",
"опасный; рискованный; полный опасностей",
[("The road over the mountain is narrow and perilous in winter.",
"Горная дорога зимой узкая и опасная."),
("Crossing the river on a broken bridge is perilous.",
"Переходить реку по сломанному мосту очень опасно."),
("The fisherman made a perilous journey home through the storm.",
"Рыбак совершил опасное путешествие домой сквозь шторм.")],
"#E53935", "#FFEBEE"),
("idle", "Y-dul", "adjective",
"not working or not being used; doing nothing; (of talk) without real purpose",
"праздный, бездельничающий; не работающий, простаивающий; (о разговорах) пустой",
[("Don't leave the car engine idle for too long. It wastes petrol.",
"Не оставляй двигатель автомобиля надолго работать вхолостую. Это впустую тратит бензин."),
("On Sunday afternoon I like to sit idle in the garden and do nothing.",
"В воскресенье днём я люблю сидеть в саду без дела и ничего не делать."),
("It was just idle talk. He never really planned to move.",
"Это были просто пустые разговоры. Он на самом деле не собирался переезжать.")],
"#43A047", "#E8F5E9"),
("ideal", "y-DEE-ul", "adjective / noun",
"perfect or the best possible for a purpose; a perfect example or standard (noun)",
"идеальный, наилучший, самый подходящий; идеал (сущ.)",
[("This quiet flat is ideal for students.",
"Эта тихая квартира идеально подходит для студентов."),
("Sunny weather is ideal for a picnic in the park.",
"Солнечная погода идеальна для пикника в парке."),
("She is the ideal person to ask for cooking advice.",
"Она идеальный человек, у которого можно спросить совета по кулинарии.")],
"#1E88E5", "#E3F2FD"),
("profound", "pruh-FOWND", "adjective",
"very deep or strong; showing great thought or understanding",
"глубокий; сильный; проникновенный; выражающий глубокую мысль",
[("The film had a profound effect on me. I thought about it for days.",
"Фильм оказал на меня глубокое влияние. Я думал о нём несколько дней."),
("She felt profound sadness when her old friend moved away.",
"Она почувствовала глубокую грусть, когда её старый друг уехал."),
("He asked a profound question that made everyone stop and think.",
"Он задал глубокий вопрос, который заставил всех остановиться и задуматься.")],
"#8E24AA", "#F3E5F5"),
]
ALLC = [w[6] for w in WORDS]
# page numbers: cover 1, contents 2, briny 3, comical 4, perilous 5, idle 6, ideal 7, compare 8, profound 9, review 10
PAGES = [3, 4, 5, 6, 7, 9]
CMP = 8
CMPCOL, CMPLIGHT = '#D81B60', '#FCE4EC'
def cover(c):
c.setFillColor(HexColor('#FFF8E1')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 1, ALLC)
bw = W / len(ALLC)
for i, col in enumerate(ALLC):
c.setFillColor(HexColor(col)); c.rect(i * bw, H - 28, bw, 28, fill=1, stroke=0)
c.rect(i * bw, 0, bw, 28, fill=1, stroke=0)
rrect(c, 45, H - 420, W - 90, 300, 30, '#8E24AA')
rrect(c, 55, H - 410, W - 110, 280, 24, '#AB47BC')
c.setFillColor(white); c.setFont('B', 40)
c.drawCentredString(W / 2, H - 205, "Vocabulary")
c.drawCentredString(W / 2, H - 258, "Handbook")
c.setFont('B', 17)
c.drawCentredString(W / 2, H - 305, "Words in Context")
c.setFont('R', 14)
c.drawCentredString(W / 2, H - 335, "Слова в контексте")
c.setFont('R', 12)
c.drawCentredString(W / 2, H - 375, "English + respelling + Русский перевод + 3 examples")
y = H - 480
cw = W / 2 - 65
for i, wd in enumerate(WORDS):
cx = 60 if i % 2 == 0 else W / 2 + 5
cy = y - (i // 2) * 74
rrect(c, cx, cy - 56, cw, 62, 18, wd[6])
c.setFillColor(white); c.setFont('B', 22)
c.drawCentredString(cx + cw / 2, cy - 36, wd[0])
c.setFillColor(HexColor('#555555')); c.setFont('R', 9)
c.drawCentredString(W / 2, 42, "briny | comical | perilous | idle vs ideal | profound")
c.showPage()
def contents(c):
c.setFillColor(HexColor('#F3F8FF')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 2, ALLC)
rrect(c, 40, H - 110, W - 80, 70, 20, '#5E35B1')
c.setFillColor(white); c.setFont('B', 26)
c.drawCentredString(W / 2, H - 85, "Word List / Список слов")
rows = []
for i, wd in enumerate(WORDS):
rows.append((wd[0], wd[1], wd[6], wd[7], PAGES[i]))
if wd[0] == 'ideal':
rows.append(("idle vs ideal", "compare the two words / сравнение", CMPCOL, CMPLIGHT, CMP))
y = H - 140
n = 0
for name, resp, col, light, pg in rows:
h = 70
rrect(c, 40, y - h, W - 80, h, 14, light, col, 2)
rrect(c, 40, y - h, 56, h, 14, col)
c.setFillColor(white); c.setFont('B', 22)
is_cmp = name == "idle vs ideal"
if not is_cmp:
n += 1
c.drawCentredString(68, y - h / 2 - 8, "VS" if is_cmp else str(n))
c.setFillColor(HexColor(col)); c.setFont('B', 24)
c.drawString(115, y - 32, name)
c.setFillColor(HexColor('#444444')); c.setFont('I', 12)
c.drawString(115, y - 52, resp if is_cmp else f"({resp})")
c.setFont('R', 11)
c.drawRightString(W - 55, y - h + 12, f"p. {pg}")
y -= h + 12
c.setFillColor(HexColor('#555555')); c.setFont('R', 8.5)
c.drawCentredString(W / 2, 28, SRC)
c.showPage()
def compare(c):
c.setFillColor(HexColor(CMPLIGHT)); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 55, ['#43A047', '#1E88E5', CMPCOL])
rrect(c, 30, H - 110, W - 60, 80, 28, CMPCOL)
c.setFillColor(white); c.setFont('B', 38)
c.drawCentredString(W / 2, H - 78, "idle vs ideal")
c.setFont('R', 12)
c.drawCentredString(W / 2, H - 98, "Похожие по звучанию, но разные по смыслу")
# two cards
cw = (W - 60 - 16) / 2
top = H - 130
cards = [(WORDS[3], 30), (WORDS[4], 30 + cw + 16)]
ch = 250
for wd, x in cards:
col, light = wd[6], wd[7]
rrect(c, x, top - ch, cw, ch, 20, '#FFFFFF', col, 3)
rrect(c, x, top - 70, cw, 70, 20, col)
c.setFillColor(white); c.setFont('B', 34)
c.drawCentredString(x + cw / 2, top - 48, wd[0])
para(c, f"({wd[1]})", x + 10, top - 82, cw - 20, 15, 'B', col, 19, align=1)
short_en = {"idle": "doing nothing; not in use", "ideal": "perfect; the best possible"}[wd[0]]
short_ru = {"idle": "бездельничающий; не используемый; пустой", "ideal": "идеальный; наилучший"}[wd[0]]
hh = para(c, short_en, x + 12, top - 112, cw - 24, 13.5, 'B', '#222222', 18, align=1)
para(c, short_ru, x + 12, top - 118 - hh, cw - 24, 12.5, 'R', '#1a1a1a', 16, align=1)
ex = {"idle": ("The machine sat idle all week.", "Станок простаивал всю неделю."),
"ideal": ("It was the ideal place for a holiday.", "Это было идеальное место для отпуска.")}[wd[0]]
yy = top - 180
rrect(c, x + 10, yy - 58, cw - 20, 66, 12, light)
hh = para(c, ex[0], x + 18, yy, cw - 36, 12, 'I', '#222222', 15, align=1)
para(c, ex[1], x + 18, yy - hh - 3, cw - 36, 11, 'R', '#1a1a1a', 14, align=1)
y = top - ch - 20
# memory tips
tips = [("Spelling / Написание", "idle = 4 letters + e at the end, 2 syllables (Y-dul). ideal = 5 letters, 3 syllables (y-DEE-ul).",
"idle: 2 слога (Y-dul); ideal: 3 слога (y-DEE-ul). В ideal есть буквы e-a."),
("Memory trick / Подсказка", "ideal has 'idea' inside it: an ideal is the best idea. idle = nothing happens.",
"В ideal спрятано слово idea («идея»): идеал - лучшая идея. Idle - ничего не происходит.")]
for t, en, ru in tips:
eh = height(en, W - 100, 12.5, 'R', 16); rh = height(ru, W - 100, 11.5, 'R', 15)
h = eh + rh + 44
rrect(c, 30, y - h, W - 60, h, 16, '#FFFFFF', CMPCOL, 2.5)
c.setFillColor(HexColor(CMPCOL)); c.setFont('B', 12)
c.drawString(45, y - 20, t)
para(c, en, 45, y - 28, W - 100, 12.5, 'R', '#222222', 16)
para(c, ru, 45, y - 32 - eh, W - 100, 11.5, 'R', '#1a1a1a', 15)
y -= h + 12
# quiz
c.setFillColor(HexColor(CMPCOL)); c.setFont('B', 14)
c.drawString(34, y - 12, "Quick quiz: idle or ideal? / Мини-тест")
y -= 22
quiz = [("1. The workers were ______ because there was no job to do.", "Рабочие бездельничали, потому что не было работы.", "idle"),
("2. This is the ______ spot for a picnic.", "Это идеальное место для пикника.", "ideal"),
("3. The shop's machines stay ______ at night.", "Станки магазина ночью простаивают.", "idle")]
for q, r, a in quiz:
qh = height(q, W - 220, 12, 'B', 15)
h = qh + 14 + 15 + 8
rrect(c, 30, y - h, W - 60, h, 12, '#FFFFFF', CMPCOL, 1.5)
para(c, q, 42, y - 8, W - 220, 12, 'B', '#222222', 15)
para(c, r, 42, y - 10 - qh, W - 220, 10.5, 'R', '#1a1a1a', 14)
rrect(c, W - 170, y - h + 8, 130, h - 16, 10, '#43A047' if a == 'idle' else '#1E88E5')
c.setFillColor(white); c.setFont('B', 11)
c.drawCentredString(W - 105, y - h / 2 - 4, f"Answer: {a}")
y -= h + 8
assert y > 30, y
c.setFillColor(HexColor('#777777')); c.setFont('R', 8)
c.drawCentredString(W / 2, 18, f"Vocabulary Handbook | page {CMP}")
c.showPage()
def review(c):
c.setFillColor(HexColor('#FFFDE7')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 99, ALLC)
rrect(c, 40, H - 110, W - 80, 70, 20, '#E53935')
c.setFillColor(white); c.setFont('B', 22)
c.drawCentredString(W / 2, H - 82, "Quick Review / Быстрое повторение")
y = H - 140
for wd in WORDS:
col = wd[6]
h = 64
rrect(c, 40, y - h, W - 80, h, 14, col)
c.setFillColor(white); c.setFont('B', 24)
c.drawString(56, y - 30, wd[0])
c.setFont('I', 11)
c.drawString(56, y - 50, f"({wd[1]})")
rrect(c, 235, y - h + 6, W - 80 - 205, h - 12, 10, '#FFFFFF')
para(c, wd[3], 245, y - 10, W - 80 - 225, 10.5, 'B', '#222222', 13)
eh = height(wd[3], W - 80 - 225, 10.5, 'B', 13)
para(c, wd[4].split(';')[0], 245, y - 12 - eh, W - 80 - 225, 10.5, 'R', col, 13)
y -= h + 9
c.setFillColor(HexColor('#555555')); c.setFont('R', 9)
c.drawCentredString(W / 2, 50, "Tip: say each example aloud, then cover the Russian line and translate it back.")
c.drawCentredString(W / 2, 36, "Совет: прочитайте пример вслух, закройте перевод и переведите его сами.")
c.showPage()
out = '/tmp/workspace/vocab-handbook/Vocabulary_Handbook_Briny_Comical_Perilous_Idle_Ideal_Profound.pdf'
c = canvas.Canvas(out, pagesize=A4)
c.setTitle("Vocabulary Handbook: briny, comical, perilous, idle vs ideal, profound (EN-RU)")
cover(c); contents(c)
for i, wd in enumerate(WORDS):
word_page(c, i, wd, PAGES[i])
if wd[0] == 'ideal':
compare(c)
review(c)
c.save()
print(out)
cd /tmp/workspace/vocab-handbook && python3 build2.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Briny_Comical_Perilous_Idle_Ideal_Profound.pdf') print(len(d)) for i in (0,1,2,7,9): d[i].render(scale=0.9).to_pil().save(f'n{i}.png')"
cd /tmp/workspace/vocab-handbook && python3 - <<'E' s=open('build2.py').read() s=s.replace(" top = H - 130\n cards"," top = H - 122\n cards") s=s.replace("rrect(c, 30, H - 110, W - 60, 80, 28, CMPCOL)\n c.setFillColor(white); c.setFont('B', 38)\n c.drawCentredString(W / 2, H - 78","rrect(c, 30, H - 102, W - 60, 72, 26, CMPCOL)\n c.setFillColor(white); c.setFont('B', 36)\n c.drawCentredString(W / 2, H - 72") s=s.replace("c.drawCentredString(W / 2, H - 98, \"Похожие","c.drawCentredString(W / 2, H - 91, \"Похожие") s=s.replace("y = top - ch - 20","y = top - ch - 12") s=s.replace("h = eh + rh + 44","h = eh + rh + 38") s=s.replace("y -= h + 12\n # quiz","y -= h + 8\n # quiz") s=s.replace("h = qh + 14 + 15 + 8","h = qh + 12 + 14 + 4") s=s.replace("ch = 250","ch = 246") open('build2.py','w').write(s) E python3 build2.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Briny_Comical_Perilous_Idle_Ideal_Profound.pdf') print(len(d)) for i in (0,1,2,7,9): d[i].render(scale=0.9).to_pil().save(f'n{i}.png')"
cd /tmp/workspace/vocab-handbook && python3 - <<'E' s=open('build2.py').read() s=s.replace("idle = 4 letters + e at the end, 2 syllables (Y-dul). ideal = 5 letters, 3 syllables (y-DEE-ul).","idle = 4 letters, 2 syllables (Y-dul). ideal = 5 letters, 3 syllables (y-DEE-ul).") s=s.replace("'R', col, 13)","'R', '#1a1a1a', 13)") open('build2.py','w').write(s) E python3 build2.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Briny_Comical_Perilous_Idle_Ideal_Profound.pdf') d[5].render(scale=0.9).to_pil().save('n5.png')"
cd /tmp/workspace/vocab-handbook && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Briny_Comical_Perilous_Idle_Ideal_Profound.pdf') print(len(d)) d[5].render(scale=0.9).to_pil().save('n5.png')" && ls
Vocabulary Handbook Briny Comical Perilous Idle Ideal Profound
PDF Document · PDF
Just add those new words in the same fashion like other vocabulary words already there in colorful handbook pdf.
old = open('/tmp/workspace/vocab-handbook/build.py').read()
new = open('/tmp/workspace/vocab-handbook/build2.py').read()
head = old.split("# (word, respelling")[0]
helpers = old[old.index("SRC ="):old.index("ALLC =")]
wp = old[old.index("def word_page"):old.index("def review")]
wp = wp.replace("def word_page(c, idx, wd):", "def word_page(c, idx, wd, pg):").replace("page {idx + 3}", "page {pg}")
exec(head + helpers)
exec(wp)
ns = {}
w1 = old[old.index("WORDS = ["):old.index("SRC =")]
w2 = new[new.index("WORDS = ["):new.index("ALLC =")]
exec(w1, ns); OLDW = ns['WORDS']
exec(w2, ns); NEWW = ns['WORDS']
WORDS = OLDW + NEWW # 16 entries
ALLC = [w[6] for w in WORDS]
SRC = "Vocabulary Handbook | English + respelling + Русский перевод"
CMPCOL, CMPLIGHT = '#D81B60', '#FCE4EC'
# page plan
PAGES = {}
p = 4
for i in range(len(WORDS)):
PAGES[i] = p
p += 1
if WORDS[i][0] == 'ideal':
CMP = p
p += 1
REVIEW_START = p
IDLE_I = [w[0] for w in WORDS].index('idle')
IDEAL_I = [w[0] for w in WORDS].index('ideal')
def short(nm):
return nm if len(nm) < 40 else "getting nowhere / going round in circles"
def cover(c):
c.setFillColor(HexColor('#FFF8E1')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 1, ALLC)
bw = W / len(ALLC)
for i, col in enumerate(ALLC):
c.setFillColor(HexColor(col)); c.rect(i * bw, H - 28, bw, 28, fill=1, stroke=0)
c.rect(i * bw, 0, bw, 28, fill=1, stroke=0)
rrect(c, 45, H - 330, W - 90, 260, 30, '#1E88E5')
rrect(c, 55, H - 320, W - 110, 240, 24, '#42A5F5')
c.setFillColor(white); c.setFont('B', 40)
c.drawCentredString(W / 2, H - 150, "Vocabulary")
c.drawCentredString(W / 2, H - 203, "Handbook")
c.setFont('B', 16)
c.drawCentredString(W / 2, H - 245, "Arguments, Discussions and Words in Context")
c.setFont('R', 13)
c.drawCentredString(W / 2, H - 270, "Словарь: споры, обсуждения и слова в контексте")
c.setFont('R', 11)
c.drawCentredString(W / 2, H - 298, "English + respelling + Русский перевод + 3 examples")
cw = W / 2 - 60
y = H - 360
for i, wd in enumerate(WORDS):
cx = 45 if i % 2 == 0 else W / 2 + 15
cy = y - (i // 2) * 52
rrect(c, cx, cy - 40, cw, 44, 14, wd[6])
c.setFillColor(white)
nm = short(wd[0]); fs = 15
while pdfmetrics.stringWidth(nm, 'B', fs) > cw - 16: fs -= 0.5
c.setFont('B', fs)
c.drawCentredString(cx + cw / 2, cy - 25, nm)
c.setFillColor(HexColor('#555555')); c.setFont('R', 9)
c.drawCentredString(W / 2, 42, "16 words and phrases | 16 слов и выражений")
c.showPage()
def rows_list():
rows, n = [], 0
for i, wd in enumerate(WORDS):
n += 1
rows.append((str(n), wd[0], wd[1], wd[6], wd[7], PAGES[i], False))
if wd[0] == 'ideal':
rows.append(("VS", "idle vs ideal", "compare the two words / сравнение", CMPCOL, CMPLIGHT, CMP, True))
return rows
def contents(c):
rows = rows_list()
chunks = [rows[:10], rows[10:]]
for ci, chunk in enumerate(chunks):
c.setFillColor(HexColor('#F3F8FF')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 2 + ci, ALLC)
rrect(c, 40, H - 110, W - 80, 70, 20, '#5E35B1')
c.setFillColor(white); c.setFont('B', 26)
c.drawCentredString(W / 2, H - 85, "Word List / Список слов" + ("" if ci == 0 else " (2)"))
y = H - 128
for num, name, resp, col, light, pg, is_cmp in chunk:
h = 57
rrect(c, 40, y - h, W - 80, h, 14, light, col, 2)
rrect(c, 40, y - h, 52, h, 14, col)
c.setFillColor(white); c.setFont('B', 20 if is_cmp else 22)
c.drawCentredString(66, y - h / 2 - 8, num)
fs = 16 if len(name) < 30 else 11.5
t = f"<b>{name}</b>"
para(c, t, 105, y - 8, W - 190, fs, 'B', col, leading=fs * 1.25)
hh = height(t, W - 190, fs, 'B', fs * 1.25)
para(c, resp if is_cmp else f"({resp})", 105, y - 10 - hh, W - 190, 9.5, 'I', '#444444')
c.setFillColor(HexColor('#444444')); c.setFont('R', 10)
c.drawRightString(W - 52, y - h + 10, f"p. {pg}")
y -= h + 6
c.setFillColor(HexColor('#555555')); c.setFont('R', 8.5)
c.drawCentredString(W / 2, 28, SRC)
c.showPage()
def compare(c):
c.setFillColor(HexColor(CMPLIGHT)); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 55, ['#43A047', '#1E88E5', CMPCOL])
rrect(c, 30, H - 102, W - 60, 72, 26, CMPCOL)
c.setFillColor(white); c.setFont('B', 36)
c.drawCentredString(W / 2, H - 72, "idle vs ideal")
c.setFont('R', 12)
c.drawCentredString(W / 2, H - 91, "Похожие по звучанию, но разные по смыслу")
cw = (W - 60 - 16) / 2
top = H - 122
cards = [(WORDS[IDLE_I], 30), (WORDS[IDEAL_I], 30 + cw + 16)]
ch = 246
for wd, x in cards:
col, light = wd[6], wd[7]
rrect(c, x, top - ch, cw, ch, 20, '#FFFFFF', col, 3)
rrect(c, x, top - 70, cw, 70, 20, col)
c.setFillColor(white); c.setFont('B', 34)
c.drawCentredString(x + cw / 2, top - 48, wd[0])
para(c, f"({wd[1]})", x + 10, top - 82, cw - 20, 15, 'B', col, 19, align=1)
short_en = {"idle": "doing nothing; not in use", "ideal": "perfect; the best possible"}[wd[0]]
short_ru = {"idle": "бездельничающий; не используемый; пустой", "ideal": "идеальный; наилучший"}[wd[0]]
hh = para(c, short_en, x + 12, top - 112, cw - 24, 13.5, 'B', '#222222', 18, align=1)
para(c, short_ru, x + 12, top - 118 - hh, cw - 24, 12.5, 'R', '#1a1a1a', 16, align=1)
ex = {"idle": ("The machine sat idle all week.", "Станок простаивал всю неделю."),
"ideal": ("It was the ideal place for a holiday.", "Это было идеальное место для отпуска.")}[wd[0]]
yy = top - 180
rrect(c, x + 10, yy - 58, cw - 20, 66, 12, light)
hh = para(c, ex[0], x + 18, yy, cw - 36, 12, 'I', '#222222', 15, align=1)
para(c, ex[1], x + 18, yy - hh - 3, cw - 36, 11, 'R', '#1a1a1a', 14, align=1)
y = top - ch - 12
tips = [("Spelling / Написание", "idle = 4 letters, 2 syllables (Y-dul). ideal = 5 letters, 3 syllables (y-DEE-ul).",
"idle: 2 слога (Y-dul); ideal: 3 слога (y-DEE-ul). В ideal есть буквы e-a."),
("Memory trick / Подсказка", "ideal has 'idea' inside it: an ideal is the best idea. idle = nothing happens.",
"В ideal спрятано слово idea («идея»): идеал - лучшая идея. Idle - ничего не происходит.")]
for t, en, ru in tips:
eh = height(en, W - 100, 12.5, 'R', 16); rh = height(ru, W - 100, 11.5, 'R', 15)
h = eh + rh + 38
rrect(c, 30, y - h, W - 60, h, 16, '#FFFFFF', CMPCOL, 2.5)
c.setFillColor(HexColor(CMPCOL)); c.setFont('B', 12)
c.drawString(45, y - 20, t)
para(c, en, 45, y - 28, W - 100, 12.5, 'R', '#222222', 16)
para(c, ru, 45, y - 32 - eh, W - 100, 11.5, 'R', '#1a1a1a', 15)
y -= h + 8
c.setFillColor(HexColor(CMPCOL)); c.setFont('B', 14)
c.drawString(34, y - 12, "Quick quiz: idle or ideal? / Мини-тест")
y -= 22
quiz = [("1. The workers were ______ because there was no job to do.", "Рабочие бездельничали, потому что не было работы.", "idle"),
("2. This is the ______ spot for a picnic.", "Это идеальное место для пикника.", "ideal"),
("3. The shop's machines stay ______ at night.", "Станки магазина ночью простаивают.", "idle")]
for q, r, a in quiz:
qh = height(q, W - 220, 12, 'B', 15)
h = qh + 12 + 14 + 4
rrect(c, 30, y - h, W - 60, h, 12, '#FFFFFF', CMPCOL, 1.5)
para(c, q, 42, y - 8, W - 220, 12, 'B', '#222222', 15)
para(c, r, 42, y - 10 - qh, W - 220, 10.5, 'R', '#1a1a1a', 14)
rrect(c, W - 170, y - h + 8, 130, h - 16, 10, '#43A047' if a == 'idle' else '#1E88E5')
c.setFillColor(white); c.setFont('B', 11)
c.drawCentredString(W - 105, y - h / 2 - 4, f"Answer: {a}")
y -= h + 8
assert y > 30, y
c.setFillColor(HexColor('#777777')); c.setFont('R', 8)
c.drawCentredString(W / 2, 18, f"Vocabulary Handbook | page {CMP}")
c.showPage()
def review(c):
chunks = [WORDS[:8], WORDS[8:]]
for ci, chunk in enumerate(chunks):
c.setFillColor(HexColor('#FFFDE7')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 99 + ci, ALLC)
rrect(c, 40, H - 110, W - 80, 70, 20, '#E53935')
c.setFillColor(white); c.setFont('B', 22)
c.drawCentredString(W / 2, H - 82, "Quick Review / Быстрое повторение" + ("" if ci == 0 else " (2)"))
y = H - 128
for wd in chunk:
col = wd[6]
nm = short(wd[0])
fs = 22
while height(f"<b>{nm}</b>", 180, fs, 'B', fs * 1.2) > 2.4 * fs * 1.2 * (1 if fs > 14 else 1.2) and fs > 9: fs -= 1
while fs > 9 and height(f"<b>{nm}</b>", 180, fs, 'B', fs * 1.2) > (fs * 1.2 * 2 + 1):
fs -= 1
nh = height(f"<b>{nm}</b>", 180, fs, 'B', fs * 1.2)
rt = f"({wd[1]})" if len(wd[0]) < 40 else ""
rsh = height(rt, 180, 8.5, 'I', 11) if rt else 0
en_h = height(wd[3], 245, 10.5, 'B', 13)
ru_h = height(wd[4].split(';')[0], 245, 10.5, 'R', 13)
h = max(nh + rsh + 18, en_h + ru_h + 20, 60)
rrect(c, 40, y - h, W - 80, h, 14, col)
para(c, f"<b>{nm}</b>", 54, y - 9, 180, fs, 'B', '#FFFFFF', fs * 1.2)
if rt:
para(c, rt, 54, y - 11 - nh, 180, 8.5, 'I', '#FFFFFF', 11)
rrect(c, 240, y - h + 6, W - 80 - 206, h - 12, 10, '#FFFFFF')
para(c, wd[3], 250, y - 11, 245, 10.5, 'B', '#222222', 13)
para(c, wd[4].split(';')[0], 250, y - 13 - en_h, 245, 10.5, 'R', '#1a1a1a', 13)
y -= h + 8
assert y > 70, (wd[0], y)
c.setFillColor(HexColor('#555555')); c.setFont('R', 9)
c.drawCentredString(W / 2, 50, "Tip: say each example aloud, then cover the Russian line and translate it back.")
c.drawCentredString(W / 2, 36, "Совет: прочитайте пример вслух, закройте перевод и переведите его сами.")
c.showPage()
out = '/tmp/workspace/vocab-handbook/Vocabulary_Handbook_Complete_16_Words_EN_RU.pdf'
c = canvas.Canvas(out, pagesize=A4)
c.setTitle("Vocabulary Handbook: Complete (EN-RU)")
cover(c); contents(c)
for i, wd in enumerate(WORDS):
word_page(c, i, wd, PAGES[i])
if wd[0] == 'ideal':
compare(c)
review(c)
c.save()
print(out, 'review starts', REVIEW_START)
~/vocab-handbook/build3.py
old = open('/tmp/workspace/vocab-handbook/build.py').read()
new = open('/tmp/workspace/vocab-handbook/build2.py').read()
head = old.split("# (word, respelling")[0]
helpers = old[old.index("SRC ="):old.index("ALLC =")]
wp = old[old.index("def word_page"):old.index("def review")]
wp = wp.replace("def word_page(c, idx, wd):", "def word_page(c, idx, wd, pg):").replace("page {idx + 3}", "page {pg}")
exec(head + helpers)
exec(wp)
ns = {}
w1 = old[old.index("WORDS = ["):old.index("SRC =")]
w2 = new[new.index("WORDS = ["):new.index("ALLC =")]
exec(w1, ns); OLDW = ns['WORDS']
exec(w2, ns); NEWW = ns['WORDS']
WORDS = OLDW + NEWW # 16 entries
ALLC = [w[6] for w in WORDS]
SRC = "Vocabulary Handbook | English + respelling + Русский перевод"
CMPCOL, CMPLIGHT = '#D81B60', '#FCE4EC'
# page plan
PAGES = {}
p = 4
for i in range(len(WORDS)):
PAGES[i] = p
p += 1
if WORDS[i][0] == 'ideal':
CMP = p
p += 1
IDLE_I = [w[0] for w in WORDS].index('idle')
IDEAL_I = [w[0] for w in WORDS].index('ideal')
def short(nm):
return nm if len(nm) < 40 else "getting nowhere / going round in circles"
def cover(c):
c.setFillColor(HexColor('#FFF8E1')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 1, ALLC)
bw = W / len(ALLC)
for i, col in enumerate(ALLC):
c.setFillColor(HexColor(col)); c.rect(i * bw, H - 28, bw, 28, fill=1, stroke=0)
c.rect(i * bw, 0, bw, 28, fill=1, stroke=0)
rrect(c, 45, H - 330, W - 90, 260, 30, '#1E88E5')
rrect(c, 55, H - 320, W - 110, 240, 24, '#42A5F5')
c.setFillColor(white); c.setFont('B', 40)
c.drawCentredString(W / 2, H - 150, "Vocabulary")
c.drawCentredString(W / 2, H - 203, "Handbook")
c.setFont('B', 16)
c.drawCentredString(W / 2, H - 245, "Arguments, Discussions and Words in Context")
c.setFont('R', 13)
c.drawCentredString(W / 2, H - 270, "Словарь: споры, обсуждения и слова в контексте")
c.setFont('R', 11)
c.drawCentredString(W / 2, H - 298, "English + respelling + Русский перевод + 3 examples")
cw = W / 2 - 60
y = H - 360
for i, wd in enumerate(WORDS):
cx = 45 if i % 2 == 0 else W / 2 + 15
cy = y - (i // 2) * 52
rrect(c, cx, cy - 40, cw, 44, 14, wd[6])
c.setFillColor(white)
nm = short(wd[0]); fs = 15
while pdfmetrics.stringWidth(nm, 'B', fs) > cw - 16: fs -= 0.5
c.setFont('B', fs)
c.drawCentredString(cx + cw / 2, cy - 25, nm)
c.setFillColor(HexColor('#555555')); c.setFont('R', 9)
c.drawCentredString(W / 2, 42, "16 words and phrases | 16 слов и выражений")
c.showPage()
def rows_list():
rows, n = [], 0
for i, wd in enumerate(WORDS):
n += 1
rows.append((str(n), wd[0], wd[1], wd[6], wd[7], PAGES[i], False))
if wd[0] == 'ideal':
rows.append(("VS", "idle vs ideal", "compare the two words / сравнение", CMPCOL, CMPLIGHT, CMP, True))
return rows
def contents(c):
rows = rows_list()
chunks = [rows[:10], rows[10:]]
for ci, chunk in enumerate(chunks):
c.setFillColor(HexColor('#F3F8FF')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 2 + ci, ALLC)
rrect(c, 40, H - 110, W - 80, 70, 20, '#5E35B1')
c.setFillColor(white); c.setFont('B', 26)
c.drawCentredString(W / 2, H - 85, "Word List / Список слов" + ("" if ci == 0 else " (2)"))
y = H - 128
for num, name, resp, col, light, pg, is_cmp in chunk:
h = 57
rrect(c, 40, y - h, W - 80, h, 14, light, col, 2)
rrect(c, 40, y - h, 52, h, 14, col)
c.setFillColor(white); c.setFont('B', 20 if is_cmp else 22)
c.drawCentredString(66, y - h / 2 - 8, num)
fs = 16 if len(name) < 30 else 11.5
t = f"<b>{name}</b>"
para(c, t, 105, y - 8, W - 190, fs, 'B', col, leading=fs * 1.25)
hh = height(t, W - 190, fs, 'B', fs * 1.25)
para(c, resp if is_cmp else f"({resp})", 105, y - 10 - hh, W - 190, 9.5, 'I', '#444444')
c.setFillColor(HexColor('#444444')); c.setFont('R', 10)
c.drawRightString(W - 52, y - h + 10, f"p. {pg}")
y -= h + 6
c.setFillColor(HexColor('#555555')); c.setFont('R', 8.5)
c.drawCentredString(W / 2, 28, SRC)
c.showPage()
def compare(c):
c.setFillColor(HexColor(CMPLIGHT)); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 55, ['#43A047', '#1E88E5', CMPCOL])
rrect(c, 30, H - 102, W - 60, 72, 26, CMPCOL)
c.setFillColor(white); c.setFont('B', 36)
c.drawCentredString(W / 2, H - 72, "idle vs ideal")
c.setFont('R', 12)
c.drawCentredString(W / 2, H - 91, "Похожие по звучанию, но разные по смыслу")
cw = (W - 60 - 16) / 2
top = H - 122
cards = [(WORDS[IDLE_I], 30), (WORDS[IDEAL_I], 30 + cw + 16)]
ch = 246
for wd, x in cards:
col, light = wd[6], wd[7]
rrect(c, x, top - ch, cw, ch, 20, '#FFFFFF', col, 3)
rrect(c, x, top - 70, cw, 70, 20, col)
c.setFillColor(white); c.setFont('B', 34)
c.drawCentredString(x + cw / 2, top - 48, wd[0])
para(c, f"({wd[1]})", x + 10, top - 82, cw - 20, 15, 'B', col, 19, align=1)
short_en = {"idle": "doing nothing; not in use", "ideal": "perfect; the best possible"}[wd[0]]
short_ru = {"idle": "бездельничающий; не используемый; пустой", "ideal": "идеальный; наилучший"}[wd[0]]
hh = para(c, short_en, x + 12, top - 112, cw - 24, 13.5, 'B', '#222222', 18, align=1)
para(c, short_ru, x + 12, top - 118 - hh, cw - 24, 12.5, 'R', '#1a1a1a', 16, align=1)
ex = {"idle": ("The machine sat idle all week.", "Станок простаивал всю неделю."),
"ideal": ("It was the ideal place for a holiday.", "Это было идеальное место для отпуска.")}[wd[0]]
yy = top - 180
rrect(c, x + 10, yy - 58, cw - 20, 66, 12, light)
hh = para(c, ex[0], x + 18, yy, cw - 36, 12, 'I', '#222222', 15, align=1)
para(c, ex[1], x + 18, yy - hh - 3, cw - 36, 11, 'R', '#1a1a1a', 14, align=1)
y = top - ch - 12
tips = [("Spelling / Написание", "idle = 4 letters, 2 syllables (Y-dul). ideal = 5 letters, 3 syllables (y-DEE-ul).",
"idle: 2 слога (Y-dul); ideal: 3 слога (y-DEE-ul). В ideal есть буквы e-a."),
("Memory trick / Подсказка", "ideal has 'idea' inside it: an ideal is the best idea. idle = nothing happens.",
"В ideal спрятано слово idea («идея»): идеал - лучшая идея. Idle - ничего не происходит.")]
for t, en, ru in tips:
eh = height(en, W - 100, 12.5, 'R', 16); rh = height(ru, W - 100, 11.5, 'R', 15)
h = eh + rh + 38
rrect(c, 30, y - h, W - 60, h, 16, '#FFFFFF', CMPCOL, 2.5)
c.setFillColor(HexColor(CMPCOL)); c.setFont('B', 12)
c.drawString(45, y - 20, t)
para(c, en, 45, y - 28, W - 100, 12.5, 'R', '#222222', 16)
para(c, ru, 45, y - 32 - eh, W - 100, 11.5, 'R', '#1a1a1a', 15)
y -= h + 8
c.setFillColor(HexColor(CMPCOL)); c.setFont('B', 14)
c.drawString(34, y - 12, "Quick quiz: idle or ideal? / Мини-тест")
y -= 22
quiz = [("1. The workers were ______ because there was no job to do.", "Рабочие бездельничали, потому что не было работы.", "idle"),
("2. This is the ______ spot for a picnic.", "Это идеальное место для пикника.", "ideal"),
("3. The shop's machines stay ______ at night.", "Станки магазина ночью простаивают.", "idle")]
for q, r, a in quiz:
qh = height(q, W - 220, 12, 'B', 15)
h = qh + 12 + 14 + 4
rrect(c, 30, y - h, W - 60, h, 12, '#FFFFFF', CMPCOL, 1.5)
para(c, q, 42, y - 8, W - 220, 12, 'B', '#222222', 15)
para(c, r, 42, y - 10 - qh, W - 220, 10.5, 'R', '#1a1a1a', 14)
rrect(c, W - 170, y - h + 8, 130, h - 16, 10, '#43A047' if a == 'idle' else '#1E88E5')
c.setFillColor(white); c.setFont('B', 11)
c.drawCentredString(W - 105, y - h / 2 - 4, f"Answer: {a}")
y -= h + 8
assert y > 30, y
c.setFillColor(HexColor('#777777')); c.setFont('R', 8)
c.drawCentredString(W / 2, 18, f"Vocabulary Handbook | page {CMP}")
c.showPage()
def review(c):
chunks = [WORDS[:8], WORDS[8:]]
for ci, chunk in enumerate(chunks):
c.setFillColor(HexColor('#FFFDE7')); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, 99 + ci, ALLC)
rrect(c, 40, H - 110, W - 80, 70, 20, '#E53935')
c.setFillColor(white); c.setFont('B', 22)
c.drawCentredString(W / 2, H - 82, "Quick Review / Быстрое повторение" + ("" if ci == 0 else " (2)"))
y = H - 128
for wd in chunk:
col = wd[6]
nm = short(wd[0])
fs = 22
while fs > 9 and height(f"<b>{nm}</b>", 180, fs, 'B', fs * 1.2) > fs * 1.2 * 2 + 1:
fs -= 1
nh = height(f"<b>{nm}</b>", 180, fs, 'B', fs * 1.2)
rt = f"({wd[1]})" if len(wd[0]) < 40 else ""
rsh = height(rt, 180, 8.5, 'I', 11) if rt else 0
ru_short = wd[4].split(';')[0]
en_h = height(wd[3], 245, 10.5, 'B', 13)
ru_h = height(ru_short, 245, 10.5, 'R', 13)
h = max(nh + rsh + 20, en_h + ru_h + 22, 60)
rrect(c, 40, y - h, W - 80, h, 14, col)
para(c, f"<b>{nm}</b>", 54, y - 9, 180, fs, 'B', '#FFFFFF', fs * 1.2)
if rt:
para(c, rt, 54, y - 11 - nh, 180, 8.5, 'I', '#FFFFFF', 11)
rrect(c, 240, y - h + 6, W - 80 - 206, h - 12, 10, '#FFFFFF')
para(c, wd[3], 250, y - 11, 245, 10.5, 'B', '#222222', 13)
para(c, ru_short, 250, y - 13 - en_h, 245, 10.5, 'R', '#1a1a1a', 13)
y -= h + 8
assert y > 70, (wd[0], y)
c.setFillColor(HexColor('#555555')); c.setFont('R', 9)
c.drawCentredString(W / 2, 50, "Tip: say each example aloud, then cover the Russian line and translate it back.")
c.drawCentredString(W / 2, 36, "Совет: прочитайте пример вслух, закройте перевод и переведите его сами.")
c.showPage()
out = '/tmp/workspace/vocab-handbook/Vocabulary_Handbook_Complete_16_Words_EN_RU.pdf'
c = canvas.Canvas(out, pagesize=A4)
c.setTitle("Vocabulary Handbook: Complete (EN-RU)")
cover(c); contents(c)
for i, wd in enumerate(WORDS):
word_page(c, i, wd, PAGES[i])
if wd[0] == 'ideal':
compare(c)
review(c)
c.save()
print(out)
cd /tmp/workspace/vocab-handbook && python3 build3.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Complete_16_Words_EN_RU.pdf') print(len(d)) for i in (0,2,len(d)-2,len(d)-1): d[i].render(scale=0.9).to_pil().save(f'm{i}.png')"; ls m*.png
cd /tmp/workspace/vocab-handbook && python3 -c " new=open('build2.py').read() print(new.index('WORDS = ['), new.index('ALLC =')) import re print([m.start() for m in re.finditer('ALLC =',new)][:3]) old=open('build.py').read() print(old.index('WORDS = ['), old.index('SRC =')) print([m.start() for m in re.finditer('SRC =',old)]) "
cd /tmp/workspace/vocab-handbook && sed -i 's/w2 = new\[new.index("WORDS = \["):new.index("ALLC =")\]/w2 = new[new.index("WORDS = ["):new.index("ALLC =", new.index("WORDS = ["))]/' build3.py && python3 build3.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Complete_16_Words_EN_RU.pdf') print(len(d)) for i in (0,2,len(d)-2,len(d)-1): d[i].render(scale=0.9).to_pil().save(f'm{i}.png')"; ls m*.png
cd /tmp/workspace/vocab-handbook && python3 - <<'E' s=open('build3.py').read() s=s.replace("chunks = [WORDS[:8], WORDS[8:]]","chunks = [WORDS[0:6], WORDS[6:12], WORDS[12:]]") s=s.replace('''"" if ci == 0 else " (2)")) y = H - 128 for wd in chunk:''','''"" if ci == 0 else f" ({ci + 1})")) y = H - 128 for wd in chunk:''') open('build3.py','w').write(s) E python3 build3.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Complete_16_Words_EN_RU.pdf') n=len(d);print(n) for i in (0,1,2,3,n-3,n-2,n-1): d[i].render(scale=0.8).to_pil().save(f'm{i}.png')"; ls m*.png
cd /tmp/workspace/vocab-handbook && python3 - <<'E' s=open('build3.py').read() s=s.replace('''c.setFillColor(white); c.setFont('B', 22) c.drawCentredString(W / 2, H - 82, "Quick Review''','''c.setFillColor(white); c.setFont('B', 22 if ci == 0 else 19) c.drawCentredString(W / 2, H - 82, "Quick Review''') open('build3.py','w').write(s) E python3 build3.py && python3 -c " import pypdfium2 as p d=p.PdfDocument('Vocabulary_Handbook_Complete_16_Words_EN_RU.pdf') print(len(d)) d[21].render(scale=0.6).to_pil().save('q21.png'); d[17].render(scale=0.6).to_pil().save('q17.png')"
cd /tmp/workspace/vocab-handbook && ls q*.png; ls -la Vocabulary_Handbook_Complete_16_Words_EN_RU.pdf
Vocabulary Handbook Complete 16 Words EN RU
PDF Document · PDF
Now please create a colorful handbook pdf file in which you must describe this photo from the vocabulary words list created by you recently.My student is ESL C1 proficiency , create this description example for his speaking skills exam Cambridge C1.
cd /tmp/workspace/vocab-handbook && python3 -c " from PIL import Image im=Image.open('/tmp/workspace/attachments/79eccc15-8a5f-48c4-9b95-0653e65d105a/IMG-20261105-WA0006.jpg');print(im.size) w,h=im.size im.crop((16,20,w-15,h-12)).save('photo_crop.jpg',quality=92);print(Image.open('photo_crop.jpg').size)"
~/vocab-handbook/build4.py
import re
src = open('/tmp/workspace/vocab-handbook/build3.py').read()
src = src[:src.index("out = '/tmp/workspace/vocab-handbook/Vocabulary_Handbook_Complete")]
exec(src) # fonts, helpers (para, height, rrect, confetti), WORDS
PHOTO = '/tmp/workspace/vocab-handbook/photo_crop.jpg'
PW, PH = 1238, 870
# key -> (display, respelling, EN meaning, RU meaning, color, highlight color)
V = {
'comical': ("comical", "KOM-ih-kul", "funny, often because it looks silly", "смешной, забавный", '#FB8C00', '#EF6C00'),
'idle': ("idle", "Y-dul", "doing nothing; not busy or not working", "праздный, бездельничающий", '#43A047', '#2E7D32'),
'ideal': ("ideal", "y-DEE-ul", "perfect or the best possible for a purpose", "идеальный, наилучший", '#1E88E5', '#1565C0'),
'profound': ("profound", "pruh-FOWND", "very deep or strong", "глубокий, сильный", '#8E24AA', '#6A1B9A'),
'twist': ("twisting my words", "TWIS-ting my WURDZ", "deliberately changing what someone said", "искажать мои слова", '#E53935', '#C62828'),
'wires': ("got our wires crossed", "GOT OW-er WY-erz KRAWST", "misunderstood each other", "не поняли друг друга", '#FB8C00', '#EF6C00'),
'lrc': ("left, right and centre", "LEFT RYT and SEN-ter", "on every side; everywhere", "повсюду, направо и налево", '#8E24AA', '#AD1457'),
'circles': ("going round in circles", "GOH-ing ROWND in SUR-kulz", "repeating the same things without a result", "ходить по кругу", '#1E88E5', '#1565C0'),
'world': ("it's not the end of the world", "its NOT thuh END uv thuh WURLD", "it is not as serious as you think", "это не конец света", '#43A047', '#2E7D32'),
'happens': ("as it happens", "az it HAP-enz", "actually; as a matter of fact", "вообще-то, кстати", '#5E35B1', '#4527A0'),
'petulant': ("petulant", "PET-yuh-lunt", "childishly bad-tempered", "капризный", '#6D4C41', '#4E342E'),
'perilous': ("perilous", "PER-ih-lus", "full of danger or risk", "опасный, рискованный", '#E53935', '#C62828'),
'wrong': ("that came out all wrong", "THAT kaym OWT awl RAWNG", "used to apologize for something rude or unclear", "я неправильно выразился", '#F9A825', '#B26A00'),
'milk': ("no point crying over spilled milk", "NOH POYNT KRY-ing OH-ver SPILD MILK", "no reason to worry about what cannot be changed", "что сделано, то сделано", '#00ACC1', '#00838F'),
'costs': ("get a grip of costs", "GET uh GRIP uv KOSTS", "to minimize or control costs", "взять расходы под контроль", '#D81B60', '#AD1457'),
}
def HL(txt, key):
return f'<font color="white" backColor="{V[key][5]}"><b> {txt} </b></font>'
def HLR(txt, key): # russian highlight (bold coloured text, no background)
return f'<font color="{V[key][5]}"><b>{txt}</b></font>'
# ---------------- model answer ----------------
P1 = ("This photo captures a heated discussion in a public place. According to the caption, it's Parliament Square in London. "
"In the foreground there are three men. The one on the right, in a navy cap, is leaning forward and jabbing his finger at the others, "
"so he is clearly making his point very forcefully. The younger man in the middle, with a ginger beard and headphones round his neck, "
"is frowning as if he's concentrating hard. What strikes me most is the man on the left, who has folded his arms and is smiling slightly. "
"He looks almost amused, which makes the scene a little " + HL("comical", 'comical') + ".")
P2 = ("Behind them, a crowd has gathered, and some of the onlookers seem quite " + HL("idle", 'idle') + ", just watching the show. "
"With the sunshine and the leafy trees, it's an " + HL("ideal", 'ideal') + " setting for an open-air debate.")
P3 = ("I get the impression that they have a " + HL("profound", 'profound') + " disagreement, perhaps about politics. "
"The man in the cap may feel the others are " + HL("twisting his words", 'twist') + ", or perhaps they've simply "
+ HL("got their wires crossed", 'wires') + ". When opinions are flying " + HL("left, right and centre", 'lrc') +
", a conversation can easily go " + HL("round in circles", 'circles') + ". Even so, " + HL("it's not the end of the world", 'world') +
". " + HL("As it happens", 'happens') + ", I think a public debate like this is healthy, as long as nobody turns "
+ HL("petulant", 'petulant') + " or lets it become " + HL("perilous", 'perilous') + ".")
plain = lambda s: re.sub(r'<[^>]+>| ', '', s)
WORDCOUNT = len((plain(P1) + ' ' + plain(P2) + ' ' + plain(P3)).split())
R1 = ("На этом фото запечатлена жаркая дискуссия в общественном месте. Судя по подписи, это Парламентская площадь в Лондоне. "
"На переднем плане трое мужчин. Тот, что справа, в тёмно-синей кепке, подался вперёд и тычет пальцем в остальных, "
"так что он явно очень настойчиво отстаивает свою точку зрения. Молодой человек посередине, с рыжеватой бородой и наушниками на шее, "
"хмурится, словно сосредоточенно слушает. Больше всего меня поражает мужчина слева: он скрестил руки и слегка улыбается. "
"Он выглядит почти позабавленным, из-за чего сцена кажется немного " + HLR("комичной", 'comical') + ".")
R2 = ("Позади собралась толпа, и некоторые зрители выглядят вполне " + HLR("праздными", 'idle') + ": они просто наблюдают за зрелищем. "
"Солнце и зелёные деревья делают это место " + HLR("идеальным", 'ideal') + " для публичных дебатов под открытым небом.")
R3 = ("У меня создаётся впечатление, что у них " + HLR("глубокие", 'profound') + " разногласия, возможно, по политическим вопросам. "
"Мужчина в кепке, вероятно, считает, что остальные " + HLR("искажают его слова", 'twist') + ", или же они просто " +
HLR("не поняли друг друга", 'wires') + ". Когда мнения летят " + HLR("со всех сторон", 'lrc') + ", разговор легко начинает " +
HLR("ходить по кругу", 'circles') + ". И всё же " + HLR("это не конец света", 'world') + ". " + HLR("Вообще-то", 'happens') +
", я думаю, такие публичные дебаты полезны, если только никто не начинает " + HLR("капризничать", 'petulant') +
" и не доводит дело до " + HLR("опасной черты", 'perilous') + ".")
FOLLOW = [
("Part 4 style question",
"Do you think it's a good idea to argue about politics in public?",
"Is it? Спорить о политике публично - хорошая идея?",
"In moderation, yes. People get the chance to hear different views. The risk is that someone says something they regret. "
"If a remark " + HL("came out all wrong", 'wrong') + ", a quick apology usually fixes it, and there's " + HL("no point crying over spilled milk", 'milk') + ".",
"В меру - да. Люди слышат разные взгляды. Риск в том, что кто-то скажет то, о чём пожалеет. Если замечание " + HLR("прозвучало совсем не так", 'wrong') +
", обычно помогает быстрое извинение, а " + HLR("слезами горю не поможешь", 'milk') + "."),
("Part 4 style question",
"What do couples or families most often argue about?",
"О чём чаще всего спорят пары и семьи?",
"Money, I'd say. When bills pile up, a household needs to " + HL("get a grip of costs", 'costs') + ", and everyone tends to blame everyone else.",
"О деньгах, я бы сказал. Когда накапливаются счета, семье нужно " + HLR("взять расходы под контроль", 'costs') + ", а все склонны винить друг друга."),
("Partner's question after your 1 minute (about 30 seconds)",
"Which of the three men do you think is finding the discussion most difficult?",
"Кому из трёх мужчин, по-вашему, эта дискуссия даётся тяжелее всех?",
"Probably the man in the middle. He is listening so intently that he looks tense, whereas the others seem more at ease with the confrontation.",
"Скорее всего, мужчине посередине. Он слушает так напряжённо, что выглядит скованным, тогда как остальные чувствуют себя увереннее в этой конфронтации."),
]
def title_bar(c, text, sub, col, y_top=None):
y_top = y_top or H - 30
rrect(c, 30, y_top - 72, W - 60, 72, 24, col)
c.setFillColor(white); c.setFont('B', 26)
c.drawCentredString(W / 2, y_top - 36, text)
c.setFont('R', 11.5)
c.drawCentredString(W / 2, y_top - 57, sub)
return y_top - 72
def bg(c, light, seed, cols):
c.setFillColor(HexColor(light)); c.rect(0, 0, W, H, fill=1, stroke=0)
confetti(c, seed, cols)
def footer(c, n):
c.setFillColor(HexColor('#777777')); c.setFont('R', 8)
c.drawCentredString(W / 2, 18, f"Cambridge C1 Speaking: Photo Description | page {n}")
def photo(c, x, y_bottom, w, border='#1E88E5'):
h = w * PH / PW
rrect(c, x - 6, y_bottom - 6, w + 12, h + 12, 16, border)
c.drawImage(PHOTO, x, y_bottom, w, h, mask=None)
return h
ALLCOL = [v[4] for v in V.values()]
# ---------------- pages ----------------
def p_cover(c):
bg(c, '#FFF8E1', 3, ALLCOL)
bw = W / 8
cols = ['#E53935', '#FB8C00', '#F9A825', '#43A047', '#00ACC1', '#1E88E5', '#5E35B1', '#8E24AA']
for i, col in enumerate(cols):
c.setFillColor(HexColor(col)); c.rect(i * bw, H - 26, bw, 26, fill=1, stroke=0)
c.rect(i * bw, 0, bw, 26, fill=1, stroke=0)
rrect(c, 40, H - 260, W - 80, 215, 28, '#5E35B1')
c.setFillColor(white); c.setFont('B', 33)
c.drawCentredString(W / 2, H - 110, "Describing a Photo")
c.setFont('B', 19)
c.drawCentredString(W / 2, H - 148, "Cambridge C1 Advanced Speaking")
c.setFont('R', 14)
c.drawCentredString(W / 2, H - 175, "Part 2: the long turn | Модельный ответ")
c.setFont('R', 12)
c.drawCentredString(W / 2, H - 205, "Model answer + Russian translation + vocabulary in use")
c.drawCentredString(W / 2, H - 226, "Topic: arguments and discussions")
w = 380
h = photo(c, (W - w) / 2, H - 290 - w * PH / PW, w, '#FB8C00')
y = H - 290 - h - 30
c.setFillColor(HexColor('#5E35B1')); c.setFont('B', 13)
c.drawCentredString(W / 2, y, "Words from the handbook used in the model answer")
keys = ['comical', 'idle', 'ideal', 'profound', 'twist', 'wires', 'lrc', 'circles', 'world', 'happens', 'petulant', 'perilous']
y -= 14
cw, ch = 158, 24
for i, k in enumerate(keys):
x = 30 + (i % 3) * (cw + 8)
yy = y - (i // 3) * (ch + 7)
rrect(c, x, yy - ch, cw, ch, 10, V[k][5])
nm = V[k][0]; fs = 10.5
while pdfmetrics.stringWidth(nm, 'B', fs) > cw - 10: fs -= 0.5
c.setFillColor(white); c.setFont('B', fs)
c.drawCentredString(x + cw / 2, yy - 16, nm)
c.showPage()
def p_task(c):
bg(c, '#E3F2FD', 4, ['#1E88E5', '#FB8C00', '#43A047'])
y = title_bar(c, "The Photo and the Task", "Фото и задание", '#1E88E5')
w = 470
h = w * PH / PW
y -= 24 + h
photo(c, (W - w) / 2, y, w, '#1E88E5')
c.setFillColor(HexColor('#555555')); c.setFont('I', 9)
c.drawCentredString(W / 2, y - 18, "Caption in the book: \"People arguing in Parliament Square, London, England.\"")
y -= 36
# task card
t = ("Talk about the photo for <b>about one minute</b>. Say what you can see, why the people might be arguing, "
"and how they might be feeling.")
ru = "Говорите о фото около <b>одной минуты</b>: что вы видите, почему люди могут спорить и что они могут чувствовать."
th = height(t, W - 110, 13, 'R', 18); rh = height(ru, W - 110, 11.5, 'R', 15)
hh = th + rh + 44
rrect(c, 30, y - hh, W - 60, hh, 16, '#FFFFFF', '#1E88E5', 3)
c.setFillColor(HexColor('#1E88E5')); c.roundRect(45, y - 26, 80, 18, 9, fill=1, stroke=0)
c.setFillColor(white); c.setFont('B', 10); c.drawCentredString(85, y - 20, "YOUR TASK")
para(c, t, 45, y - 32, W - 110, 13, 'R', '#222222', 18)
para(c, ru, 45, y - 36 - th, W - 110, 11.5, 'R', '#1a1a1a', 15)
y -= hh + 14
# three boxes
items = [("1", "WHAT I CAN SEE", "Что я вижу", "#E53935"),
("2", "WHY THEY ARGUE", "Почему спорят", "#43A047"),
("3", "HOW THEY FEEL", "Что чувствуют", "#8E24AA")]
bw = (W - 60 - 20) / 3
for i, (n, a, b, col) in enumerate(items):
x = 30 + i * (bw + 10)
rrect(c, x, y - 78, bw, 78, 16, col)
c.setFillColor(white); c.circle(x + 24, y - 26, 15, fill=1, stroke=0)
c.setFillColor(HexColor(col)); c.setFont('B', 16); c.drawCentredString(x + 24, y - 32, n)
c.setFillColor(white); c.setFont('B', 11.5); c.drawString(x + 46, y - 30, a)
c.setFont('R', 10.5); c.drawString(x + 14, y - 58, b)
y -= 78 + 14
note = ("<b>Exam note:</b> in the real Cambridge C1 Advanced Part 2 you usually get <b>two photos</b>. You compare them, "
"answer the question printed above them, and your partner then answers a short question (about 30 seconds). "
"This page practises the same skills with one photo.")
nh = height(note, W - 80, 10.5, 'R', 14)
rrect(c, 30, y - nh - 18, W - 60, nh + 18, 12, '#FFF8E1', '#FB8C00', 1.5)
para(c, note, 42, y - 9, W - 84, 10.5, 'R', '#333333', 14)
footer(c, 2)
c.showPage()
def p_model(c):
bg(c, '#FFF3E0', 5, ['#FB8C00', '#43A047', '#8E24AA'])
y = title_bar(c, "Model Answer", f"About {WORDCOUNT} words | roughly 1 minute 15 seconds at a natural pace", '#FB8C00')
y -= 16
blocks = [("1 Overview + foreground", P1, '#E53935'),
("2 Background", P2, '#43A047'),
("3 Speculating + opinion", P3, '#8E24AA')]
for label, txt, col in blocks:
th = height(txt, W - 90, 12, 'R', 20)
h = th + 40
rrect(c, 30, y - h, W - 60, h, 16, '#FFFFFF', col, 3)
c.setFillColor(HexColor(col)); c.roundRect(44, y - 24, 190, 19, 9, fill=1, stroke=0)
c.setFillColor(white); c.setFont('B', 10.5); c.drawString(54, y - 18, label)
para(c, txt, 45, y - 32, W - 90, 12, 'R', '#222222', 20)
y -= h + 12
tip = ("<b>Tip:</b> the coloured words are vocabulary from your handbook. In the exam, use them naturally and "
"do not force every one into your answer.")
tr = "<b>Совет:</b> цветные слова - лексика из вашего словаря. На экзамене используйте их естественно, не вставляйте все подряд."
th = height(tip, W - 84, 10.5, 'R', 14); rh = height(tr, W - 84, 10.5, 'R', 14)
rrect(c, 30, y - th - rh - 20, W - 60, th + rh + 20, 12, '#FFFFFF', '#FB8C00', 1.5)
para(c, tip, 42, y - 8, W - 84, 10.5, 'R', '#333333', 14)
para(c, tr, 42, y - 10 - th, W - 84, 10.5, 'R', '#1a1a1a', 14)
assert y - th - rh - 20 > 35, y
footer(c, 3)
c.showPage()
def p_russian(c):
bg(c, '#F3E5F5', 6, ['#8E24AA', '#E53935', '#1E88E5'])
y = title_bar(c, "Русский перевод", "Russian translation of the model answer", '#8E24AA')
y -= 16
blocks = [("1 Общая картина и передний план", R1, '#E53935'),
("2 Задний план", R2, '#43A047'),
("3 Предположения и мнение", R3, '#1E88E5')]
for label, txt, col in blocks:
th = height(txt, W - 90, 12, 'R', 20)
h = th + 40
rrect(c, 30, y - h, W - 60, h, 16, '#FFFFFF', col, 3)
c.setFillColor(HexColor(col)); c.roundRect(44, y - 24, 250, 19, 9, fill=1, stroke=0)
c.setFillColor(white); c.setFont('B', 10.5); c.drawString(54, y - 18, label)
para(c, txt, 45, y - 32, W - 90, 12, 'R', '#222222', 20)
y -= h + 12
assert y > 35, y
footer(c, 4)
c.showPage()
def p_vocab(c):
bg(c, '#E8F5E9', 7, ['#43A047', '#00ACC1', '#FB8C00'])
y = title_bar(c, "Vocabulary in the Answer", "Лексика из ответа: произношение и значения", '#43A047')
y -= 14
keys = ['comical', 'idle', 'ideal', 'profound', 'twist', 'wires', 'lrc', 'circles', 'world', 'happens', 'petulant', 'perilous']
cw = (W - 60 - 12) / 2
ch = 93
for i, k in enumerate(keys):
d, resp, en, ru, col, hc = V[k]
x = 30 + (i % 2) * (cw + 12)
yy = y - (i // 2) * (ch + 8)
rrect(c, x, yy - ch, cw, ch, 14, '#FFFFFF', hc, 2.5)
rrect(c, x, yy - 34, cw, 34, 14, hc)
c.setFillColor(hc); c.rect(x, yy - 34, cw, 14, fill=1, stroke=0)
fs = 16
while pdfmetrics.stringWidth(d, 'B', fs) > cw - 20: fs -= 0.5
c.setFillColor(white); c.setFont('B', fs)
c.drawCentredString(x + cw / 2, yy - 23, d)
c.setFillColor(HexColor(hc)); c.setFont('B', 9)
fr = 9
while pdfmetrics.stringWidth(f"({resp})", 'B', fr) > cw - 16: fr -= 0.5
c.setFont('B', fr)
c.drawCentredString(x + cw / 2, yy - 48, f"({resp})")
hh = para(c, en, x + 10, yy - 54, cw - 20, 9.5, 'R', '#222222', 12, align=1)
para(c, ru, x + 10, yy - 56 - hh, cw - 20, 9.5, 'B', '#1a1a1a', 12, align=1)
footer(c, 5)
c.showPage()
def p_toolbox(c):
bg(c, '#FFFDE7', 8, ['#F9A825', '#00ACC1', '#E53935'])
y = title_bar(c, "C1 Language Toolbox", "Полезные фразы для описания фото", '#00ACC1')
y -= 14
secs = [
("Opening / Начало", '#E53935',
["This photo captures / shows ...", "According to the caption / Judging by the caption, ...",
"The first thing that strikes me is ..."]),
("Position / Расположение", '#FB8C00',
["In the foreground / in the background", "on the far left / right-hand side / in the middle",
"Behind them, a crowd has gathered"]),
("Speculating / Предположения", '#43A047',
["I get the impression that ...", "He might well be / may have been ...",
"It looks as if / as though ...", "Presumably ... / It's hard to tell, but ..."]),
("Body language / Язык тела", '#1E88E5',
["jabbing his finger / leaning forward", "frowning with concentration",
"arms folded, slightly amused", "visibly irritated / on edge"]),
("Opinion and closing / Мнение", '#8E24AA',
["Personally, I think ...", "What I find interesting is ...",
"All in all, / On the whole, ..."]),
("Buying time / Выиграть время", '#D81B60',
["How can I put it ...", "or rather ... / what I mean is ...", "Let me think ... / To be honest, ..."]),
]
cw = (W - 60 - 12) / 2
# compute heights per row
for r in range(3):
row = secs[r * 2:r * 2 + 2]
hs = []
for t, col, items in row:
hs.append(40 + sum(height("• " + it, cw - 28, 11, 'R', 15) + 3 for it in items))
h = max(hs)
for j, (t, col, items) in enumerate(row):
x = 30 + j * (cw + 12)
rrect(c, x, y - h, cw, h, 16, '#FFFFFF', col, 3)
rrect(c, x, y - 28, cw, 28, 14, col)
c.setFillColor(col and HexColor(col)); c.rect(x, y - 28, cw, 12, fill=1, stroke=0)
c.setFillColor(white); c.setFont('B', 11.5)
c.drawString(x + 12, y - 19, t)
yy = y - 36
for it in items:
hh = para(c, "• " + it, x + 12, yy, cw - 24, 11, 'R', '#222222', 15)
yy -= hh + 3
y -= h + 12
# connectors
t = ("<b>Linking ideas / Связки:</b> Having said that, ... | On the other hand, ... | Whereas ... | "
"That said, ... | As a result, ... | Even so, ...")
th = height(t, W - 84, 11, 'R', 15)
rrect(c, 30, y - th - 18, W - 60, th + 18, 12, '#FFFFFF', '#F9A825', 2.5)
para(c, t, 42, y - 9, W - 84, 11, 'R', '#222222', 15)
assert y - th - 18 > 35, y
footer(c, 6)
c.showPage()
def p_follow(c):
bg(c, '#FCE4EC', 9, ['#D81B60', '#00ACC1', '#F9A825'])
y = title_bar(c, "Follow-up Questions", "Дополнительные вопросы с моделями ответов", '#D81B60')
y -= 14
cols = ['#00ACC1', '#8E24AA', '#43A047']
for (tag, q, qr, a, ar), col in zip(FOLLOW, cols):
qh = height(f"<b>{q}</b>", W - 100, 12.5, 'B', 17)
qrh = height(qr, W - 100, 10, 'I', 13)
ah = height(a, W - 100, 11.5, 'R', 19)
arh = height(ar, W - 100, 10.5, 'R', 15)
h = qh + qrh + ah + arh + 62
rrect(c, 30, y - h, W - 60, h, 16, '#FFFFFF', col, 3)
c.setFillColor(HexColor(col)); c.roundRect(44, y - 24, 330, 18, 9, fill=1, stroke=0)
c.setFillColor(white); c.setFont('B', 9.5); c.drawString(54, y - 18, tag)
yy = y - 32
yy -= para(c, f"<b>{q}</b>", 45, yy, W - 100, 12.5, 'B', col, 17)
yy -= para(c, qr, 45, yy - 1, W - 100, 10, 'I', '#555555', 13) + 8
yy -= para(c, a, 45, yy, W - 100, 11.5, 'R', '#222222', 19) + 3
para(c, ar, 45, yy, W - 100, 10.5, 'R', '#1a1a1a', 15)
y -= h + 12
assert y > 35, y
footer(c, 7)
c.showPage()
def p_check(c):
bg(c, '#E0F7FA', 10, ['#00ACC1', '#5E35B1', '#E53935'])
y = title_bar(c, "Practice Plan and Self-Check", "План тренировки и самопроверка", '#5E35B1')
y -= 14
steps = [("1", "Read the model answer aloud twice.", "Прочитайте ответ вслух дважды.", '#E53935'),
("2", "Cover the text. Describe the photo using only the coloured prompts.", "Закройте текст и опишите фото только по цветным подсказкам.", '#FB8C00'),
("3", "Record yourself for 1 minute. Listen back and note pauses.", "Запишите себя на 1 минуту и прослушайте.", '#43A047'),
("4", "Repeat with your own words and at least 6 handbook words.", "Повторите своими словами, используя минимум 6 слов из словаря.", '#1E88E5')]
for n, en, ru, col in steps:
eh = height(en, W - 130, 12, 'B', 16); rh = height(ru, W - 130, 10.5, 'R', 14)
h = eh + rh + 20
rrect(c, 30, y - h, W - 60, h, 14, '#FFFFFF', col, 2.5)
c.setFillColor(HexColor(col)); c.circle(58, y - h / 2, 15, fill=1, stroke=0)
c.setFillColor(white); c.setFont('B', 15); c.drawCentredString(58, y - h / 2 - 5, n)
para(c, en, 85, y - 9, W - 130, 12, 'B', '#222222', 16)
para(c, ru, 85, y - 11 - eh, W - 130, 10.5, 'R', '#1a1a1a', 14)
y -= h + 8
y -= 4
c.setFillColor(HexColor('#5E35B1')); c.setFont('B', 14)
c.drawString(34, y - 14, "Cambridge C1 speaking criteria: tick when ready")
y -= 24
crit = [("Grammar and Vocabulary", "Wide range of structures and words; I used speculation (might well, may have) and at least 6 handbook words.", '#E53935'),
("Discourse Management", "I spoke for about a minute, organised my ideas (what I see, why, how they feel) and used linking words.", '#FB8C00'),
("Pronunciation", "Clear word stress and sentence stress; I checked the respellings of my vocabulary words.", '#43A047'),
("Interactive Communication", "I answered my partner's follow-up question naturally and kept the conversation going.", '#1E88E5')]
for t, d, col in crit:
dh = height(d, W - 140, 10.5, 'R', 14)
h = dh + 34
rrect(c, 30, y - h, W - 60, h, 14, '#FFFFFF', col, 2.5)
c.setStrokeColor(HexColor(col)); c.setLineWidth(2.5)
c.roundRect(44, y - h / 2 - 11, 22, 22, 4, fill=0, stroke=1)
c.setFillColor(HexColor(col)); c.setFont('B', 12)
c.drawString(82, y - 18, t)
para(c, d, 82, y - 24, W - 140, 10.5, 'R', '#222222', 14)
y -= h + 8
assert y > 35, y
footer(c, 8)
c.showPage()
out = '/tmp/workspace/vocab-handbook/C1_Speaking_Photo_Description_Handbook.pdf'
c = canvas.Canvas(out, pagesize=A4)
c.setTitle("Cambridge C1 Speaking: Describing a Photo (EN-RU)")
for f in (p_cover, p_task, p_model, p_russian, p_vocab, p_toolbox, p_follow, p_check):
f(c)
c.save()
print(out, 'words', WORDCOUNT)
cd /tmp/workspace/vocab-handbook && python3 build4.py 2>&1 | tail -5 && python3 -c " import pypdfium2 as p d=p.PdfDocument('C1_Speaking_Photo_Description_Handbook.pdf') print(len(d)) for i in range(len(d)): d[i].render(scale=0.8).to_pil().save(f'k{i}.png')"
cd /tmp/workspace/vocab-handbook && sed -i 's/roughly 1 minute 15 seconds at a natural pace/about 1 minute 20 seconds at natural pace (skip paragraph 2 if short of time)/' build4.py && python3 build4.py | tail -1 && python3 -c " import pypdfium2 as p d=p.PdfDocument('C1_Speaking_Photo_Description_Handbook.pdf') for i in range(len(d)): d[i].render(scale=0.8).to_pil().save(f'k{i}.png')"
C1 Speaking Photo Description Handbook
PDF Document · PDF