From 33b9a4bee0fb8378689c7149ccf87ce5a90bdfb6 Mon Sep 17 00:00:00 2001 From: Thutran1891 Date: Tue, 18 Aug 2026 06:15:36 +0700 Subject: [PATCH 1/3] 0.3.4: bo ham hinh tron moi; an/hien-khoang; nhan bpt nghieng; dat-lai-cau theo the loai; kham-pha/trai-nghiem/thao-luan; fig-pos center --- packages/preview/conic-toan/0.3.4/LICENSE | 21 + packages/preview/conic-toan/0.3.4/README.md | 2212 +++++++++++++++++ .../conic-toan/0.3.4/assets/exam-page.png | Bin 0 -> 137236 bytes .../0.3.4/assets/feasible-region.png | Bin 0 -> 14613 bytes .../conic-toan/0.3.4/assets/graphs.png | Bin 0 -> 36153 bytes .../0.3.4/assets/plane-geometry.png | Bin 0 -> 29860 bytes .../preview/conic-toan/0.3.4/assets/slide.png | Bin 0 -> 165528 bytes .../0.3.4/assets/solid-geometry.png | Bin 0 -> 35053 bytes .../0.3.4/assets/solid-of-revolution.png | Bin 0 -> 55218 bytes .../conic-toan/0.3.4/assets/statistics.png | Bin 0 -> 41330 bytes .../0.3.4/assets/variation-tables.png | Bin 0 -> 22349 bytes .../preview/conic-toan/0.3.4/baigiang.typ | 569 +++++ .../conic-toan/0.3.4/lib/bang-thong-ke.typ | 156 ++ .../preview/conic-toan/0.3.4/lib/bang.typ | 905 +++++++ .../conic-toan/0.3.4/lib/bieu-do-thong-ke.typ | 542 ++++ .../preview/conic-toan/0.3.4/lib/cau-hoi.typ | 1076 ++++++++ .../preview/conic-toan/0.3.4/lib/che-do.typ | 1394 +++++++++++ .../preview/conic-toan/0.3.4/lib/da-dien.typ | 876 +++++++ .../preview/conic-toan/0.3.4/lib/do-thi.typ | 1632 ++++++++++++ .../conic-toan/0.3.4/lib/hinh-khong-gian.typ | 925 +++++++ .../conic-toan/0.3.4/lib/hinh-phang.typ | 498 ++++ .../preview/conic-toan/0.3.4/lib/khao-sat.typ | 354 +++ .../preview/conic-toan/0.3.4/lib/mat-cong.typ | 471 ++++ .../conic-toan/0.3.4/lib/oxyz-toan.typ | 544 ++++ .../preview/conic-toan/0.3.4/lib/slide.typ | 1005 ++++++++ .../conic-toan/0.3.4/lib/so-do-cay.typ | 151 ++ .../conic-toan/0.3.4/lib/tron-xoay.typ | 359 +++ packages/preview/conic-toan/0.3.4/lib/ve.typ | 1544 ++++++++++++ packages/preview/conic-toan/0.3.4/typst.toml | 13 + 29 files changed, 15247 insertions(+) create mode 100644 packages/preview/conic-toan/0.3.4/LICENSE create mode 100644 packages/preview/conic-toan/0.3.4/README.md create mode 100644 packages/preview/conic-toan/0.3.4/assets/exam-page.png create mode 100644 packages/preview/conic-toan/0.3.4/assets/feasible-region.png create mode 100644 packages/preview/conic-toan/0.3.4/assets/graphs.png create mode 100644 packages/preview/conic-toan/0.3.4/assets/plane-geometry.png create mode 100644 packages/preview/conic-toan/0.3.4/assets/slide.png create mode 100644 packages/preview/conic-toan/0.3.4/assets/solid-geometry.png create mode 100644 packages/preview/conic-toan/0.3.4/assets/solid-of-revolution.png create mode 100644 packages/preview/conic-toan/0.3.4/assets/statistics.png create mode 100644 packages/preview/conic-toan/0.3.4/assets/variation-tables.png create mode 100644 packages/preview/conic-toan/0.3.4/baigiang.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/bang-thong-ke.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/bang.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/bieu-do-thong-ke.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/cau-hoi.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/che-do.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/da-dien.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/do-thi.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/hinh-khong-gian.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/hinh-phang.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/khao-sat.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/mat-cong.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/oxyz-toan.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/slide.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/tron-xoay.typ create mode 100644 packages/preview/conic-toan/0.3.4/lib/ve.typ create mode 100644 packages/preview/conic-toan/0.3.4/typst.toml diff --git a/packages/preview/conic-toan/0.3.4/LICENSE b/packages/preview/conic-toan/0.3.4/LICENSE new file mode 100644 index 0000000000..1f65d592fb --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Kim Thu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/preview/conic-toan/0.3.4/README.md b/packages/preview/conic-toan/0.3.4/README.md new file mode 100644 index 0000000000..626c4a70e3 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/README.md @@ -0,0 +1,2212 @@ +

conic-toan

+ +

+ Draw math figures, graphs and variation tables — and build exam papers.
+ A dependency-free Typst library for Vietnamese high-school mathematics. +

+ +

+ Solid geometry +

+ +## English overview + +`conic-toan` is a pure Typst library — **no external packages, no CeTZ** — for +authoring Vietnamese high-school mathematics material. Everything below is drawn +by the library itself and compiles offline. + +**What it does** + +- **Plane and solid geometry.** Triangles with their notable points and circles + (incircle, circumcircle, orthocentre, excircles), pyramids, prisms, frustums, + cones, cylinders and spheres, with hidden edges dashed automatically. +- **Function graphs.** Quadratic through rational functions, exponentials, + logarithms, trigonometry, conics, tangent lines, areas between curves and + feasible regions of linear systems. Labels carry *exact* values — a cubic's + extremum prints as a surd, not as `10.48`. +- **Variation tables from coefficients.** `bbt-bac-ba(1, 0, -3, 1)` produces the + full table for *y = x³ − 3x + 1*, covering every discriminant case. +- **Full worked solutions.** `khao-sat-ve-do-thi-ham-bac-ba(1, 0, -3, 1)` emits + the domain, derivative, limits, monotonicity, extrema, variation table and + graph — the entire textbook analysis from five numbers. +- **Statistics for the 2018 curriculum.** Frequency tables, histograms, ogives, + bar charts, box plots and pie charts, plus fourteen summary statistics for raw + and grouped data. +- **Solids of revolution.** A plane region, the rotation arrow, the resulting + solid and its volume integral, side by side. +- **One source, three PDFs.** The same question file renders as a printable exam, + a solution key, or 16:9 lecture slides. Eight question types, automatic answer + keys and answer tables. + +Function and parameter names are in unaccented Vietnamese (`doan`, `duong-tron`, +`bbt-bac-ba`), and the documentation below is in Vietnamese. + +**Quick start** + +```typst +#import "@preview/conic-toan:0.3.4": * + +#do-thi-bac-ba(1, 0, -3, 1) // graph with exact extrema +#bbt-bac-ba(1, 0, -3, 1) // matching variation table +#hinh-chop-luc-giac-deu() // hexagonal pyramid +``` + + + + + + + + + + + + + + +
Function graphsPlane geometry
Variation tablesSolids of revolution
Statistical chartsFeasible region
+ +One question source, rendered as lecture slides and as an exam paper: + +

+ Lecture slide
+ Exam page +

+ +Licensed under MIT. Full documentation in Vietnamese follows. + +--- + +# Hệ thống bài giảng trình chiếu Toán THPT bằng Typst + +Thuần Typst, **không phụ thuộc package nào** — biên dịch offline được ngay. + +## Cài đặt + +```typst +#import "@preview/conic-toan:0.3.4": * +``` + +Không cần tải gì thêm — Typst tự tải gói về khi biên dịch lần đầu. +Mã nguồn và các file mẫu: https://github.com/Thutran1891/conic-toan + +## Yêu cầu & biên dịch + +- Typst **0.13 trở lên** (cài trên Windows: `winget install --id Typst.Typst`). +- Biên dịch: `typst compile main.typ` — Xem trực tiếp khi soạn: `typst watch main.typ`. +- Khuyến nghị dùng VS Code + extension **Tinymist Typst** để xem trước tức thì. + +## Cấu trúc + +``` +baigiang.typ ← import MỘT file này là dùng được tất cả +main.typ ← bài giảng demo trình diễn toàn bộ tính năng +lib/ + slide.typ ← engine trình chiếu 16:9 + ve.typ ← engine vẽ hình lõi (hệ toạ độ, điểm, đoạn, cung, góc…) + hinh-phang.typ ← hình học phẳng + hinh-khong-gian.typ ← hình học không gian + do-thi.typ ← đồ thị hàm số + oxyz-toan.typ ← TÍNH TOÁN trong Oxyz (vectơ, mặt phẳng, đường, mặt cầu) + bang.typ ← bảng biến thiên, bảng xét dấu +``` + +Tạo bài giảng mới: copy `main.typ` thành `bai-1.typ` rồi sửa nội dung. + +## 1. Trình chiếu + +```typst +#import "@preview/conic-toan:0.3.4": * +#show math.equation.where(block: false): it => math.display(it) + +#show: bai-giang.with( + tieu-de: [CHƯƠNG I. KHẢO SÁT HÀM SỐ], + tieu-de-ngan: [Chương I. Khảo sát hàm số], // tên bài rút gọn — LUÔN hiện + // trên dải đầu trang mọi slide (none => dùng nguyên tieu-de) + phu-de: [Tiết 1: Sự đồng biến, nghịch biến], + gv: "Kim Thu", + ngay: "12/07/2026", + lop: "Lớp 12A1", // (tuỳ chọn) hiện trên TRANG BÌA + mon: "Giải tích", // (tuỳ chọn) môn/chương hiện trên TRANG BÌA + logo: image("logo.png"), // (tuỳ chọn) logo trường — PHẢI bọc image(...) + // Đặt ảnh CÙNG thư mục file .typ này. KHÔNG + // truyền chuỗi "logo.png" (image() chạy trong + // package sẽ tìm ảnh cạnh package -> không thấy). + // none = không có logo. + kieu-bia: 1, // KIỂU TRANG BÌA — 1..5 hoặc tên: + // 1 "toi-gian" Tối giản & Thanh lịch + // 2 "tre-trung" Trẻ trung & Sáng tạo + // 3 "co-dien" Chuẩn mực Học thuật Cổ điển + // 4 "chuyen-nghiep" Chuyên nghiệp & Khoa học + // 5 "ky-thuat" Tiêu chuẩn Tài liệu Kỹ thuật + // Mỗi kiểu có TÔNG MÀU riêng; đặt mau-chinh/ + // mau-nhan để GHI ĐÈ tông đó (đồng bộ cả bài). + mau-chinh: rgb("#0f4c81"), // đổi màu chủ đạo tuỳ ý (auto = theo kieu-bia) + nen: "kem", // nền slide: "trang" (mặc định) | "kem" + // | "xanh-nhat" | "luc-nhat" | "xam" + // hoặc màu TUỲ Ý, ví dụ các tông sáng đẹp: + // rgb("#fdf3e7") cam nhạt + // rgb("#fdf0f0") hồng nhạt + // rgb("#f6f0fb") tím nhạt + // rgb("#eefaf6") bạc hà + // rgb("#fbfbe8") vàng chanh nhạt + ti-le-chu: 1.0, // HỆ SỐ PHÓNG CỠ CHỮ THÂN NỘI DUNG (toàn file): + // 1.0 giữ nguyên; 1.1 to hơn 10%; 0.9 nhỏ đi 10%. + // Chỉ co giãn thân + khung (định nghĩa/ví dụ/lời + // giải…); thanh tiêu đề, header, footer giữ nguyên. + gian-dong: 1.0, // HỆ SỐ GIÃN DÒNG — khoảng cách giữa các dòng + // (toàn file): 1.0 = mốc mặc định (trình chiếu + // 0.62em · A4 bài giảng 0.6em · A4 đề thi 0.65em); + // 1.25 giãn thêm 25%; 0.9 thu lại 10%. TĂNG khi + // phân số / căn thức / chỉ số nhiều tầng làm hai + // dòng liền nhau dính (thậm chí chồng) vào nhau. + mau-cong-thuc: auto, // MÀU MỌI CÔNG THỨC trong $...$ (toàn file): + // auto (mặc định) = thừa kế màu chữ — thân đen, + // tiêu đề/bìa trắng. Đặt màu, vd rgb("#0f4c81"), + // để nhuộm TẤT CẢ công thức theo màu đó. +) + +#muc[1. Định nghĩa] // slide chuyển phần +#slide(tieu-de: [Khái niệm])[ ... ] // slide thường +#trang-cam-on() +``` + +Khung nội dung (tự đánh số ví dụ/luyện tập): +`#dinh-nghia[...]`, `#dinh-ly[...]`, `#tinh-chat[...]`, `#vi-du[...]`, +`#loi-giai[...]`, `#chu-y[...]`, `#ghi-nho[...]`, `#nhan-xet[...]`, `#luyen-tap[...]`. + +Bố cục: `#chia-cot(trai, phai)` hoặc `#chia-cot(a, b, c, ti-le: (2fr, 1fr, 1fr))`. +Các bước giải: `#buoc([Bước 1...], [Bước 2...])`. + +## 2. Vẽ hình tự do (ve.typ) + +Mọi hình vẽ nằm trong `#hinh(...)` với hệ toạ độ toán học (y hướng lên). +Để `h: auto` thì hai trục cùng tỉ lệ (nên giữ khi vẽ hình hình học). + +```typst +#hinh(w: 7cm, xmin: -1, xmax: 6, ymin: -1, ymax: 5, ctx => { + doan(ctx, (0, 0), (5, 0)) // đoạn thẳng + doan(ctx, (1, 1), (4, 3), dut: true, mau: red) // nét đứt + duong-gap-khuc(ctx, ((0,0), (1,2), (3,1), (5,4)), mau: blue, dut: true) // gấp khúc A-B-C-D, đứt thật từng đoạn + diem(ctx, (2, 3), ten: $M$, huong: "tren") // điểm + nhãn + mui-ten(ctx, (0, 0), (3, 2)) // mũi tên + mui-ten-2-dau(ctx, (0, -0.6), (5, -0.6), // mũi tên HAI ĐẦU: ghi số đo + ten: [5 cm], vach: true) // chữ nằm giữa thân + vecto(ctx, (0, 0), (2, 3), ten: $arrow(u)$) // vectơ có tên + vecto(ctx, (0, 0), (2, 3), dut: true) // vectơ nét đứt (cạnh khuất), đầu mũi tên vẫn liền + duong-tron(ctx, (3, 2), 1.5) // đường tròn + cung(ctx, (0, 0), 2, tu: 0deg, den: 90deg) // cung tròn + goc(ctx, (0,0), (5,0), (3,4), ten: $alpha$) // đánh dấu góc (cung + nhãn) + goc(ctx, (0,0), (5,0), (3,4), so-do: true, // tự ghi số đo góc (vd 60°) + to: rgb(255, 170, 0, 70)) // + tô màu hình quạt + goc(ctx, (0,0), (5,0), (3,4), vach: 2) // 2 vạch cắt ngang cung + goc-vuong(ctx, (0,0), (5,0), (0,4)) // ký hiệu vuông góc + ve-goc(ctx, (5,0), (0,0), (3,4), ten: $alpha$) // = goc, nhưng ĐỈNH ở GIỮA (lối TikZ) + ve-goc-vuong(ctx, (5,0), (0,0), (0,4)) // = goc-vuong, ĐỈNH ở GIỮA (lối TikZ) + danh-dau(ctx, (0,0), (5,0), so: 2) // 2 vạch "bằng nhau" + danh-dau(ctx, (0,0), (5,0), so: 2, nghieng: 25deg) // vạch CHÉO "//" + danh-dau(ctx, (0,0), (5,0), cheo: true) // dấu ✕ (chéo nhau) + ve-ham(ctx, x => x*x/4, mau: blue) // đồ thị hàm bất kỳ + to-vung(ctx, x => x*x/4, 0, 3) // tô miền dưới đồ thị + nhan(ctx, (3, 4), [chú thích], huong: "phai") +}) +``` + +Hướng nhãn: `"tren" | "duoi" | "trai" | "phai" | "tren-trai" | "tren-phai" | "duoi-trai" | "duoi-phai" | "giua"`. + +**Mũi tên hai đầu — ghi số đo (`mui-ten-2-dau`)** + +Kiểu đường ghi kích thước trong bản vẽ: hai đầu đều có mũi tên, số đo đặt ở +GIỮA thân (thân tự cắt chừa chỗ cho chữ, có nền lót nên nét không cắt qua chữ). + +```typst +mui-ten-2-dau(A, B, ten: [5 cm]) // chữ giữa thân +mui-ten-2-dau(A, B, ten: $2a$, vach: true) // + vạch chặn hai đầu +mui-ten-2-dau(A, B, ten: $sqrt(29)$, trong: false) // chữ đặt NGOÀI, trên đoạn +mui-ten-2-dau(A, B, ten: [dài], trong: false, ten-quay: true) // chữ nằm dọc +mui-ten-2-dau(A, B, ten: $h$, le: 3pt) // lùi hai đầu, hở khỏi vật đo +``` + +Đoạn ngắn không đủ chỗ chứa chữ thì hàm TỰ đưa chữ ra ngoài thân — không bao +giờ đè lên mũi tên. `nen: none` để bỏ nền lót, `dem:` nới chỗ hở quanh chữ. + +**Nhãn có phân số / căn không còn bị nét vẽ cắt ngang** + +Typst tính HỤT khung của công thức trong dòng: `measure($1/2$)` chỉ cho chiều +cao MỘT DÒNG CHỮ trong khi phân số vẽ ra cao gấp đôi và tràn cả trên lẫn dưới. +`nhan` (và mọi hàm gọi qua nó: nhãn đoạn, nhãn góc, nhãn sơ đồ cây, nhãn biểu +đồ…) nay đo thêm theo BIÊN NÉT CHỮ rồi bù đúng phần tràn đó. Chữ thường không +tràn nên bù 0 — bố cục các hình cũ giữ nguyên. + +> **Vị trí ĐỈNH góc — hai lối viết.** `goc`/`goc-vuong` đặt đỉnh làm đối số +> **ĐẦU**: `goc-vuong(O, A, B)` là góc vuông **tại O**. TikZ thì viết đỉnh ở +> **GIỮA** (`pic angle = A--O--B`), nên có thêm `ve-goc(A, O, B)` và +> `ve-goc-vuong(A, O, B)` cho ai quen lối đó — cùng vẽ góc **tại O**, cùng bộ +> tuỳ chọn (`r`, `ten`, `so-do`, `so-cung`, `vach`, `to`, `mau`, `day`, +> `cach-nhan`), dùng lẫn nhau trong một hình cũng được. Viết nhầm thứ tự sẽ +> KHÔNG báo lỗi, chỉ vẽ ký hiệu sai đỉnh — nên chọn một lối rồi giữ nguyên. + +### Đường xoắn ốc & góc lượng giác (07/2026) + +`xoan-oc` vẽ xoắn ốc Archimedes (bán kính tăng đều theo góc), quét được **nhiều +vòng**; `goc-luong-giac` là lối gọi tắt cho hình góc lượng giác $(OA, OM)$ của +SGK 11 — tự tính chiều quay và số vòng. + +```typst +#hinh(xmin: -3, xmax: 3, ymin: -3, ymax: 3, w: 6cm, ctx => { + let O = (0, 0) + let A = (2.2, 0) + let M = toa-cuc(O, 2.2, -70) + cac-doan((O, A), (O, M)) + cac-diem((O, $O$, "above-left"), (A, $A$, "above"), (M, $M$, "right")) + goc-luong-giac(O, A, M, chieu: "am", vong: 1) // (OA, OM) = -430° +}) +``` + +`goc-luong-giac(O, A, M, ...)` + +| Tham số | Ý nghĩa | +| ----------------------------------- | ------------------------------------------------------------------------------------------------- | +| `chieu` | `"duong"` (ngược kim đồng hồ, mặc định) · `"am"` (cùng kim đồng hồ) | +| `vong` | số vòng quay THÊM trước khi dừng ở tia OM (0, 1, 2, …) | +| `so-do` | `true` = tự ghi số đo (vd −430°) cạnh mũi tên; `ten:` được ưu tiên | +| `r`, `buoc`, `r-cuoi` | bán kính đầu, khoảng cách 2 vòng, bán kính cuối (`auto` = tính theo chiều dài tia) | +| `mau`, `day`, `dut`, `kich` | màu (mặc định`blue`), độ dày, nét đứt, cỡ đầu mũi tên | +| `huong`, `cach`, `mau-ten` | vị trí nhãn số đo quanh điểm cuối | + +`xoan-oc(O, tu: 0deg, den: 1080deg, r: 0.1, buoc: 0.85, mau: blue)` — dùng khi +cần xoắn ốc thuần: `den > tu` quay ngược kim đồng hồ, `den < tu` quay cùng kim +đồng hồ, `r-cuoi:` đặt bán kính cuối (nhỏ hơn `r` thì xoắn vào trong), +`mui-ten:` nhận `true`/`"cuoi"`/`"dau"`/`"ca-hai"`/`false`, `ten:` ghi nhãn ở +điểm cuối. + +### `cac-doan` — vẽ nhiều nét trong MỘT lệnh (kiểu `\draw` của TikZ) (07/2026) + +```typst +cac-doan(A, B) // một đoạn +cac-doan(A, B, C, D) // các điểm liền nhau -> gấp khúc A-B-C-D +cac-doan((A, B), (C, D)) // mỗi MẢNG điểm là một nét rời +cac-doan( + duong(B, C, D, S, dong: true), // nét khép kín + duong(A, B, dut: true), duong(A, D, dut: true), + duong(B, D, mau: red, dut: true, ten: $d$, tai: 0.35), + day: 1.1pt, // kiểu CHUNG cho mọi nét +) +``` + +`duong(...)` khai báo một nét có kiểu riêng (đè lên kiểu chung). Tuỳ chọn dùng +được ở cả `duong(...)` lẫn `cac-doan(...)`: `mau`, `day`, `dut`, `dong` (khép +kín), `to` (tô màu — tự khép kín), `mui-ten` (đầu mũi tên ở điểm cuối), và bộ +nhãn `ten`/`tai`/`huong`/`cach`/`ten-quay`/`mau-ten` (nhãn đặt tại tỉ lệ `tai` +tính theo TỔNG chiều dài nét). + +### `duong-luon` — đường cong uốn lượn (kiểu `.. controls ..` của TikZ) (07/2026) + +```typst +duong-luon(A, B, C, D) // trơn TỰ ĐỘNG qua các điểm neo +duong-luon(A, dieu-khien(c1, c2), B) // Bézier bậc ba: 2 điểm điều khiển +duong-luon( + (0,0), dieu-khien((1,2),(2,2)), + (3,0), dieu-khien((4,-2),(5,-2)), (6,0), // nhiều đoạn nối tiếp (sóng) + mau: teal, day: 1.4pt, mui-ten: true, +) +duong-luon(A, B, C, dong: true, to: blue.lighten(85%)) // khép kín + tô +``` + +Liệt kê các **điểm neo**; xen `dieu-khien(c1, c2)` giữa hai neo để đặt điểm điều +khiển như `.. controls (c1) and (c2) ..` của TikZ. Đoạn KHÔNG có `dieu-khien` thì +tự làm mềm bằng Catmull-Rom (đường trơn đi qua đúng các neo — khỏi tính tay). +`dieu-khien(c1)` = một điểm điều khiển (c₂ = c₁); để `auto` một thành phần thì +thành phần đó tự tính. Tuỳ chọn: `mau`, `day`, `dut`, `dong` (khép kín), `to` +(tô), `mui-ten`, `kich`, bộ nhãn `ten`/`tai`/`huong`/`cach`/`ten-quay`/`mau-ten` +(đặt theo tỉ lệ độ dài, như `cac-doan`), `n` (số mẫu mỗi đoạn — tăng cho mượt). +`diem-luon(...)` TRẢ VỀ mảng điểm mẫu (không vẽ) để tái dùng — ví dụ đưa vào +`nhan-cong` cho chữ bám đúng đường. + +### `nhan-cong` — nhãn chữ bám theo một đường cong (07/2026) + +```typst +// chữ chạy dọc nửa trên đường tròn lượng giác +nhan-cong(diem-cung((0,0), 2.2, 2.2, 160deg, 20deg), + "DUONG TRON LUONG GIAC", co: 13pt, mau: blue, can: "giua", tu: 0.5) +// chữ bám sóng Bézier (lấy đúng mẫu của đường đã vẽ) +let s = diem-luon((0,0), dieu-khien((1,2),(2,2)), (3,0), n: 24) +nhan-cong(s, "song song", co: 12pt, phia: "duoi") +``` + +Đặt từng ký tự dọc theo `duong` (mảng điểm toạ độ toán — lấy từ `diem-luon`, +`diem-cung`, `lay-mau`, hay bất kỳ dãy điểm nào), tự xoay tiếp tuyến và canh theo +độ dài cung. `chu` nên là chuỗi `"..."`. Tuỳ chọn: `tu` (vị trí bắt đầu theo tỉ +lệ 0..1), `can` (`"trai"`/`"giua"`/`"phai"`), `khoang` (giãn ký tự), `co` (cỡ +chữ), `mau`, `phia` (`"tren"`/`"duoi"`/`"giua"` so với đường), `cach` (hở chữ↔đường), +`dao: true` (đảo chiều khi đường vẽ từ phải sang trái, để chữ đọc xuôi). + +### `cac-diem` — chấm và đặt tên nhiều điểm trong MỘT lệnh (07/2026) + +```typst +cac-diem( + (A, $A$, "below-left"), (B, $B$, "below-right"), (C, $C$, "above"), + mau: blue, bk: 2.2pt, +) +``` + +Mỗi đối số là một điểm, viết theo một trong bốn dạng: + +- `A` — chỉ chấm, không nhãn; +- `(A, $A$)` — chấm + nhãn, hướng lấy theo `huong` chung; +- `(A, $A$, "left")` — thêm hướng riêng; +- `(A, $A$, "left", red)` — thêm màu riêng (áp cho cả chấm lẫn chữ). + +Tuỳ chọn chung: `mau`, `bk` (bán kính chấm), `huong`, `cach` (nhãn cách chấm), +`mau-ten` (`auto` = theo màu chấm). Lệnh `diem` cũng nhận thêm `cach` và +`mau-ten` như trên. + +### Tiếp tuyến của đồ thị tại một điểm (07/2026) + +```typst +tiep-tuyen(f, 1.5) // tiếp tuyến tại x = 1,5 +tiep-tuyen(f, 1.5, ten: auto, ten-diem: $M$, giong: true) // + phương trình +tiep-tuyen(f, (-1, 2), mau: green, dut: true) // nhiều tiếp điểm một lệnh +tiep-tuyen(f, 0, dai: 1.4, ten: $Delta$) // đoạn ngắn quanh tiếp điểm +let k = dao-ham(f, 1.5) // chỉ lấy hệ số góc +``` + +Hệ số góc tính bằng **đạo hàm số** nên dùng được cho mọi hàm viết bằng closure +(kể cả hàm hợp, hàm căn, lượng giác). Tuỳ chọn: `dai` (`auto` = kéo hết khung, +tự cắt gọn theo cửa sổ; hoặc nửa độ dài theo trục Ox), `cham`/`mau-cham` (chấm +tiếp điểm), `ten-diem`/`huong-diem`, `ten` (`auto` = tự ghi `y = kx + m` rút +gọn), `tai`/`huong-ten`/`cach`/`ten-quay` (vị trí & kiểu nhãn), `giong` (kẻ +đường gióng đứt từ tiếp điểm về hai trục), `mau`, `day`, `dut`. + +### Nhãn chú thích ngay trên đoạn (`doan`) (07/2026) + +```typst +doan(A, B, ten: [6 cm]) // giữa đoạn, tự đặt vuông góc +doan(A, B, ten: $h$, tai: 0.7, huong: "right", cach: 4pt) +doan(B, C, ten: [cạnh huyền], ten-quay: true) // chữ nằm dọc theo đoạn +``` + +- `tai`: vị trí nhãn theo tỉ lệ từ A đến B (0 = tại A, 1 = tại B, mặc định 0.5). +- `huong`: `auto` (mặc định) = vuông góc, phía trên đoạn — đoạn thẳng đứng thì + ra bên trái; hoặc tên hướng như trên, hoặc vectơ `(dx, dy)` tự chọn. +- `ten-quay: true`: chữ xoay theo đoạn và luôn đọc xuôi (không lộn ngược). +- `mau-ten`: mặc định lấy theo màu đoạn. + +File thử: `thu-cac-doan.typ`. + +Hàm tính toán: `trung-diem`, `khoang-cach`, `chia(A, B, t)` (t = tỉ lệ AM/AB, +ngoài [0, 1] là điểm trên đường kéo dài), `hinh-chieu(P, A, B)`, +`tam-ngoai-tiep`, `tam-noi-tiep`, `truc-tam(A, B, C)` (giao ba đường cao), +`tam-bang-tiep(A, B, C)` → `(J, r)` (tâm + bán kính đường tròn bàng tiếp +**trong góc A**; muốn góc B thì gọi `tam-bang-tiep(B, C, A)`), +`tiep-diem`, `giao-hai-duong-tron`, +`giao-duong-thang(A, B, C, D)` (giao 2 đường thẳng AB, CD), +`giao-duong-thang-duong-tron((A, B), (O, r))` (giao đường thẳng AB với đường +tròn (O; r) → MẢNG điểm: rỗng nếu không cắt, 1 điểm nếu tiếp xúc, 2 điểm nếu +cắt), `dung-diem(A, B, goc, r)` (dựng điểm M: tia AM là tia AB quay quanh A một +góc lượng giác `goc` (độ, dương ngược kim đồng hồ) và AM = `r`). + +Giao với đường cong (07/2026): `giao-ham(f, g, tu, den)` trả về MẢNG giao điểm +của 2 đồ thị y = f(x), y = g(x) trên [tu, den]; đường thẳng qua 2 điểm đổi thành +hàm bằng `ham-qua-2-diem(A, B)`; đường đứng x = k thì giao là `(k, f(k))`. +Ví dụ đầy đủ: `vi-du-ve-tu-do.typ`. + +### KHÔNG cần gõ `ctx` nữa (07/2026) + +Trong thân `#hinh(...)` và trong `them: ctx => ...` của **mọi** hình/đồ thị +dựng sẵn, các lệnh vẽ tự lấy khung hình đang vẽ — **bỏ hẳn `ctx`**: + +```typst +them: ctx => { + doan(M, N, mau: blue, dut: true) + ve-ham(g, mau: red) + diem(A, mau: red, ten: $A$) +} +``` + +- Lối cũ `doan(ctx, M, N)` **vẫn chạy y nguyên** (đối số đầu là `ctx` thì gọi + thẳng) — bài soạn cũ không phải sửa một chữ nào. +- **Ngoại lệ vẫn phải truyền `ctx`**: các hàm TRẢ VỀ GIÁ TRỊ chứ không vẽ — + `toa`, `toa-pt`, `toa-nguoc`, `goc-truc`, `ctx-quay`, `ctx-tinh-tien`. + Ví dụ nhãn nghiêng: `nhan(P, $d$, quay: goc-truc(ctx, A, B))`. +- Gọi lệnh vẽ ở NGOÀI khung hình sẽ báo lỗi rõ ràng: *"Hàm vẽ cần khung + hình: đặt lệnh trong thân #hinh(...) hoặc trong them: ctx => ..."*. +- Vẫn giữ `ve-voi(ctx)` (bộ hàm gắn sẵn ctx, lấy bằng + `let (doan, diem) = ve-voi(ctx)`) cho ai muốn đặt tên riêng, hoặc khi vẽ + trên khung đã quay: `ve-voi(ctx-quay(ctx, 30deg))`. + +Danh sách 49 lệnh được gọi ngầm: nguyên thuỷ của `ve.typ` +(`doan`, `diem`, `nhan`, `mui-ten`, `vecto`, `duong-cong`, `duong-tron`, +`elip`, `cung`, `goc`, `goc-vuong`, `ve-goc`, `ve-goc-vuong`, `danh-dau`, `ve-ham`, `to-vung`, +`gach-mien`, `gach-vung`, `ve-mien`, `mui-ten`, `dau-mui-ten`…), +hệ trục của `do-thi.typ` (`truc`, `luoi`, `vach-chia`, `he-truc`, `giong`, +`nhan-pi`), toàn bộ `hinh-phang.typ` (`tam-giac`, `duong-cao`, `tu-giac`…) +và nhóm Oxyz (`diem-oxyz`, `doan-oxyz`, `vecto-oxyz`, `giong-oxyz`). + +Các hàm KHÔNG cần `ctx` từ trước (`trung-diem`, `chia`, `hinh-chieu`, +`giao-ham`, `ham-qua-2-diem`, `so-toan`, `mien-tron`, `giao`/`hop`/`bu`…) +vẫn gọi thẳng như cũ. + +> `giao-ham` chỉ **TÍNH**, không vẽ ⇒ **không truyền `ctx`**; kết quả đem cho +> `diem`/`nhan` mới ra hình. Cận nhận cả 2 lối viết: `giao-ham(f, g, -3, 2)` +> hoặc `giao-ham(f, g, tu: -3, den: 2)`. Lỡ gõ `giao-ham(ctx, f, g, ...)` thì +> `ctx` được bỏ qua chứ không báo lỗi. Quét qua tiệm cận đứng cũng an toàn: +> chỗ hàm "nhảy" bị loại khỏi kết quả. +> +> ```typst +> them: ctx => { +> let f = x => (x + 2)/(x + 1) +> let g = x => x + 1 +> ve-ham(g, mau: red) +> for P in giao-ham(f, g, -3, 2) { diem(P, mau: red, bk: 2.2pt) } +> } +> ``` + +## 3. Hình phẳng (hinh-phang.typ) + +```typst +#hinh(w: 7cm, xmin: -1, xmax: 6, ymin: -1, ymax: 5, ctx => { + let (A, B, C) = ((0.5, 0), (5.5, 0), (3.5, 4)) + tam-giac(ctx, A, B, C) // nhãn đỉnh tự đặt + duong-cao(ctx, C, A, B, ten-chan: $H$) + trung-tuyen(ctx, A, B, C, ten-chan: $M$) + phan-giac(ctx, B, C, A, ten-chan: $D$) + phan-giac(ctx, B, C, A, vach: 1) // 1 vạch: hai góc BẰNG NHAU + phan-giac(ctx, B, C, A, so-cung: 2) // hoặc 2 cung đồng tâm + trung-truc(ctx, A, B) + duong-tron-ngoai-tiep(ctx, A, B, C) // tâm O tự tính + duong-tron-noi-tiep(ctx, A, B, C) +}) +``` + +**Đường tròn ngoại tiếp ĐA GIÁC** — `duong-tron-ngoai-tiep` nhận cả một MẢNG +đỉnh, không chỉ tam giác. Từ 4 đỉnh trở lên, tâm và bán kính khớp theo bình +phương bé nhất (đa giác nội tiếp được vẫn cho ĐÚNG đường tròn của nó). + +```typst +#let P = range(5).map(i => toa-cuc((0, 0), 1, 90 + i * 72)) // ngũ giác đều +#duong-tron-ngoai-tiep(P, canh: true, ban-kinh: true, ten-r: $R$) +#let (O, R) = tron-qua-diem(P) // TRẢ VỀ (tâm, bán kính), không vẽ +``` + +Nhãn tâm tự chọn phía THOÁNG nhất nên không rơi lên cạnh đa giác (`huong-tam:` +để chỉ định tay). `ban-kinh: true` cũng tự chọn đỉnh mà bán kính không nằm đè +lên cạnh — vd tam giác vuông có tâm ở giữa cạnh huyền thì bán kính vẽ tới đỉnh +góc vuông chứ không trùng cạnh huyền. + +**Khung vừa khít hình (`khung-vua`)** — đường tròn ngoại tiếp thường chìa ra +ngoài đa giác nên rất dễ tràn khỏi `#hinh`. `khung-vua` trả về cửa sổ toạ độ +vừa khít, rải thẳng vào `#hinh`: + +```typst +#let (O, R) = tron-qua-diem((A, B, C)) +#hinh(w: 5cm, ..khung-vua((A, B, C), (O, R)), ctx => { + tam-giac(ctx, A, B, C) + duong-tron-ngoai-tiep(ctx, A, B, C, ban-kinh: true, ten-r: $R$) +}) +``` + +Mỗi đối số là một ĐIỂM `(x, y)`, một ĐƯỜNG TRÒN `(tâm, bán kính)`, hoặc một +MẢNG gồm các thứ đó; `le:` chừa lề theo tỉ lệ cạnh lớn (mặc định `0.12`). + +Sẵn có thêm: `tu-giac`, `hinh-binh-hanh` (biết 3 đỉnh), `hinh-chu-nhat`, +`hinh-thang`, `tiep-tuyen-tu-diem(ctx, O, r, M)` (hai tiếp tuyến + góc vuông), +`tiep-tuyen-tai-diem(O, r, M, dai: auto, dut: false)` (tiếp tuyến TẠI điểm M +trên đường tròn — đoạn qua M vuông góc OM; `dai` = nửa độ dài đoạn), +`duong-tron-luong-giac(so-do: 55deg)` (hình hoàn chỉnh, tự tạo khung). + +Nhãn theo GÓC LƯỢNG GIÁC — `nhan-goc(..muc)` đặt nhãn nhiều điểm, phía đặt nhãn +xác định bằng góc (thay tên hướng): mỗi mục `(P, nội-dung, goc)` / +`(P, nội-dung, goc, ban-kinh)` / `(P, nội-dung, goc, ban-kinh, mau)`, `goc` số +trần = độ (dương ngược kim đồng hồ), `ban-kinh` là độ dài trang (điểm→nhãn). +Ví dụ `nhan-goc((A, $A$, 0), (B, $B$, 120, 8pt), (C, $C$, 240, 8pt, red))`. + +`duong-tron-ngoai-tiep` và `duong-tron-noi-tiep` nhận thêm `dut: true` để vẽ +đường tròn bằng NÉT ĐỨT. File thử các hàm này: `thu-hinh-tron-moi.typ`. + +**Trực tâm & đường tròn bàng tiếp (07/2026)** — trọn bộ công cụ tam giác: + +```typst +ve-truc-tam(A, B, C) // 3 đường cao + ký hiệu vuông góc + H +ve-truc-tam(A, B, C, canh: false, // không vẽ lại cạnh tam giác + ten: $H$, ten-chan: ($H_A$, $H_B$, $H_C$)) +duong-tron-bang-tiep(A, B, C) // bàng tiếp TRONG GÓC A +duong-tron-bang-tiep(A, B, C, ban-kinh: true, // + bán kính tới BC + ten-tam: $J_A$, ten-tiep: ($X$, $Y$, $Z$)) +duong-tron-bang-tiep(B, C, A) // bàng tiếp trong góc B +``` + +`ve-truc-tam` tự kéo dài cạnh bằng nét đứt khi tam giác tù (chân đường cao rơi +ra ngoài cạnh); `duong-tron-bang-tiep` tự kéo dài AB, AC tới tiếp điểm +(`keo-dai: false` để tắt). + +Tam giác đặc biệt (tự vẽ ký hiệu vuông góc, vạch cạnh bằng nhau): + +```typst +tam-giac-deu(ctx, A, canh) // đều +tam-giac-vuong(ctx, A, a, b) // vuông tại A, hai cạnh a × b +tam-giac-can(ctx, A, canh-day, chieu-cao) // cân tại C +tam-giac-vuong-can(ctx, A, canh) // vuông cân tại A +``` + +## 4. Hình không gian (hinh-khong-gian.typ) + +Các hình hoàn chỉnh, nét khuất tự vẽ đứt theo quy ước SGK: + +```typst +#hinh-chop-tam-giac() // S.ABC +#hinh-chop-tu-giac(duong-cao: "tam", duong-cheo: true) // chóp đều S.ABCD +#hinh-chop-tu-giac-thuong(duong-cheo: true) // chóp tứ giác thường (góc nhìn khác) +#hinh-chop-day-hinh-thang(duong-cheo: true) // chóp đáy hình thang +#hinh-chop-tu-giac(duong-cao: "dinh-a") // SA ⊥ đáy +#hinh-hop(duong-cheo: true) // hộp + đường chéo AC′ (nghieng: độ xiên) +#hinh-hop-chu-nhat(duong-cheo: true) // hộp chữ nhật dài (nhìn thẳng, nghieng: 0) +#hinh-lap-phuong() +#hinh-lang-tru-tam-giac() +#hinh-non() #hinh-tru() #hinh-cau() +#truc-oxyz(don-vi: true) + +// Hình chóp đặc biệt +#hinh-chop-tam-giac-deu(trung-tuyen: true) // S.ABC đều, SO ⊥ đáy tại trọng tâm +#hinh-chop-tu-giac-deu() // S.ABCD đều, 2 đường chéo + SO +#hinh-chop-tam-dien-vuong() // O.ABC: OA, OB, OC đôi một vuông góc +#hinh-chop-day-tam-giac-vuong() // ABC vuông tại B, SA ⊥ (ABC) +#hinh-chop-day-chu-nhat() // đáy chữ nhật, SA ⊥ đáy +``` + +**Vẽ thêm lên hình có sẵn** bằng `them` — nhận `ctx` và từ điển đỉnh `d` +(`d.S`, `d.A`, `d.B`, `d.C`, `d.D`, hình hộp/lăng trụ có `d.A1` = A′…): + +```typst +#hinh-chop-tu-giac(them: (ctx, d) => { + let M = trung-diem(d.S, d.C) + diem(ctx, M, ten: $M$, huong: "phai", mau: blue) + doan(ctx, d.B, M, mau: blue, dut: true) + da-giac(ctx, (d.A, M, d.B), to: rgb(30,100,200,40)) // thiết diện mờ +}) +``` + +## 5. Đồ thị hàm số (do-thi.typ) + +**Vẽ nhanh nhất** — chỉ cần công thức và màu, cửa sổ tự chọn: + +```typst +#ve-do-thi(x => x*x*x - 3*x, mau: red) +#ve-do-thi(x => calc.sin(2*x) + x/2, mau: purple, ten: $y = sin 2x + x/2$) +#ve-do-thi(x => calc.exp(x) - 2, mau: blue, xmin: -3, xmax: 2) +``` + +(Hàm cần xác định trên đoạn vẽ; hàm có tiệm cận đứng dùng `do-thi-phan-thuc`.) + +Đồ thị dựng sẵn (tự chọn cửa sổ đẹp, gióng điểm đặc biệt): + +```typst +#do-thi-bac-nhat(2, -1) // y = 2x − 1 +#do-thi-bac-hai(1, -2, -1) // parabol, gióng đỉnh +#do-thi-bac-ba(1, -3, 0, 2) // gióng 2 cực trị +#do-thi-trung-phuong(1, -2, 0) +#do-thi-phan-thuc(2, -1, 1, -1) // y=(2x−1)/(x−1), 2 tiệm cận đứt đỏ +#do-thi-mu(2) #do-thi-log(2) +#do-thi-sin() #do-thi-cos() #do-thi-tan() // nhãn π/2, π, 2π +#do-thi-can() +``` + +Mọi đồ thị dựng sẵn đều nhận `luoi-o: true` (lưới ô vuông mờ) và `vach: true` +(vạch chia + số trên hai trục; riêng sin/cos/tan chỉ có `luoi-o` vì trục hoành +đã ghi theo π). Khi bật `vach`, nên đặt `giao-ox: none, giao-oy: none` để nhãn +giao trục không chồng lên số vạch. + +Vẽ tay trong `#hinh`: `he-truc(ctx)` gộp lưới + trục + vạch chia + số một lệnh: + +```typst +#hinh(xmin: -4, xmax: 4, ymin: -3, ymax: 3, ctx => { + he-truc(ctx) // tuỳ chọn: luoi-o, vach, so, buoc-x, buoc-y, ten-x... + ve-ham(ctx, x => x*x - 2, mau: blue) +}) +``` + +Hàm bất kỳ + vẽ chồng qua `them`: + +```typst +#do-thi-ham(x => calc.abs(x*x - 2*x), xmin: -2, xmax: 4, ymin: -1, ymax: 4, + ten: $y = |x^2 - 2x|$, luoi-o: true, vach: true, + them: ctx => { + ve-ham(ctx, x => 1.5, mau: red, dut: true) // đường y = m + giong(ctx, (3, 3)) // gióng điểm về 2 trục + }) +``` + +### Nhiều đồ thị trên cùng một hệ trục + +Mỗi hàm gói bằng `ham(...)`; `giao-diem: auto` chấm đỏ giao điểm từng cặp: + +```typst +#do-thi-nhieu-ham( + ham(x => x*x - 2, mau: blue, ten: $y = x^2 - 2$), + ham(x => x, mau: red, ten: $y = x$, dut: true, tai: -2), // tai: hoành độ đặt nhãn + xmin: -3, xmax: 3, ymin: -3, ymax: 4, + giao-diem: auto, +) +``` + +### Miền nghiệm BPT / hệ BPT bậc nhất hai ẩn + +Quy ước SGK: gạch phần **không** là miền nghiệm; biên nét đứt nếu BPT ngặt. +Mỗi BPT `a·x + b·y (dau) c` khai báo bằng `bpt(a, b, c, dau: "<=")`: + +Nhãn `ten:` mặc định **NẰM NGHIÊNG theo chiều đường thẳng** (tự đọc xuôi, không +lộn ngược); đặt tại điểm `ten-tai:` (0..1 dọc đoạn nhìn thấy). `huong-ten:` chọn +**phía** đặt nhãn so với đường: `"above"` (trên) hoặc `"below"` (dưới). Muốn chữ +nằm ngang như cũ thì `nghieng-ten: false` (khi đó `huong-ten` nhận mọi hướng +`"above"/"below"/"left"/"right"/…`). Khoảng cách tới đường: `cach-ten:`. + +Kiểu gạch chéo đặt được RIÊNG cho từng BPT (giống `mau-gach`): `goc-gach:` (góc) +và `buoc-gach:` (bước) trong `bpt(...)` — `auto` = theo giá trị chung khai ở +`mien-nghiem`. + +```typst +#mien-nghiem(bpt(4, 5, -8, dau: "<"), giao-truc: auto) // 4x + 5y < −8 +#mien-nghiem( // hệ BPT + tô miền + bpt(3, -2, -9, mau: red, ten: $3x - 2y = -9$, ten-tai: 0.7, huong-ten: "above"), + bpt(-3, 5, 18, mau: green.darken(25%), ten: $-3x + 5y = 18$, huong-ten: "below", + goc-gach: 135deg), // gạch riêng đường này + to-mien: rgb(40, 90, 200, 35), + xmin: -7.5, xmax: 2.5, ymin: -1.5, ymax: 5, +) +``` + +### Diện tích hình phẳng giới hạn bởi 2 đồ thị + +`g` mặc định là trục hoành; `a, b: auto` = tự lấy giao điểm ngoài cùng +(trong phạm vi `tu..den`); miền tô tự tách tại giao điểm ở giữa: + +```typst +#dien-tich-2-ham(x => x*x, g: x => x + 2) // tự tìm giao điểm +#dien-tich-2-ham(x => calc.sin(x), a: 0.5, b: 2.6) // trên đoạn [a, b] +#dien-tich-2-ham(x => x*x - 1, g: x => -x - 1, a: -2, b: 1.5, + ten-f: $y = x^2 - 1$, ten-g: $y = -x - 1$, mau-to: rgb(200, 60, 60, 60)) +``` + +Cần vẽ tay trong `them` của đồ thị khác: `to-vung-2-ham(ctx, f, g, a, b)` +(và `to-vung(ctx, f, a, b)` với trục hoành); gạch chéo đa giác lồi: +`gach-mien(ctx, cac-dinh)`. + +### Khối tròn xoay (07/2026) + +`#khoi-tron-xoay(f, a, b)` vẽ **hai hình cạnh nhau**: trái là miền phẳng đã tô +màu (giới hạn bởi `y = f(x)`, trục hoành, `x = a`, `x = b`), phải là khối tròn +xoay sinh ra khi quay miền đó quanh `Ox` — nét khuất tự vẽ đứt. + +```typst +#khoi-tron-xoay(x => calc.sqrt(x), 0, 4, ten-ham: $y = sqrt(x)$, the-tich: true) +#khoi-tron-xoay(x => 0.35*x*x + 0.5, 0, 3, mat-cat: 2) // thiết diện tại x = 2 +#khoi-tron-xoay(x => 1.6, 0, 3, g: x => 0.7) // khối rỗng (vành khăn) +#khoi-tron-xoay(y => 0.6 + 0.5*y, 0, 3, truc: "Oy") // quay quanh Oy +#khoi-tron-xoay(f, a, b, hien: "khoi") // chỉ lấy khối 3D +#khoi-tron-xoay(f, a, b, hien: "mien") // chỉ lấy miền phẳng +``` + +Tham số: `truc` (`"Ox"` mặc định | `"Oy"` — khi đó `f` là bán kính theo `y`), +`hien` (`"ca-hai"` | `"mien"` | `"khoi"`), `g` (bán kính trong → vành khăn), +`mat-cat: c` (vẽ đĩa/vành thiết diện tại `c` + bán kính, kèm `ten-ban-kinh`, +`ten-mat-cat`), `the-tich: true` (ghi công thức `V = π∫f²dx` dưới hình), +`w` (bề rộng hình khối), `k` (độ "dẹt" của elip, mặc định 0.26), `ten-ham`, +`ten-ham-trong`, `ten-a`, `ten-b`, `ten-goc`, `nhan-giua`, `mau`, `mau-to`, +`mau-mien`, `them` / `them-mien` (vẽ chồng vào hình khối / hình miền). + +Vẽ riêng vào một khung `#hinh` có sẵn: `ve-khoi-xoay(f, a, b, ...)` và +`ve-mien-xoay(f, a, b, ...)` (thêm `ngang: false` cho trục quay thẳng đứng). + +Gạch chéo **miền bất kì** (biên cong, hợp/giao/hiệu tuỳ ý): khai báo mỗi hình +đúng MỘT lần bằng `mien-tron(O, r)` / `mien-elip(O, a, b, quay:)`, vẽ biên bằng +`ve-mien(ctx, m)`, gạch bằng `gach-vung` + phép tập hợp `giao`/`hop`/`bu` — +đổi tâm/bán kính một chỗ là vẽ và gạch tự khớp. Ví dụ Ven $(A sect B) without C$: + +```typst +#hinh(w: 4.5cm, xmin: -2, xmax: 2, ymin: -2, ymax: 2, ctx => { + let A = mien-tron((-0.5, 0.3), 0.9) + let B = mien-tron((0.5, 0.3), 0.9) + let C = mien-tron((0, -0.5), 0.5) // bán kính khác nhau thoải mái + ve-mien(ctx, A) + ve-mien(ctx, B) + ve-mien(ctx, C) + gach-vung(ctx, giao(A, B, bu(C)), mau: black, day: 0.4pt, buoc: 4.5pt) +}) +``` + +`giao`/`hop`/`bu` lồng nhau tuỳ ý: `hop(A, B)`, `giao(A, hop(B, C))`, +`bu(hop(A, B))`... Miền không phải tròn/elip thì `gach-vung` vẫn nhận hàm +`P => true/false` như cũ; helper điểm: `trong(P, m)` (nhận miền hoặc hàm), +`trong-tron(P, O, r)`, `trong-elip(P, O, a, b, quay:)`. + +Tuỳ chọn: `goc:` (hướng vạch, mặc định 45°), `buoc:` (khoảng cách vạch), +`n:` (số mẫu mỗi vạch — biên càng mịn). + +### Phép quay & tịnh tiến (ve.typ) + +Elip xoay: `elip(ctx, O, a, b, quay: 30deg)` (dương = ngược chiều kim đồng hồ, +xoay quanh tâm O); kiểm tra điểm cùng góc: `trong-elip(P, O, a, b, quay: 30deg)`. +`cung` và `cung-elip` cũng nhận `quay:` (xoay cung quanh tâm). + +Quay/tịnh tiến **cả cụm hình** — mọi hàm vẽ theo điểm (doan, da-giac, tam-giac, +duong-tron, diem, nhan, vecto...) đều tự biến đổi khi vẽ bằng ctx đã bọc: + +```typst +#hinh(w: 7cm, xmin: -1, xmax: 8, ymin: -1, ymax: 4, ctx => { + let ve-hinh = c => { + tam-giac(c, (0, 0), (2, 0), (0.6, 1.6)) + duong-tron(c, (1, 0.55), 0.4, mau: blue) + } + ve-hinh(ctx) // hình gốc + ve-hinh(ctx-quay(ctx, 35deg, tam: (0, 0))) // ảnh quay quanh O + ve-hinh(ctx-tinh-tien(ctx-quay(ctx, 35deg, tam: (0, 0)), (4.5, 0.5))) +}) // quay trước, tịnh tiến sau +``` + +Biến đổi điểm lẻ: `quay-diem(P, tam, goc)`, `tinh-tien-diem(P, v)`. + +Toạ độ CỰC kiểu TikZ — `toa-cuc(tam, bk, goc)` trả về điểm cách `tam` một +khoảng `bk`, quay góc `goc` (số trần = ĐỘ, nhận cả `30deg`; dương = ngược +kim đồng hồ). TikZ `(30:2)` ⇔ `toa-cuc((0,0), 2, 30)`. Ví dụ lục giác đều: + +```typst +#let dinh = range(6).map(k => toa-cuc((0, 0), 2.6, 60 * k)) +#duong-gap-khuc(ctx, dinh, dong: true) +``` + +Lưu ý: `gach-vung` mô tả miền theo toạ độ GỐC của khung (không qua ctx-quay); +riêng elip xoay đã có `trong-elip(..., quay:)` tương ứng. + +### Sơ đồ cây xác suất (so-do-cay.typ) + +Cây sâu tuỳ ý; nút lá có ô kết quả bên phải; màu ô tự đổi theo cấp: + +```typst +#so-do-cay( + goc: $1$, + nhanh: ( + nut($A$, xs: $1/6$, con: ( + nut($B$, xs: $1/2$, kq: $A B: 1/12$), + nut($overline(B)$, xs: $1/2$, kq: $A overline(B): 1/12$), + )), + nut($overline(A)$, xs: $5/6$, con: ( + nut($B$, xs: $1/3$, kq: $overline(A) B: 5/18$), + nut($overline(B)$, xs: $2/3$, kq: $overline(A) overline(B): 5/9$), + )), + ), +) +``` + +### Hệ trục Oxyz theo đơn vị thật (hinh-khong-gian.typ) + +`oxyz` vẽ 3 trục + vectơ đơn vị $arrow(i), arrow(j), arrow(k)$; `them` +nhận `(ctx, t3)` với `t3` đổi toạ độ $(x, y, z)$ thành điểm 2D — dùng +được với mọi hàm vẽ phẳng: + +```typst +#oxyz(x: 5, y: 8, z: 8, them: (ctx, t3) => { + giong-oxyz(ctx, t3, (5, 8, 8)) // hộp gióng nét đứt + vecto-oxyz(ctx, t3, (0, 0, 0), (5, 8, 8), mau: red) + diem-oxyz(ctx, t3, (5, 8, 8), ten: $B$, huong: "above-right") +}) +``` + +Kèm `doan-oxyz(ctx, t3, A, B)`; điểm nằm trong (Oxy) chỉ vẽ hình gióng phẳng. +Xem `thu-ve-moi.typ` để có ví dụ đầy đủ của cả 6 định nghĩa. + +Mới (07/2026): `vach: true` vạch chia đơn vị trên 3 trục, `so: true` ghi số +tại các vạch, `buoc:` bước chia, `luoi:` lưới trên mặt phẳng toạ độ — +`"xy"` | tuple `("xy", "xz", "yz")` | `true` (cả 3), màu chỉnh bằng `mau-luoi`: + +```typst +#oxyz(x: 3, y: 4, z: 3, vach: true, so: true, luoi: "xy") +``` + +## 6. Bảng biến thiên & bảng xét dấu (bang.typ) + +**BBT tự động từ hệ số** (07/2026) — chỉ nhập hệ số (positional), thuật toán +tự tìm cực trị/tiệm cận/chiều biến thiên, phủ mọi trường hợp, nhãn số đẹp +(phân số, căn, `(m ± k√n)/q`): + +```typst +#bbt-bac-hai(1, -3, 1) // y = x² − 3x + 1 +#bbt-bac-ba(1, 0, -3, 1) // y = x³ − 3x + 1: 2 cực trị / Δ'=0 / đơn điệu +#bbt-trung-phuong(1, -2, 0) // y = x⁴ − 2x²: 3 hoặc 1 cực trị +#bbt-phan-thuc(2, -1, 1, -1) // y = (2x−1)/(x−1): tự xét dấu ad − bc +#bbt-huu-ti(1, -1, 1, 1, -1) // y = (x²−x+1)/(x−1): 2 cực trị / đơn điệu +#bbt-can-bac-hai-ham-bac-hai(1, 0, -4) // y = √(x²−4): tự phân 3 TH theo a, Δ +``` + +`bbt-can-bac-hai-ham-bac-hai(a, b, c)` — BBT hàm $y = sqrt(a x^2 + b x + c)$, +đỉnh $x_0 = -b\/(2a)$, giá trị đỉnh $sqrt((4 a c - b^2)\/(4a))$ (in căn thức +chính xác). Ba trường hợp: **a > 0, Δ > 0** (2 nghiệm) → TXĐ $(-oo, x_1] union +[x_2, +oo)$, khoảng giữa gạch chéo (ngoài TXĐ), giá trị 2 nghiệm $= 0$; +**a > 0, Δ ≤ 0** (nghiệm kép / vô nghiệm) → TXĐ $RR$, cực tiểu tại đỉnh +(Δ = 0 → min $= 0$, $y'$ không xác định tại đỉnh, ghi ‖); **a < 0, Δ > 0** +(2 nghiệm) → TXĐ $[x_1, x_2]$, cực đại tại đỉnh (a < 0 mà Δ ≤ 0 → TXĐ rỗng, báo lỗi). + +Form cũ (named) vẫn chạy: `bbt-bac-hai(a: 1, xd:, yd:)`, `bbt-bac-ba(a: -1, x1: $-1$, ...)`, +`bbt-bac-ba-don-dieu(a: 1)`, `bbt-trung-phuong(a: 1, x0:, yc:, y0:)`, +`bbt-phan-thuc(x0: $1$, y0: $2$, dong-bien: false)`, `xet-dau-tam-thuc(a: 1, x1: $1$, x2: $2$)`. + +**Khảo sát & vẽ đồ thị tự động** (07/2026, `khao-sat.typ`) — chỉ nhập hệ số là +xổ ra TRỌN lời giải (tập xác định, đạo hàm, giới hạn, chiều biến thiên, cực trị +/ tiệm cận, bảng biến thiên) **kèm đồ thị**, phủ mọi trường hợp của mỗi hàm: + +```typst +#khao-sat-ve-do-thi-ham-bac-hai(1, -2, -3) // y = x² − 2x − 3 (a>0/a<0, Δ mọi dấu) +#khao-sat-ve-do-thi-ham-bac-ba(1, 0, -3, 1) // y = x³ − 3x + 1 (2 cực trị / Δ'=0 / đơn điệu) +#khao-sat-ve-do-thi-ham-trung-phuong(1, -2, 0) // y = x⁴ − 2x² (3 hoặc 1 cực trị) +#khao-sat-ve-do-thi-ham-phan-thuc(2, -1, 1, -1) // y = (2x−1)/(x−1) (đồng/nghịch biến) +#khao-sat-ve-do-thi-ham-huu-ti(1, 4, 20, 1, 2) // y = (x²+4x+20)/(x+2) (2 cực trị / đơn điệu) +``` + +Tham số chung: `tieu-de:` (auto = dòng đậm "Khảo sát… y = …"; `none` = bỏ; hoặc +nội dung tuỳ ý), `w:` bề rộng đồ thị (mặc định 7.6cm), `giua: true` canh giữa +BBT + đồ thị. Đặt thẳng trong thân câu hoặc trong `loi-giai:` của `#tl(...)`. + +Bảng tuỳ ý — quy ước: `dau` dài `2n−1` xen kẽ *[tại mốc, trên khoảng, …]*; +tại điểm gián đoạn thêm chỉ số vào `kep` và cho `gia-tri` một **cặp** (trái, phải): + +```typst +// y = x³ − 3x² + 2 +#bbt( + x: ($-oo$, $0$, $2$, $+oo$), + dau: ("", "+", "0", "-", "0", "+", ""), + gia-tri: ($-oo$, $2$, $-2$, $+oo$), + huong: ("len", "xuong", "len"), +) + +// y = (2x−1)/(x−1): tiệm cận đứng x = 1 (kẹp ‖) +#bbt( + x: ($-oo$, $1$, $+oo$), + dau: ("", "-", "||", "-", ""), + gia-tri: ($2$, ($-oo$, $+oo$), $2$), + huong: ("xuong", "xuong"), + kep: (1,), +) + +// Bảng xét dấu nhiều dòng +#bang-xet-dau( + x: ($-oo$, $1$, $3$, $+oo$), + dong: ( + ($x - 1$, ("", "-", "0", "+", "", "+", "")), + ($x - 3$, ("", "-", "", "-", "0", "+", "")), + ($f(x)$, ("", "+", "0", "-", "0", "+", "")), + ), +) +``` + +Ký hiệu dấu: `"+"`, `"-"`, `"0"`, `"||"` (kẹp), `""` (trống); `huong`: `"len" | "xuong" | "ngang"`. + +**Tự bù ô trống hai đầu** (08/2026) — dãy `dau` phải dài `2n−1` nên **hai đầu +luôn là ô trống `""`**; AI sinh bài rất hay quên đúng hai ô đó. Nay `bbt` và +`bang-xet-dau` **tự chèn thêm** (chỉ chèn ở ĐẦU/CUỐI, không bao giờ chèn vào +giữa; thiếu 1 ô thì tự đoán đầu nào thiếu theo quy ước `"+"`/`"-"` nằm trên +khoảng, `"0"`/`"||"` nằm tại mốc). Thiếu quá 2 ô hoặc thừa ô thì báo lỗi bằng +tiếng Việt nói rõ cần bao nhiêu phần tử — thay cho `array index out of bounds`. +Kèm theo: `dau` nhận cả số trần (`0` thay vì `"0"`), còn `x` và `gia-tri` nhận +chuỗi `"-oo"`/`"+oo"` và số trần (`-5/3`) rồi tự quy về nội dung toán. Nghĩa là +đoạn dưới đây chạy đúng y như bản viết đủ: + +```typst +#bbt( // thiếu 2 ô trống + "-oo" dạng chuỗi + x: ("-oo", -3, -1, "+oo"), + dau: ("+", 0, "-", 0, "+"), + gia-tri: ("-oo", 5, -5/3, "+oo"), + huong: ("len", "xuong", "len"), +) +``` + +File thử: `thu-bbt-bu-dau.typ`. + +**Mũi tên nửa ô** (07/2026) — khi hàm đơn điệu qua mốc y′ = 0 (nghiệm kép), +để 2 mũi tên cùng chiều nằm **trên 1 đường thẳng** đi qua giá trị đặt giữa ô: +`"len-duoi"` (đáy→giữa) rồi `"len-tren"` (giữa→đỉnh); nghịch biến dùng +`"xuong-tren"` (đỉnh→giữa) rồi `"xuong-duoi"` (giữa→đáy). Giá trị tại mốc kề +đầu mút "giữa" tự đặt chính giữa ô — không cần box thủ công: + +```typst +// y = x³ − 3x² + 3x: y' = 0 kép tại x = 1, vẫn đồng biến +#bbt( + x: ($-oo$, $1$, $+oo$), + dau: ("", "+", "0", "+", ""), + gia-tri: ($-oo$, $1$, $+oo$), + huong: ("len-duoi", "len-tren"), +) +``` + +`bbt-bac-ba(a, b, c, d)` (và `khao-sat-ve-do-thi-ham-bac-ba`) trường hợp +Δ′ = 0 tự dùng kiểu này. + +## 7. Câu hỏi định dạng đề thi 2025 (cau-hoi.typ) + +Bảy dạng: MC/TF/SA/TL đánh số "Câu N" liên tục chung; **HĐ** (hoạt động) +đánh "HĐ1, HĐ2...", **Luyện tập** đánh "Luyện tập 1, 2..." (chung bộ đếm +với khung `#luyen-tap`) và **Vận dụng** đánh "Vận dụng 1, 2..." — mỗi loại +bộ đếm riêng. Ngoài ra có 3 hình thức hoạt động SGK **KHÔNG đánh số**: +`#kham-pha` (Khám phá, xanh lục), `#trai-nghiem` (Trải nghiệm, xanh lá), +`#thao-luan` (Thảo luận, xanh dương) — thân giống `#hd` (dùng được `loi-giai:`, +`hinh:`, `lo-da:`, `cot-item`…). **Một công tắc** đổi giữa bản giáo viên +(hiện đáp án, tô xanh) và bản chiếu cho học sinh: `#bat-dap-an()` / `#tat-dap-an()`. +Ở chế độ **beamer**, công tắc tự BẬT sẵn (kể cả khi dùng `bai-giang` trực tiếp, +không qua `de-toan`) — đáp án được đánh dấu ở bước `lo-da` (bước cuối) của mỗi câu; +muốn ẩn thì gọi `#tat-dap-an()`. +Đánh số lại — MỖI thể loại có bộ đếm & hàm đặt lại RIÊNG (không còn đặt lại +tất cả cùng lúc): `#dat-lai-cau()` chỉ nhóm **Câu** (tn/ds/tln/tl); +`#dat-lai-cau-vd()` Ví dụ, `#dat-lai-cau-hd()` Hoạt động, `#dat-lai-cau-lt()` +Luyện tập, `#dat-lai-cau-vdtt()` Vận dụng thực tế; `#dat-lai-cau-tat-ca()` đặt +lại cả 5 nhóm cùng lúc (hành vi cũ). Không tham số hoặc `(0)` → về 1; +`(3)` → đánh tiếp từ 4 (tổng quát `n` → từ n + 1). + +```typst +// MC — 4 phương án, cot: 1 | 2 | 4 +#cau-mc([Câu hỏi...?], ($1$, $2$, $3$, $4$), dap-an: "B", cot: 4, + loi-giai: [Hướng dẫn giải...]) + +// TF — 4 ý đúng/sai +#cau-tf([Đề bài...], ([ý a], [ý b], [ý c], [ý d]), + dap-an: (true, true, false, false), loi-giai: [...]) + +// SA — trả lời ngắn (khi ẩn đáp án sẽ hiện 4 ô điền như phiếu TLĐ) +#cau-sa([Tính ...?], dap-an: $12$, loi-giai: [...]) + +// TL — tự luận; lời giải chỉ hiện khi bật đáp án +#cau-tl([Đề bài...], diem: 2, loi-giai: [Hướng dẫn...], cho-trong: 3cm) + +// HĐ — hoạt động (khởi động/khám phá): thẻ "HĐ1" cam, hình thức như TL; +// nhiều ý hỏi thì dùng #cot-item trong thân câu +#cau-hd([Cho hàm số... #cot-item([Tìm GTLN...], [Tìm GTNN...])], + loi-giai: [a) ... \ b) ...]) + +// LT — luyện tập (củng cố lý thuyết): thẻ "Luyện tập 1" vàng đậm, +// chung bộ đếm với khung #luyen-tap +#cau-lt([Tìm GTLN — GTNN của $y = sqrt(2x - x^2)$.], loi-giai: [...]) + +// VDTT — vận dụng thực tế: thẻ "Vận dụng 1" tím, hình thức như TL +#cau-vdtt([Một chiếc hộp...], loi-giai: [Gọi $x$... \ Vậy...]) +``` + +Cả 7 dạng đều có `loi-giai:`; trong slide hoạt hình dùng thêm +`lo-da:` (bước lộ đáp án) và `lo-giai:` (bước lộ lời giải, mặc định +cùng bước với đáp án). Số thứ tự câu/ví dụ/luyện tập **không đổi** +qua các bước hoạt hình. + +Khung công thức: `#cong-thuc[$S = pi r^2$]` (cùng họ với `#dinh-nghia`, `#dinh-ly`). + +## 8. Hoạt hình xuất hiện từng bước + +Nguyên lý như Beamer: slide có `so-buoc: n` được in thành `n` trang PDF — +khi trình chiếu, bấm phím chuyển trang là nội dung "hiện dần". +Chân trang đánh số theo slide (không tăng theo bước). + +```typst +#slide(tieu-de: [Ví dụ], so-buoc: 3)[ + Nội dung luôn hiển thị. + #lo(2)[Hiện từ bước 2 — khi chưa hiện KHÔNG chiếm chỗ (như \pause của LaTeX + beamer; nhờ vậy slide dài không sinh trang trắng đệm).] + #lo(2, giu-cho: true)[Ẩn nhưng giữ chỗ — chỉ dùng khi slide chắc chắn gọn 1 trang.] + #chi(2)[Chỉ hiện đúng ở bước 2 (mặc định giữ chỗ).] + #chi(3, giu-cho: false)[Chỉ hiện ở bước 3, không giữ chỗ.] + #an(3)[Hiện từ đầu rồi BIẾN MẤT ở bước 3 (hiệu ứng "thoát" của PowerPoint; + mặc định không giữ chỗ, nội dung dưới dồn lên).] + #hien-khoang(2, 4)[Chỉ hiện ở bước 2–3, biến mất từ bước 4.] + #tung-buoc([ý 1 — bước 2], [ý 2 — bước 3]) // danh sách hiện dần + #buoc(hien-dan: true, [Bước giải 1...], [Bước giải 2...]) +] +``` + +`#an(n)` là bản đối xứng của `#lo(n)`: `#lo` cho nội dung **hiện ra**, `#an` +cho nội dung **biến mất**. Ghép hai lệnh để **thay thế** phần tử tại chỗ — hiện +nội dung tạm rồi xoá đi, cho nội dung mới hiện lên cùng bước: + +```typst +#slide(tieu-de: [Thay thế], so-buoc: 3)[ + #an(3)[$x = 1$ (giá trị tạm)] // biến mất ở bước 3 + #lo(3)[$x = 2$ (giá trị đúng)] // hiện lên ở bước 3 +] +``` + +Bản in (`dethi`/`loigiai`) hiện hết như `#lo`; muốn bản in chỉ giữ trạng thái +cuối thì đặt phần biến mất trong `#chi`. + +Với câu hỏi, dùng `lo-da` để **đề hiện trước, đáp án lộ sau**: + +```typst +#slide(tieu-de: [Kiểm tra], so-buoc: 2)[ + #cau-mc([Đề...?], ($1$, $2$, $3$, $4$), dap-an: "B", lo-da: 2) +] +``` + +Bước 1 chiếu đề cho học sinh làm, bấm một cái → phương án đúng được tô xanh. +(Ở beamer công tắc đáp án đã bật sẵn; chỉ cần `#bat-dap-an()` cho bản in A4.) + +`#lo` dùng được với MỌI nội dung — ví dụ hiện dần lời giải khảo sát: + +```typst +#slide(tieu-de: [Khảo sát hàm số], so-buoc: 4)[ + #vi-du[Khảo sát $y = -x^3 + 3x$.] // bước 1: đề + #lo(2)[#loi-giai[$y' = -3x^2 + 3$]] // bước 2: đạo hàm + #lo(3)[ + #bbt( + x: ($-oo$, $-1$, $1$, $+oo$), + dau: ("", "-", "0", "+", "0", "-", ""), + gia-tri: ($+oo$, $-2$, $2$, $-oo$), + huong: ("xuong", "len", "xuong"), + ) + ] // bước 3: bảng biến thiên + #lo(4)[#figure(do-thi-bac-ba(-1, 0, 3, 0))] // bước 4: đồ thị +] +``` + +### Lời giải dài — `#sang-man` và tự ngắt màn + +Trong `loi-giai:` của 8 dạng câu hỏi, đặt `#sang-man` trên **một dòng riêng** +để đẩy phần sau đó sang một slide mới mang nhãn "Hướng dẫn giải (tiếp)". +Nhớ dấu `\` ở **cuối dòng chữ ngay trước** dấu này. Bản in A4 bỏ qua nó. + +```typst +loi-giai: [ + a) Hàm số đồng biến trên $(0; 2)$. \ + Cực đại tại $x = 2$. \ + #sang-man \ + b) Xét $f'(x) = 0 <=> x = 1$. +], +``` + +Quên chèn mà lời giải vẫn cao hơn thân slide thì thư viện **tự đo và tự +ngắt**: dòng nào làm tràn sẽ được đẩy sang màn "(tiếp)". Nhờ vậy đề bài không +còn bị in lại xen giữa các trang lời giải — mỗi bước hoạt hình vốn in slide +lại từ đầu, nên một slide tràn trang sẽ đẻ ra trang "đề" lặp. Đề dài tới mức +không dòng lời giải nào lọt thì slide đầu chỉ hiện đề. + +```typst +#tu-ngat-man(false) +``` + +## 9. Thanh điều hướng & mục lục + +- **Đầu trang mỗi slide** (beamer): dải màu đậm phía trên thanh tiêu đề luôn + hiện **tên bài học** (`tieu-de-ngan`, mặc định là `tieu-de`) bên trái và + tên mục hiện tại bên phải. +- **Chân trang mỗi slide** là thanh điều hướng: bên trái là tên các phần + (bấm để nhảy tới đầu phần; phần hiện tại in đậm), giữa là dãy **chấm tròn** + — mỗi chấm một slide của phần hiện tại, chấm đặc là slide đang xem, + bấm chấm nào nhảy tới slide đó. +- `#muc(ngan: [Phần 1])[Phần 1. Tên đầy đủ...]` — `ngan` là nhãn rút gọn + hiển thị trên thanh điều hướng. +- `#muc-luc()` (đặt sau trang bìa) — slide mục lục tự động liệt kê mọi phần + và tiêu đề mọi slide, bấm vào tiêu đề nào nhảy thẳng tới slide đó. + +Liên kết hoạt động trong mọi PDF reader (SumatraPDF, Edge, Adobe...). + +## 10. Một file nguồn — ba kiểu PDF (hồ sơ hiển thị) + +Soạn đề MỘT LẦN trong file kiểu `de-mau.typ`, xuất được 3 bản: + +| Hồ sơ | Kết quả | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `"dethi"` | Đề thi A4: MC/TF/SA/TL**ẩn** đáp án + lời giải; ví dụ vẫn hiện lời giải; phần Họ tên / Số báo danh (hoặc Lớp) / Mã đề **chỉ hiện khi khai báo** `hien-ho-ten` / `sbd` / `hien-ma-de` | +| `"loigiai"` | Bản A4 đáp án: mọi câu**hiện** lời giải, tô đáp án đúng | +| `"beamer"` | Trình chiếu:**mỗi câu một slide** — hiện đề → lời giải hiện dần theo từng dấu `\` → cuối cùng đánh dấu đáp án | + +```typst +#import "@preview/conic-toan:0.3.4": * +#let ho-so = sys.inputs.at("ho-so", default: "dethi") + +#show: de-toan.with(ho-so: ho-so, tieu-de: [ĐỀ KIỂM TRA...], + mon: [MÔN TOÁN 12], thoi-gian: "90 phút", truong: [...], ma-de: "101", + ngay: "30/12/2026", // bản dethi in "Ngày kiểm tra: ..." dưới dòng thời gian + hien-ho-ten: true, // 3 dòng thông tin thí sinh CHỈ hiện khi khai báo: + sbd: "sbd", // sbd: "sbd"=Số báo danh | "lop"=Lớp | none=ẩn + hien-ma-de: true, // hien-ho-ten / hien-ma-de: bật ô tương ứng + ti-le-chu: 1.0, // hệ số phóng cỡ chữ thân — dùng chung cho cả 3 hồ sơ + gian-dong: 1.0, // hệ số GIÃN DÒNG — dùng chung cho cả 3 hồ sơ (xem §Giãn dòng) + mau-cong-thuc: auto) // màu công thức $...$; auto = đen (thừa kế). Đặt màu để nhuộm cả bài + +// Gọi thẳng #vd/#tn/#ds/#tln/#tl/#hd/#lt/#vdtt/#phan — các hàm tự nhận +// biết chế độ, không cần khai báo tao-cau-hoi (dòng cũ vẫn chạy được). + +#phan([PHẦN I. Trắc nghiệm], ngan: [Phần I]) +#tn([Đề...?], ($1$,$2$,$3$,$4$), dap-an: "B", + loi-giai: [Bước 1... \ Bước 2... \ Chọn *B*.], + tieu-de: [Cực trị]) // tiêu đề slide, chỉ dùng ở beamer + +#hd([Hoạt động khám phá...], loi-giai: [a) ... \ b) ...], + tieu-de: [HĐ1: Nhận biết]) // câu hoạt động — thẻ "HĐ1" +#lt([Bài luyện tập...], loi-giai: [$y' = ...$ \ Vậy...], + tieu-de: [Luyện tập 1]) // câu luyện tập — thẻ "Luyện tập 1" +#vdtt([Bài toán thực tế...], loi-giai: [Gọi $x$... \ Vậy...], + tieu-de: [Vận dụng]) // câu vận dụng — thẻ "Vận dụng 1" +#kham-pha([Từ định lí côsin, hãy viết công thức tính $cos A$...]) // thẻ "Khám phá" (không số) +#trai-nghiem([Vẽ một tam giác $A B C$, đo các cạnh và góc $A$...]) // thẻ "Trải nghiệm" +#thao-luan([Liệu $sin A$ và diện tích $S$ có tính được theo các cạnh?]) // thẻ "Thảo luận" +``` + +Xuất cả 3 bản không cần sửa file: + +``` +typst compile --input ho-so=dethi de-mau.typ de-thi.pdf +typst compile --input ho-so=loigiai de-mau.typ dap-an.pdf +typst compile --input ho-so=beamer de-mau.typ trinh-chieu.pdf +``` + +Quy ước: trong `loi-giai:` của câu hỏi, **mỗi dấu `\` (xuống dòng) là một +bước xuất hiện** khi trình chiếu; ở bản A4 chúng chỉ là xuống dòng thường. + +### Giãn dòng — `gian-dong` + +Khi một dòng có phân số, căn thức hoặc chỉ số **nhiều tầng** +(`$-(11 pi)/5 = -(11 pi)/5 dot (180/pi) degree$`), tử số của dòng dưới dễ +dính — thậm chí chồng — vào dòng trên. `gian-dong` là **hệ số nhân** vào +khoảng cách dòng: `1.0` = mốc mặc định, `1.3` = giãn thêm 30%, `0.9` = thu +lại 10%. Bốn cách dùng, phạm vi từ rộng đến hẹp: + +```typst +// 1) TOÀN TÀI LIỆU — khai ở dòng #show +#show: bai-giang.with(tieu-de: [CHƯƠNG I], gian-dong: 1.25) +#show: de-toan.with(ho-so: ho-so, ma-de: "0101", gian-dong: 1.25) + +// 2) ĐỔI GIỮA BÀI — áp cho các slide + khung lời giải PHÍA SAU +#gian-dong(1.4) +... // phần này giãn 1.4 +#gian-dong(1.0) // trả về mốc mặc định + +// 3) RIÊNG MỘT CÂU — tham số gian-dong: của #vd/#tn/#ds/#tln/#tl/#hd/#lt/#vdtt +#tn([Đề...?], ($1$, True($2$), $3$, $4$), + gian-dong: 1.5, + loigiai: [$-(11 pi)/5 = -396 degree$. \ Vậy...]) + +// 4) RIÊNG MỘT KHỐI nội dung bất kì +#voi-gian-dong(1.7)[ + Đoạn này giãn 1.7; phần trước và sau không đổi. +] +``` + +Mốc `1.0` ứng với leading gốc của từng hồ sơ (trình chiếu `0.62em` · A4 bài +giảng `0.6em` · A4 đề thi `0.65em`) nên **một hệ số dùng chung được cho cả 3 +bản PDF**, và đặt `1.0` thì bố cục/số trang y như trước. + +Hệ số này giãn **cả hai** loại khoảng cách: giữa các dòng trong cùng một đoạn +(nối bằng `\`) và giữa hai đoạn (chỗ để **dòng trống**, mốc `1.2em`). Trước +08/2026 chỉ giãn loại thứ nhất, nên nội dung có dòng trống trông như +`voi-gian-dong` "chỉ ăn đoạn đầu rồi mất tác dụng". + +Khung lời giải hiện dần ở beamer (`giai-buoc`) vốn đã rộng hơn thân slide +(mốc `0.95em`); hệ số `gian-dong` nhân thêm vào mốc đó. Muốn ấn định độ dài +tuyệt đối thì dùng `giai-buoc(..., gian: 1.2em)`. + +Từ 08/2026 hệ số này còn giãn **khoảng cách giữa các hàng phương án** của +`#tn`, giữa các ý của `#ds`, giữa các item của `cot-item`, và **khoảng cách từ +đề xuống phương án** — trước đây nó không với tới những chỗ đó (chúng nằm trong +ô của `grid` hoặc là khoảng trắng cố định, không phải khoảng cách dòng). Hệ số +`1.0` vẫn cho đúng số cũ nên bài cũ không đổi bố cục. + +Lưu ý về vai trò: từ khi có `cao-that` (mục dưới), **`gian-dong` không còn là +thứ để chữa dính chữ** — chữ dính đã được lib tự lo. `gian-dong` nay chỉ để +làm cả bài thoáng hơn hoặc chặt lại theo ý người soạn. + +File thử: `thu-gian-dong.typ`. + +### Chiều cao thật của công thức trong dòng — `cao-that` + +Typst đóng khung công thức **trong dòng** theo số đo phông chữ (cap-height → +đường chân chữ) chứ không theo nét vẽ: `measure($1/2$)` và `measure($0,5$)` +cho **cùng** một chiều cao, trong khi phân số vẽ ra cao gần gấp đôi và tràn cả +trên lẫn dưới khung. Vì thế phân số hay dính vào dòng trên, và trong ô của +`grid` (phương án `#tn`, ý `#ds`, `cot-item`) thì đè hẳn sang hàng trên — +`gian-dong` không chữa được vì nó chỉ chạm khoảng cách dòng. + +Lib so nét vẽ thật với **nét chữ thường** (chữ thường cũng tràn khỏi khung mà +không ai kêu, vì `leading` đã chừa chỗ), rồi chèn một **cột chống vô hình rộng +0pt** đúng bằng phần còn thiếu. Dòng và ô tự nới **đúng chỗ cần**; chữ thường +và công thức thấp (`$x$`, `$0,5$`) không đổi một pt nào. Cơ chế **bật sẵn** +trong `bai-giang` và `de-toan`, và đúng cả khi tài liệu bật kiểu LaTeX +`#show math.equation.where(block: false): it => math.display(it)` (phân số to +đẹp — cũng chính là cách viết làm dính chữ nặng nhất). + +```typst +#cao-that(false) // tắt từ đây trở đi +#cao-that() // bật lại +#cao-that(them: 1pt) // nới thêm 1pt mỗi phía cho công thức có tràn +#kieu-cau-hoi(cao-that: false) // tắt cho cả bài, khai cùng chỗ mau/hinh + +// Tài liệu KHÔNG đi qua bai-giang/de-toan thì áp cho một khối: +#voi-cao-that[ + Giá trị $11 pi/3$ và $17 pi/4$ trên hai dòng liền nhau. \ + Dòng này không còn chạm vào dòng trên. +] +``` + +Nhờ nó mà `gian-dong` để `1.0` là đủ; chỉ dùng `gian-dong` khi muốn cả bài +thoáng hơn, chứ không phải để chữa dính chữ nữa. + +File thử: `thu-cao-that.typ`; bảng số đo đối chiếu: `thu-cao-that-chan-doan.typ`. + +### Kết thúc đề & bảng đáp án (`#het`, `#bang-dap-an`) + +Hai hàm này CHỈ dựng ở bản in A4 (`dethi`/`loigiai`); ở `beamer` tự bỏ qua +(không sinh slide thừa). Đặt ở CUỐI file, sau câu cuối cùng: + +``` +#het() // dòng "––––– HẾT –––––" + 2 dòng ghi chú (căn giữa) +#het(chu: [THE END], // tuỳ chỉnh chữ giữa + ghi-chu: [Không được dùng tài liệu.]) // ghi-chu: none = ẩn 2 dòng dưới + +#bang-dap-an() // 3 bảng đáp án: tn (1.C 2.A…), ds (Đ/S vòng tròn), + // tln (mỗi ký tự một ô, như phiếu TLN) +``` + +`#bang-dap-an` **tự thu thập đáp án MỌI câu `tn`/`ds`/`tln`** trong tài liệu +(kể cả form cũ `dap-an:` lẫn form mới `True(...)`) — không cần khai báo lại; +mỗi loại đánh số `1..n` độc lập theo thứ tự xuất hiện. Tham số: + +- `ma-de`: `auto` (mặc định) = **tự lấy mã đề khai ở `de-toan(ma-de: …)`** nên + gọi `#bang-dap-an()` là đủ, luôn đồng bộ; `none` = tiêu đề "BẢNG ĐÁP ÁN" + (không mã đề); hoặc giá trị cụ thể để ghi đè. +- `tieu-de`: `auto` (theo `ma-de`) | nội dung tuỳ ý | `none` (ẩn tiêu đề). +- `so-o-tln`: số ô mỗi đáp án trả lời ngắn (mặc định 4; tự nới nếu đáp án dài hơn). +- `ngat-trang`: `true` (mặc định) = sang trang mới trước bảng. + +Đáp án `tln` truyền dạng chuỗi (`"60,7"`), số, hoặc nội dung (`[1160]`) đều +được — hàm tự tách thành từng ký tự để điền vào ô; nếu không tách được thì in +nguyên nội dung vào một ô. Để in bảng có điều kiện, bọc trong `#if`: +`#if muon-in-dap-an { bang-dap-an(ma-de: ma-de) }`. + +### Chia cột phần câu hỏi — `#chia-2-cot`, `#chia-2-cot-lech` (08/2026) + +Hai lệnh bố cục dùng như **show-rule**: *đặt ở đâu thì áp dụng từ dòng đó trở +xuống*, đúng lối `#show: de-toan.with(...)` đã quen. + +``` +#show: de-toan.with(ho-so: ho-so, tieu-de: [ĐỀ KIỂM TRA]) + +#show: chia-2-cot // từ đây trở xuống: câu hỏi xếp 2 cột ĐỀU nhau +#show: chia-2-cot-lech // hoặc: trái = câu hỏi, phải = chỗ HS làm bài +``` + +`#chia-2-cot(so: 2, khoang: 18pt, can: true)` — chia phần câu hỏi thành hai cột +bằng nhau (đặt `so: 3` nếu muốn ba cột), chữ chảy từ cột trái sang cột phải. +Hai cột được **tự cân bằng chiều cao** (`can: false` để tắt): Typst vốn rót +đầy cột 1 tới hết chiều cao trang rồi mới sang cột 2, nên đề ngắn sẽ nằm gọn +một cột và cột 2 để trống — thư viện tự chèn dấu ngắt cột để hai bên đều nhau. + +`#chia-2-cot-lech(...)` — cột **trái** chứa câu hỏi, cột **phải** để trống có +hàng kẻ ngang mờ cho học sinh làm bài (kèm vạch dọc mờ ngăn hai cột). Số hàng +kẻ tự tính theo chiều cao thật của phần câu hỏi nên không thừa/thiếu giấy. + +| Tham số | Mặc định | Ý nghĩa | +|---|---|---| +| `rong-trai` | `70%` | **bề rộng cột trái** — nhận tỉ lệ (`60%`) hoặc độ dài (`11cm`) | +| `khoang` | `10pt` | khe giữa hai cột | +| `cao-dong` | `9mm` | khoảng cách giữa hai hàng kẻ | +| `mau` / `day` | `luma(65%)` / `0.4pt` | màu và độ dày hàng kẻ | +| `ke` | `true` | `false` = cột phải trắng trơn (không kẻ dòng) | +| `vach-ngan` | `true` | vạch dọc mờ ngăn hai cột | +| `tieu-de-phai` | `none` | vd `[Bài làm]` — in nhạt ở đầu cột phải | + +**Ngừng chia cột** bằng `#thoi-cot()`: phần sau dấu này trở lại nguyên khổ +giấy. **Bắt buộc** đặt `#thoi-cot()` trước `#het()`/`#bang-dap-an()` — hàm +`#bang-dap-an` có `#pagebreak` mà lệnh ngắt trang không chạy được bên trong +cột. + +```typst +#show: chia-2-cot-lech.with(rong-trai: 65%, tieu-de-phai: [Bài làm]) +#tl([Giải phương trình $x^2 - 5x + 6 = 0$.]) +#tl([Tính đạo hàm của hàm số $y = x^3 - 3x + 1$.]) +#thoi-cot() // trở lại nguyên khổ giấy +#het() +#bang-dap-an() +``` + +Cả hai lệnh cũng dùng được **dạng khối** cho một đoạn: `#chia-2-cot[ ... ]`, +`#chia-2-cot-lech(rong-trai: 55%)[ ... ]`. + +Ghi chú: hai lệnh chỉ tác dụng ở bản in A4 (`dethi`/`loigiai`); bản trình +chiếu `beamer` tự bỏ qua (mỗi câu vốn đã là một slide riêng). Khi bật hoán vị +(trộn đề), các câu nằm trong cột **vẫn được trộn** bình thường và bản `dethi` +với bản `loigiai` vẫn trộn giống hệt nhau. File thử: `thu-chia-cot.typ`. + +### Kẻ dòng lấp đầy trang — `#ke-het-trang` (08/2026) + +Đặt ở đâu thì kẻ dòng từ chỗ đó xuống **hết trang đó** — số dòng tự tính theo +chỗ trống còn lại, không phải đếm tay. + +``` +#ke-het-trang() // nét chấm, cách 9mm +#ke-het-trang(cao-dong: 7mm, kieu: none) // nét liền, dòng dày hơn +#ke-het-trang(chua: 3cm) // chừa 3cm cuối trang +#ke-het-trang(dai: 50%, them-trang: 1) // nửa bề ngang + kẻ trọn 1 trang nữa +``` + +| Tham số | Mặc định | Ý nghĩa | +|---|---|---| +| `cao-dong` | `9mm` | khoảng cách giữa hai dòng kẻ | +| `mau` / `day` | `luma(65%)` / `0.4pt` | màu và độ dày nét | +| `kieu` | `"dotted"` | `"dashed"`, `"dash-dotted"`, hoặc `none` = nét liền | +| `dai` | `100%` | bề dài dòng kẻ (`50%` = nửa bề ngang) | +| `chua` | `0pt` | chừa thêm khoảng trắng ở đáy trang | +| `them-trang` | `0` | kẻ thêm bấy nhiêu **trang đầy** nữa sau trang hiện tại | +| `le-tren` / `le-duoi` | `auto` | tự đọc từ lề trang; chỉ đặt tay khi lề khai kiểu lạ | + +Chỉ dựng ở bản in A4 (`dethi`/`loigiai`); bản `beamer` tự bỏ qua. File thử: +`thu-ke-het-trang.typ`. + +### Hoán vị — trộn đề (08/2026) + +Bật **một công tắc ở đầu file** là trộn được đề: thứ tự câu và thứ tự phương +án tự đảo, đáp án + bảng đáp án tự chạy theo. + +```typst +#let ho-so = sys.inputs.at("ho-so", default: "dethi") +#let hoan-vi = false // <- ĐỔI THÀNH true LÀ TRỘN (mặc định false) + +#show: de-toan.with(ho-so: ho-so, ma-de: "0101", hoan-vi: hoan-vi) +``` + +Nguyên tắc: + +- Hoán vị các **câu** trong cùng một **nhóm thể loại**: nhóm `tn` xáo với nhau, + nhóm `ds` xáo với nhau, nhóm `tln` xáo với nhau. Câu `tl` **giữ nguyên** thứ tự. +- "Nhóm" = dãy câu **cùng loại đứng liền nhau** (chỉ cách nhau bằng dòng trống). + Gặp `#phan`, một đoạn văn xen giữa hay một câu khác loại là hết nhóm ⇒ câu + **không bao giờ nhảy qua tiêu đề phần**. +- Hoán vị **4 phương án A/B/C/D** của câu `tn`. Các **ý a/b/c/d của câu `ds` + giữ nguyên**, không hoán vị (ý sau thường dựa vào ý trước). +- Chỉ chạy ở bản in A4 (`dethi` / `loigiai`); trình chiếu `beamer` không trộn. + +| Tham số của `de-toan` | Ý nghĩa | +| --- | --- | +| `hoan-vi: false` | mặc định — giữ nguyên thứ tự soạn | +| `hoan-vi: true` | trộn cả thứ tự câu lẫn phương án | +| `hoan-vi: "cau"` | chỉ trộn thứ tự câu | +| `hoan-vi: "pa"` | chỉ trộn phương án | +| `mam: auto` | **mã trộn** — `auto` băm từ `ma-de`, nên `0101` và `0102` tự cho hai thứ tự khác nhau; hoặc đặt tay `mam: 7` | + +Bộ trộn là **tất định** (LCG có mầm, không dùng ngẫu nhiên thật): cùng một mầm +luôn cho cùng một thứ tự, nên bản `dethi` và bản `loigiai` biên dịch hai lần +vẫn trộn **giống hệt nhau** — bảng đáp án luôn khớp. + +```sh +typst compile --input ho-so=dethi de-mau.typ de-0101.pdf +typst compile --input ho-so=loigiai de-mau.typ dapan-0101.pdf # cùng thứ tự +``` + +Muốn ra nhiều mã đề từ MỘT file: đọc mã đề qua `sys.inputs` rồi đổi lúc biên dịch. + +```typst +#let ma = sys.inputs.at("ma", default: "0101") +#show: de-toan.with(ho-so: ho-so, ma-de: ma, hoan-vi: true) +``` + +Hai điều **phải nhớ**: + +- Câu `tn` viết theo **form cũ** `dap-an: "B"` (đáp án là chữ cái) **không được + trộn phương án** — trộn sẽ lệch. Muốn trộn thì bọc phương án đúng bằng + `True(...)`. Lib tự kiểm: câu nào không có `True(...)` thì bỏ qua, không xáo. +- `khoa-pa: true` ở một câu `tn` ⇒ riêng câu đó giữ nguyên thứ tự phương án + (dùng cho câu kiểu "Cả A, B, C đều đúng"). + +Tài liệu nào không đi qua `de-toan` thì dùng show-rule riêng: +`#show: hoan-vi-de.with(true)` (hoặc `.with(true, mam: "0102")`). + +File thử: `thu-hoan-vi.typ`. + +### Form mới của tn/ds/tln — đáp án gắn liền nội dung (khuyến nghị) + +Đáp án đúng bọc `True(...)` (bí danh `Dung(...)`) ngay trong danh sách, +nên **hoán vị phương án không bao giờ lệch đáp án** — nền tảng cho việc +xáo trộn tạo mã đề. Form cũ (`dap-an:`, `hinh:`, `loi-giai:`, `cot:`) +vẫn dùng được song song. + +```typst +#tn( + [Nội dung đề bài], + ( [$A$], True([$B$]), [$C$], [$D$] ), // đáp án đúng bọc True(...) + loigiai: [Lời giải chi tiết], + // Tuỳ chọn: + fig: none, fig-pos: "right", fig-width: 35%, // hình: vị trí right/left/center/top/bottom + // ("center" = canh giữa, nằm giữa phần đề + // và các phương án), + // bề rộng cột hình (auto = ôm hình) + cols: 0, // 0 = tự chia cột; 1/2/4 = cố định + lines: 0, // số dòng chừa làm bài (chỉ bản in đề) + num: auto, // auto = đánh số tự động; số cụ thể = ghi đè (không tăng bộ đếm) + prefix: "Câu", // chữ trên thẻ đầu câu + boxed: false, // true = đóng khung cả câu +) + +#ds( + [Thân câu chung — nêu tình huống / bài toán], + ( + [Phát biểu a — SAI], + True([Phát biểu b — ĐÚNG]), + True([Phát biểu c — ĐÚNG]), + [Phát biểu d — SAI], + ), + loigiai: [Phân tích từng ý.], +) + +#tln( + [Nội dung đề bài], + [$6$], // đáp án đặt NGAY SAU đề + loigiai: [Lời giải.], + show-boxes: true, box-count: 4, // ô điền trên phiếu (bản in đề) +) +``` + +`loigiai:`/`fig:` cũng dùng được cho `#vd/#tl/#hd/#lt/#vdtt` (bí danh của +`loi-giai:`/`hinh:`), kèm cả `fig-pos:`/`fig-width:` — tám dạng câu nhận cùng +một bộ tham số hình. + +### Lời giải gắn từng ý của `#ds` — an toàn khi trộn đề hoán vị ý + +Khối `loigiai:` viết cứng "a) … b) … c) … d) …" sẽ **sai** nếu công cụ trộn +xáo thứ tự các ý. Đặt lời giải ngay cạnh ý bằng `giai:` thì lời giải đi theo +ý, và nhãn a) b) c) d) được đánh lại theo thứ tự **sau** hoán vị: + +```typst +#ds([Cho hàm số $y = x^3 - 3x$.], + ( + True([$y' = 3x^2 - 3$], giai: [Đạo hàm của $x^n$ là $n x^(n-1)$.]), + False([Hàm số không có cực trị], giai: [$y' = 0$ có hai nghiệm phân biệt.]), + True([Đồ thị nhận điểm uốn $I(0; 0)$ làm tâm đối xứng]), // không cần giải riêng + [Hàm số đồng biến trên $RR$], // ý sai, viết trần + ), +) +``` + +- `True(nd, giai: ...)` — ý ĐÚNG; `False(nd, giai: ...)` — ý SAI có lời giải. +- Ý sai không cần lời giải thì viết trần `[...]` như cũ; `giai:` là tuỳ chọn, + ý nào không khai thì phần lời giải bỏ qua ý đó. +- Dùng chung với `loigiai:` được: phần chung in **trước** làm dẫn nhập + (tập xác định, đạo hàm…), rồi mới tới phần theo từng ý. +- `khoa-y: true` (và `khoa-pa: true` cho `#tn`) đánh dấu câu **không được + hoán vị** — dùng khi ý sau dựa vào kết quả ý trước, hoặc có phương án kiểu + "Cả A và B đều đúng". Cờ này được ghi vào metadata `` để công cụ + trộn đọc. + +Form cũ (khối `loigiai:` chung, ý bọc `True(...)` không có `giai:`) vẫn chạy +nguyên vẹn — `giai:`/`False`/`khoa-y` chỉ là bổ sung. +File thử: `thu-ds-giai.typ`. + +### Hình ở LỜI GIẢI và trong PHƯƠNG ÁN/Ý (mọi dạng câu) + +Hình không chỉ đặt ở đề bài — cả 8 dạng (`vd/tn/ds/tln/tl/hd/lt/vdtt`) đều +nhận thêm: + +```typst +fig-giai: do-thi-ham(...), // hình kèm LỜI GIẢI (bí danh: hinh-giai) +fig-giai-pos: "right", // right | left | top | bottom +fig-giai-width: auto, // auto = ôm hình; hoặc 35%, 5cm... +``` + +Bố cục 2 cột giống hình kèm đề; ở beamer hình hiện cùng bước đầu tiên của +lời giải. Màn `#sang-man` sau muốn có hình thì chèn thẳng trong nội dung màn. + +Hình trong PHƯƠNG ÁN (tn) hoặc Ý (ds): phương án là content nên nhúng trực +tiếp, thường kèm `cols:` cố định: + +```typst +#tn([Đồ thị nào là của $y = x^2$?], ( + [#do-thi-ham(x => x*x*x, w: 3.2cm)], + True([#do-thi-ham(x => x*x, w: 3.2cm)]), + [#do-thi-ham(x => -x*x, w: 3.2cm)], + [#do-thi-ham(x => 2*x, w: 3.2cm)], +), cols: 4, loigiai: [Đồ thị hàm bậc hai $y = x^2$ có đỉnh tại gốc toạ độ, bề lõm hướng lên.]) +``` + +## 11. Giao diện câu hỏi (theme tiền tố) + +- Thẻ "Câu X" và thẻ phương án "A.", ý "a)" dùng **chung một màu và một + hình dáng**; đổi một lệnh là đồng bộ toàn bài (đặt đầu file, hoặc giữa + chừng để đổi từ vị trí đó): + +```typst +#kieu-cau-hoi(mau: rgb("#e67e22"), hinh: "luc-giac", hien-o: true) +// hinh: "bo-tron" (mặc định) | "chu-nhat" | "luc-giac" | "khong-to" +// "khong-to": không tô nền, chỉ viền + chữ màu (tiết kiệm mực); +// riêng hồ sơ DETHI: bỏ luôn viền — "Câu 1." / "A." (ý ds: "a)") +// in đậm cùng màu, dấu liền kề (kiểu LaTeX). +// hien-o: false = ẩn ô tick Đ/S (ds có o-tick) và ô điền "Trả lời" (tln) +// đồng bộ toàn bài — dùng khi HS làm thẳng vào phiếu trả lời. +// om-hinh: false = TẮT chế độ chữ ôm hình (xem ngay dưới), quay về lối +// 2 cột cũ từ vị trí này trở đi. +// eq-trong-dong: false = giữ nguyên công thức khối `$ ... $` trong phương án +// (xem ngay dưới); mặc định true = ép thành công thức trong dòng. +``` + +### Công thức trong phương án luôn nằm TRONG DÒNG (mặc định BẬT) + +Viết `$ cases(x > 0, y > 0, x + y < 3) $` (có khoảng trắng sát hai dấu `$`) +là công thức **trình bày giữa dòng**: Typst tách nó xuống dòng riêng và canh +giữa, nên hệ 3 bất phương trình trông khác hẳn hệ 2 bất phương trình viết +`$cases(...)$`. Từ 08/2026 thư viện tự ép mọi công thức khối trong **phương án +`#tn`** và **ý `#ds`** về dạng trong dòng, nên hai lối viết cho ra cùng một +bố cục — khỏi phải sửa tay từng file do AI sinh ra. + +- Chỉ đụng tới công thức toán; phương án là hình, bảng, danh sách… giữ nguyên. +- Vì công thức không còn là "khối", dấu chấm cuối phương án cũng được thêm + như các phương án chữ (xem `cham-cuoi`). +- Số cột `cot: auto` đo theo bề rộng TRONG DÒNG ⇒ hệ 3 bpt thường xếp đủ 4 cột. +- Tắt toàn bài: `#kieu-cau-hoi(eq-trong-dong: false)`; tắt một câu: + `#tn(..., trong-dong: false)` / `#ds(..., trong-dong: false)`. + +File thử: `thu-eq-trong-dong.typ`. + +### Chữ ôm hình (mặc định BẬT) + +Hình đặt bên phải/trái mà **lời giải dài hơn hình** thì cột hình sẽ trống +một khoảng lớn phía dưới. Từ 08/2026 thư viện tự chữa: đo chiều cao hình, +đặt vừa đủ các **đoạn đầu** cạnh hình, phần còn lại tràn **nguyên bề rộng** +xuống dưới hình — không phải khai báo gì thêm. + +- Chỗ cắt luôn rơi vào ranh giới đoạn văn / công thức tách dòng / bảng / + danh sách, nên không bao giờ cắt ngang một công thức. +- Công thức tách dòng quá rộng so với cột hẹp sẽ được đẩy xuống phần dưới + (tràn hết bề rộng) thay vì bị bó trong cột. +- Nội dung vốn đã thấp hơn hình ⇒ giữ nguyên lối 2 cột canh giữa như cũ. +- Tắt: `#kieu-cau-hoi(om-hinh: false)` (toàn bài hoặc một đoạn), hoặc + `voi-hinh(..., om: false)` khi gọi trực tiếp. + +File thử: `thu-om-hinh.typ`. + +- **MC tự chia cột**: mặc định `cot: auto` — đo phương án dài nhất rồi tự + chọn 4 / 2 / 1 cột; vẫn ép được `cot: 2`. +- **TF hai kiểu**: mặc định nhãn Đ/S hiện sau mỗi ý; thêm `o-tick: true` + để có 2 ô vuông Đ | S dóng thẳng hàng sát lề phải (bản đề thi: ô trống + cho học sinh tick; bản đáp án/trình chiếu: tự tick ✓ xanh/đỏ). + +### Tự thêm dấu chấm cuối phương án / ý hỏi (07/2026) + +Phương án của `#tn` và ý của `#ds` là một CÂU nên phải kết bằng dấu chấm. +Thư viện tự dò và **thêm `.` khi còn thiếu** (bật sẵn), gõ nhanh không cần +nhớ: + +```typst +#tn([...], ([$(-oo; 0)$], True([$(-oo; -2)$]), [$(-2; 0)$], [$(-2; +oo)$])) +// in ra: A (−∞; 0). B (−∞; −2). C (−2; 0). D (−2; +∞). +``` + +Quy tắc dò (duyệt ngược nội dung, tìm ký tự có nghĩa cuối cùng): + +- đã kết bằng `.` `!` `?` `…` `:` `;` → **giữ nguyên**, kể cả khi có dấu + nháy/ngoặc bọc ngoài: `"đúng rồi!"`, `(đã có chấm.)`; +- kết bằng khối — danh sách `-`/`+`, bảng, hình, `\` xuống dòng, công thức + trình bày giữa dòng `$ ... $` → **bỏ qua** (dấu chấm sẽ rơi xuống dòng); +- còn lại (chữ, số, công thức trong dòng, dấu ngoặc đóng) → **thêm `.`** + +Tắt/bật: + +```typst +#kieu-cau-hoi(cham-cuoi: false) // tắt toàn bài (từ vị trí này) +#tn([Đề bài], ([A], True([B]), [C], [D]), cham: false) // tắt riêng một câu +#ds([Đề bài], ([ý a], True([ý b])), cham: true) // ép bật riêng một câu +``` + +File thử: `thu-cham-cau.typ` (đủ 5 nhóm ca). + +### `#cot-item` — không đánh chồng nhãn a), b) (07/2026) + +`#cot-item` TỰ đánh nhãn `a)`, `b)`, `c)`… nên **không cần gõ tay**. Nhưng nếu +nội dung đã có sẵn nhãn (thường gặp khi chép từ SGK hoặc để AI sinh bài), thư +viện tự nhận ra và **bỏ nhãn tự động ở đúng item đó** — hết cảnh `a) a) Phải +quay kim phút…`. + +```typst +#cot-item([Phải quay…], [Có bao nhiêu…]) // in: a) Phải quay… b) Có bao nhiêu… +#cot-item([a) Phải quay…], [b) Có bao nhiêu…]) // in: a) Phải quay… b) Có bao nhiêu… +``` + +Nhãn được nhận: `a)` `a.` `a:` `A)` `(a)` `[b]` `1)` `1.` `(2)` `ii)` `IV.` +(chữ cái đơn, số ≤ 99, số La Mã ≤ xii; sau dấu `.` hoặc `:` phải có khoảng +trắng). Quy tắc quyết định: + +- **mọi** item đều có nhãn tay → bỏ hết nhãn tự động (kể cả khi đánh tiếp + `d) e) f)` cho phần sau); +- chỉ **một vài** item có nhãn tay → chỉ bỏ ở item mà nhãn tay đúng thứ tự của + nó, các item còn lại vẫn được đánh (tránh nhận nhầm câu mở đầu kiểu + `A. B. C thẳng hàng`); +- không nhận nhầm `0.5 lít nước`, `12:30`, `$x^2 - 3x = 0$`, `Cho tam giác…`. + +Ép đánh nhãn dù item đã có sẵn: `#cot-item(..., do-nhan-tay: false)`; +bỏ hẳn nhãn: `kieu-nhan: none`. File thử: `thu-cot-item.typ` (8 nhóm ca). + +## Mẹo + +- Font: mặc định `("Charis SIL", "Noto Serif", "Libertinus Serif", "Times New Roman")`. **Cần CÀI Charis SIL** (miễn phí: software.sil.org/charis) — chưa + cài thì tự rơi về font sau trong danh sách. + Lý do: dấu CHỒNG tiếng Việt (**Ể Ổ Ữ Ẩ Ấ Ộ**). Libertinus/Times vẽ dấu hỏi + nhỏ, nằm NGANG cạnh dấu mũ → nhìn như mất dấu. Noto Serif ổn ở chữ thường + nhưng với chữ HOA (tiêu đề đề thi viết hoa toàn bộ) vẫn thu nhỏ dấu hỏi và + đẩy lệch sang phải. Charis SIL do SIL thiết kế cho ngôn ngữ nhiều dấu chồng, + giữ dấu nguyên cỡ + chồng thẳng ở CẢ chữ hoa lẫn chữ thường. + Đổi font: `#show: bai-giang.with(phong: ("Noto Serif",))`. + Bề rộng Charis SIL ≈ Libertinus nên số trang không đổi (Noto Serif rộng hơn + ~2%, bài dài có thể trôi thêm 1 trang). +- Xuất PowerPoint không hỗ trợ; trình chiếu trực tiếp file PDF (PDF reader + chế độ full screen, hoặc trình chiếu bằng Sumatra/Okular). +- Mỗi hàm vẽ đều có `mau`, `day` (độ dày), `dut` (nét đứt) để tuỳ biến. + +## Giá trị cực trị dạng căn thức (07/2026) + +BBT và lời giải khảo sát (`bbt-bac-ba`, `bbt-trung-phuong`, `bbt-huu-ti`, +`khao-sat-ve-do-thi-ham-*`) nay hiển thị CẢ GIÁ TRỊ cực trị dạng chính xác +(căn thức/phân số) thay vì số thập phân — ví dụ `y = x³ − 4x² − x + 6` cho +`x = (4 ± √19)/3` và `y = (−2 ∓ 38√19)/27`. Tính thẳng từ hệ số (không +nhận dạng số thập phân) nên đúng với mọi mẫu số lớn: + +- `so-can-thuc(P, K, n, Q)` (do-thi.typ): in `(P + K√n)/Q` đã rút gọn + (gcd, rút thừa số chính phương √96 → 4√6, chuẩn hoá n hữu tỉ + √(2/3) → √6/3); hệ số không "đẹp" thì tự rơi về `so-toan` như cũ. +- `cuc-tri-bac-ba(a, b, c, d)` / `cuc-tri-huu-ti(a, b, c, d, e)`: trả + `(x1:, y1:, x2:, y2:)` chính xác (x1 < x2) — dùng thẳng trong bài soạn. + +**Cập nhật:** nhãn trên ĐỒ THỊ cũng chính xác như vậy. Đường gióng cực trị, +đỉnh parabol, điểm uốn, tâm đối xứng, tiệm cận và giao Ox của +`do-thi-bac-hai`, `do-thi-bac-ba`, `do-thi-trung-phuong`, `do-thi-huu-ti`, +`do-thi-phan-thuc` lấy nhãn thẳng từ hệ số (hết cảnh `10.48` — nay là +`283/27`). Muốn ghi khác thì vẫn đặt `cuc-tri: (ten-x: .., ten-y: ..)`. +Ngoài ra `so-toan` nhận thêm phân số mẫu lớn (tới 400, khớp chặt 1e-9) +nên các giá trị như `283/27`, `−13/12` hiện đúng ở mọi chỗ. + +## Trục số, conic, côtang & hình lục giác/chóp cụt (07/2026) + +### Trục số — biểu diễn đoạn/khoảng (`truc-so`) + +Vẽ một hoặc NHIỀU đoạn/khoảng/nửa khoảng trên cùng một trục. Mỗi khoảng là +một tuple `(a, b, kieu)` (hoặc thêm màu: `(a, b, kieu, mau)`): + +- `a`, `b`: số, hoặc `"-oo"` / `"+oo"` cho vô cực (a < b). +- `kieu`: chuỗi 2 kí tự đầu mút — `"[]"` đoạn, `"()"` khoảng, `"[)"` / `"(]"` + nửa khoảng. `[ ]` = lấy đầu mút, `( )` = không lấy. + +Mặc định GẠCH CHÉO phần loại bỏ (bù của hợp các khoảng) đúng kiểu SGK, kèm +ngoặc tại đầu mút. + +```typ +#truc-so((-2, 3, "[)")) // [−2; 3) +#truc-so((-2, 1, "()"), (3, "+oo", "[)")) // hai khoảng, +∞ +#truc-so(("-oo", 0, "(]"), (2, 5, "[]"), dau: "cham") // chấm đặc/rỗng +``` + +Tham số: `w`, `h` (1.5cm — chiều cao khung cố định trên trang), `min`/`max` +(auto), `ten` (nhãn trục, mặc định `$x$`), `mau`, `gach`/`mau-gach`, +`cao-gach` (2mm — chiều cao dải gạch chéo trên trang), `dau: "ngoac" | "cham"`, +`moc-phu:` (ghi thêm số), `so`, `co-chu` (13pt — cỡ chữ số tại mốc). + +### Conic: hyperbol & parabol + +- `hyperbol(a, b)` — vẽ `x²/a² − y²/b² = 1`; kèm sẵn 2 tiệm cận + `y = ±(b/a)x` (nét đứt), đỉnh `A₁A₂`, tiêu điểm `F₁F₂` (c = √(a²+b²)). + Tắt phần phụ bằng `tiem-can: false`, `dinh: false`, `tieu-diem: false`; + đổi tên qua `ten-dinh`, `ten-tieu`. +- `parabol(p)` — vẽ `y² = 2px` (p > 0 mở phải, p < 0 mở trái); kèm đỉnh O, + tiêu điểm `F(p/2; 0)`, đường chuẩn `Δ: x = −p/2` (nét đứt). Tắt qua + `tieu-diem: false`, `duong-chuan: false`. +- `duong-elip(a, b)` — vẽ `x²/a² + y²/b² = 1` (nhập 2 bán trục như hyperbol); + kèm 4 đỉnh `A₁A₂B₁B₂` và 2 tiêu điểm trên trục lớn (a ≥ b: `F(±c; 0)`; + b > a: `F(0; ±c)`, `c = √|a²−b²|`); `a = b` cho đường tròn. Tên là + `duong-elip` (không phải `elip` — `elip` là hàm vẽ elip cơ bản trong `ve.typ`). + +```typ +#duong-elip(4, 2.5) // elip nằm ngang, tiêu điểm trên Ox +#duong-elip(2, 3.2) // elip đứng, tiêu điểm trên Oy +``` + +Nhãn tên hàm nay có **nền trắng** nên đường cong không xuyên qua; vị trí nhãn +(`goc-ten: auto`) tự né đồ thị. Cơ số mũ/lôgarit hiển thị **số đẹp** (phân số, +căn): `#do-thi-mu(1/3)` → `y = (1/3)^x`, `#do-thi-log(1/3)` → `y = log_(1/3) x`. + +```typ +#hyperbol(3, 2) // x²/9 − y²/4 = 1 +#parabol(2) // y² = 4x +#parabol(-1.5) // y² = −3x (mở trái) +``` + +### Đồ thị côtang (`do-thi-cot`) + +`#do-thi-cot()` — `y = cot x`, tiệm cận đứng tại `x = kπ` (nét đứt tại ±π, +riêng x = 0 trùng trục Oy). Cùng bộ tham số như `do-thi-tan` +(`w`, `mau`, `ten`, `goc-ten`, `luoi-o`, `them`). (`do-thi-mu`, `do-thi-log` +đã có sẵn cho `y = aˣ`, `y = logₐx`.) + +### Hình lục giác đều & chóp cụt đều + +Nét khuất (đáy sau, cạnh bên/đứng tới đỉnh sau) TỰ vẽ đứt theo hình chiếu. + +- `hinh-chop-luc-giac-deu()` — chóp lục giác đều `S.ABCDEF`, có đường cao `SO`. +- `hinh-lang-tru-luc-giac-deu()` — lăng trụ lục giác đều `ABCDEF.A'B'C'D'E'F'`. +- `hinh-chop-cut-deu(n: 3 | 4 | 6)` — chóp cụt đều tam/tứ/lục giác. `R` bán + kính đáy dưới, `r` đáy trên, `cao` chiều cao; `truc: true` vẽ trục `OO'`. + Tên đỉnh tự sinh (A, B, … cho đáy dưới; phẩy cho đáy trên) hoặc truyền + `ten:` là mảng 2n nội dung. +- Bí danh gọn (cùng bộ tham số): `hinh-chop-cut-tam-giac-deu()` (ABC.A'B'C'), + `hinh-chop-cut-tu-giac-deu()` (ABCD.A'B'C'D'), + `hinh-chop-cut-luc-giac-deu()` (tên gốc, `n: 6` mặc định). + +```typ +#hinh-chop-luc-giac-deu(w: 6.5cm) +#hinh-lang-tru-luc-giac-deu(w: 6.5cm) +#hinh-chop-cut-tam-giac-deu() +#hinh-chop-cut-tu-giac-deu(truc: true) +#hinh-chop-cut-deu(n: 6) +``` + +### Bảng tần số & biểu đồ thống kê (bang-thong-ke.typ + bieu-do-thong-ke.typ) + +Dữ liệu vào của bảng và biểu đồ KHỚP NHAU: cùng `moc:` (n+1 mốc số → n nhóm) +và `tan-so:` — một nguồn số liệu dùng cho cả bảng lẫn biểu đồ. Mọi biểu đồ +đều có `them: ctx => ...` để vẽ chồng như các hình khác. + +```typ +// 4 bảng tần số bố cục NGANG đúng SGK (don-vi: hiện «tên (đơn vị)» ô góc) +#bang-tan-so(gia-tri: (0, 1, 2, 3), tan-so: (4, 3, 4, 7), ten-gia-tri: [Số con]) +#bang-ghep-nhom(moc: (150, 155, 160, 165, 170, 175), tan-so: (5, 12, 18, 9, 4), + ten-nhom: [Chiều cao], don-vi: [cm]) +// (kèm bang-tan-so-doi / bang-ghep-nhom-doi cho 2 dòng tần số) + +// HISTOGRAM — cột dính sát nhau (tần số ghép nhóm, Toán 11–12) +#bieu-do-tan-so(moc: (150, 155, 160, 165, 170, 175), tan-so: (5, 12, 18, 9, 4), + ten-x: [Chiều cao (cm)]) +#bieu-do-tan-so(moc: (150, 155, 160, 165, 170, 175), tan-so: (5, 12, 18, 9, 4), + gap-khuc: true) // + gấp khúc tần số chồng lên + +// ĐƯỜNG GẤP KHÚC TẦN SỐ đứng riêng (nối trung điểm, kéo dài về 0 hai đầu) +#da-giac-tan-so(moc: (150, 155, 160, 165, 170, 175), tan-so: (5, 12, 18, 9, 4), + keo-dai: true) + +// BIỂU ĐỒ CỘT RỜI — giá trị rời rạc: số (đặt đúng hoành độ) hoặc chữ +#bieu-do-cot(gia-tri: (0, 1, 2, 3, 4), tan-so: (4, 9, 13, 6, 2), ten-x: [Số con]) +#bieu-do-cot(gia-tri: ([Đỏ], [Xanh], [Vàng]), tan-so: (10, 7, 5)) + +// BIỂU ĐỒ HỘP (box plot) — tứ phân vị Toán 10, gióng nét đứt xuống trục số +#bieu-do-hop(du-lieu: (3, 4, 4, 5, 6, 7, 8, 9, 10), ten: [Điểm]) // tự tính +#bieu-do-hop(tom-tat: (150, 158, 163, 168, 175)) // (min, Q1, Q2, Q3, max) + +// BIỂU ĐỒ QUẠT TRÒN — chia góc theo %, bắt đầu 12 giờ, chiều kim đồng hồ +#bieu-do-quat(([Đi bộ], 25), ([Xe đạp], 30), ([Xe máy], 35), ([Khác], 10)) +``` + +Tham số chung biểu đồ cột/gấp khúc: `w`, `h`, `mau`, `vien`, `ten-x`/`ten-y`, +`so-dinh` (số trên đỉnh cột), `so-truc`, `buoc-y` (auto = bước đẹp 1/2/5·10ᵏ), +`luoi-ngang` (lưới ngang mờ), `co-chu`, `them`. Quạt: mỗi mục `(tên, số)` hoặc +`(tên, số, màu)`; `phan-tram: true` ghi % trong quạt (quạt < 8% ghi gộp bên ngoài). + +Hai hàm TRẢ GIÁ TRỊ (không vẽ, dùng thẳng trong lời giải): + +- `tu-phan-vi(du-lieu)` → `(min:, q1:, q2:, q3:, max:)` — SGK Toán 10 + (n lẻ KHÔNG kể Q₂ vào hai nửa). +- `tu-phan-vi-ghep-nhom(moc, tan-so)` → `(q1:, q2:, q3:)` — nội suy SGK Toán 11. + +### Số đặc trưng mẫu số liệu (bieu-do-thong-ke.typ) + +Tất cả TRẢ GIÁ TRỊ (không vẽ) — dùng thẳng trong lời giải, in đẹp bằng +`so-toan`/`so-dep`. KHÔNG ghép nhóm nhận mảng thô HOẶC `gia-tri + tan-so:` +(bảng tần số); GHÉP NHÓM nhận `(moc, tan-so)` như biểu đồ. + +```typ +// KHÔNG ghép nhóm — thô hoặc bảng tần số (thêm tan-so:) +#so-trung-binh((1, 2, 2, 3)) // 2 +#so-trung-binh((0, 1, 2), tan-so: (4, 3, 4)) +#mot((1, 1, 2, 3)) // MẢNG các mốt: (1,) — có thể nhiều mốt +#trung-vi(d) #tu-phan-vi(d) // tu-phan-vi -> (min:, q1:, q2:, q3:, max:) +#phuong-sai(d) #do-lech-chuan(d) // s² chia n; hieu-chinh: true -> chia n−1 +#khoang-bien-thien(d) #khoang-tu-phan-vi(d) + +// GHÉP NHÓM — trung bình/phương sai theo giá trị đại diện, còn lại nội suy SGK +#so-trung-binh-ghep-nhom(moc, tan-so) #mot-ghep-nhom(moc, tan-so) +#trung-vi-ghep-nhom(moc, tan-so) #tu-phan-vi-ghep-nhom(moc, tan-so) +#phuong-sai-ghep-nhom(moc, tan-so) #do-lech-chuan-ghep-nhom(moc, tan-so) +#khoang-bien-thien-ghep-nhom(moc, tan-so) // mốc phải nhóm cuối − mốc trái nhóm đầu CÓ dữ liệu +#khoang-tu-phan-vi-ghep-nhom(moc, tan-so) +``` + +## Nón & trụ có NÉT KHUẤT TỰ ĐỘNG — kể cả hai khối che nhau (08/2026) + +`lib/mat-cong.typ` bổ khuyết cho `da-dien.typ`: engine đa diện chỉ lo được khối +đa diện LỒI và KHÔNG xử lý được hai khối che nhau. Ở đây dùng một cơ chế duy +nhất cho cả hai việc — bắn tia từ điểm đang xét về phía người nhìn, tia gặp lòng +bất kì khối nào (kể cả chính nó) thì đoạn đó vẽ NÉT ĐỨT. + +```typ +// nón có trục là một đường sinh của trụ — đường sinh nón chui vào lòng trụ +// TỰ chuyển sang nét đứt, không phải cắt tay +#mat-cong( + mat-non(r: 2, cao: 4, mau: red), + mat-tru(tam: (0, 2, 0), r: 2, cao: 4, mau: blue), + w: 9cm, +) + +#mat-cong(mat-non(r: 2.6, cao: 4), mat-tru(r: 1.1, cao: 2)) // trụ nội tiếp nón +#mat-cong(mat-non(r: 2, cao: 3.6), hien-khuat: false) // bỏ hẳn nét đứt +``` + +Camera MẶC ĐỊNH của engine này là chiếu TRỰC GIAO +(`chieu-truc-giao(ngang: 15deg, cao: 22deg)`), KHÔNG phải `chieu-xien` như +`da-dien.typ`: khối vẽ ra NGAY NGẮN — elip đáy có trục lớn nằm ngang, trục +khối thẳng đứng, đúng lối hình sách giáo khoa, khỏi phải khai `cam:` gì cả. +Muốn lối chiếu xiên cũ thì khai rõ `cam: chieu-xien()` hoặc `cam: chieu-oxyz()`. + +Mô tả khối (TRẢ GIÁ TRỊ): `mat-non(tam:, r:, cao:, nghieng:, huong:, truc:, +mau:, to:)` và `mat-tru(...)` cùng bộ tham số — `tam` là tâm mặt đáy, `cao` đo +DỌC TRỤC; `mau`/`to` để `auto` thì lấy theo lệnh vẽ. +Tiện ích: `dinh-non(k)`, `tron-ngang(tam, r, truc:)`. + +**Trục khối đặt nghiêng được.** `nghieng` là góc giữa trục khối và `Oz`, +`huong` là hướng ngả (đo trong mặt phẳng `Oxy`); hoặc ghi đè thẳng bằng +`truc: (a, b, c)`. Mặt đáy LUÔN vuông góc với trục, nên trục nghiêng thì mặt +đáy và đường sinh nghiêng cùng — toàn thân khối nghiêng: + +```typ +#mat-cong(mat-tru(r: 1.2, cao: 5, nghieng: 55deg, huong: 200deg)) // trụ nằm nghiêng +#mat-cong(mat-non(r: 1.6, cao: 4.5, nghieng: 90deg)) // nón nằm ngang +``` + +Mọi công thức trong engine viết theo KHUNG RIÊNG `(u, v, w)` của khối (`w` là +trục, `(u, v)` là hai phương trong mặt đáy) nên trục đứng chỉ là trường hợp +riêng `nghieng: 0deg`. + +`ve-mat-cong(..khoi, cam:, mau:, day:, to:, hien-khuat:, mau-khuat:, day-khuat:, +n:, duong:, them:)` nhận `ctx`; `mat-cong(...)` tự tạo khung hình, mọi +tuỳ chọn khác chảy thẳng xuống `ve-mat-cong`. + +Đường vẽ thêm cũng được tự chia liền/đứt — mỗi mục của `duong:` là một dict: + +```typ +#mat-cong( + mat-non(r: 2, cao: 4), mat-tru(tam: (0, 2, 0), r: 2, cao: 4), + duong: ((pts: tron-ngang((0, 0, 1.6), 1.2), mau: green, day: 0.9pt),), +) +``` + +### Hệ trục Oxyz nghiêng CÙNG góc với khối — `truc:` / `ve-truc-3d` + +`oxyz` của `hinh-khong-gian.typ` tự dựng phép chiếu XIÊN riêng. Trong phép chiếu +xiên, đường tròn nằm ngang chiếu ra elip NGHIÊNG trong khi `Oz` vẫn dựng đứng — +đáy nghiêng mà trục đứng, nhìn vô lý. Muốn cả khung hình chung MỘT góc nghiêng +thì dùng camera TRỰC GIAO thật và vẽ hệ trục bằng chính camera đó: + +```typ +#let nghieng = 22deg // góc nhìn từ trên xuống — MỘT biến cho cả khung hình +#let quay = 15deg // xoay quanh trục đứng, chỉnh cho hai khối tách nhau + +#mat-cong( + mat-non(r: 2, cao: 4), mat-tru(tam: (0, 2, 0), r: 2, cao: 4), + cam: chieu-truc-giao(ngang: quay, cao: nghieng), + truc: (x: 2.6, y: 4.4, z: 4.6), // hệ trục vẽ bằng CHÍNH camera đó +) +``` + +Với `chieu-truc-giao`, elip đáy có trục lớn NẰM NGANG (bán trục bé = +`sin(nghieng)`) và trục khối THẲNG ĐỨNG — đúng như nhìn vật thật. +ĐÁNH ĐỔI cần biết: bù lại `Ox` và `Oy` đều chếch xuống, không còn `Oy` nằm +ngang như lối vẽ trục quen thuộc. Không thể có đồng thời cả hai — đó là tính +chất hình học, không phải lỗi. + +`ve-truc-3d(cam, x:, y:, z:, am:, dm:, ten:, ten-goc:, huong-ten:, huong-goc:, +mau:, day:, cach:)` dùng được với BẤT KÌ camera nào (`chieu-xien`, +`chieu-oxyz`, `chieu-truc-giao`). `ve-mat-cong` còn có `truoc:` (vẽ trước khối) +song song với `them:` (vẽ sau khối). + +Nếu vẫn muốn dùng `oxyz` sẵn có thì `k` và `goc` của `oxyz` PHẢI trùng với +`chieu-oxyz(k:, goc:)`, nếu không khối sẽ lệch khỏi hệ trục: + +```typ +#oxyz(x: 2.6, y: 4.4, z: 4.6, k: 0.7, goc: 30deg, them: (ctx, t3) => { + ve-mat-cong(ctx, mat-non(r: 2, cao: 4), mat-tru(tam: (0, 2, 0), r: 2, cao: 4), + cam: chieu-oxyz(k: 0.7, goc: 30deg)) +}) +``` + +Phép thử che khuất là GIẢI TÍCH (giao tia với mặt đáy là bậc nhất, với mặt bên +là bậc hai), KHÔNG quét mẫu — nên không bỏ sót nét khuất dù dây cung qua khối +rất ngắn. Sai số duy nhất còn lại nằm ở mắt lưới của đường được vẽ, chỉnh bằng +`n:`. + +GIỚI HẠN, đọc trước khi dùng: chưa có mặt cầu; đường sinh biên CỐ Ý không để +chính khối của nó che (tia bắn từ đúng đường biên là tiếp tuyến, xét ở đó sẽ +chập chờn làm nét biên lúc liền lúc đứt). + +## Engine đa diện tổng quát, mặt phẳng Oxyz & thiết diện (07/2026) + +`lib/da-dien.typ` — nhập DANH SÁCH ĐỈNH 3D + DANH SÁCH MẶT là vẽ được bất kì +khối đa diện LỒI: tự chiếu, tự phân biệt mặt trước / mặt sau, tự vẽ nét liền và +nét khuất, tự tô mặt theo độ sâu. Thứ tự đỉnh trong mỗi mặt KHÔNG cần theo +chiều nào (pháp tuyến được lật ra ngoài theo tâm khối). + +```typ +// khối dựng sẵn — bung bằng toán tử `..` (trả (dinh:, mat:, ten:)) +#da-dien(..khoi-chop-deu(n: 4, R: 1.9, cao: 3.6), to: blue.lighten(86%)) +#da-dien(..khoi-lang-tru-deu(n: 6, R: 1.8, cao: 3.4)) +#da-dien(..khoi-chop-cut-deu(n: 4, R: 2, r: 1.1, cao: 3)) +#da-dien(..khoi-hop-chu-nhat(dai: 4, rong: 2.6, cao: 3)) +#da-dien(..khoi-lap-phuong(a: 3)) +#da-dien(..khoi-tu-dien-deu(a: 3)) +#da-dien(..khoi-bat-dien-deu(a: 3)) +#da-dien(..khoi-hop((0,0,0), (3,0,0), (0.6,2.2,0), (0.9,0.4,2.8))) // hộp lệch +#da-dien(..khoi-chop(day, S)) // đáy = mảng điểm 3D bất kì +#da-dien(..khoi-lang-tru(day, v)) // đáy tịnh tiến theo vectơ v +#da-dien(..khoi-chop-cut(day, day-tren)) // hai đáy cùng số đỉnh + +// tự khai đỉnh + mặt (mỗi mặt là mảng CHỈ SỐ đỉnh) +#da-dien( + dinh: ((0,0,0), (3.2,0,0), (0,3,0), (0,0,3)), + mat: ((0,1,2), (0,1,3), (0,2,3), (1,2,3)), + ten: ($O$, $A$, $B$, $C$), to: blue.lighten(88%), +) +``` + +Tham số của `da-dien` / `ve-da-dien`: `ten` (mảng nhãn theo thứ tự đỉnh), +`huong` (auto = tự đặt nhãn vào KHE GÓC RỘNG NHẤT giữa các cạnh nên không đè +lên nét), `to` (màu tô các mặt trước), `to-mat` (mảng màu riêng từng mặt, +`auto` = theo `to`), `hien-khuat: false` (bỏ nét khuất, ẩn luôn đỉnh chỉ thuộc +mặt sau), `mau-khuat` / `day-khuat`, `hien-dinh`, `bk`, `w`, `le`, +`them: (ctx, p) => ...` (`p` chiếu `(x, y, z)` → điểm 2D, dùng được với MỌI hàm +vẽ phẳng). Bản `ve-da-dien(ctx, ...)` vẽ vào khung có sẵn, `da-dien(...)` tự +tạo khung. + +**Camera** — `cam:` nhận một trong: + +```typ +cam: chieu-xien(goc: 40deg, k: 0.5) // mặc định, khớp hình dựng sẵn +cam: chieu-truc-giao(ngang: 65deg, cao: 22deg) // camera Euler thật +cam: chieu-oxyz(k: 0.55, goc: 35deg) // khớp hệ trục #oxyz +cam: P => (P.at(1), P.at(2)) // hoặc hàm chiếu tuỳ ý +``` + +Hướng nhìn (để biết mặt nào là mặt trước) được SUY RA từ ma trận chiếu, nên +hàm chiếu tuỳ ý cũng phân biệt nét khuất đúng. + +**Mặt phẳng trong Oxyz** — gọi trong `oxyz(them: (ctx, t3) => ...)`; phần trục +toạ độ nằm SAU mặt phẳng TỰ vẽ đứt: + +```typ +#oxyz(x: 5, y: 6, z: 5, them: (ctx, t3) => { + // x/4 + y/5 + z/3.5 = 1, cắt ba trục tại A(4,0,0), B(0,5,0), C(0,0,3.5) + mat-phang-oxyz(ctx, t3, 4, 5, 3.5, ten-dinh: true, ten: $(P)$) + // mặt phẳng lơ lửng: tâm + hai nửa-vectơ (4 đỉnh = tam ± u ± v) + mat-phang-bh(ctx, t3, (2.2, 2.8, 2.6), (2.4, 0, 0), (0, 2.6, 0), + ten: $(alpha)$, truc: ((0, 5), (0, 6), (0, 5))) +}) +``` + +`truc:` khai phạm vi ba trục để xử lí nét đứt (`auto` của `mat-phang-oxyz` = +từ gốc tới mốc cắt trục; `none` = không xử lí). + +**Thiết diện khối lồi**: + +```typ +#let ch = khoi-chop-deu(n: 4, R: 1.9, cao: 3.6) +#da-dien-thiet-dien(..ch, mp: mp-qua-3-diem(A, M, N), td: (mau: red)) +// mặt phẳng: mp-qua-3-diem(A, B, C) · mp-qua-phap(P, n) · mp-cat-truc(a, b, c) +// mp-song-song(mp, P) +// thiet-dien(dinh, mat, mp) TRẢ VỀ mảng điểm 3D đã sắp theo VÒNG +// ve-thiet-dien(ctx, dinh:, mat:, mp:) vẽ vào khung có sẵn +``` + +Cạnh thiết diện nằm trên mặt THẤY vẽ liền, nằm trên mặt KHUẤT vẽ đứt. Mặt +phẳng đi qua đỉnh của khối vẫn xử lí đúng. + +Vectơ 3D để dựng điểm: `v3-cong`, `v3-tru`, `v3-nhan`, `v3-vo-huong`, +`v3-co-huong`, `v3-dai`, `v3-chuan`, `trung-diem-3d(A, B)`, `chia-3d(A, B, t)`, +`tam-3d(ds)`, `day-deu(n, R, z: 0)`, `phap-da-giac(ds)`, `phan-tich-khoi(dinh, mat)`. + +**Điểm phụ và đoạn phụ trong khối** — `diem:` và `duong:` (có ở cả `da-dien`, +`ve-da-dien` và `da-dien-thiet-dien`): + +```typ +#let cd = khoi-chop-deu(n: 4, R: 1.9, cao: 3.6) +#let (A, B, C, D, S) = cd.dinh +#let O = tam-3d((A, B, C, D)) // tâm đáy +#let M = trung-diem-3d(S, B) // trung điểm SB +#let N = diem-canh(cd.dinh, 2, 4, t: 0.6) // trên cạnh nối đỉnh #2 với #4 +#let H = hinh-chieu-3d(A, S, C) // chân đường vuông góc từ A xuống SC + +#da-dien( + ..cd, to: blue.lighten(92%), + diem: ((O, $O$), (M, $M$), (N, $N$), (H, $H$, auto, red)), + duong: ( + (S, O, (mau: red, vuong: A, ten: $h$, tai: 0.22, huong: "right")), + (A, H, (mau: red)), + (A, M), + ), +) +``` + +Mỗi mục của `diem:` là `P` · `(P, ten)` · `(P, ten, huong)` · `(P, ten, huong, mau)`. Điểm nằm **trên một cạnh** của khối thì nhãn TỰ đặt vuông góc với cạnh +đó (đúng lối sách); điểm khác thì nhãn toả ra xa tâm hình chiếu. + +Mỗi mục của `duong:` là `(A, B)` hoặc `(A, B, tuỳ-chọn)`, tuỳ-chọn là dict nhận +`mau`, `day`, `ten`, `tai`, `huong`, `cach`, `dut` (ép kiểu nét), `hien-khuat`, +`mau-khuat`, `day-khuat`, `vuong` (một điểm $C$ — vẽ ký hiệu góc vuông tại $B$, +giữa $BA$ và $BC$) và `r` (cỡ ký hiệu). Phần đoạn **bị khối che tự vẽ đứt**, +phần thấy vẽ liền — một đoạn có thể vừa liền vừa đứt (đường cao $SO$ thấy ở +ngoài rồi chui vào trong khối). Phép kiểm là CHÍNH XÁC, không quét mẫu: xét tia +từ điểm về phía người nhìn có cắt khối lồi hay không. + +Với `hien-khuat: false`, điểm bị khối che, ký hiệu góc vuông và nhãn nằm ở chỗ +bị che đều được ẩn theo. + +Tiện ích dựng điểm: `diem-canh(dinh, i, j, t: 0.5)` (chia cạnh nối hai đỉnh), +`hinh-chieu-3d(P, A, B)` (chân đường vuông góc hạ xuống đường thẳng $AB$), +`hinh-chieu-mp(P, mp)` (hình chiếu lên mặt phẳng), `tam-3d(ds)` (tâm của mảng +điểm — dùng lấy tâm đáy). + +GIỚI HẠN: quy tắc nét khuất chỉ đúng với khối LỒI (đủ cho toàn bộ THPT). Khối +KHÔNG lồi, hai khối che nhau, hoặc mặt cong (cầu/trụ/nón) thì dùng các hàm cũ +trong `hinh-khong-gian.typ`. File thử: `thu-da-dien.typ`. + +## Tính toán trong không gian Oxyz (`lib/oxyz-toan.typ`) (07/2026) + +Nhóm hàm **tính** (không vẽ): mọi hàm trả về **giá trị** (số, điểm, dict) hoặc +**nội dung toán đã định dạng** (nhóm `pt-*` và `hien-*`), gọi thẳng, không cần +`ctx`. + +Kiểu dữ liệu dùng chung: + +| Đối tượng | Cách viết | +| --------------- | ---------------------------------------------------- | +| điểm / vectơ | `(x, y, z)` | +| mặt phẳng | `(a: , b: , c: , d: )` ⇔ `ax + by + cz + d = 0` | +| đường thẳng | `(P: điểm đi qua, u: vectơ chỉ phương)` | +| mặt cầu | `(I: tâm, R: bán kính)` | + +Các hàm nhận mặt phẳng cũng **nhận luôn dạng `(n: , d: )`** của `da-dien.typ` +(`mp-qua-3-diem`, `mp-cat-truc`…), nên hai nhóm dùng lẫn được. + +### Vectơ + +```typst +#let A = (1, 2, -1) +#let B = (2, -1, 3) +#let C = (-4, 7, 5) + +vecto-3d(A, B) // toạ độ AB = B − A → (1, -3, 4) +do-dai-vecto(vecto-3d(A, B)) // độ dài (số thực) +tich-vo-huong(u, v) // u · v +tich-co-huong(u, v) // [u, v] +tich-hon-tap(u, v, w) // [u, v] · w (định thức cấp ba) +cos-goc-vecto(u, v) · goc-vecto(u, v) // số thực · kiểu angle +cung-phuong(u, v) · vuong-goc(u, v) · dong-phang(u, v, w) // true/false +``` + +Các phép cộng/trừ/nhân số có sẵn từ `da-dien.typ`: `v3-cong`, `v3-tru`, +`v3-nhan`, `v3-chuan`, `v3-dai`. + +### Điểm — khoảng cách, điểm đặc biệt của tam giác + +```typst +khoang-cach-3d(A, B) // = do-dai-doan-3d(A, B) +trung-diem-3d(A, B) · chia-3d(A, B, t) +trong-tam-3d(A, B, C) // nhận số điểm tuỳ ý +truc-tam-3d(A, B, C) // hệ thức Euler H = A + B + C − 2O +tam-ngoai-tiep-3d(A, B, C) · ban-kinh-ngoai-tiep-3d(A, B, C) +tam-noi-tiep-3d(A, B, C) · ban-kinh-noi-tiep-3d(A, B, C) +tam-bang-tiep-3d(A, B, C) // bàng tiếp TRONG GÓC A +``` + +### Diện tích — thể tích + +```typst +dien-tich-tam-giac-3d(A, B, C) dien-tich-hbh-3d(A, B, C) +the-tich-tu-dien(A, B, C, D) the-tich-hinh-hop(A, B, C, D) +bon-diem-dong-phang(A, B, C, D) +``` + +### Mặt phẳng + +```typst +mat-phang-qua-phap(A, (2, -3, 1)) // qua 1 điểm + 1 vectơ pháp tuyến +mat-phang-qua-3-diem(A, B, C) +mat-phang-doan-chan(p, q, r) // x/p + y/q + z/r = 1 +mat-phang-trung-truc(A, B) +mat-phang-song-song(mp, A) // qua A, song song mp +mat-phang-vuong-goc-duong(A, d) // qua A, vuông góc đường thẳng d +mat-phang-qua-2-phuong(A, u, v) + +#pt-mat-phang(mat-phang-qua-3-diem(A, B, C)) // $19x + 13y + 5z − 40 = 0$ +``` + +Hệ số **tự rút gọn về nguyên tố cùng nhau**, hệ số khác 0 đầu tiên mang dấu +dương. Đổi tên ẩn: `pt-mat-phang(P, an: ("X", "Y", "Z"))`. + +```typst +phap-tuyen-mp(P) the-vao-mp(A, P) // ax₀+by₀+cz₀+d +khoang-cach-diem-mp(A, P) goc-2-mp(P, Q) +hinh-chieu-len-mp(A, P) doi-xung-qua-mp(A, P) +vi-tri-2-mp(P, Q) // "trùng nhau" | "song song" | "cắt nhau" +``` + +### Đường thẳng + +```typst +#let d1 = duong-thang-qua-2-diem(A, B) +#let d2 = duong-thang-qua-vtcp((1, 2, 3), (2, 0, -1)) +duong-thang-vuong-goc-mp(A, P) + +#pt-tham-so(d1) // hệ cases(x = …, y = …, z = …), đổi tham số: t: "k" +#pt-chinh-tac(d1) // thành phần chỉ phương bằng 0 tự tách ra sau dấu phẩy +``` + +```typst +khoang-cach-diem-duong(A, d) khoang-cach-2-duong(d1, d2) +hinh-chieu-len-duong(A, d) doi-xung-qua-duong(A, d) +goc-2-duong(d1, d2) goc-duong-mp(d, P) +vi-tri-2-duong(d1, d2) // "trùng nhau" | "song song" | "cắt nhau" | "chéo nhau" +giao-2-duong(d1, d2) giao-duong-mp(d, P) giao-2-mp(P, Q) // none nếu không có +vtcp-dep(u) // rút gọn vectơ chỉ phương về nguyên, dấu đầu dương +``` + +### Mặt cầu + +```typst +#let S = mat-cau((1, -2, 3), 5) +mat-cau-duong-kinh(A, B) mat-cau-tam-tiep-xuc-mp(I, P) +mat-cau-qua-4-diem(A, B, C, D) // đồng phẳng → none + +#pt-mat-cau(S) // $(x−1)² + (y+2)² + (z−3)² = 25$ +#pt-mat-cau-khai-trien(S) // $x² + y² + z² − 2x + 4y − 6z − 11 = 0$ + +vi-tri-mp-mat-cau(P, S) // "cắt nhau" | "tiếp xúc" | "không cắt" +duong-tron-giao(P, S) // (I: tâm, R: bán kính) đường tròn giao tuyến +``` + +### Hiển thị — chính xác hay gần đúng + +```typst +#hien-diem(A, ten: "A") // $A(1; 2; −1)$ +#hien-vecto(u, ten: "AB") // $arrow(A B) = (1; −3; 4)$ +#hien-so(v) // nguyên / phân số CHÍNH XÁC, nếu không thì thập phân +#hien-gan-dung(v, chu-so: 2) // giá trị gần đúng +#hien-goc(goc-2-mp(P, Q)) // 77.7° +#hien-can(4030) // căn thức đã rút thừa số chính phương +``` + +Các đại lượng chứa căn có bản **chính xác** riêng (không làm tròn): +`hien-do-dai(u)`, `hien-khoang-cach(A, B)`, `hien-cos-goc(u, v)`, +`hien-khoang-cach-diem-mp(A, P)`, `hien-khoang-cach-diem-duong(A, d)`, +`hien-khoang-cach-2-duong(d1, d2)`, `hien-dien-tich-tam-giac(A, B, C)`, +`hien-dien-tich-hbh(A, B, C)`, `hien-the-tich-tu-dien(A, B, C, D)`. + +> `hien-so` chỉ nhận dạng số nguyên và phân số nên **luôn đúng**; muốn ra căn +> thức thì dùng đúng hàm `hien-*` ở trên. (`so-toan` của `do-thi.typ` có đoán +> thêm dạng căn nên với số vô tỉ bất kì có thể ra một dạng "đẹp" mà sai.) + +### Tính xong thì vẽ + +```typst +#oxyz(w: 8cm, x: 4.4, y: 5.4, z: 4.4, them: (ctx, t3) => { + let Q = mat-phang-qua-phap((1, 1.2, 1.4), (1, 1, 1)) + let (T, u, v) = khung-mp(Q, tam: (1, 1.2, 1.4), r: 2.5) // khung để vẽ + mat-phang-bh(ctx, t3, T, u, v, ten: $(alpha)$) + let H = hinh-chieu-len-mp((0, 0, 0), Q) + doan-oxyz(ctx, t3, (0, 0, 0), H, mau: red, dut: true) +}) +``` + +`khung-mp(mp, tam: auto, r: 2)` trả `(tâm, u, v)` để đưa thẳng vào +`mat-phang-bh`; `mp-de-ve(mp)` đổi mặt phẳng sang dạng `(n: , d: )` của +`da-dien.typ`. Mặt phẳng dạng đoạn chắn vẽ bằng `mat-phang-oxyz(ctx, t3, p, q, r)`. + +File thử: `thu-oxyz.typ`. diff --git a/packages/preview/conic-toan/0.3.4/assets/exam-page.png b/packages/preview/conic-toan/0.3.4/assets/exam-page.png new file mode 100644 index 0000000000000000000000000000000000000000..b350b9458571a0cc32adb1c73813d5dcc302c03e GIT binary patch literal 137236 zcmcG$by$__w>4~2(j_h3Al;&LiimUx(w)*EN{4g{2na}br-XEaG)Q+z_c#5WbKdjE z_uqHD-@2~NX0Od!&wB2A-gAyI=9nw+^(#qKWJ2USckZA{ONqU?bLalTojVWR5%0k> z(}zfS@Gm4ADRuihcd#7({d0HDh|KxUou_xC#a_O3p4v`!(RwvW)-h04ZvBQnr~GjN zR%W<1W?*;X;|LFuI=0?UtL@8YX>{V1-FOs{ViB2Q8s&SJCl;qSH@shKQnDkTajc$t zG}YHzt?`|v*blg*xCyVhHLkg-amrJ|wg2=eav3u!{fn_&?Y9|M9sO8TbGDYX5uP|2xm!Atd?lcmCh^`~UB`JA>>0 zeYO9+#{Z?~K1v|`_gnw(HU2+7*X-r}sWj26@*YLd$w8Q)<01Xg!~NvpX8c&yaZ!Og z|B0oKMWPmOVlz+Y?&H6|%|U)U_V-s2IzKVq>40ob>Qz=GDKIhkXSk{qY5V{=My3#ulAmc^KWigaG9tE=DMTo-BA{aQM>Y28uR(9qD;yFe@mY21eI+qk=%qn_LH1rDziUJ77X;z(J;Z&-^QP6R zc~4DP{NVQ7qN1v*DoI2km%qzfc#7)k=O;Uw=H^?sQ4s?TZl_vNcrIqb@|>;Ci#Lw2RC6YF`p1C1p%Z3c++f zr~LT%co;FC-PPHFva+)IcwvL>lHc-}7{$;~h9o5gg&LEJo}Qkva!gK+=|GB59E)}k z##E_c&+*oH2}4X|q(+6=*q@)D6S6xxIxa6RrY0xlllhgmyE)~__#NnYdDT=@DsywY z2M29_etHNC6Pw?<(i7*jGbOHw^ITS1x~sFZ6E?c9PhM2?{u-a-W^_>zYjArDL4AGw zuMmRI7|6)T{4ReDJUoQrdS*-B`^UuKHs&mKg!%jX2aBac?C0h(K)kcP{}rHob8~&U zHD0uKc#cTU61Vl|&qwAJ0jJ%^=;)G&6_(T7&z`-0^$LwVIx5P3d!i&VGV=QJlxK`f zv(lpG!wz9m-!Go@!a^$x3ky?IKG!1?dV2b=4k1 zt1TwkwFqd=&(H58puK(l`n9TR$LS~8H5*=7?23vCHa50iwik!KmG$EvttA!3E&mjV zvH173&r^$ya7M<(48K+^X4`$b`m?2%iGIrYUWBYQcI(OF=jKO^`!;G{as2!A$}mN6 z+Q1Zk|#32@S!YwJCXYc(Rr6OocqL@phsbWJEP zURUBtjaB(=%WJ_MY&ff4mz|KZtc@W4oUtG`!$d=D=(>*3VedP0EI9+)wZxUCIG-s2 zeIZU9bNj*{5mQ1WF$zcn&Dg(34_9kWYMq61DpOc76a~Fs)1{x?i+Dj58MA2igF%C! zf^4);D(EuMWp%kXy{-Hz@N*q^>W-z=#>xZ^JqyCTu=vQmG?ST+EK{4^h~&2cw2jp4SyXKbzXIC%6MU@$Z0>xBMpK`%`=A3 zNjcrB3l@?0ksK%t->YHie7)3z?AS;At_^#;8FOGE652(U%|%B{Qp^>uHe&qg_mCOY z0;tn}T$P$7N+e~YV{VNs25HDExA)g>W?ZV5D@SISxK79jLq^o4K^4Tg2=CFepRjXKz1UY3T-`yfKo!H&v0RUH1Vo4F#V?E}7pE4iBwD ziWs7}xOh!<_1W1OJb65tqZFy?cDUMCZM*c~-b-ia>a;X+5jp_@w~M0g8If!Aq+eSOTdw6uJD>T(&XZ{J>>ErlT=At@>!i3cwS$S`?$Ni|1Q){ijA$bS!fx{Q{9_uxa=VF z5<|R(_khDgtS*jQsrlm&{|M zqn%w`T-@B?3HF|yk?et=pAjIj<>loW52VOzsmHQtCq_rV)zZb3tkLB+wN+TU1xuzHT!|V%cs4)sb3sX z+Mdsx7JXPn=~pV06P6lJuCi}4!>`Q?%@usYUtU(0NUMAK2)7VuUB=5hmRh+C{i-EI zSbX0~S}%f_ux=_S$SZlJ9<9XgWKGA-c<#){$v_SNkDuhccjRZcnyM&O`iHNfECsou zh^7_F7p5T%uf=9P6HlnP28ObfjI|x-84s$k<`%K>Lw9GZf<5&$ZVJOM9Z~NAMhJJr z7Vt265va#NSU-9yZI%-BX?xuhkNr&_LExA&PBgATRmvBJ+#lkI8|FDIB;(nk>BOeL z+7XIgbM6dJJA4Ud7U}#Us%g|78@891J^6@itJ3_Cj@nprP+DIu!v z%4yf8kZezCJ+)8zhBE2(am8CQDnsTeFF2}AuSf-FC$n;#1#sjCCTth38op1mRN6~U zwl{Q7KC#h!`knn1t2O6Tm)SfKEUF^sY__cARzHcxkdnOVQ75fwo(JU|u1YV-nEh2V z3q_D;gwFofK@uRgLoGMUud8Pjgk=ehR>#_$7w`3qOfu+<3+Js=rFt9I2oTBl7P#LN z^ozink2zk|xQRfnI%TyX-%_W(bV$}+PJGYvNR5E7+&W2DV`sypU9XHjagO-OSTjBY zW9Q7o^FA@v&ssH~{bUxWuPQpMH3-d^iRM*<*zVfVvU#jTY*nCM`R+D&5f2xnXFXqV z-1Sqc%G+|*eq51$+V9ixcijKcjlH1f)e~|90|TF1oItThj~;b(b#cl!xg1IWR#Z}o z8d!_Es{615!H|%^#mO0<9n`U;muIi9Pr=cUl_jC8D@;jw|JLQ=$kYeJzcnJice2d* z@?<9%L%&1~UeX(uC719CW2(xUOxo1Mr1^f}$B%cqWLH*J7Fc(n%-hIVT3U`5YGaBl zEG+OooUX(aIX*t_weRWa!4$dY{V~h*d1{zyQ3|i^r-ba%(o)D=k)ba}IPvmpYin`D z7jV2^e9Or>Uu;K-q~+lmpPJH8S0^BM_`BR?#4lA^qtoPB@a@~T{QR#;NsO_RQ&Y8) zVWfy@rXyKwBS{-F0Cr010zyLE_y62|-rwEbB9DWK>8-?~Qy(82`#VxZbI81zXXQ_( zS`6lM21<9lZ4}DDanc4W0Xq+Ys!)FxAp@fuN+vX89hc=P-PsQO52-b`QfC=iL5N=a z$f+xpCmA<=%!S*$deg9zJ`_O-g&bCl{i(Vd*EQk@b24p{Zzay!Xp95L3?>)ya#^+x zvgOAJCi5~A+PcHHB6UXjmDiy? zFiWQ9W*s%hSWV7O*41n!!kqWEzidc9ZrEYoVRu*7NIwIY@+i^ki5KB1L*3YzfYYfV zeE3JCO~$w?;cb%ilO(^a6bTkM9H(ottPGqwJ>X`Ibn1i!Q8wm3@8yK-OZPCw^lE7+ zXKyqjjT=17*b=VFruyk?7CPs?r~XSa>|*o6isM9pMd0L=a}|G1JVO|d<_1&2#&3MG zJu+3JZ{jp*z5wqeXQ}l4vEW_l+rB-##!{eE=|O~DMtnrapAt&>{(R|1Or1NtrK7w2 zk^9haY}RO-=kE-f!jkpfe1dEAH@doE^6ijBzndoZ89|C2Zfko$UT)M!=>6iE*(hC> zW_!8m$oI_5sHiA~ue{_Ray%|isA*_4G&SGT%R#=~KRu0$i|g>xcM6b5E=DKj-Cs-f z`mj9_VWK4W<_#A2^n0i1(+>g!1gxyA7MZd2IJUJ<5&r)D%X?eza#)Olkn>s1a6k3z zmQwOdTSrG;PLBLn-UG_k@1>|U?5N=@axH~iX^JiLau31+kb?;ZF-5EA+?q8HX_G|rcw!*^_H@f3kb%PvN zzPE$cIL$T`Qyq4m!i)Tr$fNQ}SAJx2&Yt$lqRd9Mk)4#aud%BCLN9Kn zop#F)gCcZheJkd8d@Xf%M3SDbdheMASymdPXlERkpSM%XB*>1WR)r0t%qv^>P)dEB?mr8*cc*mx`}m8dv9>s zSL`av%j2`vG&CrDkWo;|%E~nD$|j^zV`9ese&Z}DDRG|r_+zZ8Ei@|X)2C0%^pNW+ z%F09$A3l7z5dFU6i{8SQg~Rh~h18U1diNSffDm0>T@7q|dV)nKHM;VA`YXyd+ zp;4Qg8`O(cp3bQGio@5l0HOv@o*D7z$xKYU2xlnXPLrGuW*$-%YTR5_--8zhA#U^8 zRK|Fr=Q~HS1fn#L%PrpWNaY6M;c36tSz0j&CDCMUr{dtzMz4?I&1&*Bnm)bwmUBH3 z7qZhz%wfZ~H*fpqYm?504_UPnJb#LE*%_obIfL65Z3tb&vsPjNhfH#H)HEbwiL4vB zn+0?G8wchU9-c=2CT?=EZKaYGEufh$E{Qeo#v1y&X^B#+soW!q7`kiDax(OIjr8nY zdxW`rn$+J5L2nH5BmO#ry>kR!aZ&Mw<1Xr1PYhk6r|IkO#~1OdPYJEeL&Oc1qso6D zM^B6!o$Sg|1g#dUmq|>>{4VxV{mhrKErxT=!)cVam!3c-yJaGsT6k5hz@?09>r0lo z+zS~ug43WxD&|aNs8>6VF43weYjpi{dT`qBSb|rfo|+(HedJ|lXOD=8m|%BZ`h_Yh zD{Jwa86GSw5{S|gh(kj|FA*C&uQiy2*w_@Agu^;|`uh3?23YCnI{W%?1I1F`x*?M1 zYw0$)9v^LtoEDug8~B9h=H^b6A&q|@>gEheoO1IYf-F#3?6EKJVfT^zk;*bTIk`yM zGhtz2Zf?j~CCek>;o*1hKcb_j=i}x5k&_cBmj36@ABiIS{Xaj8i=X*mh${l%Z!KZ% z3Vr+b?egmCVsw6f{_^rN@LmYq$3)zZ*?JEP*1PeB<iTXyv+ZFLu1re&Sj~ zS^Cu*10Am;4aHYk{v~J$)VxH;S3TDI(q_ALEyzCMU;L0``y-i%##Jp`*QU8vn|xxA zOeZ@$_-Fdh1{E^)4{F?4t5O=L*JIx6H1;s&h#(SdnQ>Og=v4+=YEHNv6WuYFxJ`Y;gCR1(=TmK16@IYP!KzbV4aDqZXBt=(DJf~&7($o?10!R} zkM;|$r%&Gu0{xY(+w@iJNkMt}TX}hT zMa2XAx>8?GrS+_gBKc$^5e3&Lon0cOl*kd+D*Zs<|6fMZz4};&9S#S483St`3K@JVc^oGfg zAMS(eP+{iNhyKdss$3IzW3ZbP)tVQU8Td^-y4mP#GuLdu5XrbqAVc;Dz~HyPteN=K zWaONzEZ9%GF5#YA5Qt`b*DUDhs#Lv>C5XDQ%gSyLLS$_8uw*%Mv2>cDOIGJQ0AF=S zArU&}A~`#%lM$0kK;sh$%fr{p0tzGJ3XaWlIQqm$UfW#d0wN~G$LI{mrJyaSk~S6= zE_wVdO!5!m6EO3fJCU1DXS<~?FH}%x;C-1@1A}!z{0`tIVRSrpvMX< z#=yx7tgjaw5v{GkpF?OZsHx%8j~`=XWK5WZT@NJul`<(UEscwdo67ZQ|LCY~@$&3o z$=UM`HW`_&va;h^fAYtFk2~Awhs#~q7LItWlamBtZze2G0x&7}Bem=(L28AJgP7*$ z_efZ{DT&7#VE>~B4`v$NR6zxJit$y*4Uc>@sL%qb5B~z!4fQ{Iq=gaL42BKDt;^=B zQeBPz?{mq>$KA+#F|Q>&6DDe06D@(MoRo1s8yoqiC|j^jt?Y=dW2DHbt=*ASzVTRL zFfnImHJF7dPenPhjBAK(#M&Q2rjNPdmI9mHfogk5ZjBU?99LXl()uhYG~ueS6NF0A zGV~7zbC*oaUh&BXv$GsTsI*w_mo*WUgee)tyEAw97SH#UxhuCT<>+IDWqUwmmFZG< zyD~aVP{uLvKUrrwDN@Az{ddz4FM8=dTAghkoh`4G!zXb~?ekSMYq#LjjDzkeKXC*W zRv~v~EoozXVYz-n;Xy2Ns=k%#Bkq@oT>HLqaqGVo)u&%jtr{ON6CUce#2sx6U?Wr? zv$b{{p87p^r`|16Q@QpOWIDDDzx=y-u{4hLYS!JA|F1eId7x(1P%G~nJPJi~|Kzy! zJA%$RLn5@r7O5mxr`iS-6;96b{OzLglj$n!k>TOd(b0Ic-|^~YMz*|Pd93GV>YT!; zO4XPwMB9x_eZmc*J%ZBFNchX&{k-Ss=*ab-hgK+{0C+j24>1YJ0lP}cSm*?~+e&xL z@}iH#`*VLkKMEh$qjf!Cco?O{#mz?B)zx7q+HPn2s{*?gjH-=Gl5Fz`i zV0;OvGv(!N*9bc= zY>@K1Ko4&26F>VwtRO~L^rOV-%PdY)z8gyMVbDqhO~ko9}dOw zqsRZW+v|wVCow2!DL6+dy?L$kIqngbfNQW;vt~%d2hDlIT@Gyv`a}s3+@q#P2UiAq z!gP&r;$3&RlWIFGHd8*8o)Xa4`bFhRa_C~U~l8>yR#F@Sy!Bi#_D>lVuB!R@^Yn@VW-)~bod@wO~)~p$*&$WOeF0@9{ z!iD4&F57b6ygJobf{FFiAJWos+=b0!@F!>dG2B=onrxpQF~J>A_OK(Vu! zEInoT*|t1U@*Xh_m53XIfEfev3#m}8`}to99;_BMb@k)*q0i}bS@(c)^Shleu|)sb z-TjhjnW3mJWv}PLjDrmL=Vq}zm{iC$U$===_5|wJ0t@AzZ>#8}g3WlR13TSKq`%1$ zeTl zeaSx{M_zc{T%5Y5rTpALz$%_E}JZ?#e>s6|7?c)a58i00+mqU`hUCt zA-BagGd*32Vr6%sBP_4~{(bA(VL^U=mO|?8@-je_0sxZ4yM*M+Yisz##L%bu*X*!g zm6Me07#<$>@p-U{EJf3F^;cF*%%`%l5=cxqx#;RZs<6$UW~_5FKwU0oP({N>IFB~Xpv zVu@VlyK@at`bYydFOIjCeucJna2WMI;j>$A_P+0RdwmM=1+1T~VCo;&7I3n%vMLM= z>;hyhBqRi~_7$-F#Kgo%L3nAM?UDrKvWEKlqvPY9?QQru-{6LihxhAih=K<-u z8E;j~)}*!c8JwOOrp<9-W;Kx$4&G?B#1Ij|bu$0-kS9HrvS?bLxmTI-YU=PQn2r<=o5*nhyB#97>lu$FNYuX zm_b~qS(82;WZ95Ah4>C*x1;nCSegec&)hj zLsmu0&8(E?9zojlVicI-ijfi6^TO=Q%V?y6)z7HHA-@El4jwq-xHvfb z=k6pVB&!siS@GH4GrTS#LGE^V-YsNHJ*#4Z19A)RY&{_+svsa?qUr?`~a3o4V zpAx!dF-(c+>nKCg{X+6mKVs1~yNyVug{1qorp3v!2RSbeyO*G$7~~=mMlxu4+pgyG zdOvv$W!BA0MIo@nHZkLH;08zjQYX-9>pctJaT!)}-7Bjz-JE-!wEPHxt{QwdC` z^O?I<3t}|);DQ=>bZgN}!iLatbQ75l+-dACNwhkeqQ65)wlD zD;8&6jyW$od;D*4z6H0Yf`S6OK^oW|Vr|u|N+&I#k&GAIzF-r-7;ivH5RVUPf~n4? zEiW-kT#tT<4ZS>)gOMj*FqG-hkUiwW5XifYjh?a(`28mDoq4wJ2K213=P|6NhfaI9 z>o1g1^%#~-^4>qxDw)O?$Xn449B3y@o($3Vjg5R0>Hc#YbX z^7w%YX#=Uv7JX8?250SQsjY{!QEBnE;|HXKvVO*##3~*+y7Ul7LMIEU^FJJiG;2j= z+;T*pd=>BOuGefl4pdcB(bm;cWK^lb!y`HTOQN`mNM3Ij-7YecY}x7987IimR8PGV zb)(0cB;gUK>-A#+ID4tTtb)=kF80Mjn~Phkjio7J!C`Q8Of1$k8v^uQiAD z`KDW*-Tr6fMZ&~V_Ix*azutAE9bK`9g&KEqzX(+ckz%xC{cire%Jq%bNg!@U-4in) zi^*OmJ?HN-LOoMzgw9ebYwh-@kV5^q-YUf!mkdUVs)Em=vue@tLm(9Q$Py2m?!YZ1 zQy0V5?U45V3eM3MZ<;8M;QgBu<~@2g*MzMd-Srl|GBctx%JHxD&cXzH+{&+2%GR`c z2V-bszii2hZB|+Js?0w;>=2=O^QJV2){3_k8&SX$G0piz6`ce%#%+KDj|7o6$!rBj zwSOMJayUbn0MWxLm8OYNrVlfTCYIc4MY8DfEP@Q zLEXgIl3CUyQ7rr7F`j(uAi|lozMfu0bo3EWy=?^a@~=qPG++7bf6dR64Pf256)6p7 zpta(UidK~JaQ0*D>iNOY4cZQ5a;xMi1%j#B<BN#cJs)68mIoCy4L2TCbZ)H}>d_h#@r+fz$Jzv=B260JfVYV?N{Igr8{u&?WdH&oJ$cLR>aaGG2 z$kLcko>aRW8g=dOG&em+lt6X&#))hF=U5TJq)w;93m#jqgud(HLkbpuW`e}fbkm3c zJjJ&ehvTU3U`cQZAwoPN<2ye4+$rx-UhL zuUd6WWEIT`y~vLr>6OL#8agT(lA;giD_)BsiNnXF8CyaTUyeNxHz5pkyldr7&*I-| zG{k8o5fOreuS%Rs4MrFTEg2^gc(aU}6*oH)@DhrB%hOSX(}axWz`G_`A}HE&;Y zChKZwD^EA;mpv|@ibNUa#0n651I{rM?{NK+@FRZ{kF%a;y{22NKShi;-5#ac&!x3L zKP@T`YS#xPEnrrgqv6Vt=+J)e6D!UTXN#^;R}qW&NazP86-99u$A-kxdcDiazJVm} zk?dtge}PZ!7v^qnDUMRJRvp4_ zFSN31*FKQ{k3sjAyHMEkJ=go&fkw8l_`H}U3yY@$gjI^|&Jk=uL3_LMu-@hAxu9d`X z`Kj`s)A`PwGrQ(5H@eNxOaoaHTxYEdh`u5|81f@;-n;>06<|P+L{5*7}t z>}U%uz7t#Kel#@>^P3z=)=#G?F6Z#-d$&WN-b9jqL!)jk6avJ$;UH zo(d|d;2Buoz-9BJpun&MoYh1`L`C2pfes^FT97nsV2$X>6C8Z}D$^08(I1L%J52QS z1iUuiplwuJ%bz;XAwI&{w_pQup}D#Fdwu<>N?i22j97tR$b>;aX2A*sGy)1B7}&u5 z^O-1I`qiuclJ^||r%(3g!<2w?)#CAw z-Nt(Jah7@xI{6WJnS_MI>gp=6FG9k-)_1A=j@{$q`13^|mdC}7$Fsf15P|gvK9*lp zv}RW$@L{|8T1{KKL9f+secc4?hZh&F;C@W?x=~kE#n&~1n=&*s3n{3HDl?6G|G%ZqoYTCQSfcL!3?yo4h#?+MH!}3aJ%GfMrG2Z zy?;IP1;clLDd+i;^2t-s;Q|N%+vCf z-!WJ1A3-rmWMN_&9T^ctlur>n{}oE~;lqdjI5JX&y-0N}Aq?+%Kc%6OP*vrjrQWLFAsB0&);9i7J7+C-{G*JGKNFYl5&Y)`O7 zn!JBs23)v{5#&Ym@$QZe|MVZAJi=z=P==ziEWeTwV9cjNn(G0KR0x^-oAZ|jNC$yo0lgqCx;;d4H7WC(vH(JGDgM3Fw@aJ zrGNhXIrug}WTw%LjG|TOhOXtsR?+_91K6jJzwZ)W*U<_K&ySDCU4cI&0OJBYWe2Hn z%c?~>nWq1`>WFEuQP?>_1^pmOw9-#J55;IP(*9IbRP_1t{pZhZ7axdp&dw$$=z(s4 zNN#OyO`&<<2u3b!EG(%?=+|$<5hOcw7PQcVk7%Hi`#-Lg3|yW7ZNS!F)Gi%%X9&p4 zVF9w)`I3G|Dyn1)$-WVE+Dze@dh~0bC4T9Is z6i8^#u%Yt;?lQi8xo1mDTPQ4@rL zjE>$x|IhY@YbRuBhB<)-t)eLpxL&I?E`Iz_Z&7dGRUtsCQ35EoS?0p92_ibWySJp z*@m|oZDKtoKHkLAGW3`lPVvwAd0icyn;z@%rwJ^v>4i6*tOu|`r<-HJ>d0kJRaI35 z1)qXZnCkg+9eH`n#kRm+2RO`LUS900Vh|cVX zMk3>5jS?6dD*n$!h51s!#?G#Vlu4}O>vIp+7BC6W$h|-mmylqO@rLvRg#kkZ+{ve> zr(kS+D(V|MSY@s2^BOV;)I$yDZs3trmk|!s8@cu#cG=nAKUZZv5B6=aTtnxWRM^wv-MhgSd$89Wcu&+yL&MwR$m9BgOUdl^ zpo0uC4O|JXuC9wQUmk1|_0^;OzuMjfo|e70@lgCE?2&z2!0%!?QB3~yY4g<7#bBCf zswQ4zaBK)2$jgr&Kl%3k`|_VZ-?Fl3umVApP9X3@CwuOGwjUIP3_t-Y!9eb#K&Nv^ zv;&(mUB^Yxm_Tm{Xjc<5 zUT}rO#xTfAAy3#So;}0hC;W$L_WAYd*nETgtca446BmPV>E{gI%;awCPNgKmOIemMJM8x8$Oy7 z;U(^G5BpiGgaypssEnD32?W}ur6mA0w>5@(ap;RRbC2P}!k*oLvII8Y;;O*-IusGa zx}WfIxRe(ZTFPf+Pcp!o6cf{)K|d1{nSn3JX*S;|G;&`?T6zOk4-U-IfpcMDAq1uV zHQnzRd={wgEv=c~1)89!>ef3iY;ImCj4elN^IDo`J~mOkv@`8RT0;a)2@UtV&8s2?RC) z=sp-DH3@=klA(mY7*l|~08C2PR#b55tLb5iKoo*o0zw}#4Q^$rB4(b@$;pX_2L?)N zD|Q{WBO;@tsebG0Lw8?QS$Vd`p7!Z~9&~hwf7QjvqXvH07G&eVcJ7fAk4n~-HDbTKq zbyvm^f$~X1Lj$ja3mK)v#K(gQ{Q`W8va&sfJ+XnfRdy>f5UZd*>NL25>kI-E!7jtwb8~a+XG}uJnb;0;sZ6hz z1vC%;d5?;VpFDf_0?y=NpPHs-&(ZH5(9Y*;g`23K5s zOdK3ei-~6_D8Y~pP0Y+rw#L!3Q+aLE!LSK37YMR&MKD(Vb7S*Zr+@(`B4Vh*d|Xam z9v~K^*ly0B0M8>RB@>g9G}YALh{BA<7k3;YA_1e`_~#xM;Cktun1DN(o}5gmdFFR+ zqok;~F_`ulOmci4O z{3H278lb2(-W2+TO9rh~Vi@m79LXxPTcd4cpIZmv%KX5{De31TtBigv@^^G(tb+WuNH=<%S}bG4nAoQUq0E^Day=z*wnFyV{B|ZGY%P~*CFk7!21{qA0T+~ z!uIIs=%%I{(4zW^g^5WH%}_vfZ%h(tdLr6AF-6h_CnnAysYG%SfSwMi?(p!?G?50) z6q@f?fnqSvR0h^jb_2xz8lY}0ak3Q8!M$;{--@oqqWX6&@EmH|!5Cx=YJwL?m&FUr zIPqiv=5is6^FB~gYWwx;Rf?dxipmL;5vqgPIwyEv=&eIp7eTD4tqsyX?}0Z4P*Mo3 zbprz`Q#E2ze1spFrnTR`-4P~ZVrHff(!aSXktOnPrDtHE7)scGmz03*#N*#eE;Rji zkJ5*UiAf4$4h|7pU6{*DYwNv0Y}!a+B0@rFjI{vI5K~Ref~HbP9|lZg>GP11kickz z+uoc|+H2^qO2>9UeuIfENine>d3j6m+GnPL82?(WUxK;bHQy1W?tDD$xY|qb)e@$y zzRRGYp&^nJ5G2IMKQY>XC`KgzOLLu%7h4|z+y~6EgTupbR8&4d$?f$+Mn&BjOlt)) z0~3Q#nNy;oTHR3qTR=)MGc~QSp1*w1abR}nZVeV=_;8N#K=#8$ZrX~?NKt8OJ zBf!>rt+XyWj|tcx5PfzLBz;xybqjqIAThIDJ(OU9nwW?elPfAL{G~vEC0>%B4=1-; z)E^C^l436#W<}V7p?3_#3d@bn8d_=O-}CZhDVYB{3o^L?aG47tqS~`Aq2=`)2aa8y zfFm7Xo0#*A>Svf8R4ojQk4k6mH;^j)9uvF){-xUs3ULz(B7050&{s#r5?_#1w&#RAxLtgS7)wMpSGM zW@l!KnHX7FJAyN7o%UYI=>xF8zBq@Ym6(|@(L$=UthAOxVwSXhB zI)P}W(L;dr(L0V?K=%7(T|+~dv>@FKB=N8c2_+}68~1;eK)E;^5C#h4UG{H_2{g4M zp;7*rR4~401!Sk8-@jEfH9@2pJInN3Y(w(!@bDRghG^hI+rU8B51<(r@1lA?Jwyo* ztPv% z9L$SimXC>fVT{pf_`CG(?k>QGwj&)yMK-XSp!oy7=nYwYr8o-MXwrgP2?;1Mvu33n zL@BsffQ7+};4W`AvQyh6XqugTe0?9XaR6yb{Ekk@8NihJ^ye`)Hny!CRf%ayVj>~a zCv9!*K#70e{R9B<;7So;heQK(p!i%Pq5;1%g!sJWy)cV(6yHrlmHGIq-oG%I`U|X9 z3*QPsm~y`5VhjBu{|J#BB)dr9vFpprqi<1R5tbGg1Ag-I^Ct?{#gD{$-JH|nMf5%H z6vh|K$b`|-nu-?K9>_Pyl@hUPaJzuJVzj(IybIvU*@ zJZ9MT8LVMP^Q>}eu1iYl;eBz3{uh>XAfQJgodsnSCE8R5+m`)Q#@EL^C8CXqw znGUn*-d>NPAddfrjx`Q)pd<0^_k)j`R9FY+?pe*<5NIG!y5+|K)}^Knq!(tBk(H$u zkAF?qhKs@YP%}E=(i{uyTi{FANMB2=c?m-{*52uK-Y-5{!Te7ECB4n0&it#7- zPaYeJ8FGYFbW&02&V4T9%|?{@Avw67w@6Y2hx5wC3(2sM3e2(Bik4}0bdK@-WeA8 zrx;+xhXcg|6f6fj`RiA}MWIzwhPk(^T>O5#JlK{-ELrOc8@6?9=2324`^aJ_S=PM9oa^)ohkmFwak z*(leSl$3naT9iHn^4sNzf-q(rQT>KbSs6zq&w@x-iYwsy_10BekSMhp-T4xBeqK6< zZ>+;7?ElKg!NG9|Zc*LY2IRBfHa{tEh3`YvXGBZr2q>XMPwK-9zBgxrg9&goS=+VsMw5JdmMAwq`b%IUm~jZ5;Z1(EnlShyI2z_kZL|n*xCmdO?z)TufUxL2}FSt&^78{Z0ec3qk5Ze<6a^{^ZMw+Q$Qq?+`BVQtUv@ zWzPv9&nQyi^yFyJRaE@BeGMoD;lYCtQXvgGoG%0%G~C>=kA0p^42a3g7x)g$sfa29 zFbz{F(&^JL0VeBTFqG=90iuQ=Sn43UV4tAIKSyJ`Qr4L0>@AL=PRJTlUimvDa5 zz5(!PLY1_L2k4A4T+hha=e35$6sXlMAf%9n!Hg>>q)jicTf9bl?|(WHh;hxjI5;@( z{HI{3R6)coY!TtoqCq(Fm2J#-yZa5HM8=G~jj@wzrA#@jI??{6oGAqw(RwJTV@g-wqB%gbCn5 zgT4CSY+$|tq(IF10rYPD62HWkfQ*5KLQw=5C1AH40J^c#nx`}hGzJ)8gaMv)M&T>f z<}uZ?5RKU0Z4br=tp!FRVnS5Gl2fPO9t18|U~@oe!I10%!jk0!5DWe->#3Nh=kv

%N&7J(o3b$2H|_uvDc4rYh>F5aSST+q|JOAz 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 literal 0 HcmV?d00001 diff --git a/packages/preview/conic-toan/0.3.4/assets/graphs.png b/packages/preview/conic-toan/0.3.4/assets/graphs.png new file mode 100644 index 0000000000000000000000000000000000000000..29c4e98d5b953b96722a1510c813a920047cd265 GIT binary patch literal 36153 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 VMb4pTCzK=roE=>850Ck!{10FIlaK%a literal 0 HcmV?d00001 diff --git a/packages/preview/conic-toan/0.3.4/assets/plane-geometry.png b/packages/preview/conic-toan/0.3.4/assets/plane-geometry.png new file mode 100644 index 0000000000000000000000000000000000000000..7a0711bde73c73c14a296ce6c1e9cce36a96afb3 GIT binary patch literal 29860 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

<8@94bo?9?n6v$6 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 Oh?1O|Y^Ahm=>GwFv?GK7 literal 0 HcmV?d00001 diff --git a/packages/preview/conic-toan/0.3.4/assets/slide.png b/packages/preview/conic-toan/0.3.4/assets/slide.png new file mode 100644 index 0000000000000000000000000000000000000000..d33ed099d234c83d7692f8d3668186ef367cfe82 GIT binary patch literal 165528 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

4aa{KTb3Amph!Pbn6R)YJ& zIIYe1V5EwjZ4XwitX*c@K4*kJCj`$-zVDJnI&Q3CMihWFKl@aZrp1ikGT-WH-S!wE znZ|Nw<_x6qfP+vn8m$fkivXYRedO+bc+h;OvFP?ZBId(yprre71O%?FHs734YptDl zWePA*vae8TVM$3v!LQQUV|H%_3y5$^U^Kwc`w#3LH~l@(WkU*YhLATrgp_jRNwMUZ z5q8sHlri4gDK-A@^naKqT8#uP10E5OGpP8ukcY0=u-7HXt^^NbD(m24!U_1jXJ)bM zT5F8FJ_h$?&?VL4;ADYr^8g{%-8Wk)2HHzA5gUQNy&%>t@2e4=l%A_+LCrys7rt*%UC6pO5GpU3xpW8#B-?}O13?%oLhLHE#H-*fK2 z@wUd&((yd4b{HY7*^f3L#bLJP6oi}pqg64o6h1AAh^jpbaH~e#k~hsab2c! zJIqy5#$uYAfs26y5dlJEheq?2Sn5sz;5>i=;3)DzN132b3uw-qngXdc>pRQ=){2T7 zRZ@m2jDIOU(;iGZ>DLw&`8^K{CTTLz=1z5J0L`CKUkeYSad?%Dj4m&}50!{+hfRJG zgIakHZz=lchMer9gvBMLC&wzkN?Vu0#+v3gB!z$2D*)Zy%R!(4UqR3}xr}EO266sT znO!MDL4S`K`Z53#JGSNI@R|NWPW`>#bXroyg>=5m>p$mIl7cOy!X+m+2?DvD{H(d9 z*@ma5c(rZ*#chj*Km_>RcOOaU(u9cjVl_!cWR9`(la>P`s-#aKGmT)@#1V54s9j&g z4hsu2013us!dh05grcW+ZZu*OCP;Z!g#>6yS+G<9dnhA4pL)E1+M1A%^zzV*D`*A^ zH4bL+|9ylC`sRb2r+kTqo(2Np2zh@UAI(kAMkB&KXZM6+sk+%tI^>Q;-$U`*s*)cVa)aIj@WY z1*$Wq#+hdFS#Y_kZykund3WM1may$qi}ByO_ucJHFxX~ssl`55P}~VVLu1`M8Sr^h zMAapCZwN40v#-ozJF3pG4vV8eJl#?%ODF<3iz=2dBh84}ePXAcJXGcIa8Z0!Mbw?h zCjW~zIB2bQBY(lkU&Po=U$msPamO7J^!WI!;)zxT&f3rul07#wvy%qXZvxvMmaZ>+ ze`D|Sz;5#e2Zcb^eTy<_6g^4R`=N;P`pOiZks$+S<5!>k%eqNjrZlWnq02LWo{}U zm$EsmhDstrl!yXCVmY>Iqk$o{vc|t!`oifzdrtMDz8}1{q@oEllnjK)N_5Ji6lm0C zrQngU`cisX115viR72EM;GhQA8xKNmIMDE)iG)$i{s#dTo{)IoVwwSGel|LMP`o0l zb+8H#gFs_tm$j8uwsW^=CiiQa8uIAK6GsEPK*66 zyQhT)tO++12nO~;)-Dw;#Fq2&#Ll6`cbeY>xxRAh@`=vmCiUSvkYo4+Vr_H*P80Os zH{s0kTOE4%8_@{lVQA3S@9)c|N>nS9DN4QPWfcI!E-o%bcOb0n{!H_r zU-CNu^Iv}HUELq3Pk@bzrV_Wopga;rq*M`gfdi4G%oI%wkAZ=TEq(Dz8Bvq3&c9cGeM2vikX01P>hnUBV-xI@!aR`QX(UUCsY5>t%-GFrV9TyrD9vhR# z>P+t*pwL7}seV|nf|qL|l)BoYAgeprRbmTF{C^f`wbDg-2G>J6>MGcyB39%{{KnK_ zG{j5~pqZ-^1@X0ba_Y&=Yk%QvNw=5Db}tkq%a@)$aUy<)jc%u9wF2R$=B5^VZHc03 zteQ1xbtcf(V@8w4!>%yoc2T=QV?xO(34#eWtl0=$oIEqG8+F zXS4_)z<0*I!)rN^VDbwH5Q2Ule%k`XOH)gIKsx-p3K=MTf^e>s$<^#J%Girt_q5+I zyH@@7%RrAW!*!%c$*_y`U=da7;d04rdgQg?ux90g zHufG+ZP1d`CJj1h(V{+q24nCI_}!O;jT}$1C4#q&^yF+$J22Hga9h7TJr7M9qNM{83nl}~-zsU+ikuQ2Z2FUJQA`I2X@_0R+&D^RhWa#h^ z*_ID?qgdvM}l=ZI5RqRb>;M zs59lxVnu+k1@IchTvtAid$Mg%g;!%~Ej2POmqYPRsn$fhd~WxVtvW)*Y+DYGuCp=> zuqjATFd-nq95#KpS*c==#4GBx3CC85mrv#CwbU8jW8XSm4}viJ!@<7)dbE5k5Aby0 zak@=DO|cCo?!UKJ$a~iRxTUmO?x+zLnwk>c^x8eSDx%Hs!6CvGus#qi?3J2n;ITaE zuUEXWn6YY_&+=FcxJ{OBk8s{4%vwOepL5(FJO}g~JFB_;z(5hLs90cM2sxY(gn(K9 zvOe%QumVzcEC_sF8|Tqgoyrud0AX&N=c+Rh9Pqbm=3uj4_0oF}M7gW}H7Or?NFEc8Qe;CE7TQ*;R3f|TUYiIpurm&4aD zhX?snFllPol5sdD>))l+{Ut5#?aESEu3!OG+|Av+rB+V_yywJ!IUFug-08T^(GEVMBH2<~|sM*JCphRGE4nK9C6TMSkCF(U<|&sYShZ zGZAQq)#)S)L_#au`R-MWmwFaw*qhW^08C_MHH)d(owJrvGZ}8Sxhfeb*aInvRjyhn z(40OOI4C&SFQ?Z{;V_oaWCzXrFMW^XK$G3Xa;u-(uB7>)mR+DYhLTb5A;5S?M#jlS z(~+Cb{k~p$804@LX9MGNcG3?ekro*!rTag%005}~a_MA+U;Y&gu=9c{6zQo*gI4cFc&`7Eo!0 z0$g4;iwPGzU@~LH4k6-1@IzZZLw~{q%|{tRqVifNhD6%IP((x}!MRAXm|2;OkE^ap zeW^nOzT`*>^YcO4kL43QI}cTtO8o*WTN~Aiy4}5G_ImLd08{;z%mq>dnp)JdvSb($ ziK;VroXmD6Yi*UQBl~a!Tl7@uAQCN_@D=itumKYjoKj&F4y-7}vZ|g6aawcMba^Up z2^Gko@~VpIW4A}z|D~n#X=t@*V5~*m;wo6Bn;-j}^#T1)RlY!&nZUHFYIGkC>ifhO z=I~+wEhu9QFlVcSj@Wspxzxx6S)RJGhPFQWsX%CGX(f$dezrc|KqitJ8g$fdH>=;J z7kwjvq)hrnvge~{=x^vRa#N~+fN&e35STwVS4^pqyvv=NnG1NwmGsPVEfy)^2*Bz% z^$b1}VFkk3OGyOLUMr(RiWLZ>C8}x5msZG(X39H`FaaG^`>7bR&`S{shUlQa1NiYb zb2Q8~WrB26T|LmG~@9<_yrHf^;DDD5=S*LZ(_ED-Td`9N=r& zGUOpdOm@-#2{5c?bK+d&Ht9yHGgIXvz!*hD3an2xWs#dIv80uiwUcv_hOBMmo5W}V z&WKiDtlCNr!0GD;JVLN4UGz7Za-j4Np#m5ervuZ-p|W` z_t9}TQt2^E*#olSfWwxD`%pjMLiu+u%ofazQJ^^4-PIi(YVz;OZ~ts}&+_VZUQe($ zI6Fb{w+TxYm+eYz%;)OD51I@e9>A$<`Zj<-NvY!s3cBxY1EhS^i;I^}O2~SKdVsF8 z+4X^t#|^-Jt^Dcjx-Gx~%Pl(axV<7&!~O4RvBW2D0A*D6#i+|aFEO>pCW0LJ=-Z?_ zuOq28Q4$$h#k;P3zprsOahoPj-j-!4__-wQT7N1Qb>} zfyZy=4aP)U$k%)JUU1m#$cKHtM~uPSJJ^SY2xC2RO9SWAb;^8uWT1+!2Py^)I^CXy zu#$d#k;fC_C8q-veACA%QlLiUlOHc2;CjC z_G0%1pivVHoexE4GY=y1-T?(s@wx@dButwpI}PMaj%DmGK!=qM8)w+6;Rfl%H#Xjyq%(v84CK*hkqMAO}=C-H~aQ}2;~jfxmK z1A_wSn|qu?()p17eV2sz=dk}w$5FUI7$`#^0O(6q;Q;}!|NXCB%O6BQJqc#TCb3 zn}$rdRF^m;S@xpe$z9ybknm)aUT&Ce@L4o!Rc+vCe-yB(=o@WUP-9Y{2@s98+vHI%lcBjW?SqoO_k($TWl6?G6p=g0b8-RO3bB_-PN_o2yJL+*l_k~`u`^a}YF0I)Xed8}Xi zD_}ft=Ez|69upX@d0Y50Ye)ep&GK^Vk$eOyB-pi$KQ7Dj z#A!Rn2iewLMpU(7e2WDr$=J&U0KQXO*Rmp=F<`#$Q2wd;4|hJ27Z}N@xt=#xtMe8U zg&IJ5l0&|-yj`+n8~$N$IJTSsLTef_>00i{d2 z8|jdcPU&u>q@}w{k&qS;P`XnnwindCwW=+9f9p*A|TrmI`xg>PY6<{tqULu`Jy_Y)?d2IivLs z0^qYbi#X&;{gqD}SUC~$K`Zb8L*NGA)n$!}w*~`Pz;?m+D*}zf>A^kX*cPU&k_JYz z!h~c9_<9K5A+=;eYj2)P3=WHtdnu*el=km+a35R>WY@|`gpB^2c_IXi??#de_;szE zyi3vz>%=l1l6r)IZ;si60^mF--6*ZwUraiW>InOe2a z*q|Vztd`U7AvI;~Ta& zD1VPL*Yaw%M;U2ZGFx^7e{F-G*P%__T-`A1H*P4|GUEs1oXV-Ezn-(C3F$*dLe%p6 zh7}rXih_bh#BZjksmKzwrc!<^-lZrbBll>p(H+#6R?KZj_nghL+!lU7a!5jCk!2xTKAwtXPj@c}!LbBN-eryUr3`C!b z^Yzk!fk4VznkH;F*GQMLe5%&n-H#hivk~VslO2qHf{x;Oxh>bcI38XwNDZBjg?@C@54FQw+9)2*`nXH|9Y|o28pdLm>mT{ zD~8!djbP2kS|$jy{9`npY$-YI(&+SgNa-vtIzM^eWjj^oJ{00Ngb8=;7XuHVE9x?9 z3#e^6rhS@lFGR^;+}#&rC2V2``-5};m(0Itdv|KONRg6bQ)4AM`}DO)sNA|T97*mU z{MSx?&_)u$K>f4T#|>UBps0K-LHfbxI1c~-3HeFjdN$3osz(f2DeLPE6w%y&` zUUc9GEe zQsx8vZND=yaqz$k$|+e}+l1Mu`B*+F=4&PWl%PaKLW~XzEgs)c&Bew$1xZp^k#>mE zB4$~0v^bookT2p5VJ-@qIyaAg7|=0$BO@l0*%{7Y;?(O0R}y~3jJBsYP3O^3FBur&1?a=2KXs0-}4 zDV@d?e_>^5by0C}oSG$`4+CK%!Gk>1KW2Mn3;71XX*^aRzJ2}Xv|qzRkpZ&v0961< zeaU*|7?q*k&5DW7#Wr1G2VbjC^k-6u5?3xj>ileEEM!S zWgi)aK~m2cPrm&_=iw6t@bf#i+_#gM(M}T+5M&wCBC=jlEVB{645u+J53G9ZU3YqH z!#E)VCRLeBi8d1=T4Z+#<&iR~b1_`75qKmr#R`B}6LENjStNt_-w4=NBG7n2G6?F! z8UQoP=HuYvya)9*t|MZZ}P%VrYEsYx$gj1>cWoa=b#=*;#W@38Z{Mm zVXD`<3~{CJn%XQEQJ#MmC8Vm^6Uz8)Ui)Ft6X?EizneU69FP@X7qwKJwv7N!pIdVQ zaH!eFt5ZydZ!B(1H8!97^>}RwKEP056>@^y4Q%Yj+EO}&wTZ=Jkg3yK5=2$z(QkX^ zXmYwA7g504DCyS$k$LP)=H-|Bp2BVMF8M`(ERTnp_=`3Iww%gsN2zFvY}Hz(jq5CVie?p#)gLv997=LysRl`7$@5J;(_>cjwjv=|}q z`$qA#H3|%bpBUE$nvnn5xF;*w(hoH{;5&eWf|^zc23mGKf5!ga`(Sk9)Q%Kn5U{nW ze}n7|@SRUiwCrao5*g*+ab2P#&@(aY%$JLcXFmrDARJ_~a9_4msRb%c-N_Ps?t-No zTW>&9e$l}q0@4hpJ?)RdsRCdjT~^y$&vw*}G?yrGP1^tovAKj<(0(Ca%Un>{>)c}N-33$ z<5#*^Cdjf3h}2#Z6_ch83{aXF_c((nz;nnqJYH8`Q!@!vQ%O{m=)Hx-o)o#XqZQ|m zAgcnl`qbD&z2jDzEBYd!HA1%wz}JrIo9v>aMt++GQo!fa7o}C%Lcfjt>Y~ zS1#Pt!6O$zg7j}qm&?b%P6pc^*{>|~H~9bt(C$yr5jI`-u`r0k$h$*7H`edZnwoFC zDVfC}NhXh%Z5I*(`IleUd00l3x-tVXcim%5^B~{s?D^&M`6lnjvmizp`a;;-b0KHb z$EbTLAW=uJy$Q>LwedFO-4zqmagFC=(Z$2ZN-$KebBJo+tnOoMUWkD2`!cOz!D`U# zH%A2hvOKRS#Z4_L+Sgq~A2e{OJ^0<%+k=Q2J-^%Z+pb##YMbvYh7D*ByS}Srxh3M_ zd{^n2sLeFk)3;S)>wGcorxvGqGzYq2T8{Zy&gUl^PFhL!uhDBLX=aMl@OfNWLi%(Y z&8AjfZZSOQpyT(foD>K-m3m)0)CoA{|sEh?gr4o`K%*V8R3$uZ-(M?d1_7a`NwW zvCEIk!xsxT+Nk)Me>yQ&#*DhB=|R=_CQ8n%V*51Fsjdmg-=Qxt;I=^?4bsxk0e}#3 z<8lKtA%-4^JRsq`K#S@K5GjPbT~F$eq{%l=~yB;@!!wwl;8s#&c=gu-N}AVFVBNE5>{s! z@(^r+&kf>Nez**TFV#P{ zv0|CONQ5%w%0wk>*YU8?@Ofj}HxyDEr#H^vL)F8`zktK4RYbrD|Kel-+`B38IKJ_Q z4>o#&KgPwz#WYUZDbAW@z>%W{88hJPUPPy=I7*3qa%pyoY^WTa74q1NFnh$zah~K# zb_X{U95o%y31v&5MA#$QBKDV~Cv$UIu-eDmGddvUj<8mdfH+7h3b@fYD z4LdCnKw4(Qovhy?miFD^;UP}A9H_pxv%ogR!ir1f`AIB13)%%1b4^0Pn_L0;UYzho zv7F5MeMVVc7a{0Wf-;PSbR|qVGk#9H{QrmJHZg&COETXa-(yP3zc2gKtGkaCD4-mi zOv#K!<88kxc`w1rbsAi+mm@VY%+_Sp7hytB*HTO7b0n^{B@*}57-Re|7Aw;Ar#9!I z|6Z!Awz>jdn+0-$9h~Jh##=a1m2#ZMMqPI0y|y+q8yim?B6uA5aw0f5#2&LmJGUA5 znu7yB|A$qwf4c1*IQlvDbAY_x%Jy~=w#_l9t+&T3vyHzx34S{N&agQoz#GrP&j6>70SW?BF5#RKz?{;i94J61)f=JI|Q%L$*G7wp57DdV2J zC)0=ii9Xz5VbAhKi`uh4vkKoHU8fi3zu|BD+f#}8Jz5VGnUeAONlNMNUcnfGfyLwx zOV=vm{txNafUygFNqR{pSnYD&1;7G78HL2f7wNgtN_=db!>~*F&K%?R=q*Z*Rfpa5 z-1$~LcR!YdD%bUOCwr7%0j~Gc$R;*YeMtCA=wm`>ozub9R~X;*^#ZgDH*QVblp+S7 z*+1D|w#+tg^hsK}Yztp8o;x1`C^P0;>`i6whx`&t6H9RVA7Az14J?%R@So@u7`hK& zHkT?6H!J(_eN_Hp3##$S+1`6yT6$)MNQFg451)rLal>pmtX42xNsLPa=c-Z*9dKz! zo4(k7I0~reQQVptD@>%3?`IjVi4< zV5*|J1`e8jr}I-=Aewh zk>Tjj4n~!=V4(eQMEN9g$M11nh9Eo2rny(GKIljNN!qISVNDXv z`Jeg_!ck{3dNnQT!ED2##XW;h7-4YeviqYUNet)&eMu$0t;~*NMgx6}s^K=s%E$oY zEI#y7LT!Kgv7ujX;blW!Wi8DMv%5P?Y5cHu4LBzv)L0XQ6;T8$j+?5>`(KBv8524A z9_}t_f$iq%`VX6cB>8K~>cM7F@fgcW`|Bp&trHJZ4jpQ&jm-^rHuh)GeEs8BX=*;# z?ZEWk7WYN85gx7HHUiHtF`snupw$C0IjQKf^-ud78BG&-%feVUL!aiplv_gi)#ovT_?N|aB&%JGBC z^E86>5a1(C-a>0r)s^J|UG%Q5)i~C*Y)($%rG|kdg?W9CY8^DxzKVCM#cZINl&6(^ zd8g#v#&fUz+xo`)qZH$EkC~(a4h{|;Sj9D59=K=6k1}e6H-Lfo@goV;svJ5${hAm0 zxN~~&X2z0f2<%&1XnyP-omX#&S5Z^jA1nU5X2H57 zDH%=5jW66gGa|}2lNMTTwvT@UJ_Odj^g5J+|u#W~s z$`L)Bb*?*RC%v-0=LwK_2A#RPx93~+_VsO7jg=O>U%mAy-fWE`Ms=OYTE!wwN1u2Q zVhE0j<7u37NB!ijq@|$||GjP~B1OPN{GPo%kWHz5o>4rHAfz1OgQJRLZp0XHXllI$Jz> z;@|9%%83benH)n^rZYUQjWFR3kut$E<)C0;6)iPa$DL<=YNH(5@q3_e>@H1eaA1Pq z5%ki^fIi_byj1MG=f3LlFWo1t%f`Nph$b0*zA2mi#SH92%_G?95gVH8IJHrO7GJ|+ zHh#LY*nvV_JJXCM?CkczdnwQyEmo>8g8Kq)^>KE7cs;nqSxOgbV}C@3pud9`=YDiL zKV{#9jg2c?XF(2OW+j^V_FsUow;P4-SWp4Xu9z!phL+M)egNB8y7+|5-#x<$HMO@` zu(=l@b9$MZ>o5GlUT;>Cd{RhRk{b8X`hI(UTg}IqHWE!M0PNFi__vb@n{}CVpA+CA zWfjezG!dWDSi&`AEKJ)mD~iILyv#L+n?pcXfK^cu7WoPnZ|1oY1-9|_4^C#(bKWt! zP#UaqZ6-{)4&!n->7UH~t-AzqExSa?_}~q2PP=kWh|sX@RB5{R5N$7rw-2B3R&Go4 z_AYsfLm6yEV_R}yAai-b{M!mcXIh}a5_)QVwQDTLH5Sqm1bp=RBs%&9q?Q*yC zinol3{jfkiyXuv#IeS1tI&pGR7czui-txo{vfx4QE$xu}7tzobS}uoEw&R9K`7`p0 zDtz{FB`>B>R=BwI%C-B8Ov5Fz49oe)9Xu79j9>QjvC8*lD3A&xB_c&l=%+MtI$_84 zFx;76T~SySF*NY)Csk~PM5ja0l}oIl(fF_5*}aX;jWH_W-n56xssyNO^2c+ z4?_%u4#QDo%b;K(UK&(rd2@CAEU%=f;W?zNqcitdmYWL$kz@IZ)%YFQ!QPx9HF>)G z9(hgwHwz%LWRS`~>lYE}e{*u4P|bLZ-)5NaXt}E}I=||{l5)?sy8X%I!90(aZ}!~- z9*dUN)ZX#TkqHGCmm@`|@$p4-$Y-)Z`?KF5y;{xR9UUg54TRFd&WrDj6LNn2*dvR5qhr*ZGN)R=HT+dmX@vj`7< z{zVr}nYM&*^tb&pRaz@}a^{Kz(Jg%l9glAxvnL2|z79J;ZR32Bs*b735&|%v>I)H} zk1=t&`ubGOl;r)<@6TT=Vr&2+-77u?}37I@MG!r!#C`8&JY z+#_^USfK}kVGeG4DB?H%9&g{w_7C>xa=LqoYhI6@ARRFapfhlT^dd6;?SitZr6#Lx zmDfxb?soMapMyFz?(I**&P{c21ZsbGZTou~5|zF^vv7^xLSDqs`%|{Y1Cj)aD=k}mYtVwpX*n;w%_{^#sHZ0{ePn3M)U-1s zvM?ox~i2}%!|J5=&hYD`Q-r05+nH&ozGOmLm;>5-e!X{xH{D0dTZdOeRs zgI+PQ%+wE|Lx4G<#z4p%`zUDvL1AHXM=_NWlz^ClTN={-jP{r^|9tCLPfyQv`s3E_ z3&M7e4=$BxU2M{enQ;qMp(d5ir!|LyD5ygY^p=dym7UHO+%96}5-89YIq3jlUJZ@K zWVhM5cV*ff=m~AOly}?6SErgspSA3TpK_wG_x{$YQ>qN{Ui{Q&!^dObqQSq~&*D6{ z2iC8G;9Ops4OcAtV1!Ck_?D4H4+;Nk#96E$Gl*fu_^DlMwiFW=Gm=hNl<4`w1QK!V znfkf*cSz6Fx#;K)twdpdE9Z6MvvdJ}RH%*=J1a9UxbYdFf6)k zwLepXIixr%+lG`7uQfjIa4hzQAE#AEL504>f2|f@$Uc4bWw@-YJ-qPMRY`Akm7qzl z2gtYs{$$ZRCyFuS;3i(}3E>kHUZi#j`F7d+`WRO{UiX0$>A`k^8*GF0kHa0>Est;j zp+@n)z%u{(3>2EWq|^2A=^-`dr-MMR`%TVS=X!V%36AXNdd(Y+j|idip!!tRPyv%W ztshRfBKGvmXgUSwxJUoZSg;rrmKMT5|Ek7X98SI_74?4OmBZ)t`~@>z;kNM&F&nQ` z8}DMBS3OUhuF*@M_%&XM&-G?=au%d}NNVaaS=q!`8A-0VEUQGRc)`S3rWT}3NNQ^tTu@OxtBxO;IQwd57SC9mZJ zhrKs{^7xrN-QPhOL)A!>vrC(~X+_=2ZLN8*AfGeub}y2Qie`>VQ^#&t*HGu?C=&CZ zID_F4?`R_j-}tXCXZRhouFxB6RQy#;wy?G>EGY!@nQ;ey2)WJPfyg95nVD0ymvN|R zx%)u&`*6MfD4%s+FJ)jr9+W5#^={rES)1ej864efpU_IcOVTBuig!=nVprePr1x%* z=uAbD97DnWA3r`(VR4~TQPGu8OKfaba`(MUp;8?-^h{TC7?d+oJwENU~8d&LlYq^03>>?NFXoU>Ac3 z0zuAdaeseL1&OVINRzq$S)LZ1jAMEFA=O54qFbiYiaWl(x(SVY0IM{W{MU*Z>7M z-<=uab_+JV;a)ZAJD6|r*jlW*oEA4Zf$QHwU*pM?O` zb->C(zq_)duBj_LS#njJ-|F1e)Ngfx8eTmsR zckFeoYNa2`GDCh55|oi$;(Bof^TNB)Y%v*(b3oxw(A+e?w9^Y1aHnF}IKIx#?+>a5 zJXTV&!O3M@+G&8=Z_{%kq0FC6Vv11>ri(vu!neA76^pdybdfYzq5r%A-1eZD4I4c~ zhxNrFh#KW(Xa^@Gq-tfW#CiDY4nAM&HSP$j3@k zi=kwH?UA5VdDZa>o8yy68=E{O3^vjaRt)9tUlM_#Yy#fS%xox(EcA@U1;ZngT`zX3 z!HK!zP{fB%^uR`k5vL)O+uIA?$9HD)N%@1Fm8$(H6CYK6aNquW8@M$*j!JxW9UTEw ziku=a4l5=*H*?X0P&gCAL_-sp-`Mfr65`{N-PlTN^Aze=(KBB=ffd9wFpxKJX==LF z*@SePn(gc5RvMcXkQdB%CzJgetj*(e{}fQ7?Ov)}UkA<3CV}|1N>aDP`dCICD?yI& z#nAHKTgn)Wc4}k97zrEMlWcHcSnBXDYJ&K{#w*4Q_lzQP5PX-Sjrq;vYii0+dD`E* ze>TC(^rW9U7v&PP&n^jhK*9I#z@b6Z;?|b1e21eu|h@V#sg$fFY zK%x0831XZBsFf(?sTmpD@el}j>z|~EQI1J(PZoVPxtl8R&#3(>p7Cvn(6nX|1 zwzEU%1+A9uKWJ3CaoY7ybpREYVW5 zF~`|pV-XVGITsb3AcghwEU4qt-L(4KN;r+-}4)|AZ`6Hb@lzDKfnz7Pz@P{=Cq@H(rq} zpL__g=YZ0bTh>WrLXv|egb%B+69^w%rhoN1_uT176CuE5VJh#9J;Lj;uu^k2#SU2B zX5FMe(rwEzL|<~rOlvN|0$qYRmI7;pFoKX9Ux)aK4vjTbK3g$~sE9RQP@t83o<}R` z|EUuOIy&pL#x1U}6J8ymBq7=oA4zP@po6dF>%?W75J}dqj`qy zm%84%y1Mxg5%^xHF8`hO;L+OJCu-LC7C)Df0!qq#AbL$M+=AHm2~Jx#sk)203*=iQ zw|6H7#T=~eXrMNtRWFmmE*?lf6kA3_xLYsO-=~~j8^q7#|x@{3cOlh znV54A5{rmQmdaI)JUf4dEt}j7|LoI8sI8y=C1H>ANYQ(Iw?mbV`j5r(xQukHhN1?F z`U(oyTlRuL=M#q6a1kN*OR}yNk5yCUDK#j~ASWZZx4gMCi|dM?HE1VlTR4z3A`lUGXtgWZVUHv#gLn4uD&z`+WojV7AtR;5B{5ph^Kam5Oipu~D> z4D_pmiRR>zYMN0LaQh@*z-5{JGo-ToD&G`@sr2Ga@1ISR3Xxo;1Z~8pIG=T%x-CTgpQ%C%+d!MF4)Hz z1FaX(*SJJhTbEZ%47kSMGh0ZZk7=pJc|}=8SrO__q;jj+yXTP_J(^z=_I=(7U3s_mT4kenHTW)2jM}sH&-Z7{?)das! zs=@q5F$y=jqozs|3vCt*1T)iDqne0FxxDux<=O&HCn;z0?-WpL51##~E+{J1R4=d? zl>qRkrOtI*OY?|YB)FFDe;=GTT#qRg?D_x%eN2XV z7$y086=(`F9s$Ez5%M!Z2X!@Z=CX{h1Er&*>h}4$uKR9Bd}X;*A!uT=VA+2$uQW+t z+I0sFw-enWN|M2`F)|_aL^wFu5h&;3azI`IjVoZn68`Hy+52Bo6GVxG9L+&M--;sp zSxvv!6aKNdwkzU@JWiU~l^3}Zt(G;NK-dJF7hls(g{PdWTbD@Z;T-6XBwv_hKlpk* z>|KLAw!0dcaq2oy0L5Nl?DRb2>$wmBY)-+junHGX9nVfMvxD*b&t#Z<>yk#$1BdaY zHM-U6JnSwY3sf^8q5qS$3jPmfQL!Xyxm$}GsetmGYHO*4CfCfm_NMf5=H6mM=_Yp8 zyIWgMsunZO+{|<|*-6^rgNHe8wgxkKd)Lsfo!Pzx6TJ`9V|iqCj<<`cw^0aie_Btf zV`|>`gem!8AKRSH;#{p7-QFqk@w*wcd^o5GawZk>E6Ococi%MCq)C>R2=9EteaPs+cH+-Lvb%y)M%QCEwpHAs?}n8?Un_CZY}L7EyI zWf;cQy`a$5bvsDrcHE0;o|9oVa(Pwu9@G{%4(F5Ye}~>({B^ zqY4_}fS+ z&0_f9)g-9R+SEs#oW>*Mz2xV&D1Z~;=6tPoGB=|gwD@2#TtHuc4X>a>eKONm+cyO| zlem4bU2+$vI}Jm`D3%%>d&a!gK3t9EmFW@?KAiPNW*}yV#7X;Kv+?t^zVF#V<>xN> z!DobG@Nd5Q6ch~5$B2F=sO|d-sgt^u>bNsy%qwi;t!K~5gA*yI?91lznw-JakE|k` zuJR6{soI7x)U(oA|KPqCp)FQriXlE*2vb7(Rb6w%2h~&u(;V29_Tl!^+nY$fU_`E* z=84|j)T|@H;xiBYD|xuV?3#&0TO82yhnWO|xRYU9%np+TZCCS$LSdy8K|6!BWtvdM zrIu!nUGFNI8f^qAM@IDQr6;HRf$(Uqn$+P&_!o)Q&Nd$)&tzNNkScsf~+n}sD3QQNg+C-*|lL3x$Bt8i+$A+~K zf&Y0gP2H5EY;Z!wdy5&?oEXCuk?iy}h4|dO&2pO)?kJf)H6KDNSxE`!e%oy6yD6VM zUZKU#!=;3HM4X-I{-L(RQ`|k}y;~fPDnn)Yg zn$OhYX77nSJ?s4ki6`vKhzRbthpa?`#bAWPwAPQW`=;zJ<|=KQ6SwY(V5qU!f%Ax4 z-_ZD1yEV?j+N{AUO!`gj!6GQI&Y~0+kEnHh(Wl)H$%qLb5}SDe^kuN?8%1^o>_i~3 z0tjpShhe%l%i(TS>SfHxpnglM-4&$#6|hF8E#t!e$~P>zIz@$l!ykcoOm259$yC} z5!xxB;x&V*5zmhChH3M)IQw8I&2%!B*Vcn;CX-8lFfe-eER{w6`zTG#xp<5u;Qowz z4%j~at<5P#MF|b1@~|_NM0ki64?hb>g>TOeFmZ_R?#OMoi*Je|sR-YT z@M@JHIFvN-EmP8K5=i;CX+0`&we6Y}sM=q~s=W^=1qIG_xR%}7-eQC`NF`K!Nu-|4 zOgW73I!Dp@A&L{UHEeHIn!+mkn99u!JiChpI;mVj!hcc%-ZHc?tSNkO3s~TaejsZB z5~HlN(DVA$m!2ZM&F1>LD)m<#dZ~>DjSiGIV_W;)=wK8eVxSmO5V6OB#HF1+QT^n- z6s8~D5Zc}r3pSGdV!egA)o6BdTyu%5lNBMf-0?#Wbn*1{^wri@qc!^ykp5Xabw4=m z8|_D7_B`8TEzw~!RVHQ4sgYq7Cnb%Swm+E^ulGkrCat6+GQJK| zzrr*uJW01 zPC<^#!lb@&O!sv6GHB-^vHgj*f1#qG!a~nX#P93pC_@juKQ2P8;J~JgaXG(3#Ao{g zw&{~ii29$^9+-S3Qabjbb1}`7>$7Hak_dSYdj)R_1JHqYT*Y9J!{v&Kt$|}6c)zR zFK;~nib&OUbq%vod^pPAqy1F}s(64-VCe1Lui}D%;%yY4Gs{WoHXbK5<^_YSkOqtW zj2Yt#-1g5YkBRT{nzX5zO9on6`6P>;tCJ4&9N00^5q&zpO=F~UO3^N?wU`jSPnt>@ zkiuop=5}|gr>+b6gN(G#@`q%@kleVJ;Ng$p0}|fZ?1jiDw=$JwupMoZG<9@~kPSwF zHqqI4NkFm#r2@&(f)o8wp2RcYprc>eS>DYQyroI5bXu5{1?b*JP++jd=s&>j>^3E2 z+2cN~Tm^J(g}un6EB+IG+E*9v#8fA*sPpb*>gxhaZqe^c8u!vE zm}3B<`(jh{$=RaI-A7iv^bg9ymyTmwbMJHxE_Be{wFT_KXztmDl9q~!M{ZRx(aP&` z#iW0?%;+OXq_oQyxAZ#%eHKNp#u9-iSnqT3y)l^)5>XT1Z&P{n^`pDLuqoDxcxl0s zTJJ)0c43A;0_Fg&v)d7-Ex|xvMYD&%$^&zH!1?+ds^fD{t@g-$4DwH4O$z5Jduh({ zEiMkZ-cLnSGME)(y7A(4#??V2-Qc+w>bn5Y89PWK{Jx;pYnU|yZu6Vi5U0?D2*)Xf|_NRE5P*w$IH+F z>vvwWrma7`eN8-Dn_F9W*o8$!#Ebi{GnC3^C0|e;AAJ5L_#!%9gN+?UO;sI5z`Y50 zw^!t?nCLyYOZO{E20#X~GiY>j@V|7`bqD8#5&m;%8l0Q4p09T`#K2L&e|atXw>x75 zC>wnHRrZELjAkz6eu_60j&o_OmIV00m8&C||4F3#8VmK( z5Xjx2Ab>s|X9w>uv>*yAQI#fltd9eI7TBg`CAl-h%09s6fW5sC@??2G5Y{e;OoGvkDi~IlgoDB=ROdn)ffoE_DNwq5 z$cM^gT4T_!qbwyar-}(=g(6F3w#8r^d~?Y}NOZXv#dDHdVEO^D4JiEV6q|>&F9?I^ zB1w*4t^UxaB0%qqxGwssR?erCTIFHNma!uLBJ$RAZx6Nn9X4c5DS9#~kR z#hJCC%-j)yb%1&`P3-_*6XN`IBl)%iw;$5B9~$ zy!|&y*45Fe3Fl6P@4N9@R5NAFhn1A=qx_gpEk8n^hJwk;_7Pg$U_PzX2(;F}XGddb zUxD?>y4X~vo0e#(T>OO0>$;P8kho2COk=o7pb?+DJ36@lBNqD3?wOJod}Ca8&gpg0 zWVVB96*byv$_4IWR~OwI-{daVfr_9?F0OHI zo4J?3#rZ6`RPPxXAEaaDwK?vY=A7`e!EMEK=wP9RM>S>5$@4LV*UHjv$N%A^g^KQ; z)j-I751OB?3o+eo&;TY9;O=gY*l9WUdSb6DSyE5r2D*uPY#%~jCvM88OT?e>E~D(t zdB0y;yePeYOSw0&_O*e(3H1JTrG7mx7D#R6>+n@f;`BaNc{~Mck0P$8c|nhK0da5C z`D@dBt4<;49V_=4*jyfV*eXTykSyrZ=i$E~3FhUR{Hd*XXuDA1yp-T-0E`qx!zQQ4 zk(cxTj#kh4Nz==+_5J-fR@U;+UC1BK*t> z+ije0;XS|SWl{jy7J?)SCz8NRPQt_FJbZ*mnWZV?+Aizqz!&Mzl?Qp>O9E4NC(s^0 za9aS5Wy_b3`glFchO9Rm*}*h zW1dcFeCT69mNAgy{_8l0y^DYi6r)fx+eNG?BMJtPLX2##YHJDM1Tf5jSGwf)B>)7j znCapWWQaUYQrPnYUcbmp&tP75q=t|2$&AUZ<5m|CG5`q#a@HL#K0rJ1OxZyn_vY_( zA5%WPe)E&5zozsQoIxP>S&F;m$+_g=<~(}GNA@6SIo}N$Jo9qQrF-VThk)z2ba3PY zGe()ikC!LSrrRBU)q*q%Ovf4QV+}l&@WW+EByzbX<|M0o)^d)d{C^_DAi8z&G)IR~V1+71rS1wU>njXo z^t={R$$tSBfnROCXys>BRwb*yMy+;sZ7OOSfJQ*@!@xbh!+|mia9U$D7J z!IBVY$SI7*(g-I~!2)Ki;4KRWaw;AETLWqBZ)JYuLSI|Jm z>rzCAbPNzyQ0t7+G}e?}>a@y=ijqEbuwg+PKN&t|D-rFglMekg<(#*n2l9u!G!B$)m2CxW1@COYEsV*oK1C!+`B^aC7-; zkloiY%_hYDW1&i7-gSGdIOGgVvn%-d51j~E?iWLQBD66_>e!>kAMF?F$QtY<(MaI7 zIwp~6v6$0%UP4FYdm#4&$aCHEEMuV=9oQ70q()u zhnv$+9V}grLj{nO=}vk6z$Y?5M*{QP+iEL#i6IeB_~KBGZ{HHQ7GXcxZv4NJSg7;w z)Y@^vI{~jEO?`vYD2|$l4E^HdDRF0%#{%Aim6_SoR1^ST-B~(|GrkeY`G1m@pT5m>A4ZwmXY8(%4y(2m3Z1 z9Ln+n&#)p!ti2A-dlcc_hQc5q#((c);JgcMNGb|f!_!WSP5518s)R4NdMWS~Klq1> z%k^h_2|HPo8>gp&)#ougHP_f)sc7J9x@^Uq#G+K^&&4X=gNUAD0lX@=w;lqdF(P{- z1D4lG0~H(`BoUDY0!UWWr<71eKu=Qn`=FMz!NPX$of5wnCR7llcb7%~Z+Dy4S7u;< z)d9YmdC%i}SB%;gt3kcv$~wjN8&N>ea+%S*<<4hFm8`8{v##NGaysoXtG@MEUdE9Mo+p)Y&y%M_WoeHs;jdQnToD#YuCNCGU?X> z7hvdbC?dV3XPKVvK_U?6t59awkFy=(dT$FrlnZxG3`R5J&l!Y1_>G;v8veUFY7=1h1IwQdY=;gpwTb*H) zR7^XrJ%9-|0tLB#>aLDdc5=4(RU95g=E_S+^uNfUyRRugOxIct68UiT5`s!!hD!X| zLe9~cnj1J5osM%(e1N#UxW(TO#Du9aUsEb0tFpJihFFO`Z()0$7j>VG4DkA;E$Y@Va&`Npev!{R65cVX_p zUFLD>N_Slt@B2Ib224ww@>f6soWg$*dckP*|C7)gvi}dE2j@%pB)P|DyhFDtRP$9- z{FHqRaH>&8ra!b?ciZ7cpKoCyJ9uG_^9xvryfs4_1dz>xo}AYZG{ft8rNTDhVz=0e zn9n#vtVyHGgo}rbTRk;lnW19t_a!QKgaCvJ8}9su;NpUUjI5v33nJ~j$usrW4krP@ z4GhroMl!k{i;`j#AyJ~k@d+v<;wMTGr%_1GYHw-B=lbV+Te5rJ(4~N@M_Fp5nK`b> z-^KPdNz`Jk;KG+(SB7#*Y@d=I(XTRu% z03_PCc6v$7XV7CF-nj}`SZV6f$x*$n>a(739$*TAY_V;E<`1O-++u_C>t&Z_JRG?W z=wn?VbzKxreo?RYn4-7tE{@#hM9~`|Gm!b68?<;VXf6qcC@H`C2IRrG?i?`O1eitq zHU4xd7)4oGaL|{Chn&n(T%eJ3=PY z{kmn2;cAdk)!=tTL=@MseY7s|4xjk)()49iRKo*C<}Xro8GWmLjF*@7RX=^Y)*U?% zs}8~mFRDb)Lq9j`i<4p<4HPTOnTz@cxnfQ_vT{;mmA?1_!hUhAzFK>y+%nVHygg&E zbfeva212m!)fJZmIR&rT(8|kB8nehRTJH>Cz)Fb}sg~;WsVJ}bsKdQu1~6cJ2dc zV?nU%oPe)1pl7z)Ob~j1Jv#u3&Vj)J=S%!c&{G1ve^hy_*7b#ffN)bB<$(I6C~E+BluzZiU449 zP+Muq?o*DbIe3Q8GtlH5J)*YhfwD8DJWIvPyOY{>S~Hn2cR^gSJL$gq^KE6G;*s7P zDR4ZhNfFhxb$GEVftDCBx7#x!`6(k$D_wZ6Em12Zu9W)t0(x(n@%ZYWtc7;AoT8?gW!U0(+woi91E~m8{5fd70k05>Oyij9 z>V5Vf&v%tIW8f{{T^1~W69{W2!HdYQQv6f?!GuOG-K|4@Ds(biYX&MOkje*NC?6Dvkrp89{Xpl6WN>B({3-;}4fizwRptzqSv z4KvITzY#O%&>;c`_S`$xdtB33;MEp0ywFDw59ck_G!0ZV^iiK^#1Y&4dvWy82o!iP z9IM>^zz5=OpEy?)6&2$O?Q%y%^`bpcHEA-Xy@0y2EW~4u7O1fh&*072o%m*E+QcmW z4uYlyA!qtw>+L&P?!SlYGoa9gpiex9y?s3cNqom4rOBzcca+(}F)}&93H@-;cXu}M zLgnDlH4BysUJcj7bN?t{Kkq2;1IrAwDa9&2{>QayaM>Pbe|1FsLxMI==GQs4F@B47 zY(tXO)k3k0q#|nK-0x{=#V91m;h(8geEh`J7${tg++pcWIYK3{kl6o~U%qQ-^S%Zs zJ)F{8YApSf1_>c#nm8HYlzuT{^Hmju^G}$XE4bF>|E>taW6t8R)O-D z$eaR4N?=!oL7<7*OYb-8zQcyrdU)x&YH7ARHqAmt%DlY33e< z`QqZ(3)G4S-Y;!7Tc>z7bWw~Mj>NlK2W3?hl$FPe9LbR$Ei>pWFc#!1M0NkuQ|lF; zz)*^-6$TY06+kj%vx?$|e?)UxVb-_+qnA^@ZKbth2)4amE#NbO`x6rvOJH+cTxnVi zFOkGeu(@jb$$vR{{k3JZZAw42rJ>7QC8Y=BZ*gt|?w!PsB1ya+1sWqe=_hx+M@vo< zf$LjsXHO;n`;3XdH_qtW&+eTHA%{p(0NC*#nbvAcX0;L-x^9Xm)Zj``CxFG{%%uu} z0Hy=>joTQc?9^D&O$`JDe|dVcd)`mH1t=cfdGc9|%uC?eJ__=^r;)xd9xGc*8=2Uz zPBoJ6Uig7Yhd#ZJYrr3gLV8bp-P#&bQ&4yUG}fv2U1P8{M`zqpJ8v@Gcj zcLXA;Bj-)g!T81?GybbShk-}13&@H2rB#l>FCVa36Ro0KYAbu`0g12PPi`sL4HVlA zDJsR{6nF)oLBYGH^NfmY5EQb$!5a>iOQ4T#ZEfZI?iQC)kjcivV*{%8`l46M`C*}v za`Nia6J03W!hiqTh~QNmH7R#+x@>FsjyYv*gd|1d^RJJf)aD{aMtn=nyraO~sLQMU zm9^*HGY@pP#RoXE7M-1f5Dl}gCF%cH>Eu_X0zy_3^EF7%Jjck-Iw-6l5UP58s5ksm zRbBmpPcyCG+o6X-ig*!KMB!Qc)8-Hqrv0x@Cl%EV;D!MXQsm)`bE!FuF>7PY(BZYAG zha982%Nfot@3jmlCkTTcdRiTFgZ-%v>;}9vLp=3i-Cq-YP`8ufbb4#AeVJ*ndlJ}B z2TwhHg-EIl|K35IfqEIR6+7b4>liOCe#PgAmLvg(WlIzJlc(!C?5_CBp(f@51Z!{& z_#6%wA72KGruyl>Uc|y&V=kchPeN>PA5tGaTRa#v;=1^=O<2+?eos|9Y^`i7YyUhL zbC)H?ym@kdxu>0AH`4v$yU=SRY{Z++w-4E=nV+w-zv8&#JzDA2@H&#Y&pMye=+o`0 z%D>l@Bq+x}XEl4el0O6Y)k92dV93>dc@l&qECyMALl4JdDt_k3^d z99|#)o$CX7tR_7O#*F;(rO1lYG8OedJ3mM3>TX;#xXNbKOS9M47sC(KbG$FF{|`D4 z(AUD!GLUZGz4>bZTsEs?wNEKA;dm*mU>2f#P6yJ=u~u1^A;<{yJrK8z`28`04u z%leYLi!n1XgV(_#u3UqvDO6!VGT;G7z0?;jK$_Uy`YcqGsu&kpjsbjnB!#{pWV&-x zk__Fi2~2m&%C999iuY){rINoDtU{{*ARH%4{;7-!DUi${3OKjYHrKNlC7}eD!Efn= zme#I?y>}q@y>Qoxf&vO`8PWaK*cxufFcAVZC#rNQixD0%G`W^J43Jjz;&FOzmY>W? zOF1$^NUusdMY4 zU5ti5ESW>>%qM|O=~-vvmi%Y13COyj;#4GBF?GWLI1%Z@utc$(thv17KbloKp6BU>*4Aw_-c(p$WE319 zuPLtnTEO;8%Qp{tX|3@(7BciUpgXF;P3yGD=N~VtCbl;nC{Y~s4$!|Wr@fu)SEf%( z<}i$pw&%cR|5H8gS7Obb3K#RvI}dH~qKp65dY;ch>wSUig6?EpgB|{fby(+dEmTpz zg*sDp-I~QC*dk^vCD!2;H;Uh#*=Bc;i?366+B-}gA>89=d;G#_RZdZJ{FcKNIi284 zd*ATbrL{{iy>{i?s9i5`c4gh$z!ljz*ANlP%d7v+%C^O0lb>nd150k;PR+FOaa(Z| zRQ=AUXInGMSv0I4;ODP6=UEvffqvi4hwyN_*tRyPE@C0EvBli1;yXnTU?yc1pDfT? zN@w1>)3iWm9=KBp-;-6l0rHp9cI)ZdeCs7Pmv+qujp~9vpscF(2^cE3l@t~Kc|W5| z2d?3lXJX~MrMCT%E>2z$9ObaCX|QWoxYdSC|_y zJ@RFFz_9W08(Z#}&;BX0>xjph^4H`!nN(h4V+NF1&V%S;10@?Vg}9C{97!)(e3)F? zCn4YtrUJw3`{)kMSp3ZQNT@Dgip15X94zk#mhs>gEd54SjYc?7ZtHe(V#OYg! zKolAp*_Zsi19Ga@r|nx3KMtR7er6^3yR*D-*1_oVZ~eikhWE0o=u zp%-;H_9r2Qkv_Q}dfD#w}plfLx2#_V%kX7l&;V7~69p z4L;|`6zF#ftLAPn%+%=YES>fYO<3~?LH7ESIzzg1dT{W?#>67B@Z(Y6-(@Tm3ecTt zJ0)j!KTi|{NGCFDhX3#N5pDJmu%NB&iFQlvoGIYCn(uMWfKLXw}XZy+JYH&#VG`IdWPU3SjW z;1E9#iWV|nyx8Re0qj=wvCc}dMAf6C+lorzyv>xz2w2%`qjs_&<1TdwJs@}JU40IG z`sL>Pu-{*~$~;#h@SN0TpWLeyn^QleF2JsrdN1{}{TJ;~*8;+`UVRC0n3jOu(PB$M z(mlVwd+G_+czj=S+8GH*mJdt5V-|(HhuEgUrix*T|6Mthb!fJ-%{UDGV3Os~gM;yz zyIeR?!3T|dr$qdp1t`$t#-v>X5|&5J-;&lgf+)mkefk;#i|cz<-yYY&VfH|lm1R&x zNzTf}+D{<)m0|f~!W$6!1#Hn}_gaxuNbWS6nuX@Ct=Zc_+c!bQzOh|;A8d}gc5Jx^ z^f0zsijf1KvtWN9?oNg^J6OsAMLu38z>zt1rc1b<9bN)P7EG9B;p?ykUl5gq7L4KV zc4@wJ9T$>9Ed?fa%weN=qd~5|cEZCDbtHW<9eNqc8qcbC+Yi=$LGsq$?zCZMXE)Ij z28;h0FOYKC=pVuxvD!0KDw2!yF`@aZ^XlyngA61U{iT3N7lj{mUG6MDpsTiv%tIe8 zOd6gZYyUOXb*OtXppGrc&Tck)it!cg2;l`|Y893;VM8^ybZ^*6lq@c~3qcyIt91nR z*Muyoe5M|8$Z@`4XrJO4jW-QrabXZ)_$%qYAbLump3Oiqz!!AWD%Yu>8Jfe;dL5A1 zXcVjPn`lP~sAOmfwH`te&W_Lbrq54UU828)%AXWRp>Fj~bPWNc0HWxf;^fv=sL$*8 z5Quks%g~>3+soEt*WXKm=-78wcr_6YoCakqk*vrUx@dOh_lv*L7^5DHm#M;IVRqbH?JRv$R_QxMh#I;&1q?M)1;Bp$g(W!_KB1zz= zSy}r2jr0A=%%|HDaRTZOOF2R)a-!(et8MSPVXR2T*{|;=p8aOR^x`YYUDocr#Fux` zBc?GR_}yt4qVScFMMy(}iOJt)%kc#QB0rQ~s98v3#VL@VmDw}KDKuvrn44uh%7}N-y`<% z&a%@#pFig*JUO3DPl_med=-w2gjmAr3@Y3M9h&IhPjPc3!spfJ2bD5u|2=Q8SHEXmZPv zEhJS4Zp$wzH{ub~5?~#QV)=8lrHCK(KN-W9j!?V3P?fv@H6;`oMg##!2sl+h!v@66 zpmpIM^o{=thVcCKGPC9YAn_B@RA@v&fXatU0)vz?NR$kA9wjA}fz>gPF_EE*k;mP@ zvqf=chwmkRG#XD`s47Vj=6f>*yczZwc_}|8r8O{E=AM?&5?F4z>|a0tjp=~}9jb%i zjoR$>*hb23H!-Q1)!NCQ(|O6t%CwOULTCS@P^zN1RWl5A@9UntIon*iuDy#M2;>P7 zg-^KS_3axmFa|FI8nZ|gS}|Gz-W0Q|&-92iym+3IX#KAifR^qf^sSuHPb#Y`dG1xs zmrmcFQI}U&{%PCHMNiw z&l$an)5(3GXI-wz?!H3Uk~xb^%nO&z)@0suHr*$YG0l(snZq6tY zQV~kWy5?K=kP%=a4Gfw3IhwgW6!SwIH=%DRIjn?nd*i-d+~3>`Sp(EVt!{X3|sA1ocNbKi~XI1i1brxamkWU{lgY05b_8 zBIy06R=_mW`Ir*D6q>}XxqF~Wq9zxH^rT3VX-uIkcXj(TO_&>4;otNeSUZhJu zS#bJeAuGDt`FsJqsR}DD=|%>~2=K*xOm71aD83f6bzvXWcE~*hEkShD&$Vi8s`-dt zF|zo6`AU&&do;TQY@F!>7K_v%Sy}fPqx`##N`$qFCVrQXM zx)X#Fb_!YqTdo=;i0g>)jpXb%7q(=~QIor9?&tdzhyfKdX` z0D_CZBY?xAtgX!nT+Ux|fdgx2awRGu%vj$rBtAAVKGgADxy;P8Rey#%5mvEji?%B# z9+u&fhSSc3s#^S?iSOTYy|p#}o~ksUcJ9S7^_6(f3jCOvzM-0ef*qJoimnTtMs(#Y zED;SebgEU4F;SWt1Rg6Y-mGRUY({&~Bw+Di6MaH<9Yhv$CN)#aCbf4R8EA|t64jAacc)_^-<*@%1 zlG}SeH8s-_>>e2WZuw{2?)Qj{q@OoQh`UG}#%)LCv(f9LOA&Kc=MWM6#n4~hBI|bi zZcI4s93S2nmFkJ+vDs|!sBMu>NPuy*&3OyTJy=8W%dW?(0`f}NH3}8S(}Dm5NQp@h z@w$v|u7zo0)60lSz$mc6!NJkG>F**qoMSOD4{iy|O*n4x+RxL%C=X%CE;>o#79bQ! z;&Qd|>6!autL@`Byb^SZ-~$CsYRogno^T#G76SC z%Xgnj4QUJww`0!DG2TJmxra6qv(m#p`*Rtw;@2v(}bn(D^&hF>|ue zTu%3GZkO?D*QnI+!3NG_Jl0outo?E#8l04@`DY2bldHO}4+&-}+?~Y4)&RC`B}Rr; z@37iYkTx=til)mchNs;|mRX|Wc(-j6r9;(1*n~ug4QYh zDSs7EzwvUD+P$gQPNbP^Gs-^r5MU85rR}O+HzT-D5Q~m>p5IN;F%0EQ^<;{J_gKC{fu>{FL@2;&}Qa#o@YHW)5Hr&FO(d zJo$k|?pf^}&gLx6wW!y3`48DqLH(0nh~)kJ&c8_@@;OjzCpJMmxn;uY{pL}ODhx? z*&*Kiz99c{51%5;;jDL?i1Xx*0PnoLOhi483ZbJ|<>6bU>KNL?9iorv&I7*@e1w{R zGoCA5^31PwA)UHiJn5IERUXs&Ad9`un(1^V54~OyMrIl6m#n z1S#{#4HI*u2Jz!l2yv-?h>z;>6#daVjU2)V~vC)+Jg$1SUmR4#hb z%{@u&XvVmDj2X`GVlYvvGQ@~~o(PtT@I3pF6jC`9FHEyq(~DQW!}78mcV~0lbF>1!h>C(B2zQ-& zZz&9OSC>%~;9^!^4b}C?hQ>fVNI>-LWM-))IP01H4%*(`p2GhOrFBBU!Q-a{162wP zq;@$iJs;NC<_y*-e};wPlv^CpqEJy}$P`xE8ev07Sjy_ywLskBljC?XZ>JiD3|KUp(b zb0J-O8*W@Jn!5;tTgO-T!`L#ho>nrV+9Ui|FUzZ3796FH8Z!79+BmlVu#;%iS#St61@4-m*5ci~HI!=4aE!oDY{Ld{P6sZ%q%#nb1s;UqcdaZz2 z+OgsbcNsIvyGZ|)J<$sRtMFht&ZE+;{vc_!`nO}aaIebei0ZPGXkgZEs&h5{0ex(2 zB){#lh>(YP%{3!SoyUDVj=77e+cI#qj+>lhu3pD>_O|x1P;IBmKi45P85|#Ooup=G z&h6LXa#^o|D+kUvR9?RA0HjC-;* zQA`CddFEKCzQ`j|W#vz~O7_|IPyGzzZk_&L>3FC6%yw6yG5b-iTN6YnJfR`k=%G9W zH46z^@Elj4<)q55g{?5arD4-+2u4*@<-anW&Z|fjay0pTMrRRQR@l?s7I7JA+4CMI zI~-VC`UEn)EdSys5zPI?{W7nO9^QuRc`r_^YayA$(r^6(?eJI@+jFul7lso=k796# z%j9A4v97!+emRv;?qiCFjkI;rtmmix^}8!6sdHPu(K3np&yfU%w^K#Mq*!4k zGyaUBFWh1;XlV#3%0P!DbVjSDs%T+4VK-W_cJKh=iieNfzb5T9e}D4q)754lO*Sd^ zUA?3`7&^lS+GsvqFog3&r`hU`^{;8WO3m#Rv`(!}4&;V`Z#YSbb4)Rg1eOS57W$w1 zdo^G{v&F*FV6h%g$#Ryqn!Xqtc+HO158g)E+2D+^nb?$=E~AMu>-&hG`P1)c6K67v zM>5JUr!^$Y8Llx zY^si@A7Ep};N&d;H<{@Kc27wb=Y(rN6@in5AYG5(E(+ z&t!h(%wUr|E*;gs-1P3?!tP;Z&T^U^U7M_Nd+FD*!s~`!GGr$#MTjbu#HOqyjDmne zMYXOK3!!W|svMTEWt>lpD6uvls@ux=L2hR(Z93VzY1-St`H(P-&T1q%VbX}h<;-n2 zX9UipR((g7ILnb_Ep3mVTdqN7b?5FhVJ;32Hzy|tYaaVIF40hVuO!mrDxQJjpq;LU<{>% z!bi7~4U@(0vsXkd@Prcj!yz2flXAHV4JKGJ)9-YO-<*|~&@XA{``|ERoHsA60 zO#U8#oIw}-BP7|5#o@HKUYfCRd|zm|iln1yQ`nsqleIOAwP>q~@aM+v$(TW8P!(=I zzl|8B#YRNqj)W2Hf2etB`7K{2Dl;?lZx|A*j#9A1mti^$2#wm+kl7!K|J67xa?zo{ zreK!op; zm)~}(eOoumcg82idv&t(O?rQbE!-QSNjpfB(UoJIVlDbiLxV0CX3XFl@U_xrFEyDz zcl>kd=YW{`GPkWhcWsbIoWy{xw3qet`iW@D^%T?)EO7*np8|mr5ngd!QG8j0^+S~v zu{u$#VK1pEy2|;dPO|xa(fz1=#+Xk1jBbB&^e-twBJG-%NGE{(^dPBWAo|$a<2?2&iTiXT`dH}Wr54l>2)et%YY=Emz<@@{B zkZkH7I&&v~E{(u@lzldSMa5fyq@ngozfhG?hn2WeGCLi4I9I*yt}78<6kk7~L|T8z zK_^TmQBHz$UMYg%ktS-5-Qiho%?D(Pk9QGnR~c-=5FCwYrFbZC=h?x?EdnzSxnyE^Tl>s@(&aJ59<%v#%n3XgO!d>R$izI-o*!rP)At$dq#5R^%-3m;<(GNSkI>{aEwPyQQ?2V;II{_6S6Y@C!oey@~$&x$-#k?c3I74c(^t#or7^iw{0s`&NV|Vs;*c(PI8r9F9o34#j zR{nMfG=r59fb|1&jV&xDLQ9EwS>GZ)@#_&;MhJQq=#E6m<$+W{F%5f->Nzc(tLa1r zAwh)|RmnTQc!h2>RD^tWsj5QHT2$ap3*{(9HJ{GL5*Bj*(e^$oEBT6%xH75OuJ&E2}9GS{7;4Y>+Zj74t!-HqVSHq+=etE4I}t{b^VW?lHK;sm5PfcasfySv}R;P)I$RUNC;a7b-^i)iKA6Lr}c{^Lai8?l}N&x6hlCWRaBweCJL;SmwmyQ=b+*50*2Z`CS(I4kT&Z0}1-ttZs3 z${=styt@SW;k?b4M*^O9y>l=)Rxum~H@nUif&ZSlW71;0L|RgM>)>Qer!1y^2U8pC z&!krk|%zq3% z)#1JMu}_MEaSjgg#GAF6)omMphVW=`dUABTbHB7ZbCHwNKQ;OzvSl$-_(5qf@FAk; zBXKLtt-izDNY*7+lW^%py1Vc$RXJdh2o5p7)i*F>58eH@h)&T(%Rc7 z%g&apO_tZxTqx{E?gRLzD+e(XSFAQZwuJeyT|%Kk_q>12xG_B=^Zb%Oq&nomQf`_Q z`+#I$>f(kKazpfE!KH_z_he8~lO%9sW03iv5?52J+#F?v8VkXv{o=>7m%&}W&(J0u z&+Z6^C%?a{{LW)*ZKNcuUc7?MTTnK$SZ~UQXl-jF7%d-3xE<7^Q~ zv_t8}lhpRc(PG5t>x8W^iuW1C?}t{JKRmovAnd?qKr?Xt3COmR`@Kh489$rN{1Sxs z(D*ii$8@QvaJL`!+#CZoPW|7S~mbJsF zjCTOn50UBAd-=$G#6LS&9r%7l_F3gAOMPcT^dQGV{eh{ra*5k1v8m2Ns-8 zzn4Jr>T1^h2t(pUy25p(gi0JMGvy8AivbsRv(6O;fqVDv6P(#cbx9&2KJ+xkj+a?? zym5;(D`4+lZsQKfmry~)u?kjYRmDrCJSuj}; zcRnH8ijxw-fWyC=h`C~1-Q94`rQYjEPLVHE_YhQts>=oxI`UoYI~x-Bs$9CweU|Q;(5pC8T%YcdoJV} zM^}`r3=a|VJQ;XoH;h9I+YR$o5N~9!r*)WLlINY+710$y3&?-zVy#_$(VrzKa_rn? zlXwxtx)_KTvv+#Yxrsj}fPy9>C`iXi>C`;lWnVhb&tzW5dZtr<7XkB`bLYB`_LU1r z3RS^Nw^gQ`yGj)_iSjhzgU!lSV-Z6K$fBycXWZp9P78_ALzX(aD6%b14^RqgEnN|8 zHrsj-=(K6Ev{Yklro_JWgLZD8Q~}qyY0)>QhmiJk?VP;fxV!p% z%)2JYm!z*G7BVE=a2~Vk|6$|!MqC~f8_ha6F!Y@MIk(Fn&9oeyuFD4k^*77K7}C;f z>pABEYl@79s6`mfQM44&$4Gxaib1PZ7nCzFEH@%Shyxd&-64_Tk+0C;+Wjb2*BunE zsC+^AuT)R#O)uCtUkwfoTTDua{z5cBa|VZ1oy(d))ipueC`r^)>gViO21WZ|_QAx) zMwxczlT(zl5i?VN4b?R7758AT>}{{i)#!j9o6y9I>AlHP6vsYiWYM;|B2tkh&4@FXhci0^DH9D1XOxaZw4c?{Ho?^?Lt>g&_Td)?(zO1O* zl>NFC71eC5-rv{lisX}KyJS3jP-?kmy@PQ>S2f~Ao2fzAZ&Ze}+nV=l_SbGPaIQ4; zDL{~frLpDd>6d4B|4NMwtuxWXka8`jp>l z%Dm`rK}vRMs5d{~7B8O#6aN_M_zH&X$lWX2w1SZuQYx7sU1yuyjl;Sv8(tz1`x0QV zT6U*o1{GfaNhzBzBn}Zp4-GZ6UK3LQ=i1)8iw7|Qw+vYNl@Twj4qk-0yoL+?PT50( z)zR91q*_13K)f60h{p~N+rE(j_dgfSAXXtq7lZEj2a1H4OUPFB!(a{M{u#>MZg&I> zH^^bJprNK;zJJsC6orgumB$)8s^jqR;b>V$M#+yKB|kbx7~WK_P>_>bF;~m3R=@pI zm|9{#S5ykcOhtD*iPMzih^AkM7Mt-cgvDsx`E22Uq2UrdU_ko1_pt{=(msF2Mck}+ z35$;{)1OKHfb1sx^fGHQGT(B>9pSuVdeyIVu~}&xeF{x;nWP9xH6Bd zZd+o0i$7SelfV5iiK~2bwAj7;3JwO?b#Ee|8;^(xZxzKrU5~q|%`*k9gtp-CfOs~2 z0KFjGgQsf593W_eXA21{p~=45pFRNsFG-dH=&#lp`{z$}C|*nEtUYQa$Owyxa&y@k zgK>P;0c@782?x`esbr-V!d#9c)w#;bSd;JX>;5A2F=Omo)$H-Lt1}rQQ80$nah1pG z6CV63TN`XFI?Jt>a-|UuS;@#8h+fPO*ZLp|+>bqc<`&wVFX}D$Kwt&i=Q%$67{GV> zIntKZ2gPMOQ%&O@4n_f{;ji&b6kB-MM`ld2EjKu5}k$n-{Ah zAoPX_qU_H&zFLlEFU_p#QCbL_l0prHeJ;J*MnX~?Linc&%uF=}`mabS zJK8&t8ootd&2dkZykdSW6B`p9fA4RyI1P3e0k_+(y~;6A4`TZ2Wfs~>_2O{qmu>w) z^1|-wbh0|KfCm5%E+`2lj4~K>ykQBiyD!{$A0j{;?$Fo1QNEoG6Gy3NAl}_2eBm25 ziX;MEr~}SzsY*oVV=JAWAk%<`hp{k;T)nO|S@2JzVuG^+;)ia)%23KonOd+^ zi{Mnlv_)ln=8vLM9VRnV?>m+t_Cuq*2tvO+_K#`>lL(HBPe@+=pb#~9p8*# zOZYFtiKA0#<6lza+;|IeqLh@CIUKI~LzVCrm0PkSy!RJvibzRY**$(c9bb`FRdGaO zckSQ5XJ>22tx~~iIw?zw-J`KNhw~N#0e$V}LTy&|@qR*j6%{pgoxiWebSmft#YM$! z5*Q6ZT7U51pPu=FJ08!FVY2Xkx*TurNx;wSU`1rl~8B%yT&(PfAT#K78 zdQtzaKrQUGUSX4EcYww*VPdttupGZGu-!S4v0ichB!Do`pLgRlD5cXth%oxq$N zTt&T|pZcsj+;D*5qh(kTG?wr19<22>TW(9foTtzse!}JQats^MTai2lP{d-|l2@FQ ziuGh-OoWv7<8>qJYKY9@pxYSu76o*HEM{7MMzzgh|Bl4+hu$48hDR@+O0vW?zj*=9 zTi=va^ygvCKg7lppoao1{j*W}qV+pTP2@HqVi79LW^yvz)eTcq)Aq|({%zuxk*@!0 z0rZO17M;XaNC}gAbc@w!BSnS8Qql8L^i0)a&f6UXZy!K#s-`eKc{-eI)-#@N*L@1- zUs*EHw(_!FNFHNh;8<&p_#U8=ee8QXY5`6ZIX!Vn8!Rjup3u{%(ug2eaWdrO;7&mg z#iO*`>vSZf2D{>N=kK>P=~d+9-XjIf_xI1LMalg(3LiY ztIEmo6JwH7WSnT-2alN=m-W#PQ;gQc#<>DWTlGx=n8{Dl&}hYAWpOyyQq(LiCP6_{ zQ_skZ)-@$cJWcGmsiC1EYNsStJz?XWXPG?&+w^7&1KU=YVSz3|$ z>N&fTghFHH*JlVY-BM(v4zV&T&)bU0n6kt{0jnm{`p%r3)@hN zdK#zDNDP>CnVd&4@iVd6Af%X>9Gl6D|FcKtoI}41w|CgL8q) zP4Zln-2w`6VUj=JQ=3=!tB`#J>s$Eczz$rL_m$=8M@zS4WChR+fQERx9UgnD~shWgq5tdExJDly;q zX|Z8q_Tusikbt4O z%&6!lqe98RjiND&FDX!>nc?BK8fG)l(fG?q4iEI@dCU%hs|(hNUq_&fDEb$n138(6 zy;BS9xV*}g4<9yN}pT>+q#kQDt*oM|gvl8-N;IRO2&+c9~!MIva#9D%o#nf8y zz{=Z;>TfzPtD1|4~|Lg4dl-r)N)fSWqlJz?}H zSwO~F;dL>;I;8OhbR)CmKr+ti5{6c?0bo6uDxs3M!}Ipw=b^u+J1LaO+%C9~8nUv! zP^q-m>&^$~CEE5lwMt@Q$XMaZw-u-Sc5ZZV5nmzgLjuN@XPI747wmvkR8&8XRRcIF zp@LB-Y zB!4!E3M%~BWvxBlrI5=plJEM2+^E01-uLDUxGJ1 zl7QZ-V@ZC(LlDn)v`6PtYPw-7HiU_}Sd5k#js5!a_;%G8zz)sc7XoAk?2M>X>b6AeL5o`#UU0A7fwq{v&i8K~~fpj|>kT2erkqyZhA-Qy`ho9}=9d`P&B82xrgayaLgUyk+F1@tWbnj1fOnJuke;V?# z(T`z4ob}9ZU|r(?gi}?OKiEH*dHVbUDpXLBSD)|R{rFRrtz~Sw%)Mh->e@FV@T(sl zkB5tx=^9lFe;JFVv)ffm#R;ecw6-{;pDaut3%i8>RE?cHQ)q3Jygpx_;>6^TnsLOTqenpds+m>ZozRlCw*$%^~a^Vk# zvW49h7S`)Y(B+Pac-^7(dUOX~_?SVa_0c*y3U{G9&mM4mn>oYu__?DF#%2GuaPc%f zzoyQ?3S*UXUPD9Zh^(jGA0IbO6hQ^I_Gh6R5a;DtMv1TR*-;J^R* zA~^{eL+onU?GhR4;Xh9K-}$KA4k;w*SFr#6hWZ8_EPGLj9!Y5_iJn$a9f-sKi;FGo zonU388ylpjrcqLO#>6IsRhy)(=mj0tH>wN0ZkeobfA|;d8@8Sp%jCc0HNgM?5OgQ; z=~b{vUoCXMgku`fUd^P9Q+Zo5&fU5BQ%y-dw1@bh64WUXy-HYqJi+v38EkJ#vV2*o!W*_+AZ z3~QsMr*}SiWwP^g0ct=cUc2+MVH|1vRra?A5Lw*ZS2H6lByH!QprUZU#_fz(x|%MPa7B>{j3S`8RGCTf)} zDg-n%D=UL%ZHrz;$k(DU=t?k*A+v<4X36Pu+XXWLXg}K&ams;N`i;5)1>yOp9<+`~ z-i>r8$oL9Ms)gqR%CL_FXP@d&XXYzY_}#;Zigr;aoQT+Mj`q4bKHn<6vN7^`jPYyQ zyWZ79MS`Nz2{dwzyL-T<3bi!(35A2w>DrLR0~r4NnL5|2XqmX-T+zEhEywj9#R(Bh zycE5!|5-B6gwD6L^B<*sUfSN$nB5RxYI1KG!NO!Sz_%!Fw?bpbFSF4iMZ92&sIOLr za<$IWN6xZwR>j(5sLFb3O(COfZj98GOQj-T^_A1qFaa+E8Cit5yUgZas1lM?r@cK_ zr@nUtePKg1Km?FhJS8B{I$<&F7LLtTUD_2E(?7C6Nz_9)Hjxr@YP7?Bz4Ups(r_z| zvV6_=2BK5Fz`pNZ{i?H|qUFFQj8g<&wBK|bqIC;zpG+5?%b%8_OmQi%$9Ks9P zN1k&7kf5Gw+QpF6a2FSnAW`-JfUxl-t&=90&R^SDCmLfkg_U< zpD!RVQ-^vj%jiH-*(EJ5=8kv8Plefs3psbj}AeCq#ClgQW z7mpL5j(~T&{?1*vuv=cM(QL3XpmV^)it62mlCAE}-M}9f1EOjDz2$QT5lqE)V7KUy zcENcpW=%|(BvS`T#vL8bLnY|{-AJCP)w`33`9VJqLQMm?0lL_S_}nlo!sU3? z_U%ffxM#O%b_bAm2wC}gdZ#!}t8h{TK-H#EJt6%?FPRj=plcb)vfTZDi_K{jqmuo_z2YpTa5j8+!W9AaZ55^f-vmKo#}XJ5qpfa}H^Vr=3i`tK`# z;B%XR$D1(o&&W!9BmMJo!aOt9ur#aPpcyvTzU!Z1D_9*0G+Z9M0DHANXb|4kW?{~{ zjfllv*Kph{Hq{>*&c;Fn;4Nk5eCO7mp0D_n`kem$?Zb_wLIV$b$;pNg3>?=Vtk#1K z$udDW$HNpt@4lO$Ph#>pwqNf>Lj-K#!V&&G!wMj!UrRY7 z?lOZcuMl3rz2*8D6$cLZc<MD=D#c`?W+g}rGSEoJ-;UnHGhwn9$li&4w&NGeo^ucRM&0iI!cEj{KFu3Op z!enOH1@7!Q8c_x*8}s-edRyyVrcsOG()|{(l6fG~2sg@pQ&RLX)WaLWAr%KIww1t~ zDCCClJ$(yTG&q|f#F(6y?mxAU)lj?Mh_VbKn#05XeESAy+BjVe7}S2%ZQcQdH%`qOc{!YOEzI^$D_?Dk>zL-7($O$OJ;6V?8$l_|~ z-%SQr?@BBJ%l~Gf%qKqv^Op=hjmy;3iWm8Mte;qQcPgQ4TN?-t{2%oUu5TOJ4YA+9 z$w3OkYQU!heIpb+8n2jp+PdO+UB{u?QCE+>cP~CsYYT~(Bfb41YKN2riI1QEy_-EQ zdr6MF$e$e;0|zsqK`X&Wf`)iwIcB@*`^d)i{iF2HP?^GG5uB_`DuyZ-TJtU`ye)36s5Jtz_?f=Q8qiYpGbttt;;1qt{@=4@%pPXNKiM{2POoEWT zLkV4M%Z3|oGPpPTf7fFTvkceS$qy%1} zD&mcqri^3feaI+6yyzXtkbu>U_!b-P6NnoPkSt-Na zV$ks!V#-!1@6K@2{``;NK>DYuyn6PJRI-UsW}Dw54-wQX&9Fo1TqF9$$e1|s5YaEK zHC9bbk@#%(G8|sL$~>fBB_z0lYQcg9!gDfG)BE(bi^gaXPv(17*6eE5B+d)O&Sz)- zr~I1uh(Ev#f)k?4k4NEJg>68t!sw5yE+n#=8r^d zl>g@JVYVK81GPJhA0ZFZ)5BPOhZGpF9TO7)LJgYfC)W*Mh9*ZJ3*LZn^|Ls9AJ8bf z&tPJZfa5Jt&;mD!ldt*E<4|2fLE)Y}Ptvs%?_<0uM3S9ZJG1bwl@o7=oXCTCA#rsM z|K=i-=}+{a#RuH}xElN2`4uUmwX+)uF-9Agl!7&`exic-Pu2VElSk;x+2Or1Z9yp! z>sqz)aCNi`vBwYt(!ubA*>pKpO1tqEr)K>-+pd@8NkhYu%iFmd(nRBy1L+QfEu*G+ z{x!#hBw-L>34v~zs*WB&ivlAJY5Olcql~wqfsK1IQfqfD2c@Ewwg)hs=Vi(ViXh%B z^vL->@CsTN`b4&OAY;~&+KP*W-l)Ub4G~Q69%jfM*w5PCzKK{J9xDTUQelO`r|v$$ zAq)ZnJ3MYRdVQK>i#T;2ND1d?;8^A?u=9t)%~p%@--7<~`WC@xDXux%Od2PRY?k+p zkEeDtYg|q?t#RdK*t-gzR3tBP5t|(2Cy8ut^{&wXSQ|;aL7tX|=Y>znpQSJj6)iFXe$&;}*H#`^|B&DT(JvMP@@Ier4{HfbS@FCgG5A?D z1oyf#i95IdRs4*|8L5l^zc1x20uQYU{t-pHCc^)ap@)WUBN~Uu?-7Wb0=i2I-pGhY zKriWQd+>uC4P+e~FnGHB%vOi;I-N?~k)7lVGD4gpjYLeIN`s_j$lVYcrc)=J>aVa4 z!HOa*OpbVA&&P|Xu)U1j%olPGK=A?%p<-qFj0R(90}1%9=-ay6P`twL3A!r2ax3_V z>{EQ*DhBWCPc)q&y4hs{M1h$h|4hpLn*vo?IgrM(YIehG;oBAB?b$}+{@Yez+Tv1>*2L#5op zNS2ZKTwbTkQjt6y1@aGxn29E?&M-ne&2;T4BA6sB>?J!n76Sa>+1YWSq5XA&Ep7GB zzkm+ZM0Td>FNUUCX2Ijw)CN0~NZu5F| ze8EvVd>?Qlgz>4+*N7)j5ESfz7X1}-!q;8^jEjmHId;GQp7HzZlkx8H(D45kKlQ90MJnonf8yh?1FDf@(BcE18lKN-&!E`|>D zoz)TORG=b@#l@wWT9Havh>Q+T$xE-&9qNY?2M$6I@!jy^r@L%)Ei*MW3rKv$#K?|? zP*hS>rj6xvIhkF{+kWq~9rnA#V(enQQXeaab=rBmk+R2f0?r3nn$;z9!5i*7f#%S; ze=}yYzv!%Mq>Pp{;xzAQU5$?Hip$gFN+Nt9@O32d z#6JZfhs$a0OUj2|qalJSbZW%q@c{6p}S@c%GAT0VHN(GG2JCQB~feMY+xbI79xFX_B@fhr< zNjxQZh{Rj9_OUEJDO4~N2g)pvK>2ub{&(YBy{1`(w7uvD6oC1>c!EieEztzRq!Y6|SLJIqR)h0df-5lKuJndotj!wC&tZTt(SJ? z-u&m!eF!H6F;M7hUq1=rEON7XeG9kmw+?A?l{EFxIGW zOG*>^-MD5F<|t9}qA{9Oc~dt%n9@ZuMpXnS4AhBu9N!p%dRC}ypEE|pMR}fIEu04> zjysjGJD9M>&^=?UjBm-Y){}HGX!=bF5@=8bK?Z9bA72^I7zUv1rk0>-N}*x_vwY#A zIxQ2Yk4iTabC!=M^Z(pC##OH?8R%yxlM@anV-yzK%eN24*6i4>v}6zWaQ}T6`;%DT^WQ%3dB%uxy}^2l{NBRZO`w16zQ@F{ z^Bau+9=*NR$U6Cao{2EZDG|B}Q152QfufE>ZHygMFon^b-g!)mwiD_s`EzFO{6HfUIvuXN+rx)q?I9j;?T4RV6O2S($|({u_uYMprANP;6&gzWqn{ zWi$|KD$D1-)K*auG`I-cNaPPBpZ|#d;BVN`>zfs_J^4Nl zi5W_fNzw1d@SuI{`O6*BzKQN(NrV z!op*gi6}bac~S*6r@$QYtrir8_Z{ulhYIa7?Vcy|4A;qD$@)CfXw zsY7STw_)+0J6t!t+j?EzEWejv`cb)DFKi0cMf*f`j3v z1K)210$5|q>8N-U@mTI@4 z+HtYcTz^DR%lkhyKtm`aCyS%iG^&m&6T=37Ls81tJKpqhv1FF7c=EO>*OPXloXC|5Vs}vE8|;uBB>v7pA6p6p$29GPA;fFQn7$R2EeI+m6f=}_7!{X z4VKsp^k~(H^oAtF5mVls9;f4HI24zBWjG`YJ+qLQJGMK?MR0NWzr%1c>TxO-M(~hr zk7l#5u`Dw^@0=y->3Z_^UGT|h^jNi2+-;qopI5beAUM1?arPN}px;ND@bbhrSdGksqYZ1fG)cjso_GIVf$HccW_@ zOW~ft@K?6G#wwI;jAx&qoA zH1E)}*_-#?*l1iFsXaholMi~%tr+cf6^l-@YSiXd3bQIJM}n>kR4X8^G#aYoV z^~EEXoi5-OpdJTV3qa6nWiFzo>cCsV<-!1;{VNk$Hik3NK~MmYaX;dC)Yn+{yOJQ< z%-Gc2Y~Wze=Z8pY$&M^jC~#+XoBab*leMsZ6>X||8hC0DE`v8S?F|l46ajVuhomPw zA*#AM%qla>h~;o1qj`Qf-|Wrllqgq#yVEAu4igULDUV0%Apji*lops+0aq+1Dfv=1 zKPXExuMfTZ8}l)|arfMrlrK&+oqy5I4>u{V8fI7$smFnsk>K&_`JtHJ%(DwqUy5bc5e$K?z@e z8Po&ojO*@F_k|=SEsfo{vp}NvosMt4*+7kQrgF{h%yy-gpb75g@?a_@;?@W!=5==o zxk(V*`20{UK26H8c!fZ~^U(I(W*M|OTR&g3$_B23^9@zkE!EAR^*{AIQef9k3h? zl`1YO4sbYHs{etc{p?5tVC{zM)-B36?L8g#aa`G|RXv89z4zwYejQN!ApS1+&-GAl!ql|M{YjvfLS)qxEzQECmw15n@s_>&>VLJXN^GB2{F zs4xlf!2WFMpep7wINVVqo{0%7O`?A()^t2**LEp|(yh_=See<(BD(QXlv|*fx?~n796=DemKcsl4`K3v{~CKjxX-$(+gpv?LHt zWP86AuRQ#JJ8QMKW{QWnIgOE0AN=tu0;Kah{jZUIMF*4}jdO%y2UNVg<|SABz$duE z=tt?@4^U0qe(X@QzzXbjdO<<6{1H>KxK=tJbvROlHVwR1hYS38{dhvGST&ZI2;>7) z+&W`Yin2_HI#Cgw&S&Kk2hg$f7?(@nl42n6<#k$#fgbU&)NZKAymk!i{&1lknA*o| zwda^JIqyug@O?m|g`5TzEs7VG$H)?C#1~BvPf>@v+=cOHI#w->Q^jmNElLaM0)*Lg z2CO{azi$w*-I@3z-z3?Lf&gKp!gyawYV5t+$bX?Z|LmbauNoiy-zl3IPO>{cZFD_< zM0GPq2{{o&B*f8clv&Q}2e<4hD@Alj_!C@t0uJKQT>7P8FY_g7ErtZP$s=_CXV&J~ zYh`8A!LQf|;=--Hmye6{>n$uqueiMVfORnfnvu*F)tj($Z1W;L+wJ@cad+Y|jz&a_ z{#Vsy2BE;*V_t^&U6MPn7>aP++-PxmBDWqiHh`^ykO1w(Wv{Bnu`<&a35$W*b25{Y zb)I-K|2I-(-~g35AVoHjfnVo#flD+RPQKE-{Ss!C`kWu(;F#89)kqv3{VxCs z^cHA{MzgmHxr14OboK8q>T3@-Cn1b^YKWXy!UZ=*8cKnq>R6xY|0hVN9;8w#wuLFZK@{|_;b?%egWtR9DC*JuVRyY zAAEeo`>Ez6`(}65J%<)F#jll$iFp2JASMj3LgE0U{<>$%jYa=!s6O{}>;!_r`uZrfN#ZM?a_$%lq+87 zGeGBI#zTpPzY$#R!U&>4krDHW>Sf^1GFt>AQy7){yNObc#`&rm`^xqHmW{#VA25xQ zsjwUn(g*frK&4fk&=Mv6fD&x^#DZU1O4|AHMdjq_D(O%vhoef%2wRHXbSl^|p*vV^ z3)nmnQw;T|>$xn1<{htwnJZ&F*I?n>o=o-tC zjoW`NU3~>XxBRsovGY3b1Lp}zPURs`ErGfKkk|otDuh?t;;xe$ha%y|qMJV_@Ow?6(zR;VbvWyLFBj}T9($avn`7Iy# zH!t0KlanS=lNEROz&d{KWW(C~3HVkD-<;#beE)v~j&QnVfH;KJWv6_{+~V?OKk(&Y zMT7o!G(o7FABE~+BuUUFgQ%`U>FTd+6ID3p034a-<$=74A97>oz6J=|jmm{AA^@e| z;_cPXd{2XaUi;|g%}7{Scrp+BuzNaudaWKJygh1hgpRMwln6T20Z@W@-vJuElfp!DEiLzbas}_R(|!-(9^zE`Ir> zuonIwva;X}$0#gquQ}XW>ad%8%J&l|COkeG3@ecu-x>c?w5CjN>|_2nl;r8yocg(d z=-L6+!4!q3Z z=TCKKsMSj{;>R$5;&>amFpu^AJ9skeeUjO6@MGCoeZwb5X~7DffT#$Pvm%i6TtxtV zL7TzeX%hm9ONgL~n#9J2Mv6o?_jdMU?v+<3w_ytZ3;WXslKIcwj@oQA8V(lfeIeu^ z4gOm<+n*N8E3iLYFM*^(Ww!_OjzaZE{0=AY7LBcl3vyFSf4WZ%@>+sJ2{5nZ zDy)Ihl4G7oLp5h!#I3=fLUktbQCY1^&p2Q{9Oa3!xKT@5HFPFP8!NY+76o6+{K+lu zM?(K+{AlUUZIqe5ZH`miWL@^y^#azr=?3atRyDTUZUCuEsXBlv2+G)YUDArZH;c7A zAVa`F!00_l{7G&S=BF2C78O+j)Qt83guScdzsTXhb0crgS|X<{scbH*I*Q9>!VW0lE4tWI$6*0Um8o>BN*SYPbux~wllM)m)e3qHdLs1Ea>5D<@b4*M?ZnNl=FQ2T}(%@(2 zr0BkSX||YE5gDZWzsl9=PyZvpWRzlw`Us*vf#2zCN?`Tm*C@k!|DLSW^P!)E)aSTT zQqtjyKUd<(fQvl)O@w-Q7Iqxm| zf+#XFF+M&SAUGZA#+_7R_Odc*DXG=?!E_zMoQNlkh6wL&V-&BCX#iRI?iyV{M)DIA zX>FX@K03bUQ`V284tzdT&bc-Mqx}#6%V2;yP&6(yKL^~-J61SuVdING^h!SW~bS?jthekfHu=j&#}G7uvM_u>VA z;`3ZKg;A4CMfCB{mMwF;qbywU-84dZIVoQa*-@zfgDaVmlP9+;E78V(q9>3qz3xZB zHQqINBqWLP@0KoI(g8$c(A387_{v1Uae=6M2c$j@Clo=RBuWo03HI0x>vku$X3#M) zDMoid;cAI{w>8*7bLRIaoPfX4+7q3g>)T~H5Q^X@;y&#N=#01ahn)Oel>|dm%@Ghd z{Y)YwIWJ~rVr&-&^g&KlLR70l{tCy$zbLmi@-?ODk~90Y9euC*jT5dj{sw7%%!?7L zoolG0JMh8-BxgVZ=A3CzIOxf57rNpZVS%6#{?B_J1tHbz;sKO3-Q^?%=Q4AsK^M5BS*1*N~g`wdH2I6WUA*#? z>l3C;BLM%30d~-x!U!>8n1UFMlmnVVQfO>sTvTXW)VAnGEm~0AD@!Aj5tDWx3}B1- z+w|Pr(tb1UWg!@twgb8Argq{*Q){foxc$cJts72+vf|eA3}h@qwd6hN+27>6wJ&lq-9H6x{_HI+jVuKH z5-2Fj!oeEgq4V%Nu#<|m`7b-EWY!w#kQ+od@UJ%GHg7yFZQ0n^uq^hQyyovesZYYF z^-~Lr#k_l?feNkF=5^m`+Tro#Q3-!nPZzJ9LvI%cgWIOi1$QI9^Ko6#`3KH74$EVn zG1Kvfbl&)|pR99s;bJ2UB$9vf!1t7`6MTcNrf16{CGy60imx3chl;$vv;M0NB! z>PY#yiqO2kYinb)EK2hTd7N)1*x+F$^k#Xn!D*v*+aefku3DBga{>?c2Ev^SygaOpIq?c9axk9>3QzwcFkuv0=-~BOb^Y$~E%n$G01~H-i%e8{bTIeVyrpK400sJd*8y z->=`zUR|Q~wng1{*zfN2k~lswG%X zdiMkF8QtOuviis6+62!VT^+>Is^D<7yJKGe+rWx>lScGxyv<|T7W4kj!1M|9&8f0s zyUPP$PzxqX>e;i?cX-tl!dnB2l6YY&=z!-j7G%x&C|X)Nt8u%n)|PM%de@0v!OOMz z)ha5%^B`33znNfd%;^PtEOoo;4L8;5XF?UY<-ckcA}@1nv_zvT3zJUkosag!0Sfn+ z%Io^k<~XpDx#vUJip`+&*=#aRYn3>1BEa8&r;aqdePprNNyz!4v%;O+R1qMP@3%mt zv9sUz*4_2Z^2^zE=J1XejOo`;7AX76#3+LX`v&-{UBXdKi<2^m# zWf2e93+&c4?e&=>SswiNLy~&=Mg1&Afqvd|zk{bQaJBq>`?H+0#Sb_e=m?VH$qij4aC{_Q88W;)t1i~BhUF*QuAV{nfA zrb|S;gN1c$D{z_X`aOYSqm2#aUhZqU;7bkbS5J*qe0*6$B}Kl|oHbsXM_Nl?*M8C; z6c(l^4u*6t`p>?GBwdpLh{?B zl*IVTWwF;uZBm|xXeP()$VueRGu*$kJr|9zv`kWBVzsAyM-4C9*wz|3w&2r+xl^IU z1fuKxOJXrOEcb4&uQMSf7!6OTBc+0q_=F6*kUyrl^9kutl- z>LFFQ6nXLNZ_Z>`_VTE-e12iUIb)@2B7iy!U5KH&>->9<1q9f3Ql4{o6IRqc(KAU^mFwzqh zmoPw5l*HqB4;GI@+;FWd&?tA#jt(w{?Wgkc7bb132Nvp<@VNi3Apa9l1Nc8%*bmzY z)w4a}WytwHaHkC(;LX&z?nFvbgq(IA6KO8KzZi(y&X4BJ2y35)%0C3WplK3~MTaL~ z?GxcGib})o){>8mxW0rJqni+#YA<*d+DUF)mG2!yLdF4fOX-3B;>BHfv6cQz15J_N z^%8RA>YTSYx@1))ve~z@IwOvmN31CM92Q*7HBCkwd>yT{s_YLzsAR|e01 z?YTBj+Da`(GLUI=XGe4?dIJZn{QEsUvr%?k2Zem&T$|KYTTnO9bM=H9v#zWP9?Y*g zqNZ-G`}XdWRau*l1IrV>YG`O|zr1J(@3yk;yR+L2FW8)smQKcH8Rr+s;VgYGyo0{$ zrLO+mW7qAxXXV7#d_NFg-R=zyhnFQZVa=$#K|rjhH~q7@6`bWFDxxLibmD%&*87R* zhOO%9ah1dq%@h{e&-GK9SLo*sNmam#du*k3%JM{7En zgi0&RjLSf)uB$tI3Y4RU{S}#yUQdoTxAO8lO5p23X=!WhM}x8nA^D=Z&FLR_Qj6XUNV7vpS@BYz!B?y3w=~ zYz>zqz-aY@-du*}`a*a%9uzUJ2B^2;iwNSS|hb9(FB z@Q*(79%;YuZWb*Gei1@w)_Vl3U|63IPu?$Dx3~YioPW*dJ>}M8PLI6@D|+m__%oSi zf|-^o)I|4RmiClBo4c*0*+D1e?=Mj6kWsuVK)(S1jwsqep_!rN{nrb~2c7OO6l_f^ zOzzL@9&4KfS6B@wz(|ntZWi&r3`cocuJ1UI<_DErh_CkyFhmhY$jg0rAV8M&ut?*L z(p_KNJTt>rLto!D#8t=UE?%rxat*T(o5g5&A~0|QGA_n*Qk5y)-RZ;CdbZwb@l{rp zR?0|9W%Y_4A07XIMem{B7>KWx^}ahh&I1gAtfuBo##ZVTVsD#Z2R&OX^q})Uvy6`K z>9fb)x6nX&qV%RKE^`-9s?3a>C0!FUE3@#>SWeFcT|^zo^VeTUjs=xK$(z{Seuvfn6+E)7i$)uY^5Pw^1w;KEYoTiE&W*!vr` z-M*{&nZHjtgYzfwdFUVN-{>Dz-q2-IWdGMk1z+bYdDG2_zR{O_{<8X6Bc`gQp~bSU zNqk(=_TIdiZ3ld7=^t>SG`dfRwfw|(4w|y7rAN9nzv#muvOoqRHPf*Xv zO~plgKtmfOP=@LxWnu2jhoQagu;Hal1S6hMVDOV5R)yJ(_9Eg*{_}QEtX&sGh^ku@ zu*lrp!GgP)MR3%R^NXs>G0ZHx$k1Elq+FJRAr23W5h2lS`KBHJ#*5lY%`Weh?XBe3 zb}5x@HtCizUEVwVcHV&Iqgk^sP>~*RIYRBH^s_8Be1cE-&Me}AaZG$eQj!uqh}76~ zWthUrMmU-5AeQKjdq2=<|;i{juPXkZ15>g=>I= z{p~?lof24XA=+7!YTst$<)r~fX2?!@nSBqfV{sNlGnd~R28RaG_BKGQwf}oa1My|e z-Y(*lTK$3u^@(NCS(CN9yB0#1-NW_mpHKOH2TUX|k5L)7w~}R8Mg=o>xQaAsB0IS@ z$C7)Y?S7|)9uyaQt2c=+Rf;^Du^U>1)xz3$rC57mkg4FotQ-DUTz~4vV;3V3#SV0dn{sIb!+&V zmJ}P7rq4UhW})z1C%wU0fU!{S!ixQ;`wa-Bv!|n%@zt^SINcDDLZ#c+!Tn<|7-?;X z^0jqr$B{~7UhaiYcKY|7ull7c@$sPU>#fb#*0=jojeAfDtELTBCV;pHRQ&N)bxvWX zT4tb9Mc3W`j@*4P%%;1l_Ntm{Fy29;+NK!O7msneBU>ahC982kKwo4WN{|h-f7s6@isS2(P1K zjE*bxudd%I_fc0kCRDnZ%H5fnb4_k|z{#4ZeR~4vO~j^#<2(Tt;>yWh$awVokapHo zDrCRnBKqD&se6;u=5CuDC{|Dg^T~8a# zsV%bzCi_4xS*XDiAA9S{lU#RZSVZ;l&Vn&LZ~%AdyQr=B6@UI5F`sgVCoXrD+j6kP zOd`6@Z7NNf9Hw@jFiyBG@$f3l>}wptwz}}%IbPmhfwoi>(IHG@M@cb@0mi$?9M?escGQ)2n2E+SKh@;!&s>7|m zwKyfEWl%9B@R-4lJ3iqX0AbwbwaHSjVQ{m>+M|R_zKe-@U9RJ#{FITH6fH|r@ zTv9VuseC~z)!Q!f{25)Xe?)Ls2bdN}MLCxd!G{{SYli&R7oanYYw%ACph+i9|KCvN zl2Fi{Q+JCKv$vp5o$x$cD;ha2Q)%y*y{}!q++inF8uLOyX>jm`POY0BwA7vIjU^$C zx-B*Qcu9=bJOBdh8b2?sC|z0H=&!f)jp~lWbtF&8%R@!9^=dwL`^^&GVLsY*@6qF* z-+CPOe;$Lla-L#kO0>=K-0mu3jeLA&#H@hKr3J@4Fdu053)auNrI>t?N4Dw97^@t-7Uc78JQSwkq(VET6l+h zPp&#rL18U7B>*lUEJ^(CXG?mJ72`pDbNoP4TH0tOh5(DwU9{V}PL}p9Yn*s)_Gpe7N|C;>MT5 znNpAX+6vQ+$2nGVe+DYdxHjwDH$!uuY2~94qfEb(sKJY4wcOL~@oN{%&b-f$k*8EM~2rX<(k;ZJJ(U2nclTz$pb!JHkSkCV@L_r82Z@&j331z*`DMeqci-P<7{aBq5KFVr z6ND?Wti(oXveK~%2qmjdPZWfSqCIsFhMklnBLmkEu`={GFoA^$XraOI6JwHBP|9E< z3b;<%{);6lh8i@i)ns?>L_|h(%Dq;SVe##@)Rhl5e+~v!%~!wu;bQ1VKWhuW6}@rw zPAFJefU5yL2E8{lJciVBC8LNZHwZ}Ei9D0Y%QBW?UIw@rca`}fZ}>I$KP>%#b}}}m zzBdm>7ah8`Jm%oBl~rVj<}aoX!ece|*iRm36u9x$0M!?gQE%5Mie%`Sc{6=v=udBxXM^oS&{g4ku|vQL(GrAL%Vq|(=uf&I(egY> zv$<Gq>MQ6 zO*-+G&WJ9|0QA2Ug*Prns`~7@uN-_aI4*}W!f)@AY%WcVxUXSUT`SMhzyFulHzlVx z{-#6CCAbZ3=PjYUJiO#9f~TIRcX5Y)ECzuUgCl@&cOnuiQ+e38 zZ7fUa>bR*ngp*W@+!fZtU{@Lb1ahK12AJf*wG ze9t}D&unAuSADtJx{x{tp~3WNFw7#yCxzZ4@JIC)h4W~=w%76$r!cA~UOsSC8z!xQ z^p~i{E3&fu@Q+y8i=TQ^ze1`8kfQVA=hWqzOD|G@@xNnW0d0m=x`ap zO93nl0nL~%u!ELxDdr0xe|ipoZ{Cf(%0^Qe-wfp7QF8|Lz3}IK1`=ZLm-o$eZrZ$5 z_yK+vrMJ_(9 zyTa_+*6P36u56)3LrH4neAo~~aT>{V1Y$WSiyCI8;h4~&nr0~VQZRLQ)=eN22 z8#I|B8=Uru5qveD3xA>|Qmi4oTJ-x#T3Z>wBf+87Fc{2=yLBop=ki(ruQ{6hdou1Q ziB@bp*GRY(;+&(}Q^70t^bMyVhe$xTdq9o%s49+Bt@^WagVyed!2LQrLl3P9_1B|@EY46yUvdxIti!Fx-~r6&{4?U zYg$B<#!yegAhzcjK}b_K>%62<8Jxkx5UxWq)@lg1`QDXwXjI%!@HYc7q z{-1*Gd{h{Q&A&eux6c~Uu3bfgh6~cvN&mvaJSch~N@{*z#IpTTUH9(Yj>OX6mhaAc z)AJ``3fHHn11^UoS~_G4o3NvdCr!=gCX9;RZf_pF)!Hk?zA7^N!NT?TZ$b-;0`Ddo zfP5XTvE4sK2OF# z^qx-@Onea#xT`3wcz27^q4_^2oBo3geZDe!^DckbrIW<+rGbr79ImomY|qaJEGAgq zPoMvR{Xivp;=*@a_b#OGV@Z3?IX@Tf+JJY%SOQ(XPUHF3^1SkbJ|M z!C~@*oOHO_Zk@tc(fG;Yo=<*0m!+e{^g_a-2O>l&^UBjtn*;S&oDSYH^yGXFd0}C{ zmz!K)vJea3&^p1HTUcmtUCGbLk+jfTFS`cJ3mXG7oCvLx>O;`$!9O9xx&fATJClL; zY{OeCkbxa~z=-y09i-tIlM+VH{4_`j)l0ZLHt+nmF^MKvq_!i2}3R^`orxbXko?oalIR3bLmSLoZbA9L!M> zK5pMK!_gt&fG0zV@Hq-K56iog!AV~^fXm`}IqyuHn&vnK2mm19 z@dlef%Sq>u$nYk^2Pm*kv(r85HY`1-6TjxCBvo&*u!KO!_dgsAgTjzFV5qrB-pBW| z)#(Qi2gt^jBj?Y__Zn{LKg7qsSy-IU0kCPV3}8Y=3b9H|k#7q^E0pIIDQ}aqMoQVg zF1#hB+~M}`@p_+^r{Y+wyQ=ZJdVMH)`^jjkXT@DyS|Iz-`85z20Uv3iuUewBUUVw^x%-y=t)V#9XthR_!t4F7LBS2{h! zom5w0Pvw0C@T-x#-#Qn)mFQVZ@y|~FGdhv}4%b`&i}lc3DzHYCpO_l_B%aU`50Lna z{Vslc2Vkk9b&V@u;}4T9v>rj7A!~b&H~{5|h@{6@&>y@4O5(GbHZ~my zLzY^CT0`D@JKM1FYP!bKxiM+7=75UJWINB7n=Lm#7X<-*#_REw{_7mtV9GF>E z{2&?R;6y=aQL-YOHV&hDNf2L6F0B}Pwb8twqNA`ZlB7lR+AQ7vGb<`N?WCf@g=lVW z4VNyngWE6x(H;M4Xnb^XOhi~@qUa5=WAx**p>cZ+$IXc^n15uzkp7cn64+h)nQR*o zB~BY}#IO1MDQ`3?xi|$xnIb<(Je5nTk69s4%Z2-)gNh1s0-%IEpS63^Q4c+BR}2yb zA+2I1^-MNgDibzRsFiw(rxQ2t)(-Ikv=SoTfLO@%$ny~iBc5qxE$*@B{AN_GpoHNqwx zre3cg+&_FeCL?uLK4q0#c3!l3Q^o$SYr8`O0`Cy1mLT*0-U$kC98EF*_7MlTEMz` zj}_v66Cx;9z+<-`;ivv!N~e`0vGNB4LZJ6fMC+ z;eU-L+5;drpdO;j=f>F>31GVc{{f$XhJ*QZb|4xkrIo(Qq2oDpDFI2a#HI=DG-NM> z2}o+)@?X&JCX|MCU-U0#rldTezpt$!{o_kf>V(6Na~*8y%RgDz+n1&zCWJn9Uq%)p zJA#^>uQl(jIGhUc`0DhM5Lk2l%F*Pm(tW|we%|&%BOgK z_q2%}SnawdWc&NHs_N#`tN+qeYU549@{}Eo4uh3h3d zHlGp|sp3|(oet3Y(VoG6wbuKsMY3iO{^|EH&ZG4X{mJ0?t^&!-r4p6yTf4lv3<=xq zjNq$2J4sKhv9bQA<9{lTrABOTT+V$u>aC$#yorg&a2gHnHaQf6SB05g<-e#Ua_h~1 zf_$IVTLZrT{9vi-Ys$at13L#S?EnA$h|3t|zrLUo#WTCP!p-rLu8DVA11{Bk!)@5Y z01s>wn=XeWL^>{N{W?UhVUBdM5iO8lZtqpWeh_we53WhXNquh~$CM0WHS&Yb+(r`aAHDC@Ei z-+X_ADc7OmO^B2cjR*}5EzQF_MAFUg#b~22uD_$sFy~U#QB+nH^=UHh7Yd6BT`lcq z_SOiAY9joIfq1#v@4PWnIlngF5uJliUc6^%WM{jrYun*hn~dT2MqzV7hJFr@&l!@= zH(jMKb!S5x4R}JAj8CLxm-{9zIE6ASqP36eFrO_F8_wTE^$J6EeWEdLYQA{k`;nC- zIeU>P!_Ju?7aNhZ7B^#Vp-Em=PbOYOMs;t0+-9vu0yluSLA6}ykc2@nkvuYHC@nNM zt_=0!f@Y8E{mt`6>xa};$nC`(O4T#8&*h23#YUVfwR;rW>bP=@QHZY}wzW)f%+Ebp z9=C0{8J5Rza|aD8D4tM+Dh&~x+ct4)21#~x%@8ARVxpu3lpFE8+MUJSegC2mm+#Y* z?S4&7E41v9@JJO#k zjDz=D%tV6Opnmz(GR#Ujf@f&_IkQGNrvV=u{{=N2f8#4f{{;20^&v*vp(Dv>@%JB( z+wb2*5Mj1(TzZbUapF_NFYs-~#DwrYdf-;-$o}_+y3cF5oY;>g#lr=*7_#4@j_k;Q z+K&E;mtw#q+Gl3}WhH;zhS)Cd)U)<>9J%&eqb>5H z;-x$2x+$5JPB8)5n{7vXnznufi(PcUl9MquRR8-s=3xoPZrmVuM$}+_`b|3<`QJmCC~e03W&;?ADIeM{{x>GS zKKvl?BSQRQG5+y1i_n!jTkw0{(wz@+W#20*?v8O4RgIkPb6hU%FD9wSE~%5PS~i~T zd~5qSo!Dw}(NE~$#4j^pg{&NHx3C-NKvzk2X`a8Hm#F2iVBh>c!p%A(?k%#rNKxPmV}PV_j8-7Gy@)4^A5lYY?9AoGwS3*= z5y2QbNV$TzwUQIOwDzRl!c0|8T9y>ig;NpCMlsl@6s&o%WEZiR7(8gY*ESQG`&KEE zsHl)qURBlLENHdRqWiWbm8WOCZ+XQwQj1$((auBhXY+f*70cwe`D|hgCdIo;wnqC~ zQ-$UY*@l~q7sK{br6O;?EQ}6yeiv-VyKb6TpZ%GZ^-9R0F< z{;hO5I?3al?w;~}K}ya7S5Lvhiu@FnigxAAyBY1xUAeXHe7u}rs=DXC9c)+bW4BuD zO7(P}&Usj#e6EyNRY4$B(XlwLu%4ZZFEuW+6)nwE)SrJ_R1R$A%1bY-^w1=64d5Fj z;0nmq{B2VcT2xb0MuVV;87NMyuSU&Y`sVFvxFbJ}7ubz+dp$;1E1^_5GcV&seP{m8 zaI*VYyMf%Mk%_ijU@qL=hmZT8XjG_ZKI0(8Y>{)hwS~3AKzo03;80XjlkY->`aNK| z9q5V_aP4N2}r{LYbIt6AD&(ZvQ$t$a*-o(&-YHhgTUK&+)}V zS7yaZYGlK#_`c978bV?L=T=|OaHe0&#&Z2A3q7JaYhl8fL}hpF=Ll0dDpyM3Q%_m# zZ)@>Gjz2OsUh~%Jg#Fa9m;J6gIK_ZA#PiuSn|{l6tSq!>YV~POJ|o$3p3o?#jYzTa zol?)K`H$792UUc2Qxq{S?&pkULyy2HcG;?MX&E(5K_$uUNILKP_0OqpbvkH~nheZU zTT{N)5?~`JgvYq`+f3%NveL22<<@fE<@D-aS#!3XqmHY%_S*=~2|3ncbWaOn*)eDL z?f%muyWneWS+iExM3y#RkY{&g;O)?oa8$Bi;AhMA6fB2NX|s|an2v^cYzla`Tdh$X z?P#zdK38&l?UpVhy5`x)X=IQWi=|# zltcwupHK3;lT*k&7i2H;2bnI&6H_xP%f`Uk$c0tvZk!EhCNB~{l(nH zuzwN_5uxJY(cUXd*+5ZwLT{}#l?11k)Hk3rn>otpdhD_)j`(u679SH5v^vXj_^sN~ z!pu{zy<<_aIPxZsr>wwBi-D60b=L(OW6M;Ce60)iTZ^c7-V6JB#;1J`#xGnQ8M^0b z^!B3@A(4b+<^l1C`Q{@7g|fThWtcwtgaSAWKWdBwyt+GC&u%6C>Ku+Rzs3~aAt*tc zX}n7|G+^~YwTd0RQBO1aP|wd@WBi45T6lT;$1CwI<|aCum+@615An&O?+_5OlEq%^ zd=B@+`SgrqVB~YL6(cvhbEM${aUIsPJ_SY7pI>tlHgb87&x-r+jXIyzxU6s)qp6!p z&6_LIgnnQ!eoAXhww%NLXy|UZ8jj{|q2d?q;Fm3f2AUpP1reG;Rf+tCOeIG8v%BGn zc1~oS{(&o`5#?E8H1LMZME+xsiX?g;QsNl-qCMSHDSKn{dIFx*HMfQj{%I9{Q?0u& z4$-BP)$~dNv~sC> znTgQE=;Bw*)Nd^-*#;}*b_p@@3I^t8F|Q*&6uV1l&rB$=f=LS%fi|TD zgdU^BbFyJ?+xG9_36ax%O2w(U9=!S2TC(Ue7X6zE3$GSiG?O@+AH+_4*x~ zWh5ql)?UJ3_Az`(`OgJ0<=SaSNGFGErn?$$_Iw{29Q(4|8!mW%+wH}ke$0={{D9LW zv*M&{Zo#tM&$_z8rHq1bJ|%4??wJi-(DPW%RhwA73ux`LeWmNs*j^L`*y zv>cn!-`IE;W_0Cu@@y_r`KzXV9+F}U$VLvD-$wk3S>8+-D@Tf{fD8KN zKKD3VV}xX84H;91+A*eiltgeAd8?yx!O5-3MXGD&wR<#;P6!83CMg6yu1cZLj0lme z(sr?FU(`x{VV+hT8WW0+fL~^ou-{wik63w2KoRfIb!hO)?WwgnSuoqr{udV>dic?8 z)7BfK)xH-rBB+R`d@CkVH67O?)56M#sp>BWg~s}Sex%M2E`5#Yn`osy8k~UpM(9W~ zOcMG}3!rT^)t7Om1S!ur`H)ompU95v zKE5gXV@zJbSUcb~I@T)#_3mGe0dD;ht;qhMZRI?4D4BPiZxRqE%dHNDp1yB25~DSi$}HO! zNQ@4&PAuIVhQp0?AR8PS<*hL3W}p_uNS$893-~0+c;Q1=jF8e=93Jn>QpBBbrP5QB zOD^z(&S<{F*H)CKn19!B!eP^DTJc=K&+SI}0uqE3;d;2c!&+JkZkON&*J6y_wGFoC5vRG&EkNc#A`OIun_Xg}D&zTncWH$(MO z=8kXB<*lftU8ao0dS|%kBrLS3aAaoEp=z>ALZqsb(Y|$ZVEU#7xy1cxw^?2tTEWs+ z)_XoavW0>6oJwP(BhrgIJN`fJ)MvD0+Kl%I+Fsarxn|QtzCo4isdzXLEor2hbnQO> zg_n9@iy>2qTB^;mArSIz(;upP_#KeLuH+ovfU315Zb^vPrcal*QTa1 zUaRGtE=5xXcLFw*8 zcQ?`vA|>73-F*OQDd~Z??~ZZD@+AzkQ3sWV|<(s;*{UQ8pdD9Ur)ZO51Z#I;=YLAC0VJl zJ>P(IdeJhl?`M}}+1Z`^y;y6Mlf*)`Z>V;g0YL+0@K9sQO)jdF9_=T*!Fcbd_l!lH zDZ~7G98Wkplx$NBZo?5*gBmt%C;=_#-=1v-M#u07$=8_{QW zK0)B&2%kSpi|?H0%M+bRavj6Qaj0&k*O)C1AjKI?*p+%`O=>iASBqdfH%OVZw9uYNJT4p=^cd44vuF&RssCe%`7?Yl|uB%ic;tY0$XWy-dN|i(R z#mXyt_EZ|ldERe)|?PB6o_qt=SeRCD+8mgnEo=}KE*5zzY2w9F>d zDiQz;-gAv55hDlxj!OWi@J^5bg^k zXrg}_32vWumxJXqnV@9Bp=bW|q_Rr-LP0m#T&VMTw9|tT+ANWObN0A`BJOg{_U_GO z%ZPY)S|eZ^EO;ABFSeyTj|V#$-Y(9+N>8;n8JIa62qDOShi--(7$OSzDV>90uQPuW zDGGz(zdHPhKSZzcCoRA(z8>=2r+1!VW^BB7Y+TIR^D11LE&NF0ZmR6p%~Cjx7kILs z{}#BqT*qgK>Dw0E!;*j{u$8fTt}Ui$*v|+jB)myIbyK9Ks%&Df&#xynHHXx@gU*nl zvLgD|aTTw?lG4`PvDRM5JDIh7hIr@|NqO$jM5A*v^gD@evNl zJe)lyYunDR3KYN6fMfG3;=lcSbe?5q6)mmv(cvHk` zXY=^wsmUn~4?wIUyF0Ijs$z^KNZ|S*f7FX5*4(*b5{SSV5%WJggh^656LMRk%slld zPJsI%rBN~9MGiy|cJvSsu_lkn`K`B8SVD_4ZmlV6LI)uSeaC3&uG{<$i+RagQ$L4M zhzqvjZ$&dOv`|92C*>g7+D{!&ixR34oY7!Q0stv5th5|_KtFMGF$d4gpify*>#eVu zLCS>eUeR4f(R8R4Zi@Usprg4!7;VdTFYjqL;3iv^D*9k$xY!pQ+w=YTB{g}^Q-ler zn@?pH9-v3Jcq%=&CJD$?Pp9Qfc`TF^Va?a@-|ND>k#5FHvw|FL`KK0P+GAV8r8y5d z5qm}CzD4$vF>K-LJO>_8Dt2&Rw1y_>2vTXeBc6E4o9@rO!YOa_3#p#AT{ToxayZDM zl&X2;DrTY4)9vjYgI{XE>=e=qexQx{{uusdR}Vm6NoZkrFfl9VO+A>S`}XNldi3>XK`O zR-JhfOafHTwa^Uibkjl%Ev3&ij1}<24`Yuq6nADAzUGs@lX*Y<$qkH)^@Z^5v&M%$nOl#hK6pnfRj>Zk3vpG;1D$NaJ}! zEp?afXOQRv97s9#$L6WD_0HrX4`#3AIk@rK>&;-SmXQMLm+G@OG~PGC*}b)q zF{KMBLg`UC(Z!{X$bI4uQHgyUuc4E%)-A6w-wR`Nx#1Qo4Q|ZMfqF}Sd4HmmLJ}<# z<5~aNZ!MJv=v3SY1EG3e6w?58p6o+T5$w+`f7&$tFfs``^Rw@fVh^M-MKRjS-Ug29 zqRSuEk2L96jjExF*c0b(`9Lav8j*^d&YU*zKKS)1u9kZ9OJ2ITXev5KZm?;$r6ryz z5{1U2H5?hJ;px>RO1!bGw)s}X8~N^E{yH73O%^{m<7^>!_lI7Z)=QKgKrj?1Gmk@g zIJJbAe{$zEaMC@uEAmVKD*r)?h9vi&^p;&s_WDz}VMa7{>BluVV$}dh<9r+Qa9^RL ziaPGQHhbgW*o~=)yw@(FT|R2EjTj^?+c(TGY|5HvWi=k@ZcwZ4; zoFt&5v{LC#u3iu;y?uB_htHyfp@qL!U&CHs|G9MBU(Bf2u$sNy_ASb?)q2$y;MGqp zN_AvZ6phk)BFaP8wHS-*dULbs$*9TQ%5it^O`oHoCm_HP+wyN-tYI)UdjZg4<Vo;oZ4)s!6&yxMltNki3?CwNfts>HEO!8kkAH0Vvp zK;hu@c1!_{ z>jjm*-epwc(-9j@PlfyL6Mye0NNn|MziSW>8}_g2dFwsIJW26SFWmu0eIBNwfpt)n zynwcipx$Pz+)Li6xxh8^?04GL3QPyVWDAp^?d~<)WE^JrFrnN^PwixEGc1g34F$kd zfNEV&$%8*$xjU%zXhZq%F#&MJsb4HvnOQU;yhT0Q|7KJ2>Vkpoy~%Sy#D`~?o-Q5~ za7dbttrwx4>&spEu%aiK*#kj(q|l;Acmxv*gQisi5^V-w8UE|%dC0_Syh?i!9<6fJ zaSs*+L9G=T1}NTNcvaW>9}2M8Z=n`(5`R3mWu?6iX7Yd^^xLYZls>HY(Tp19i+5mPEK7~x+Ps|4=e0=!^5>%CLZ2gtXb?TItbH34K8UYUN=j&ZZQf7F# zO13HAY~nvewDdWtUkPapjWeg}9ZBEw4#~U?A22-TC3ina>X|P%FSWJv;%WUUF_>Ks z%lp*xN2+bENw+EyAl&8)E@*lA?KS=qEk)<#7Yh&Bt-MZd98;BDxAKOtzVA+T&dY&q zsA0~j=MnM63UBdZ)O^(y*PN9CbaeMe9kFi&vNf1X21)`LlR|sD2-ni9ZEtP;Ml#v+!tx&{{}uG%6iwD4)AkU=!TEDgOduf_&|5}Tmm6&&CwL~Xn&9TEF> z&2&p8QN9tyd=;_&if>ixy(J}X7KN}gA|J?~O;9SnAyYd__PQjU((c$Yx~$=u*U7to zd8NaJV2JAk-eR1f!lh`T_ZZA|mrz>!XCj_?3^ob>tLOctC>ywsag8UZ6+Y{3cZ%#P z(z|#H@F+oD(TZ361;$$u2TPL+a(4-a*bj)3eC?Uzlq$`;69EW6q%Ejo^0 zccX!8&it{*IBHMnLcpT}wpWAtfI4`{UR*-TS}K@VPHPqj-CZYbjfDH&P9G-8Hy?%9 z^;J2D>BfdHM-U`ovg2~{sjQW(o_$S`F4Wtfp$0K^PQO)sj?aalz#Lo1${-l@JWhbyM7Cru>a~3rE$Y0kYfqCwoUFpsyQ74fiEuGUsE3?`olHKusLuBd8gh z7tCEFMkOoa!ZF#?u!<3T`lWlQ4}FU1JAD(9Tt0@k>5p|4D>=}&^|srvB~kk$-Ecom zY@gwz(W{6j113qngVzY2DX#v{)f%Q6z6d!a?wRp<+~sF{`q_FxfdL1es9}0MfkjO^ zJ~x}7tp{H13U_)=t(6yIaf9VZ^bwbhUk=+Q{n8Ar<%$!4%-T-x(+#!XQfTDBn#vt? zoL_Qzlc&B;nL^m=t?a^s5Wa_#HnAgd?Pv?{O}5EEfRbPrz|l3A%M=szQDVHF{0`dR zvQK;BRXi*~;-5VyGQ8kSbF{UtQXAXIHSK(4Dyzji5qR%%UR0=YS{W`ah0J2MB`HQ@ zxBAB3a*iNlhJ_vu1F;Jv5ydL%Hoj}HbXS46j5k{b0|ofn#AUhuSb@QXeWKejDS(w7 zwShk!po|e{;1?M?<>!-*;($KD+WST(8&Ebmnmp$-JbVJr>Gue0yO&%lxXj;%?KNw7^82E z+OCLyBxn8RXG&^3%f&4_@$Z@`({8G0*{%L7WNgdO1lS3unh5oE0kaP2s)@N?cI(9N7dhOVQ3bWg{m6ZZ9rd1TUbgmRwqQs98Fi5LKp@)e%oo7G!L9EJ>oEvbgzpFCTW z!=~=F5I=qWsx41Dof?EXg0Wd2O*L^J&u=pP=JY3L5%9R73$w26uLs7O02$+?KbF~# z-5{Eg-TM|r8j^Ry8$!TEZn*Add6BKIs+1G^{CBxoOA0FsMMd1F!(Rjmf8zQKdp!8P zf`BZWlhcKh%z-5&D1F{_)(nytIW z5XXW+A60~v_TC7e=RK#yVOC6P<#o2jEqVIY&_exe=;8r~ZQI_0fv>VoqWAXj*gWCt zM8o-SEJq+k*Lu`ZDEh@P5F3ue)^Pe$O1q9BCdktM@d~iQs_i4>- z9PRy9b!#ky9SOYup+A0ng%2`XP$AkNGup&&z6p18zn*q?r6$vJ(emXyd>jGwyJV>u(Wf0g+pCFVk9B`*$l2g$|sq{fshuP1haj#)lo0giVjO^t=1pAb#GaA&~T_;Nn8Y5OQ8ra4tfHC z{K9t@))Y}zinx;R!@Q>j&v{u(Up@Of`7koMC%Y0fd7mbpG^Q9eF;71FSpP?c!LGl~pzWOfZbWq2a`EIV_ShDOcMm^^^~l9@oDl zRGpTwB8Z8-D=D`aEkZ8D}}mRKO+}KY?NkI{tLEMJ-_G+A{Hu2cPz7u3hE62&3$s zjU@qQN)VtfEHg8Ea~?#zv-F|||JTXk*p?V0u~AJTErp<#u&okykN+XP9X2u; z5F(qZ+gdXRr`gD4Lls>q^N8+(Hxi*1y~d0QNcL*mOH1bG<iKVyd$13hcbzxe6H-0+ZYBk!#FANAW;t3d`mpMvf|5eQm%Is%Tek0h ze+Lyl>-3ox?URWV39JUTu%Sci3nCj&-a6N@r>UvgYMT_1DVn@>$l6WMv!?Rvs0e+K zQMh{X*xJ5?1}==NXT6LJzW zKUK%?+hO<+TIk0igCT(&JqPw411}kiOt01V@aut}LkQ`QKDSc#v z7ua@+I7RL4EG%orb@Tfwc0YPx2)PzMe5lw%SO+VCGu?-=2=F=93T@}lf}UqbH!<*$ zs~mvl8jy_95^O5Sq^@ospyG}2lg__Y#oa5%*_o>`5+to3V)_7noKu^uDl_?9=dQ@q zI)WW+@??KE7eaKphJuV#aA~o#sn2dVF%_6Wa#5>=c3^-s zwSVgemswWMGn03c%$QbmLqVg48eh7PgT&GLO%7(P98c;T>=lk*sWu3Nyq0XG$#mc{ zSP=2^#9jlXIk_rb8_Ia@rh=IPwc#H#a`L4!-NBf6PD)(?nC_kQ67soM+V91PjOLFb zE>1&Oata9Sj_ywE6VkvJJM)11oztfDX#ZGCn$=SffmAQ;F5i<}9s{Km1?i--f?0#Z zc_UGEf2L=(eWU)tRt@S7U(b;+*(MrUUQQN8bT8hvsTAI*_2V%;WQ!J>-uj7Eb z?n{VRc@YrPpYZnuRp&Q+X~z!%F=XIC*!DxVcyxz2`X@2}O-hXTg3b7CGy4O&P>ej` zg7u^i+sU}wluNo(;4EBByFR0MG>Mo(z&mwKl&=bQp_xYmx7e*W>dQq}xp zyebLcX~n+X<`q_M_{yLKyJy;A5p|VZLXNP(J=3PlG+tf3R8DnispDBoz8z$-N0GO|lu9IwSuK#oQBo+SXSQQj^eN*qTUgop*$=V}j`od8HCl>A zYw_7`^6WPS6#2npDOyU(L}=_a=DC0L##{|?%KsRT`H$xOu%_w$u1AGz@Po|44*2fC z1wZysxiv~5z}u>31Vc=BRM$;v3RjS03`Ztr#M!~Nq62i z92v5$STRFYNjydM6jAc6WW3#JsJCJ3m()~6OS94u=3D3>F|cFp`B^b1m8D7jl&Gwm zSu)#IsweGg4)euS2<YQa_QElhZwQ@jge<4*3F# z%Rc@T&<+m=cdX;g1-?)8Qq$u9+BZ~B|6BV+yOEiKm~~a;>dw%{adpfqU}}opv$kYC z6<^8AVYvv5MI7vT>aJ^$@;1iZZDN{H0^(!E(F7Q-8lGZC!Qm`8I63x;&&Ad4O-uNlfplcxFL(q#(R5ISbo&d=5UF==UU)FAqZR3it2Vt||ix)w#I5)RCI+d+~*# zR>|A3%~L&xYOLdJjb7Qd@CW7$&UMT=uptq$&Ha{pfg7+M(;wNZL;86FoHW_WU$FqI z+b_KNP|aHoYD{9k6~mw7d_))8;6tES2X7y+fvVf-l2lw|Z{=zmyLKNPJ$wlQ+;<(6 zpF41qvWG3wxK*1-gARrM+2g0gG&0?}m_&Y1)=9V8I!4w}s)YXKg7vi*oaM45pv?nD z@N?{`f1vgg;iykjT5=-Cdt7@_*~CD9 zrPAzug4S+5IG_LJFW5`+kq{W>%6)%XMX}? z1XlnLcds;l`eH+YbDtE#9VO0*N}>=fF+h`>SmYCKMEU>%tofX88MP5+^&Z1&w0&^M z05$ZNIhKf9eAszYZRHQ(ZUqPTgul)EwNx4xIMd%(?6iCT6bk6h-I!OynS!G3g`deX zrs`5%z9a>q4|m&i@LDf~5B$;Z9l3h6goK_h3J23W-n1bM;k?|un%Vt7SUjeB;B~m` zPr&zk0~@2(^v|oEU0lYhlR(+j*=DDPMqEo9BrwAnMJhgIHTI{7(YiINNC+(v?ADg1 zg<0)o4p_uOkz`*Z!$Oe^R##E+xfgRR3aUt-u8ZB%TF|N|_<`-KY*?pwxYvo+ZiL=7 zp+Nyzey*~TSTnDQyHrMQKZGOPomO*cVa#hdlNvD?KXJy7V3Eu0TLZsz zHjuumR4vVWyXA2ZQeQtJGL_flvLVuYq4Cyu#B{OxrfuH0W4u~Y#YP5~J-W}Ze7?HI zpOlaX1&qjFhwC^Fd=IMgL*Lw37C$r|pOgRH@U1jm@ZKFrwrRZbw|wj8OI*cHO7r;#J5j}i-E%3 z#Z zQjY-xQx>BH9!z@lc&_eK6VlVfk@( zfPGJe_L1}88{hgZm>u>q?S5UQ@};O4lTX8mSB&)XtiE5#x!3EpdBbn-UTQVp+FabN z6)=F4n{H+7@k9V9Lv!4)6hPWEkJVvs+4)55h^>@=&a8~9k~Go_*h zta&h!f92PF95(sEUiWvg1zicUSMnox$)|!rIj{fPh2i^Guzca}^*vOCW#uQcG4~Rs zrVr`%RHw=m<>VC;f`n=wlXLSfVlH(OnYx9;YM>9x3>j>cD(ch8)x)YDOB zfU(y*0lE}!Z7xrtS>6}mEhh~U9qY|8sDj>5EzGOnRKLJBeAHbFbbDefuPjuJ5{O>k ztB=A7mm3On8uC*96Xo$_(s0v}hCHUJ$Xzqi`;dv_oP$e$@l-hH!>bGGUiF-3uQdM| znkiR=9>j9$DJ7sFQ5*kVOtU;MFDWWcYa+}X)-V;FqI0}@F+bx+7Nx4W6Dvj={__^? zhEIefin?H614jl8SkL#+P?KR|qW>T=Z}6ucC-!^uCPdp`w2PcVrL6f^GY`ov{6kZ= zz$qI^qY#nOaAaBx_Pd9#m}ddmg}Jd%?22U|1K+oicuFNLjm(y!rAgv~_fCrf-M0s< z?!Rq>edwpC^b|H8r1mqE*Meg5Xoiiwd;p_({Ro{*1Tqh-gygCu+n$TJ?; zRe+O2)J(nWrxzMdt*+P`lMBR*RIGfAuA%dzMj@M{6fl{m?|&s#m}~4GKXU6|hEmyM zl>y_?;o-gs7;L_3tUz|;@G}tP0YN)+yZ&%4BRSo6ayjDBPn%8(A*Zs?W#hICc<|*? z29x#qv|TM7ZI$#M?{B1}JqDK1(t%@UKj^i0W`XjYsJXMZf}WUy&8tU*P$i)psHB<<=3y&;q`OCX&rC+lXFBK=P{dQy{t;5a6RLE3KidQb|$vcA_}Uaox`{B;1DHO zy$?7 zR|NiAjZQW3W-(y=llfqK>sgDujZOlHA8=V_8ZDlT+dREelxa?R-nbJuFm}1O?_XMJ zhzfb)@XmU|9>1UD1dNGbibv;sYN(klwb+swRURArO$4r}Fg_k}a3zR1l*BkYn%O=+ zm7?A1nvj^hMk4o7UHHWewX|6#ff>~{q*8%=)HZw>Oc3W^cENV$8{`Ej&EIEu1kC) zhniXZkFW6Oy}?!qJS{dGWYf582eId~oOZS~xa;laYOFw_KbK*^eL>J{I2BMM`tVNR zU=BLhW}w)%vi7oLe8a-?;^Mr+I*`aOAT%9%bbK=7Cv=xzM{E)SG$?Lv4;G!bufd&R z3@Knn2oJEbvd|+S5c%ax5d9}UMO-Cs4KuTE6qlglEf5l8XxJYYj5Cc@v!-y> z+2>!-3z%;IUY5rFQ15hWxd+5??N)$}VH!7(qj$7~{b{TcmbVSkf7W{wxwrC8z9wH< zAyG{cDB7EEemjy^ojC)?+Q9+mTE$m>a&E?#Ui><4g3_&N2gT&+h*`C*UjGdRI_hIb zs2r|*>s$xV3wcd|0DF|@yyzpNn2s#0Yo@o>UBypsfBDVBLk|aM09D=4;Zh!{Z+m83 zQ7~7%`ch99iQnYpq-h?H$>wDI5dMzh5lIdnn`LJ2Y>tLWRwWAs+u{6NsnZUK(M&b= zxNMUuGdwaw*9_Wi5L%+zx@t?yzE0a_NXVx4*#aKhsLjlmHXTBh;OtZlPCBj6^BoIF z#4N}4ONd$5UAP9`=r7*A$E)}$sAp*U*?XGBp835WTEL2hM?Jg!Um99MQbFI1kWF!; zYU%l_--WN>;0muQz2kzbkxHRW?xq~bdWChudGnDz=gJCkE;yIjyLQrZ?<+ovMyG$C zZ4y=K9ux;#LX5~Ca`GP%u-%o!HW zpB}K-X#CZc+mWqLDhTiLZ_$0%Q5rj%ZN5uFh;Wb?NJw zOP6&?=%q%pj$HWsq3xqh zChX+%BG|-+`j(|)W8>ojh;cz(tWJa>M)O^}HzmilDGv(;GAv9i*Lx#X=S5o;9TQB| z6wXJ*scoY5GPk#G;7K(5pyrSU77E-qu6uXopL*}p4uNeqLb0j8hiGC(#ngIn$ven% zM&&ueluL|B^a@4_HUi@FZtO=OQMN5h9;_b|t@utL?#Jh`CZBxjS@AcL0<$gpde-rg zvC&C!@G@Xx;r=ElpM!Kee1DP8VKDZkq>4sj3goUTi%YspSzK{%Z|9$F%`dyPjg)$@ zYfuS^=RKhw7B&t;jDZ6jdy2pM)k#gQwVOQp zOJ4$9z~{ONvSDA4P~+jU2>lC>M1JQEwKR*v9Scg7w9?X&uK>b?`|^-4!%(4%$2#Ra zZ?4ilg+1X5nvlOxaPeTlXC|gE-Xn8`FOli7i6?O|c)$=G z@6p^Eqg=LH#P_YA7S&*Hu5Wq@z9tjDwOrU_)8kqmzU=7h1WN6bQ)4$7p>b`dq z6G$yRdmRq7nAq(r8vtP)qN2j>dMVJRw+7ZVnt+30|G0vD90ik7xAnhy|Gjf;P#P(6 zc}seQuIiiXuZ|BcYGpK`6P5VP{xlo7K~tK#SgedG#* z7ypq10H?TX5C=@(=3IVgTpl%@&X9L>%Xrgk$fUju(|y&8CJ=WS9&dmnAofn_HL;%_ zj=;~~K_fLQFTPUZJ+lMst0pI^Cvm_UE2P4r;1UU8ZTO)FESX@Tk3BlgC@SSrdD0Cu zn0ssvL;~BZQzIl_JwheFAR^;mH`$Rqx^jXA2Ex$$o?q>w{}#e-Shh|>SB+MRGVsN; zZ&s$Ppp_HO({6LbCKh@XnXhwU#MDromIPWvr^P``OgNypKF7a1P73?hF164S8^S~#>w0o~0Avt% zmK(X|N}92W{7j=j<^r;(>rJg(J2|=}&Ggjh=~_j1)&1At)5!#8Aj4MJk$aZghJa&H z(7*t2@sN*WKxf(5z%1HHDhzJo1)2iHvC;A6Vp*hOMutXI37VRkKVq@I0kI#+JOx`< z|4OJ-6df<~TjJRGbTviQS9kerYa%G5;&vU-*&~Bh%3uj8nY8^wHMwguli=7%2R}Jw znetWi;B2NEJB(TZMCqi@bM37={*ikyfunIM*O&f*;V;7pS(^ptr~B(eWTlh`5it?{ zBt6+US?!M#*`V$kENDsUcZKm=YQ3x7co1&;CV$ zvL$E>v=j)&VkRJNPdzK%(|ife?AyK;MZ$!z?|8$aVp&Al9UHJ8jz`3vI37210BoWO0N@36 zKrdq9W=AECEkCCt12}_NXr6!tC_y1+mF}k|Z|ay@SiIC}lt>7AT;lV^Qqz#Mh`V}@ z;LA8*UzBa(bi_~6^KF@R;BOGPxx%Z@tpn>VTt8sJKwgj^{f*>KCx|5)urkZB&BJfjeYNKwuM zkHk4opJ!nB*kOQ(NWi04>p_~v0k4ZC%!kzBoa(K8&G(l1k2g+`J@M#uAkTRDDitFk zp`@h9wqbXmWi!kckD8Va?`A6$s2KX8`L^S5{jFcrfF&UM3I36iksJ|1EVAXu2|%6Q zUJFBu7R%+stJiwlV$sfQVQzr`0iPL2eSV1Pp?UrlAPJ{k-k$>-$;mkNRvTorbne&} zUUF@Gl03Z7TBzcX>S5#EQIAuVL zFB{c*Kb<#WLz?}RTvI4SWVxv;ik1u0yaEP6fDHh~>ug@<$5WEn#b6&^9i4vp=_em= zQ&nQ=Rz;_~!vVCtC#9rZMm~0HdJ{p^qj~NtJesn}C7viwv*3d{g0S05Ul9@jcLCes+4Bx6Ay{`IhivMK1e7}dlMXNu!f#5OTh1W>LMIaz@S~B*+vdxqhx^- z@SCH>vd15(Rh(J{(V2zgI{U@Nw*tb$?MlRDMAK!0C zOA#_;yu#tL_A3AZo0u%YpwDc_5^JEb>SfB%A6p(i#@WHiTm>{gbq4@X}+q?_vLd>dWddKqee|`^lTmeG$tvE<==bZ?@APRe4 z|GN7fRftNMCH=qu=X(Z{A7*LB`X;u6DJ#g z(A&K5Zq5$k8>hJC+bTJ$oNAVVeAZtoc(}|*`HEB&5DY&6^~VO+2G<+#@0}zy^dRW& zEU@$f?r+dg+8`lZf_JQ*hpWu(32m3!0JA%M!`~jAHrkI>Tb5Tt`N$ZxT<0F~XblrQ zDssULUt`{+o3%gQjxZeu{Ib(N>u6TmKrg=uC7_&C=>m)2r$kQX0AW{qSbGiyIYpk= zm{M`FSoAvJ2tfpQNd$}G(IS%U!@Gx-@!HWEYysDNp6TaO$d}|_jNU1$ybiC|1GOE|b0lH8%Gk&=lvsr|Ef5Pr8E<<28 zZ7Tu^UY!SNO(+Bp8%seyZ$BF>MJs;A-tE9?z4}ah>WL(<4RPs;!>do3WAWJngz-17 zNi&&OI=A+Jno-K(=dV7F<~$vOH@ZdA)Q$35W^$-nHzGtW;LKzz3*fqa>KuffmSe-f_s>|D?cY`}Gvn zGXG8hci{=P)mM}7`0Nu#AX7s#7EOWE$9>C+41PUp^Hbp{-h^`NsRmi0^FWE6Ce)8w zEoOHM-86bxXdr@k2ZQ%#+Z@h(R&o4xIKit25Z(sm<0Z^+8AePqZctLD!;||C%&@UJ{16HYC z)?V$8q@SL{U#hE}{v890DiwwQquQ=yDbdP&xfYN2^ud4$fU2U1Won0xliq=82T%Ba z0DQI4;j=IuMI(mx&z}`Op|qxzkuD$V>wZT_XtMDB%|1h@Ne>KFJw;E{XyX;_Mg7oX ziWFeECdM~6uSzv}mM0A6t`2@i*uL(OeMQKRf4`{}+n9{|6%G72addx>s_{|_iSxaO z8WF9ugm?;{vu(Z1LEDYUoVq$xr_CwK7U>fedXMc!SLId(3L)zGW$FVT#k9@xN->E? z=SM8CxH5;S-Ef11xU9-y9Yz)&Dhy5Ec8{|gm{sUbcpOcy9`?5AT#XBU(DQg*R*#cQ z!mj6L=U!xRn2uJqM<}bZ5*Hp~UUem%neC{xe?kFRK!Oa*`)sfsFdL+Ps&rUdya==t z?~xNg-azZRiAapM_Xjd!$MLT~1}X)$aU^4PK~vom&w%JreBXwSCpSt;pP|PxmfYGm zC3e&B3>57ehn4fbwKjKApg0+i=y%Rf5h5|WisazMkoWINT z;2+u^KiQnTDTU=r>om4V^Lc)St=^rHi5hP|$F zuI331K)nMMmGi$8IARv3HqGIJ_eU2O*$M)!xQQ}aSHLmhY8+FS)%*;T1r4BIMIV60 z&6C~+L|%2O+WbMeV7LY7Nkga}fLTZz;Sv|oPIFo{cE{Z5C9E#HMwc!_=M;1+O1xn zq9mT;3V=iDY)HJjo0LYIUEgfZBVF=I#LIMFeo$4LRM}mRPx^oA80|1K?l~g4l6Klm zFytApmu7xAUrCH&$Eiu1KZ-(qg4ui`u6__szUFOm-kEKA`@ns?APnSqbuS&dX954f z*XF!opqU+&<>P1&lJS`m2T6q7U~FNMZ2Q{FijK}N5>OLo1$@k++g^YR@eKq8l}tXW zTM#8|mc7q=GQF(SYeDmcim3b8LK3^RHx_nZd#l-K)x%SH7q3=hvm2i6jrukrytj%> z8?Hm@V^%>4^WUo_n9U+`#LwNyrJJ59A;+dXFsJPgcaN!;dQ3^|3@@hjF^bAZA<#^h z&3xzb)Cactqb5~WU{X9iH70^sHM{RfgOw0Ol*;y>QX@7kx&k^TiL1AC&IaKeea$Sg z*WVO6VfiEeKS^v%0K;Y~ibW$u`~(H2R3}-mT^br~&9GHjXI7{{tVa1kQAI|&B2w(* zeoApkJ}xuZ(a+wv80Q0RP*zM(T8UAZ06*nZgcZksUvu%$XG$np>MFK{bb}&^bTmc_ z-{*?V%3Pt}^)-;Ox!<38s92@x6Xw}#LGYUs0Xoyb!x`*``4=&@h^llP;h*wyqsYvb zC-cX#Q;I!-XG3i*6d)!UY1sHUi?&Q1gJX^9Pt1VkXZjf{*t$5u7hVw4tC1=`5t?GSztfxXbkt|9Ja#aHCGiaIwE zTh74Fe9AXH-*|bo{4Z6;tU(qv9Y)9B^=E#5ZZdZRJo>;BSK2CNlwm|C7b+E-nA)-O zZBPoA3fdqrPyoEAHn2e+mR(RvU04|TISGh`_z&wcnZMTZLDSEaeQvz9zOlYBJ3B{t zE1=exyUv0TBVXnKFmqUF*mhQQayk`yhz7?g<^e^N#2R`u?uQS_Ny+!`qkxA_Tp0)E zJlL-OZ3J-9kwTi0LjI<8P7_Wix;sgp%d0@pzhLA-LP`QNAt8gKa$+j4L^C`rjcX4) z>0-34wkt|%Nv1o=(rU8}9N`w`<{!_l_7<|r>|TRbot~2ecNBzFo{%+;@HLR!1Dbl8 zFYpX7ZGgy5OXdbFB!W)kZg2hB)jZ(ja@QSnML=?9d66G+62uV>x|(VST7XAQQ7=)< z0L*6g25cLO_JLiwKrDJ4)U^}nk_A)v0B`bxL2GDm_{M(!=PYynpc<5fnVXyYldO)8 zO^-+hKrAo?c2Kf07-@Hp=-`4{DJm(PUhljO5-Kiz3uv*%A+MbeYT+JDKz2HD<{BT* zQ&X$aEABg_;fd1nvSWjmjfRg=O-I%l6mvbbw2BW%yUE)VD)2e%stvfeQk$E5w-dwo zmLBdd1rQrQ9(MmvFM!p&=ik+-6bDwXI|C$vLLEXFsAP4(qy8(w(A@s$$nL#jv}DuZ zG*%=B+kM*Hla3d#|s} z*n>gLP3LhBLc;|ny+_F#!2(MnLJT_fvxB)&b#>DqS_AsOw2T(;f9%fdG)tYbshr*{ zpL_EpMYAgRZx7r%UtKLj-PH1Clc%DS()QT&)W7Pz3+c97XXkY^jUY+v#y-G;K%4D0 zI{&wVgf(y-&aDeBt$bxaX3Fxq-xwD>>Cs5O1zu+$IRrdE zU>GDrT({cS98(@727s2adR7Vx6Q6f=ohlL%#TiH_rRSZn;}E}ngpEz3@HC4uGUPk$ zVA1K^{q3vi$X=B$Nw_?m^&HOLup!N1L}E8H{AjmHDINuiuDq_707ylXfm*u?u4HkD zW)mi$jO3OxcX6^m-PtR%7CkQGy06s6xtLiWkl!;pQAZr@*Xl1`^1dp0+}q}g>~Z#b zU=xUHt1JZ00P;MoXA{^JJF%cdQ7F#r>R}>llkx7oNppxtgx*Y)qKT`=maQUh?9s-| zaGr3FcFZDg{r>sLV1JZYUX1bZ)=64KDwl>NvfvRP^qcA3=Y3AkUl!hQ5l1}B^MQOw z9JT>H&gpDD7Ndpy89eS(aedm&Uh}`y?v%A|b#X+th!OR-6RE7<=l5-7C~WUaCv#@- zb=lr4jlLvUZh&P*kv^rq!^;qpSF-fbPqA)0xgG|Rm!)}bYx!?T=MMemoWn27bsYcw$;@s6y$#wOJy`Af)BA)D?o;&hykpVC7mY**pJM2 zNrSWN4(FCzx4z&15V-v|c3R8RYCjnmajvH+fDY~eFa`g>)Fig>6X19vjutNJv#6K$ z_WjWamO05;)A%&&XCuI9ViKIJVJL%#DB{R+IX~qTVvj`!24+MXQ9gIU^SG-~_*J*( z+czO@DBo(V$B4n2iUMbM>%b5g8hM+Cjm0d2K?cgu(@OP!hUT0d@C!N47pv;8GUcJq zjv-D1*tH(DarIWSY7Kfcwt%ZPI_m_b()!mEJIk-dsif5%csvi$Jzb9Ua3YSHExf;< zZUC=Xg*riV{o)=0f@Rmk)wqdGm+r1VYaFdxzvT&*Zz?!LlwxH7%{5mU*J=o=VHF*u zh(9usYkWv4!V@{x^k&Z91d%bcRk{YIJ6SbK7**S#-GnHEjr$AlTEZMt%x^<+y;CTx zczxsBjHY94@l!E9{bv@_nU1K4*lfX_T~koj6F`KB<(fnsS}ay`rVJ7AS|SQiQk2{M zw(^nV089kvZsD<41Sve|&v++LWgEl_DTPWwEr;^M4^jciB2<6`*imuPwP~4*Zu59C zGHe-w!|4X!#$@##Co6!LF)$M_urMb~MglcKnL3%(DtgtvC-x*KEtzw{9Rus~9#i0F zx4z@Sd&OAT;&8Jr%`^VQoa$`~8iMS!PT;<%|94hVkYu z=cqB{xNPsM?P)bi+OVLoJL+d8Oa`YeTwi<}lKO(X&(H))*sUsxBKSN)=iRqwVqfQk zh4Bq#<<;Zzy7Rr@`A5aIQR^t2caxJN67%BYV-C1u-=u>+ou_nu*&b@Q+tsNEMOBdi zgydx0)Di4+wd27k1t=9#C+ynVX7jho0-*JEZ?au+0vNO#(q&#p{N!?@HeF487ra3L z7$#OeKjVW+GRZR!}Sxz>XG+vbQMVF{mjico^X z-BT*-$-4+!J0wL~orC`t`@)iJI{U|0M-S=3J-D$#Sm&&IRm2O*^F?G$rKJZrs-4|I zZB&ZFe!rLB)R$WKsE$C38#Hx+oMFD!3?fh5r!beSWd^4uaX zOKQMprTOe}tKQ$m8o(@qAfTnUx4(ShjU}6a7$@0t-j*`wi1J`pYjFD%VWE-|FXGCl zCZ={~%EPLQa}E-dslnVqfQ|OGd?yA~JkJ?>gtc=2bd5Xq2F?rRcr3uRmGnLUt4PU#jvthGAGZT52fZPU`MTpl7RMNLax4y}u%xE^Ok z@Exo2#M=;JWn~3M>m1FljB<*CzK_x%qWVK5)Dd{oAi~c|K7{N)Mf_QIyU?wf1q@yA z_jhr(fVfY2@wM3np8zl8=Nd=A3TQ<@%-W}RulW!4C^9y2r(IxVSgg7k09oQ&dJBcJ z>1O@6!Un=T$T*}!!{~M1=1~?K zG))I1!FwO^9$zgLi#-4%aJr_;3@&KX91hxlC%RUNkB5XBwC049L0)rf;Q5WN#_f4F z1L569kmFEh+acjij?}ticdw#Esg@RHWo_r!fTWo>_El(nzHyNlWCu;)Ttq8vz8->v zhfJ>$);TslDW`2-T$UGvR!n|tKu1!d|8HtRWn@d8I6&H=ab&X&r!c!|g}dY4!w6qR zE36P*p=#U<1}(^(>kVN6!-SY+u5-=L5U;jVtJ5%(yRPIXmzOAbZk<__v1fL%3TAGA9eA{2R@%2|Nn)$xbHg9Eh)SYAQQyEz#OxKtakRe_>Fdr zS&IY&kO@bViAQ0GM{OGCA1i@5xYs{Kh{NQ4NNa;iDaGtHqXU1HPCb6lliSnU(O-UK z@V){3I(iUtM$VVSz%^Q{k5Nz0ni?bi&nL1J9K7x>3L-yX0MJ&Gx)2qAMnC$d(Uz#oz3?faOpAVbtwucce;9iUs4BOpZFD1o2nvEAN(v$(-5mlV z-Q5DxB`u90A|N6lT>{c2(vs5Moze}`4gd5U{px=Azju!_#?f=OviJMGYppq-nijT# z>2ho;DR)AYOJ`xLSiR=p;*2t$ky$-1^B{PffX?sCEw~;lS&Bjk4cLd8@~htWsVNkc z4EcRSLXb|*b|s5@m=Xz9jc+R{iBVHiEa2f?6&z3ynm>)=_Ynn+jmaQRceY@&=uOfy zca$@wd;U*_ea!WA+@BVl)d{`%Se>8~Xmkd&z9tWwb$^z~_z+$A;aB(|h*eJA>}6?b z0=AxEQM7oh@;r&-#)sZaiblDU>m0Mef}n@esF+w=qRGj6*G_XwC^a?wkw_mss!MZo z%^KxVR*Z?6S)OJ=nW0^?Q^?k^<>;~H>_8rnA8yjm(85|LDlF?)ysAJ62{Wf-_^r2P zE)gfV;-P@}mrF-=l@?aL-1+5?%;TV}!@oP3dYW*@@h17DFwJivjj#K> zpP^`I#GJT%rx0J9Ur^;1Ip1h!z(#u{lbD{8vQc9YRvf+%eCrmOl8%nN7P(1NX}+nC ztfU>wn;rn`%o+TWsb00fN>~j`)@5|%7FzBpc;f%1DR>6JkGLB|9ra*ey+Q+*~O6DT)(6$QuHbBNe8bm?L>J@MTyL{_RFu=$^1<^I-I zzT|epqG{;S@EqV5J|raQJW*wdmPT1#Mh3v{q|o-BkQjaQr`rp6-`=YGdf8oF&v=9E z>Cc9x(U;!$KTR7f+|A=PmbLl6a+c+&;Q((dzp#kYl)U-bA&=)PW-uB6far_v%65zyOvSsia_<6mpHVrJNw`x978m}xKg zdntg*33wqz+RsN^=#ubK(@4t+{)elM_Pj=HzcSX^^LZxLDWpv>?S-wgt%Sy~%*M*t zcl(WZx40dt(Nw=t0qtyXJ2Wd7RXp^nV2{K!oU`3S`yj+?ZQCam!16j5saOcq&vyHhHzsvdlyEF%si}cE0zI4(HG7%#Cw^Q}?vyVCDbaX;u68y7X zEkkzuEuqJzD*?FN-tx~>B5Ye58y||9mTOz-R4Y;AMXL5#&#?liikWfU==C_Q*I$+B zo2#jwv!&sPkiVG{sZt$qK27imYYtkSN;QynXSG>Jqmk5+v6=$j3&>Qoq=~aDi9zG^ zx|e&(qQ4naT>Qi+5R1-_w*PD&{%HzVFXLK3LZf~>cV)V3DbwV`}v>cY^WbU z?6g~1t-z`;$=&w3&ZvCj3(rqgFZ!jA~fyqez1*dMj88YQR~Ny}=~Gc!~ys z9u?~`kF_b`H*$bWgRrBCQR~05s`}MUo9CkQcQ-4H?zwX|SyI(N%K5V)UAsFAi}*h| zFF?p@9?;ngUiHrz`U(DhlZR7}|t*orT5FX%?;Vi8?|1_Ebv;tlOZ)R|>%-`}8U1?JWt_9pQz=g)jxi3cvByr93y*IQx;M#0ix7f)t>3c63x2Cp6J&q5 zmlmFw_u-(9i)G<%$yN`Oh9w6x^WNdE<<^AR)(?XUOroR|QuU}OkWrAS-+iBi>@i;D;O_r~^F_ytu_%CYpWP(= z*4s=-MiCW#h9; zIMsb{aaSzS9sjsmfixa-L^4(-(4Bq46Zfqir>eF#OeEa@CejvCB`O_re5TA9*tj|j zsYVVophTK^{rh?5h(rJWlJ=LDnTwGkp}F+UAeg{vrx>;p)_ggX?ML3d-Tz~)bp1tv zI{Q!%h#pnww&U1`Y3TSA0UzyOHS{?>Jg-KVO!9nG;o9_&@*XP~-?9sBx7|?nrDE3x zYx2!2*mIkL37+MTd!EehgWhrnBjCHenW~afojw!C32kwqWinMdbXxiwi-b;_dUx~k z(u~Z;Ob!cimkhYQf@@)|$t~0qcJdQwG$pRq8DTnJ6?Ex@lkHNzr;%pW z%xq2+OSj6N?4Lspe%vh~z^I+9)vd=HvJ{THb*j~i+~_u}din;mfSQ2@3$Oz&r)>$B zXJ4wTr+^B7aCuj$!k(~X!{ccFo+Tk5rRBG64-TqCC5J7NF_=tWxV@NmluS78VNwiL z?7t<{7OtfILOJK(Q*%K63-i4{(;1MPuX|H)T9nLom+q*$pp*M`K10*v>@q3gQ?8-+{v`jRJG&pqupqRNrFBQ&B&p)7F-bKZsXlcH!P9neR(A zcwRfzu}u<~< zK@J9nhU&_-F)f|#9Bif(VeN{2%tfqhK%cZ3uQaJ{KH$DXdGqZ|Yo;j?=6G9Qa%ff) zBQFzIY5|ID!P;*plU(=b8ow|KQ!Asa5l8uGV5RvUWWTN^$)ys}w|Qpdczdm>EoY`||Kg(yVWx!v4RUb>Fz93QGO2F9WQYv6cptWoav9wp`D~DQ=)5 z*S{2b0dqzT3Zi~f%B@ds4E_VFQHQIdHgBJKCntyE+%}kd*7P=fH8Cy%t~j*x&>s*r z&)p8|gpm{+KgA@(`}+qoO^@H!$rvz4?# zek}B@(VG0QiGy4in7Z*n&x{Cjk|+EI3!cbD^1tWswV(K2j@*08V=DtQd_6#`{(04Q z`(2lo(@h$V4;ARkv|pQg!2PVL=A9B{1yG6PD!2c>4o8$Ngq1Er!^52&(MuUAu;#J*h z74lo(q7v&wN7Xns*H*7|{uicRs&?h?+u`4}STi*w{eoIL7u3ATGRlwH=zKnOQAHXA zs^--kGOpuWYTl@Skj($CVb+D$qgwh|K(c~DKS)1+V)f+9I6I4IpCbSLyBz{f*6r_$ zGn1dxrzoLa?}WI^Fzrp&m>t__2iq&%FMB_!V*pKC?P~crfPmSEdAi*7 z(4#(Ysxs)7g5uor+8U^}Q;k#NxJX0e!ww~j&isOH=+EDd^EeUnx+v%?WCf_saPk8m z&UY<%$l#uMC=sg}PK~8g*YN^$1p+3>k^A0Gx?h&V<|`t@$P)(+o>NEFRdry{P?2(s+J33AU@LY4_pG~@l=^4AwN9i?LVG0xrHoXz#Yn`p@`l)VB%njhq9_< zc<1}EIBET_hx~*m`!;;voOo2V&nrG1GtvLHd%5ibsE+Z`6I>#}P462tu_dWX$W7@^Ai$}G@GZdn(NP%(;$@V+B zYMPO!?&F)t08Ri1`kRUcJKmCOewL5C=@y?`-?#FV%MU?Bn5VAcptlhd$bEQ)EUfVh zRTl7Vg%`RW7H-K>lo;Hr`T6M>FevPw*HLhNUSN1K)r#L*UsNoCj8C!l;@6uwN;Rx| zoAsbwJ7>z!ja8w`N*EmM)nRoqN{!^eo4yOWhMGHWv!d^^Gc%KpDoI~$&Yp|Py4tVt zI?UeX7SWaESRO&rCbm2|Z1t&F9=%nAPJLqUR*8T1T{a|u*#pm^X4+ULUQ88>jCChi zu;y^H^*Q6J#_-!}7denWHVhC)9QiXi!w*@!-EtX+}R2F%{6O z3T37Fj4!U!wV`q6AoCzC?i!}p$q(J`P9C6W_jKgyf;pJ z)u3;NMwsD4+ z@9fZ(*K!nhA|w38LeIE}MQ$}Tm%V{U>dNb{tLmsH1C^jR0{M7Egnt&@)#h#B!sZ8x z%xWS;tApj&Z(+j}uITs_mwl*YV84Hu`bF6iQd)_y_sMqL-#;bKb48Rb^lgCdwaMD6*ih=`&06QvOYUOS0Yf%CG)#|YG?9A7D5sa1x2a; zT^Z8FMf^%d^@dM8(Q)WX;pbLu>0*Y(F>A?hf+BI zQv3PNzu$*mC-alEe7!Yc?cfkQJK5vadeKZNl?_G8NP~jw;_$aeP~nM%QcRYR7r=}F zi7$N;=j60>*{Pv_ZtAbVy1X?!itFMHDB?=*jLbPK@v$OwEPvoaLry;33@15A3fUIq z73b{DrVm(ZFnnIm9YLvwER)dq+cHOJo(&$^@bqUjLGN^`tqP;XA6Q!Eg3jf}O@hsl zvhN}+{O1WP)wMkA{NmK%&-iMb&WEbbksh@@O=b=`I^^ebve@hG9m-4X!j>nEII{T4 z$ic$S>~^p!?dXtS{QpK%#&f&sfp8TMFSLtzEP=-Z0h6D@I1D3<^=+x)s)gUb#xqX3 zhM$~q+qjGL<_+7GK6xtI3XjvNU;mg$(|E(EG_8(2JVr`$n_2D;q%F`j~&l~%#4i>)eGTJ_=?$3cvNwNHVKP3MsTLw{AYX zqNn1Rr$nj!ld1kZ-@j;}`0x}wQ|DNTV&alpgDGnl#MS0*PH`3Xu)}!Y2QD~)^K0SQ z_~iG)mxFI}i#UE%XLc6bJz`}o16c5S|B}ah$)C36peEofH7hqcj;Zt(LiGvR-oEKR zLzIsPMBP(RQ&pR^9bNLQcg0~{=d=M$ddkPD^qfEwm=TzUDt4;&WWh3AXVk zFF0!QRWvkAPBmu-vF|Kr9$4=>rt|ImE(Ls4t*5rfoieQRN$$RluXxos)bw4_&k0;d9(soAW&ZKwy0W7MY9;}OhhZ1PD_IsVh0@^TL|?gp@n8mmQ%MCTGCqSR_^%vBU(Br zPPxEB*1q`RbHu-^!pj9kx%n2~XkuZO`2OY42u6NxgQT7L_XGJ63?MYutc8_p;thBr z?-096J04DeIiH0_lssGpXwPORe%sX?^6trNvN@XeU}TEpr=_9=tx{)+;S!j1?IrZ& z3)X6}fv*Mdgy$Sf@{0mdGjz#w+wp!uu^R1@y1t@n+$9)Qryu!YcmQy!yGLC}kGiOK zEGCO<+`EyEQGEAik{8T@Yy=JHN_{rR)~};3AaPaaAE6~{2VVgDsqt(vc#{?&84ZV( zu<-Lt7%}JwROSl)^MK?u7uxylcmNByEI+79S@3?CbHyZD*p3GYwODzHaI=(6ETc4i z^YW*GRoaTbT7U=XUDT3FWFZ}z-vXMQ9Ag#A%c;?=MDd%*0Ebcuw_{7*_+~>#8(b(q3JMAt5v-7(23!sD;QPZpf1F_4JptPrqNr8jB#0KzM=C_hUNWE5jeMIqS}Gy>@d;1U zOz;C<^3~xL5R-UNKpvF9uy8AF?Y-h_u9V@|$v?5%D-zzu#{UB()_?&tD7V z2mbk&Bih_0!e|fEzx>80brTZ^2quQW_>!nw#qw2=F8;;X`&qONytp5rU;&Q&coo z)7C+p(43|dJN#o*WpRz{LfAImpPBCa2w?k=yDvsvCi;AE~3Bvs`} z6WDUb>G_->KY2QkzF>MLo2Z(p)TtfINIDr?p`j6VO{Y@9S*C3c>9xw8#XF0|QNcbZ z`!6WpW;kU?U}ST84a%UP^1a$|+kF3cRhf~y)Y`yAZZ^L&_UlXj#O}*7$hSI~*)GKI z5cR&l=q!Wc6*z2VM&htC7Bnci6j`xH`R^D>0q(BgKDoLEnf!YDjiz&Ty!@A@W(vrN zPz8l-2P0wRa}~N6Q~oDcyiVc|!2ba9xu7*UeKIglY7*YNSB>c&`zh4O*!(7f$Q$8h zrOlmjs8^sya_(w9QE)iZkH3)i%@q$P;#1sM`YA(k_?ois{K{`*28a0j@|0 z1zfQk2*Bq(rvNJ4Iz|DubO}o<>&tQ_XLSqD!zBi@e6zoQ zkqy>8**i)duv-0yZs%j~V2>WazktE%d~gL(#%`72-16C@YD(m3!p-k})j>Q6#;(BCBouBauq1%=xcjpT%N?1mW$I8siDpdKuF5h zxYY0INph%mmRhWDSk%`&KyUwIk#R;qbGB*288hRxm?xc3gYJxvJ({V#);;Bv<_tG$ z^*K^MFM^6TXT9qK0r&D(&nM4&!V4bwF+OaTLCI5NEqz*#CKR8VJb~>%xy&rxZ@4v9 zU4LQhlh={RC%v1Y%U!Hk!;+kmVyTmDt`zYEfv}kEkB8^+Q#jp6$kX6^BWcJi zFPO9_d6TlK2_cu!yQFNYhj;B)x?Ms|hkk)Zv4zSGMtfMhz{4Z03b&a6U%P>n56LD* zTA5BJgURa1=_h(yQ+~dptd{e24Wds`d}BPl{~bPB2Lnn8tr&Atru9&3AJywReg-3~IOqw0_U_y-Tnh?t)ra@a>jAI!;(<^ym8K^kSTIQ)^>n zM5gJ=!chC!lzH;DtE>8!z*FSfC_NHTb4r9OWGZESqE3}CH8kCBJa08B&@8o2S)opi zra*JFBwX7WC%uP^fOjUY={RQkvBsngkj~BCo-vRRVoPg!8YIf*@G5lx+0oaX@ZL>C z_O#i7<@xyDy>AcGe(Gvtp)gfH$h4$?u658cvsS;{NCz z&c2y)53lt8+Mwym(4L`~n!QD3ityx{h8L*{%99hMAghro%3IfNZ@VLw%51qIyM4uR zg@3-6pcOxs!*Yj`mY1HtH(5m*A(yGNa!o)kzo=y;zz@c!ea9g%#~&|J*K1 z@pC@g-DMP@U0T3c8!R~e#?r$|7t$8R8B!23lU~ina_r~czGs%u#ITzB##gi>uJnr@ za{2;BC9DTo6ja|gRi~F9z%7Z6_<-q9)9bEE$YuYl{o~i#&oq*i#>5zCM29wQ!VcBA zjR-k8xeRq+1yXW5Bir$ER%!2Z8|;*2b68tXEokQFRXIzuO#3tk1U`fWnyj-7$gKgT zTJ!hZiaW2Qmu|)7x*otSWI86uo~kpu`P~00b%@xg1-o>As*rrl&W>)a0<$lmpk%;zS1*`@M!} zb#-so;Vas)tT|^?Ng1ETQ&7=SxE$NoP3`Wmnf8enB8!?XX9zJgILHKkW9o3Q z5{0{Ur*-HYopg@h_HdurX_MCdKcjW0o!hgYI&%$wTpo-CF1Urj7k^$7}&=v8(~9SD_S0US~&N z#|pH~6~hSfr{1KRbj(N6;BH?R-_R(fWqX}S?VR;f^H56KY;Scabt3J1MfsIl2%dU~ zllh&IhUXfhkC}O}7`e8@h`1vFb=(N9CZ(XT{Q@h<# zvx@s5;?5$Ns-XNDru95qPnAdo30kong{|d;9A7PPA$B0(^ig2K5Q(nK(lStPctlwh z_%4oH?*@y{T;XB-y>Y2FueNcmh&#cD<6ivUaTC~c=~&B?DeJfM^YZdCKFisufh%_a z>D!NU7v$Q;Y!247fB=7=U@l^|ap6ddFWz5FVpRMFuXJgfjntaH9^NYibL8p9v0Y|q zQ22szh55AJB~q2Eg+|q0T+OuJ^hSarP1%jX=J!F_hfK zCPa;vn(C@orQ6J|DjEAw#Hs*>VKd&`;Yb-pb4A1#QJw&^$kcYfJ(lY zaPP?bC9==`cXQVqolM48SA)jq(z{!6<})}NA5u8eX*jPUU z7W54aQfy;aXV*t^?fmM<3l6^x0<1$B3Be@>I5Y`0xa)IHMPjrhEVCRunrrVna?y+k9y;+m1vdWbi_4`_0?6XbUI;&fa3+#t6_e?roS?(rSUsmMV?m8`NrnJ%U~oK z%>7V>zKbX>1{TdH@&m9bJh+Vdv9yhNlBq;VnQnqAhq%wdpsS@t8{WoTc=;jpJt<$u zXfGM#a@)dBO;11Vt6Zm8r@;i=QR+priqqB3*&YfvF@k^X^RVl6{0i`pUED5{Ty(xt z8#+h`ljWsDl_KsNPey1%m;%&Clx*)2KQB^@J8s^*?(Vd#hCqBVet|B6f{t>XH0(0< zQ^&oKxKLCAXQb>`L555$ibB&>#{2+q?MCESt#Bp&k zC1&;Pi)k%*0nmnVa}C6D&-9y1lb$k4Ym?K{ceS@)3wlF*EHqQ7O1AAU&MJ-Jn_RRf z1y#qYU6}ojZyhX8SZoXEzL1mmfF;m5Q79L-5;sKT0++*;*vPALi&A2#Xxr^8*XhpGu?%kbahfSk^+&3KU$ncDvWTtE?K6 zPxeFdXUqW#F?KeY};U>=5ls7$ltHVOc z@zQpg!`|XBk9abcgK(4SiK?n76>g_)!9mx#D24cTanX&Nd#dya>Cpz4z5V$8i?ld+ zxF9neD9*2PJg)0sqK_|snd!N;xuH>EPM`B@5G=HG=XP^oSP~EIo86|RHQN{~8!HyC zhfCL~x$|h3Dla>Q#q*$OJH?OBZD+#nf_X9B^}&Ps)TzR^n5)+i;P-=PJ<`W|R85n6 zpcu;)pUHOlbL-r*OwZQ>t}|_MjWj_wZi398u$&X9{YMn39Wf`(%X&Ve;ngm7Yg6?P zJ*_%A+JV1%QEISd`i=JBSIq}eM#UB#rN`O3m{r|5gq0+8`D*z#Wo;UjnJud^f~6{;Tov8 z`!Y)Wn_5VcLHdZAx|MoG<$4+jdu$Y|lJ)1L%N0zt5LuxVfR}aC53yGxEb`r*?20XQ5F) zrnfLW4VEuV%G_QLKb75r9TVBTYM%!Ga1E}Q(h|e9?wFeO#YV4Hqiyg*yn!;^2Xi{4 z(k}=*7hWQs2!?85`rSucRJEdyOi`j1516$nj|ndJ6`3vC zyKy>J64QFj=7WhZOYG{Ntyb#(sSTZ4-TOOpGccDG~u{ zkoSvKQX+8V;NH12Se!3?>x~rsqmiqA z!i@NJl{+xDJW5a~^pisBye;QoRg)|diab(cero5^BMM3yvC#5;ynV~j(=MsuC0odc z4KNiJvCMl~azC;gl=#L61A8}86$8|?tZHUA z)y4Wg*+fHpx438^#e0S5&evDGRu*ov7xfpD-|as?){qQ*r20|(apv!O{#i5qUJR3a z--zwF{Vse^azA1JhTy;kk=LJ0CUUa@L(- zRca9J!p`~@)di1uUmH6BT}Wu!w3@>ApD)qVjqZb~D=Hsy7XezsuQVQ?tKBbFJHcT~ z*%jv(f9Cn4a_i;ES*pT<)24uogt%cu4c!tIy{MAhtGO=sPN8s#;`-)%=fbqA3a9e~ z2Clu0Q-DswjOp$QxNzou^8e?DTwuHlr%}~sIJ*t?;A=ru**HGgP$dO3g2e88OUuRu z+XCYYu7G5EoeJllE!UI8e&?`cr)bxYjQm_PJRKe$b|t+xnZK0Z8N*sPb#bV!sm|+s z!F{wi>4Z(Hgs|E6htIad_bTUWhRNC(9^OLi*W!}zw&w@O?Za2O%F)ZodC7NXiwUoN zh_iRJS6gN~55%uNc51RqpCaMdoXFSB?}8?!dMSv^vPo4nx6{`ev23^=dq8Jcb`%axX!hmp>@Bv!f6(MQ-@O{XR2MEou7 zk=&sW`0(!i<8aii!l-ZpP9n@g)>&z*1M3I7cf18F;8n%4zDOTe0K;Hg4GUROK;mq34#QYxj zqSblnIW8s(sYyga2jE;WxQzq(G7o94MA+_h_zrD$w&$fBUn;0kknrvVgK7p`v<{O2 zWXPw?3lJEQpPD+AI{+r~f4jO%HUVYwR$!to`JE$NBl+og4mStPe`W2D!xqQS_dT(S z(Xq|qkluZwVsEAIv{Zn@0^|Yxy+a<`C!J$-Jif`^W`k@K2DKqIjtDRA40$~0)%9y< z=!wTv&TDrg^XSwH4ubF5J;YgY>gz^5aluE%{`Y#4-Qad(nD_4%7wm{6=7A%e5MO4Hk^PF7vScOJu|>9S{s))r!<uB;&y8sSHmZF(W>{)l?wKy1VQ!sH zpQGBmIdQ$Fxm%|VFThN-dEx75&MIkRu0)TZmezWe{r-=taU<#TIfYtmXdV;Ff1OV} zwu+W|1;{|^mo%q!wvj5yMA*-)bZd5W%{%tW1D6;yA5c>@+AdPJaf$!>3_W{Q+WLMCyG|j!B0%x zvy|&f&=nEHpwe3{&6P++zq}P57Mo5lPaV!6TqhQ)&u?Uep;L98hGWZzTS>6rUP?)9 z%#ot}xydIQ)u)3rpNd#n1~uaRch^WBG3s(?oQ^YRAQ^%k;K;(7GO7-=4zD(CUCBs&W& zeS3>DXwU+eI$s#wTK~1pV|Sro=F+tFz$2E)`K-fPQnA0kpXJ;8_cuy&HG82+^t{yJ zbG|V1>qY`E4*=F)cKDjbFLx)aUq>t9kYE=-Z;Z)^;-rrxB;Hx>+4jnRf_36;2*7sYycACIs{;Mer_eQJ9{2GXM@<6{igTSE7qY`MR~RY2_$U*l$ZmZ2A3`s*Sk9cjt?PFHmA zw{r94@k0proLpJsFk8Nl>4%JvNVOdq$t1$GIo^|BEI|$> zI_55eM)}2YHU~Jzvm;%22J`XpZgcDe-Yl8-C=r}517O*HwK1=ngr1Q z?ACk5XL6E8eP4JtL&#SQl@Atdx!mY@5+d@yO>3#4T)P7sH!LVyvbUR9otbxw*7Q8X7?1T=8nj80jtnqz#pXr>8oo z;!f}|a1f_s?0___s`7TBYC993IF2O_==QLkU#5$H#RF%>)F?rDnZS=e=Dp z;1EPG{Nr_?F)^d$FDm?Pc%r7F{l=J&f_T)egFp;7J*3x@tI^paC+|kp)-z&yiFO46 z-@!=by5Vn}6b`OD9VzRT&(b|t4#JU zYx)+hA`zIf4OpHgYbF1AT;6z$d`8yO5Ur`d;gQ0xe1pSHWIc3^;aBl1`)Jmn_0U}X zSv2L=MK!Z7l;GxmUPgL;Y{ZSdebz(0p;IZ^V~Xy_qhgqT1_p-rZbyEMiIA_i$%2@g z`v~!;f=cYmwSO6shp`lB?1~0>u&XvSJiMw?d2*)wt{@}?U5Pn5r&8zKenvxs7tymm zxF{W4c^xA-P_`&T`8Gm{S{cHp3sdUxpAy~u&ON5H7CaT(0wkdvp37h7f08xO6&=;Kf#e?U`_7M&@6bI!A{lgI50 zlNgJ$%xNuOoBJYc7m_B~11E>7eA5qMl6n3I?G1puEpxJp%)}jb`KgrKn2l>|uK!)s z`w5#h6{yi;gf}PKoi;BTXHd(vFcG%C$!EFfTnH-MFGfk;fK5|8cDODcig==E-#SSZ zlcr1^{@oHibT*taz^lx#lN!i)R2zB)<4*vHa;X|*@g^rRLZI1k>)b7DQ-PFTI^)iW3P0CRSB z_RA+F#JhLz=_`uM)Iy`;&=FgbR?<>3NM3Kgyl}W4hQ#!cPI|Y)+4=Z$hyoc4jV5K7 zf8fJpZ=roFkhvp1eu~o&*YE@O3nA9tq7<2-lr>Ov!=Vbn=&{FWEb0er4j@WQ57oWn z&i&2-JzSrfP~NrZE!bYI3>Qi==?&Nq(vC})B|6RB!tGZ|6F~BcR>O8%qennMJB+e` zSU6aVm2PCaywGr&xu7CUD0ieJfR>&guy;VC)TMGepr+tw@=r5Ol7n2!$JA73>$s@G z!tRJy7pu||8YeQk0!eZW4cO>`4e+2_tjZZVyvK<2DZI*s_%(l=z1_bE{ZX{mW|f5l z9Aii6f4h;$wi~{T=l6++`C9rUAaj>1BoHMg@s%-ZO6;5WUm0GcyI1S$t zg8?}Tz*Rx6ySZyB@9zrGvBN4I`5(B@?k|ha`5aD*xG3VAlb%u6IR=Z&X}Z{1x>|0)^N4eCJGOZN zlX${&GQbxB=z~hmxGmRFL{M?6u=eR!C+r7)m@&LopIiik0OJ~}*<08>zO%>%1SG)t zZoBN2fU99trhyRIqyD9^{>~Tm-4uhl=xh@Jw;H|bldPvNne23`<>qI#64Sz`5)`c` z%1K{tCf^C>FdO-KkUNQ=rxh7ahd}UYPd4rveIQx1`8AR$&Ermi^BAF~5e1`s(%@SG zmM-j>2(92yPC-e~{<~>Zsq?wRfMogL#3~}b*0AJ^7^5^o6*>qRsK+F&@oAP}<`7dA zO$RUbsiV~ZZv(9Uo8t$!u8z0#e3kSof$~4-%;5V|^0f=e^r^`Tc1N408W`L0Z)Gaz zC;P&dE4Fd*CE6V*C|PsAloNe8!Pd5;k?zNpw&Nl~+T1uB0SoB+E7C zM>FQ8y{ys-xPPH0FBAl|9Fwu!M~Xs$U3R3%b|~B_M7GN&6eN}4@&5K`n3h6Z0)?86 zM0WQhEE2keh~jXO%r!%AOycp9ox}04m;|!$wsrGy+w$qN@O!BWk5#D#R0Vr+!@esd z-Jc5!<+`e;JN~wQMh+K`n9$l(&&uM3m^^O(jMTMnfOAEr$bFO|1?VWv^}070wIeT`+@4_XAnjlAiqzpyDv5WuP#cFyTt~DeQd!uaP6>x()IQP8 zSK^9dDDmdj3BWnOswyB)V_qG@$c2HfA~Qqm2Ev!L zQ0(F=AOwgwe$Py$V~~-DDJU#;MC}clLmK|}r%?W`(}PV<=aWqfl>!|nOwwV$z0(;{wiNDYmT zers^x$*N0E&NWvB9fIdR!Q&VOsur6Ee$0U;|M77H7}M*6S37*$kA<**7-cI_Yh>n_ z>z)qN6MK5Ek&CZx);B}jWLrK_R~ZqX>j0bp8;kjW{Au~;J%6wEEurZKPrh^?pgTZN zg@O70-TQ>9mnenwFh2Xwmuj0mlfn;62oa$%#S}y!a+MTtEp_^(G-I(4(W6&FDY&jC zDIkfpS;MEyRg>}g*IBB$fheY4;b=C;hRHy!xZPMyS(%!w z>fxGBh!XYq_tSis=rg@RSf+*fLG@1~CnWGOah2-;kg^}8n@tZXkPy0(+3tjgzIH|f z`Ks3tGVxUasf*j#6o7%fzlolQJ!kBzSpkS07R)(V6DznY#%5;b2V5Ci%T?S z`6R1Wp#7-BTZ>@$6~kzBq*_T%9~l-+S7x(xvEBa~%F_DnzvuY!wVlZ-9Wq%r8Fc;X z^^xPk^vSZ9S#Jxl5jh&O?k&cIw`~w!D`8FQ8Uw4_lIcrM`Ek5urD@TRL8@blhOl6l z4-eZCSijtK{{r9#U}Iw49b-pJIGwqm56M)b7W)>;LKWWTij_Fwdh}&gwPEHZjFDim zm#YD+5%-S2P37O41pwTC_aS51)1TYBJXj>U7`&TYhC-h&U&9t=xHNCJ$*A8_>m+L}8_$>) z8U|pSS;xFOX|jH{li$dCr+LK9)p3^PHV2~ozxUtc{`A5c-v1M>STmuI%*LcbcuAzj zl-je!O0SO<#oT{qo46|A+I6QhChpgG{me_mXYqP~Cx`}a=rF#AWE4V3`XdR}oVu03c)7A?qvuHpf&m#u~u|t#h_p0^CE>_4= zWPVA7#DP*l?mQAuxZHjlG05$*D>L0N|4EpCJX#!mLBCK9DysG&3rHhO0K608=v~fc zE8Kk+`N@*DB66loOkqZ%>>)IZX_RawxtgObjsBzDt#%jx)?zRWVyJ^Y)3oD->|$d; z4JL_z1L(zQ6V_TOM`E2$nwlQ5+dJ|)w-_HCWx|fo^z0eKrgrb*E=|5_(tS~>r~@+U z5(qFOcvboJt4VLxJ_NAgiU7t&dN4ZGROMVwSAO`bH3x;}1@k?Gz1i@|(d$XX&*E1L zuoE7;UHtT@V$adg3eQIM0xECg7uR!`A#d^-j${4oJ65a8>>v^>uV3;~$h_(W(SStL zHG$Vd-opm&|MkZDxZQBt;L*)L^iNV^^eT_yUJr+XbNtSw<^t9C%J(=fqF!?E2zu+{3I2%t-kTG)(v6%9G6l} zC(CvgWdFgN@_rKpvZ-zLD9t7gMy+ zTGbwFR%uUTyR8T7ZX%*ckF!%vAK*U_78aDT0zd=#WRKJN<<_XdsXDES@+xG0*qM*r zMNlY=W(vE4)MoxACk&>z=JhV4O$EV}spsZ)Y30sW1>9*X9(q#7b0A7ZFUIubvyBGx zx&LRw9-L4uH{6$GnGHFj_;>tatX3NR4foTjrZKbUi6nQo{y@n|iBnj&?e9yi-?(PQ zzFV0)S*ELeFkRs=+4)&fC1Md;Bzo-%_0|L+Dqg7cycikPK=$IEtA)HWm=2&t$T7hC zMe%e9Qpxj$f6sq9pX9|vB3L2I@~tOB^S;Yn*5IIAtOJV9-&#cD9B|bBW(j}U-MgJb z+j*(Q%)riX18omRM85Vxn7L^&-w;Zz-Mu+x#6FiM(=SNNF29dVOuR}mQ5X)2Bjet-p3*Ivb??xn z`R!MrrF($z8acX(U}U8V;R?NvMo5E!5R;O)pa$zlN+*SlT((NpdMFq3u?Fvn!70rQ z6F?Wb+{D}@wZpRxYZEgVW*p3{@BAgzN*$bP^bD%*lK~(7@-uJxI=p4w7M}-O$Yej8L#F?&B-y~(E}Vbd7sJJf;#Hg$m zB-@kna%!Al3GaNXoO%G%i{FQ97U~rifQ#^wlqPGuy}8-FwA&q5LyH>#7$)&R{v(gI zzn{Yg^D_OVPi9qbPgR;?zFEcI$+yG?0LBn4c<2tT-%xbuV%UIOk6)=$%G{oM{kxM+ zGwrO0&=5pua#S!R*s!x@Zm&wKK}VNPjT>qPaRyKbjPr^_yW^t!dVi6? zQ_0qeNtRR*>}Az9Z>* zg1QRC8rGAppjk^*I&qqM{ih!u;1#wtBS28NNojsg&jvXy5u|=~nuna!YFA&wqbSxJ z)Zdqvy3(j%iYqDLK}dk8js5xy;QeTfNZq$@ki70cD*Y-(8`jE7x|OYIEuj%(gf+jg zfc!+iF(^dBP;rUYZ1}rjVOkKP)~@Zy#bJHOVrqNcP;AhgBUA4N0;;+zUQ+bBl99LRO3ZLG|1kei^ST!COS9iC7)}hxde60C#yu=}JV#3Y) zdA?>rhDQ~5$L>Kb_U$RpclrnR_8G1tP8GoPj)K$ye9xGzo$bRzi?WdBpObmp@q~o; z)%ur=&$kdD(&jEPud-fx)+tP#$pIvT{#g-dfDn)q_FFKnT{>$hHlmd8+rd)vx_Sy+ zM4*OM$-4Kb8#Q;bGUcn7R{hAb#^;*wROerv6)aVeZFuN+0vFWsH*+PC5_5?Wz$Tyn zU%K7!z(q5avFiUr-dFZj+2-wHpa@6^f^>IxgDen`ZjhAjX3-%nARyf>-Q6KbN_U5J zch^3h+k0l7+0X1Ru-DHQvS7t^o%N3+ZeWbMu5TPI+WvXdz}gA`8UWL@>;XchsEABS{PuPX z#S6Uq^kIyi39w6!S{78S*5 zLHKhI0YY-`zhqtk(=NxmeoN|(fOJ2uzA=~rI=Ly=A6zE9*FfC_JvcZ=20NG}lb9A` zXG|~^V1qPkaSQu5VrQr$e^Lypq2S>R8dd}#?%s@QoK1xbVY69dGwQlcODF@FS(?d; zf6F;RHdasm9KdY8HmlL7^PpKOWe`MJ96S^0>b$_acI~PzLfT#M*2Dj6+)g{18zyP~ zd;eKM6c;p(C&oZm8sO7lKCK}%AzhjOe)i*Rin|F`yq%%s0%(&(Zj?}$057OyctmkA z$@_=%@qpa8NF(}NvvI2EUeJC6uqM0pHiLLcBp480T&41h%0;=PzN480*csj&N!5Cn zl^bNaR6e6>LPsF3B_OPe3LuG(K7CXvFxb#OF_HnjdF>YH zg(I{KKzFi@Vc##Oiv1~P2prA#+Y^qq1?`(JH=Ir!toAS!sL!`M^A;CXDK*O~PBuGC z7Xg8(>JX@NgWIu9Cb-LN zf5z$lm<`Bn0^}Ywmr;(E&ujROFFh;G4<+!4{l&fdPw`{h4L$Fpea!{;kE;@&gkFN$ z7ydre$KG97fF0SN$(whyEd!k$Ki+yB{L4zv|c7p1N&O;WKtXK9&CUZFZma&;su=_R!^YzmGPdA`$H2=&MZZPY+?O|cZsow zRtpKeuwKN#0(&EUbVHgVEaG6 zy@Cv%s961a6%i$F@XjL5M6FmB=97Gu{!E(e*R1pR(o(>vM`+sCA0jBy`J)N(#ZSzj z`~^8s*8mNBfH92{SE`eH0n_~x)8Dt5hW1s#6O`BCV&4EztwWb77C^|x5&f9&v}uAtGpQ~$b1y*}EaGe*-hxxxM=OiHuU zy!(sa{j=-#JSRfRF!Jw5Dd%=kH>e{krI|LNrPEYLt>|DoGUT?NO zIz%I>mv6bh0a@8p7^QkJ0SX{ zcjLh9Xxg8RSF+G(F*n4;PS3>6?or$I2~^XW8TdgZv}dDr7>LqoOEYQ%!kc>gAee(-S$?{qkSO|>XUuv?DLfgXps^~2%k+>B$0+82pQOUuQEhsok(u?V1_tlLz7ZZWUU>R=Q4 zIq2CZ&;fa#)DRaDvoO_h)jO@Xeb^7m$B_yn*qq~GpMwHFe_5!ud>0$Kmp}XQ&mQg> z{Qs(EoM_d%?CiT6JeZAVg;hn){`@L?b}ybaqIddD4$Vj&Jxe}MEMj-c&N`C4g^>lj zm#xRHxhhg!SijOxXS3mgeV`wh%J6&4B!O-b(5zc$asgq1VxD5Ka&e7=?X2Twt;>e* zH*`>nz({t8)4HtQNsIaUN{gD) zFD1^6NFwgsd zR0Q}25#VhXha={&8n7$OFk{i|B{gGb7;Efk^`nFGI?<^!E$} z;xnT@F#{e8TSJ|E4BAd713soz#s7PiCqDkX%A#mlkNbsxQ59K~ByZWrD{PR*b2;;# zlmYN$c8G>rm34tK4oI*8YyN(=wHeTcwokrbb5nxmNozQoWX~T~E@ylTC@jDnUQP!G z4Nyh?+@Sw3URfpq$`njwZgfQ{^W0|p0HT}!Qe87C8FY2}qvGNdXyZAki6($~*I!rw z2_W#|sQC;jV6+%2@w+?P56GP^_cLop{e%E#@@UC?C>k6AIa0U<(mhNbW2K$+12qQ% zE~lrkFs1(wigWAjmqAW^6k`J&OC7D?E6!YP85mBxW!v%Dt6$JfABw>N+`EF^FK_+> z(b9M8*PW|+hJ@pI32N~yklv5gA@V0k*a zz9$S?axGpE6yX5&{Vb8)#8O8#e(5!s-{sQJ!m!{>-Y!vB!v6AC9&%l>d`tO~Omg>> zTXNEJE>)@O436&_pe;CQ*A_C3B+Fr{ST-1d3o*%kj!9NT24%!INZZ1rw=RatRj!f$ z4)iFwW*LOSq|t-Gf#2yExbqs;*KTO1;xaS2$*=XmD=IQ9nj zyCyKDfZ*{OVI!9&gEGNcyks+}1kx4}p>SjW@UEg%@m2r7_|!*!|G}r$EV6w1$xsTL zi-+4d21xilg6Y+oZ}{9@A*eD_W253&80}XVT#@NQ;L4*8n8vp^&?g`qrKV&Znp8OW zsA~?|aqr$KnJ>>j598Q7%Y~nEA$IT3C*ROBN#`X~RFtW3614F|Q zo(sSg;+WI>bPZ9fuQP3q(y=na!N8ZhJW>P}W7~WM9z-7=KY}R@lo{rT#1{E5{WxBu zxc`9%{hhUV*p%7EEwu7MdgQxg;YWX|otS>M0Q zM17zi9=XTN#^>|G&aGMr+*<%Y1H1Z6p$Ul|NxSWZPAqP&T`pmL8B(Z^AZV>holkunO>KeksvNK4h ze-|cIiIFmXlW&bGK&>M<&j5IWcv{Z=-mg@iggpS>?6DoZt?f8nVXf8+71P0jhNK^Dw|jnJ7_Jbk+o6^ZfP8Cg+X&V_2=&$DvWl13rI>4QsrUo;m&b ze}jnnUo0#_q*%~EQ@>aiz@MxpD&?v|=^S5oivI%$ba_Jn07j1p$?3f^a!vUhVoZGi z&;l&Pmas4TS7w;dbVYI?-v-^=i%U(;>C<|1zJE758=KEr);81onE&2aOKuO8%V3t= z_3iWUe2GzEq8gCIrloUn{|&hm{|%X-fb!D(wZV&bus{@Cma@A{!4RmLH?W)3y5ETd zu*G1Qq_?$$J?Cyi(@iTXsKBzCtTKZVx~bBh>?Hng^@ZSIvIMUphK8?6Npts2^^5fv zho@J;Y+pi*fFyCQc1dpw+rM#r6@$A9^i*yZs@~!h^iKHoU{nRa)!d8! zX^?`FI~|U`sTHd|!(!bHroe3Xnk~#%8uT?QrS9KVrT8$(?eD<#bAQJ1aA?6|n>CVE zN`bYgV?{|m+?!M)7C@6Cs2eCw%;TfUWMdgepZ^1w)U#p(xNjDy{M_10I{R!?qn z9bIpC4Q7?;sJDXTE#AzPTnzvA0&7}kbI;&*LVb<)oWPC@)C*Hv;B5evFO(B#_lnme zz|75SJX!*ldfTz6OM2{Hm0w!Wo;sLM0>h)u0uCL}KLmszf(A9z)URX3(4W1TIQm@k zVllH#_)o42XUliyvdj_$1CZsIU>HH|Kg=j|NiQQvUWt*0lNoHw1Vas7wQ@oQs4Vi- z@p6xTQ&Ac?O0Ym^=xtz@gu4xnq!JrZN-bwhF^|F~|1!A^Bnu!nXM47u3|dE~TA$y) z)FWqkS7)Jo5m+yqP6()ozk7jd=|P|DV!;|zTVlJb;CSb51`-!K^uX)$+jo5EKampi zS>OCAz-XUr$;ND8hI`VT& zV-K$kjB2?{JBUo*NA$j0{e8!_LwH_8{nFDP!3e+W0(f?Oe&>P0#-*8W%YXt6fG5fp zQ=O7RV2S|kKB#Rj2ej<~Caol=Yt0tyY4O4U)Zh)??#Wvb!x)Glvp{w1um6>~Ys>vl z=I+GkxMs68QgBDYdKD-lF+JZu=F26_ds%>?V%viF^31^2S!@%Wben+cAo<%)28NWB zJk~`OPrxM(6of%zZD$7!IRf$deTiQSkG+243L;t&oEpn4nE zK5l{skPpnPLl1WC)u0QIy$YJj7X*BoRZc0~GEYZmMW5@|_KxFzFh4(611FEAR#RbkaHPvl2oG}Hi-*1maPK%Q0L=pk zs@P$axI7R6vW-(?J#^Q>^0p#EJB&cUmCGf9;+-5@za&SY+8hAQgSOA}uw;FFHt4@q z<=v3+XG<-R1xrDe-e7Os@WXYYTOeD4w0ytCetW*jOb^otrWSgz93jAAXlP(|C=p7WdXE@zI|1?M@TJuUYFN!kb&D7Mq7oEd%2lv9T`T3wCO5*A%FYgR* zF-ML$8{Ameh86*g$rHHm$kZO52a_u+b~PM4z&xz$r0>SiNTfey{XLLCAls?ltv^{R zgPrWFPM1=3T2&28XCV(EnPdPMmQ`R6D((vmCEoL`pgnhFceG@->Pb+)c+%~$|8aJ6 zLdojo%d=p94&~y-e0eMt%#C;ja#S>AK|kTTQ$+OK(rj+Z@mx(p;_leA-t?R>0wL zjk~-(qL|$NUi|%y(R3SdQi$k~N~$|F_=v)_RAXU`W_=Q*L4UxZ%Zn8Xi~)bQy-QcL zeTN#!%x(|(6pN`Ndv=GjA}5nfs9il}+ek9`$OjZ(l&gNQ?)vhE*KAuDJEYoa+#k>l z!oMRZL|%h#>DK)GyxH0{PR%|R4i@(I4u_)&v%~5TkskTawK%fLxtq;}{stt)inc#e ziE!MvMWpgn$s$I~SghDD+tJ}QzUwmQt(`^-kZttPcn0;Z3w{HH^7zE~+tR|MMz=CC z$^DMc%>yh(O!06;<(tQ@k3I|k0`yKZ2?Qt<2|`K5HQpOhP(C97p=?QGxAdcD<`6Jn?9oXN=Ao=wE8VFsc^!6}R~pPt$GoMDUmwt>3bGj?UA18rF~ zYVpCoatq`3kBs28Bs8;~y%}!If`N=AEI&JYBO3b%09ST+d|26I&PULwR8y3uGBRPa{ z;)W(*C;A&dPG{s?T{%m11ZIEQ&*u~uq9b3W#<{iK=!O+1qsKv{Lg=D<6h=m@7Mwwo zqNUAjKWQsvmBTNeCGtCAgRHcchn9zzUp&=^Sui4AY2VUAlng>Nbo?IuHL57~FiV~v zhHjA2jOf3>c@)1I=w_hYWpCB{6}Jp%KesgW`UlIA--!s^2k;fR?#VfU&DIBgqp&Ko zQTa6zkFqqq1EL(H{S16DqMo%Wuz|mZ_dD>591{97X{!8?q|$iTfueG_#ytXN)6c%% zI?O}gB)|JE_t4MqE)VGw6;e`zPKTGynn;FM*NX~z6+dOt7}=S}#)r$z#tyBv$|kKA zhKGwk())x&!$$Ue%U-5*J-&UNs;zMk{JR0t^Bhx?=dX`jDuEFUumWg4UZW%GMRi9{ zO;oT6C)8PYL}HdaTy_BcPf&(&bs7LPhWz=YoB>T{_xll!??ljZ5ta{<9%Y0}TL^1D zPX%{*Z1W8eVm+Gb@96kqdUTuJFNjE3)UQ4U%GO%$?1vP@#!Zx{e5aBmhje!xRQDd` zFgyl>fZ3mny*pX>eZzhF1XnuIL0mId>{`81OtS z#ZOcO6%L3D{5BHW+&b}&h?q~D1RiJY1b^njTRLwxyw(TNTix$K-~eD#;t>7o;scv3 zglyGETcp)Q3|k{Hx$3TFX4KZR$&2ZwaHdq1+=Dbi`CMOLk}-==M<*nV)l>tf%B)&Q zzrWU>x6OruN%r@nmEn;Y_uoIjEX96G`O|J+g`~^&DI5q#G1~F5BF9XOA~+Y-`hRiiM*L5PNH^nW=Wz*J^4CFD3!jgA3Cte zF=F28tWWUg#-=3~x{CU8t2?;Ye04jbks?dnjsn@?YoNoOXbzH3@eqGg(UX&!*dA}t z-c6Eo;Mt0PS?N)8#4U<#=>2AJ+ z$%^rosgVZ@qwQMUK($6@)8rG5Bu{@7YV^;_A3{<$;W zTdhLV06*#^aJdOx#fKOVe5~nLa8VAQjkBb$UKgpjSR`IFwN@Q3>_iTB!ind${K+Qi z<@ve4vDSt3Hu~?SG?Xq~TRB6LldHe#PqX}Ug!VR z%R$3^&D2PF+PGRd>hm`ww2L=8CRp3X#sw8wwtlj-bd(rt$A0A3YH~(R9`x$*8fDA3 zqwV*gexY8xyb>L$&Sl-BDgsenuBKtIY7TY5PguQS{gBq=XkW+TWVvYe9jD{l+-VYex#{f$YtgS zchl3x*Y<<9D!Y}w0|%$!d9ykg0eIMEJ3|6jgq6ixzaZ)UpA7VStL9HU|JL~T6i!wj z{r=;vt7MJg*zCDf;c4XFe-Y2^T&3oQ#X3 zA@^xlpueHNKx~dGH9_qQWtQ5KTT2n7l*oRui$dsyUCLdaDlryPnFT!6RqspqNQx~?=(Zk=O)bWzgC|FRFkBR!A&u3eOjDl=;3KALPYddFYa7Q?)@xU03x=n<#PmI#08g_n~ z7=)lbf&99HFk$^5dY8R=KY)>TS93-z;%MQIVtc_|r{{72pTkAy`^?c8*OG&vE6`M{ zO-3;9u#gWuOMP81bAn}*VTb>TbNX$*s}f|fVdntoGqZ^EvE$!=vqcq;iUSukhV zm^EHQM>nlQml=3%|D-V-G6BzvWcD!mrHA@o%Y;}JI46FyrDf%(A<-_iR&CgAi=dYO z#Tg#a9{iJXbN-qFGe@jvnF)la$iTP9YE>Iuxrl9l)nl;1@kb{ zQRp)3l`l%9;NsG>`@lzzXnhWC(fu>`$cT29v2nr;Le<30gQVdGQ3-YG{zok}2N|Q8 z@+aL-d5liuCtAick&hmdoN*Ji-ezMhwi`UmJsO`jXC@1Wj=<5BND<3VOyzpI=2)odTH~ZS$N?SmREN>rjepp{1(nYL;VO1 z8BT4Bd&jjU71mmW9_Ic7m3ltgzC(|46xOBep0~hS)d3A2SU%{lX{iyvREMchN zYU_po>9UDzHeN(XtDUDXc}R&g+*RP#Y4^P0^j7zHE_`1q%=u+;eGXL3nwXd@*2Q&S zZnI7_-2kuCYCN!kh(MP)W90Z33y|t|H9W%`ubTF?m`rt(d;(#M4onqZD zC9cNG($iN~9LWki8Ti3Zkr~QPL(&q5?d=gXcsjSvV-cGg=v5u+kL`Tewo-~yLY%Mg z5VcFJ6<9lXTp>kqun{_ED4;XA#D*EV=v*SZBruE(W?kN-p+C1dMy5!PN)BvoXT98l z){|K-d86=?^ALNs%mEiF0p?b;ATvc5HY){bx%VXo3k_~sdhoZ?`&6h#82%}7KiMXz z&_6eZ&f(NvdrNAaM|zO3w1XY@{q(z$5>ZwU({Cnv+3-_k`UT3ZQ6Q9@)J?&Y?@b3)qVb`3Yq1!k$jP4Qrd@2h z%$smT-fvxtpVRZZEIM!BM_xd%6R>1$Ld+PN=R9yUp?5WsJ6jbF=v7=|o*d6pt1`jH z=~CLS5YjO=AuBP+Ku&@LoZ>Pf@)~ZtKRsW}x=P~3L za1raOZ(wCEV9XK@y0XB1F;$#pt*n{?9W0GsIqDo0Nv-%G4a(!cygz)!2%D0ktwD+MepOZC`AKm< z&awCp27U?#fuqu$UkZ=IE3+*ke%`P@`aUO?lbxed0lR6hCQxIw!=GE$+#iaS#tMa8 zIYAXq*o$~>R`oQvX$7;F&%9nNchZlea3X{kq7IWoZCWP>gBB*WRAK|hP~k78uT2!+;Gj77xX1~hR4k{ zdunPR>gH}{kug~ity!Y%V3ACV3Q1{FOHya@7I!$=BO33>9_*as)+~z1Ou66@opLc5jrV*KLU=Yoi$c<#JL&gWP70^` znl7#6&SLL<{fGxLx46v9A+uU!VW_2oux;_+Nx8AKD+SHAlZ0zA-XtY`CJa&KhD(~! z0R~uB+68LLWb1?c?QJxu$m))P3{e{DJTvoIf>%$YUo)94(%F*vVK^8pGh&ArT4bbl zF}#C=eU~jK6H0lJ8*{#{&j?F2Ru>;a9-u@WPU745#Xh$*fo?L_EB+A|A5lT6+D^_d zrw>zJlo#Q)TZSUs&(~kRg$uEF#Q01)_Bek0;O`rI==dCu-N}JqY#_WRZO7CGI{};8 zD&tCs6)ptaD@eaQhIZy>Ro$~nYtWmI8~j_ zi|N%JSHUu-JOkvwA4V~JU%qN;j;48YkQCA>Z%Yni{*ml!2 zTt=T6AY^Wu*hH!H7J4lHnNgowcsA2A)0LNz2dcaYKd0qZf?e?dwo6uqW~kP2(fe;K zfSC!cCLY~hUr*W-!L;15xtR00?bL$C*hkr|3bsxmeJ>yCF>)c0?2%vCizO)^b<>Ji zmj-t>)(;7O_O8ULxtQlHGzW?m4ezI?qS@t64!o4YE!vKr*A6JhnMn^Ew=&~ER6*ia zj4s~}(A~EE``f(*nMR~uP;glLxdt*VN>u31!tJ&|=J`!i(-k*7IME-Pv*@>J`jut% zCU+e;)y6B>hh=)G)FTdiD+V9F99&OUy?q^(inZ)1AeWVrqP!QARZ`?}mMZ7CAVP4A zECR=O7v)61H2On}F5kI6?R56w0WU!@(%FvwXXH zx{F!d$lB9efmzF1`LstR&O^&3n}4S5rxu7Nt^nnObU|xDRqVHnV};zXs8L=7Dbg-Q zkYN;<$UpKFR$#@FnkMZi*+P8yFV-NU3{^MBM)_nB14AJ*k@!$V;nLOJt@DI}(pNPa%ba z5K<>z6!QuQT<>su)-}E=t2Cc)ZY+^00ETAz3b zju{5@?zaT*HLtQxsyfnTYwjyB(We$-JGVu%{DCZA>{gx{S?ftlwg4OuMrv<0R^j6KXQ-v19{izfrt%- z_Py<2owg?HE|3?xddkXmbd235x@skl+1QA*X*ga zZugTiUil6camXpIr~WDzjwrWLvvrWWoLqad_QvC0&iHjSDV+Chf4mAYe%bQz#*baz z6zl8##8+6IhRhCw+h$frdsto4giEh_*)2ZMnOu*63#9$m5-<=+bx$PEU%wP_iHPs{B7syqoST35cgCqgxxI)%bjrMQ|gXJrNn5r6HswA zQt{l)^zqN=ZNS}zZKt;17cj2v<=WrPj6&a@_R`q^n5(@}Njktbi61Y?z zHR;Drh$s>GVOX8)T6k7SvbtVKo%LKaOK$k7_`gO=(MvhUqYSrYZDxKc?wE~vV)VsIRT<+v@1GU`%m$~6Uh|Nf5UNj@l>J>M_> z;J&kCRuvER)utAN`!G2<%hzCaB4Q|&v&#_ec41bE9ei|jIk!317>-kttEeOt^4iWr z&Y*z$+s13F`b%0uQZ46E!C3$7n~k#^|IgpRmH@&n6e8Bry@Rg$*;+ckkReIpU`%oJn4VpKg3e-hI$;vS>X= zmV5HPfX&XCx|LEU;>`ALEkdx8Lo*>;q6;BgY>DmRMD|f~;xwTz7?@XyT4{4%E~=$@ ze-UUvQQgw@yT)Tdx$mn-a%x6N5r2_(K^8t<&w@HBiqb@;^!!8G2ofvyu@<`qb(i7p zkqXt=TT#BPkrC=^AyvrvDjr8fd2qLbgQH2QvpBLl-Pv&#iLW7&ag&A1LF)d$LWZY! zKjgUt2{9rE(nh!Jv4It-U^kQBV~WbL>FAf$z`NTGKD#x`R#II^in_}>=_3)}Q9{w+lfLQ%^=`kHqdXkb@bIvq%5J6G*`gA_PNStN$gNfUHPRiW zd}6c-OKhU-Mc7h1CMMcm-i!}zT2sn_rF<&*ZS~ML$Tw8^EcZD#afBU+WG6-^uXQ9> zK}wk=Od9f&@+0#mIMsi8Qpfa~uwq9Fstkyds~D@idO{uD9f!yrDNTVnNotjg9sK>{ zP|qBAreZf6&DPJmUALF<9WD$pw zX~_jqY!Qtr-i$ZWuccr_#{{hSMnc_9{ex|{o(fW0gKDoaD|XJ@0M)<3-$Tr&cz@y+ z7??wWCk`!t9~Us7^e*^!ZvqD9_SOHa%Kyi&a>dPjK&E4|)qtGt>DvD4X?R>xL_&&OW<{v0Hm}od-=fmSL#o!ZX9yA!p6WI-DJ}QO z9e-+FrmLRRnLvv_TRs zjtXh6*=NG~(%!%kY4uP@(|m7Hf9_~L?cBFIEg<`A25dz?k9=832Skx0lM@3_Tu|Sf-s`E(7oIF;R4TISbb6ntxS4Ux z8ZkT>)M(5fSSIwfJ%N`8vi8gspv|Y0lp#wA%$>l`0Ea;%Nf;xh)dKLYopjm{4TIWE za##1;o5nH;Rk@XlPd)qpy3$;qxCaT$3Y(h+6)SHp+|X!0uEhQz88Pw|Sha)%<(Rp- zMmO8qa?hL2^d~pUG4Gx9D;M`}c2wx^(a+az1D}|hvkiVgU`W1hKfx10CIFzaZyRZH z+uKR`3gQOkfLj*$?w1O(6*UDpobLDIDlfqUzqS#sQND+6rBc;Cb(JPCQtlVakpx=Z zueBe5B733cKAS4}J7wfk{0Q-Y#|YxE7Gh?xo|g=m8x z%|r@sEyDeuMv&|zNLG+WRzEsx%?&2w;KVZ8e_R}D{X|jYzJH^|TWW{qJhw4d7T^&J zc|xGc=`1TG#Mjark0gb=m)hdudU|>VTag14j{1+)hG)jJ=qtZuYLXxY2<5PNSc{t2 z*GYz=`94;mBv`cDbP`cM&!}TL$LBvH=UkQ1g!TDuZ8`FWNI2&q?Esb2Yo%6_J|`3r zXUuBs@H%6w22Q*l<1YYsAYSr*b{}Hw^y3}DqWdiS7Xr4% z>f;}b=F_csm|V0L%3 zT8j#d!VVUjtmGRm2GZ}xr?htD zZk9Iq(9<VED8sCWyA4cZPmP}TgaG|K#^Bq=Qe<^SJuqO zsGD07hN4^Xp|^)Tw_%l`UJ%vKZ>54{k1p~`pk;fGP zd3MiTKMeSFLb{8T5LzU1&d$#6PyL2G;c@_+NR6~~6^|M>dI z&EqH@L1o$MPdgQK+zdA#VS1hq3XDY_wBW8}Juq^KO7&N#ha96~hlt$C43CX5vJO#b z=X^8xW}r{(>zkF8N|IrXn-jsbi5SXF(UMl0dyMty9j}LVcMc$EtfA3F#cHFRK!1>h zi?@?x0+l#wjUhSGR{@|)9TbQrLCP6QaM8@E+jd&OVVS3a4iN)_VPvF!?txLg`qc3N zVpwy)Pl>*WUjp3Gzm0AIHXk>8v#Kmagdpl&SEqtXLQ(=gK30(iUzzYLZ2a)(6rkRCF2 zKrP*%`@rHc!@FjxI7PJR4i{3Ki>BOSY#^ik>^K+}Kx<{`C`($ur5tUd3ZXz^d-?8L zS1^tA*X%71L>6GToyyWyGr~#$XZ}#|j)unOdmj}b>8CHK#U)MyhZwt+a{>*D-K$7G^5iyeK)RVSP2E11{q1#s1J;LWfN{bZE4cumj9{l*`sA5Z zfo1}*;D$G}S|dA=&WXb}C+`9EKwdr1h9)im?l&B&8I#U2JC6nCZt@FlT=cPsAT}jgL=(r#ppXZ&!&w(d_ zR8+QH7#CEsn#qEGMAZj;xAd!#W+#S3scnpiJhgU_KAI%HL%_o$waFz?TecQ@*9%}$ z{p#h{da6L*JbtWX!}AJJdTEFKz~ziwHB^T)ltGhGB8!Srn^-TVWELGQwpF)GSD=~S zZh0Gg6JgyK{yNGS8es8mW8t_9e9G|*{j65yVWw!#>#xI#ZLzYQwMzTUcj)x$C%>=9 z00U(bm-Fp*7D8?Xzn}Yuy90+=P~pSa8Lo&05=`36p5)2Wo|v!d_0RkP)CM~w21BV@ zx1%x34CoSzz*(p^=>x*r+RiP|%>r6UU1a2JrXTN?IqIe5-%v8Xo`>MjcBUHfZi5Na(YE={Iz-{8(L`TcA@Kz4iX zOP_gHOmfXGzgV;T15?z_(oD8l3;fQfmCK4)FOBCa7tQS=GAZr=L**f;16Z(K3qrL> z#RNj_c~JoCTV*~+O~Vu5)^Kt7zSw7H?DIsA`qyY$nScxlThm8#S;4 zP_;I=Yxn0oqqvU@KFSJhR9h{&6D}^)Lc?~JTyFbdk1e!39!-89Z+<7bMh%uy0(iP( zvZb6`RJcQx!$fZ~qdktwZePH~e}%I}l~KPBz1W9lnjl|$!&G_cs6vHZYL?gYE{3~6 zqGcKU|3@c9geZt`z%bPz6>HHxQxSgvBU zX^ls+CBg(V^_iUwFoSheXDy$s(`!jJ_cKtMua0;Cw3(u}DTZc>#0|dGTz!my$E9!0 z#XACTLPL6u0jPD8+a38?I3)cIt7(KH^~zB&_*|fblf^$%zd<-1YyfG%LvBd8MzNBG5brCA$J5lOHk?L5E(PXRWy#!_-v8Tj_HQ-w$ zk~nOQ;S-_1ISu7@yqb}ED%h9GRJVIEzTU5qz+G2mTU1Jj9Ws&EgaZ(bWEN);SEIaL zK->sWEH3ufLs5dh1ZK%=)ozU`l{c z9Wx`PI@$N`q1NdhqRl(eZJi)fj3NkeG)M=OYTZpNJp}pmgnWm9eggQEw2@HjTXQgiAmCObJK!R{|{S$oLQw#Ee)}#oaIGh%bCI zoDx1B%EO2P0U|rl${Hp9X6L;iC=G^*sp(fMCP_hm#9n`)ca5=D-r#k6< zA!2KfJjf%{Z#7!kT6pZa?{?o2z=rC*0T$;3?QN)#cS{pN5gkE1w-mvr=dJip zO5ngA5g)CWTXIlk%SYbD2rEJl3D2t>c^dgWjY#e4novy%;Vm;3B1Z7mLKz_3JgzJL+;_sh2PLA7&GffVTi# zdQ{~pwtHp|8!a$FUK;#YH2l8yd#;y5PF+&=of*ws+7~s&h&ElO5446Khi(!b@qv~z z@Y5jlC5kGDT(FD1XqWNXg zskX|drslq1Gc-UAXqnJavhWv#I71f|0yMC}{X(U%K8C<~4^2#5AIyJ}&MeR<35f>Y zbl-uu$3xb@8n6T+^0kLnDn3TTSkHFzurb%jICguq_USR?b7rTqWoHb1KMDk}pvS^b z`)Hr}EYJNY3xeO=*#e7VzVT!UL@#hew$!3mek=v_YJP7ig`>^)!eh1HdVDZuory#B zsR04y@J457h#4q{RI;*xfm2@>QdgI7CVFox=YRPl3frRt_zzYc)GR#x>r}t}0KGnf z&n_!kICSr{dpwr-^7=Ju06Yk&!Li!}3L>aN&*n~=N^Qf z3~!oNS%D$|P&7FlEl1k2^33dv8bn@tyt7$N$(&H0o9S0prG~)0#w-}P0mxzq+gwCN zqH6%YCb#D45yHzbLbzY^0YelFT6Y!ZL~P;d5P&#_9`F<;A^=$rD*dqS^&)U|mhS;05lv8Hy!$Z#g}8=4&x5V| zAiFM}>h{B*)5jZ00G?y4W)jDF$avt%S@sj-(?zu&kgT~~o?eaQ3H8b_$zrRj6Z-;T z4MxO*d0Xe(UxrdRKlN<~;}MJ))TS%Tgg!m2f|S@+%>} zH5K~T`~ZbS=i2YxB7-ZX!-cNf((Wd#GoC%-)X~JgAPn$JjpzbopoF zORiK)i^EJ-pY$A)EM03z%S z1RC|4omxATgGy5cY5*!YS<0@ex6%9;S2Z=0a04NH`{ge6q>{3T?a&{f*H86vJ1u$v#__AT-Tqz= z4lN_L=S9`O1uK72F?ZPSmiMZW!fmbvhECkdUX}?9qmI!{PQx|2{BfRfTL<=wj4M2JX%Ho0N;Ej?? zYZgO{C{Taa&rI(#IbMAOGgu(O{FR#ipIQLFr!Ll-*Cm`2MJEI-8M)uAbpqgX-+Ac=M-dlEnhnss}w8_)*MA?#(%S}ZCyo54U}>Lm28`v(>Q7qhup&_+|1 zpWh_N2mwufv;kR$E#C4212O>bs;XJD0qQDqi|@u=vAd2kkTjBVDf%`xocgDB>oRRH zv5yV|T6M1jMRxz$spC|yvo^Sfc43k7`n$Dz;a|Tz$8NA9DXPf6Nn_jXzPm#R0Wndt z$0HfEm(I|%S?`5nRla*PO=`1~HRxl(W5~`7iv{XGv+KHJ8yvDyvQs0IvkgAPd>=hf zs0qMDQsw9{F>RIdqstvnugd98vQ8`PDW!cXZI}{XDGmRzCqQ1=4 z#p{UFxc788wD%pWl!IIVgj0h&yyu{WO=VpIY&N^r8w%RTpmoM<;&X7plYmI_pl_o$dh5lMDg+aRvC zoh!`92yV@=)u@fuPI<=2;R}*3M*X33`2;lMw#Yc=1Ch@FZ5It+bgqVy3?&1Q57^&V+g?P%w8T@!Cf?5#&V$f@EwIE`2%-63ZsHDFz+6~qud*J!-R){ z#iW|Qbz zP&Fna86Q?SnEie;(mYpSCI0NF)V~XLApH2t&zdlD!Z&F#$y1eqeSR9sDmr?j34QXuw1MtaEVlc6{}`}-b_7yW|+RclE=1wcjZk;4jqXb-5#2-jz2h-v-w zK1T^$$%qr&2DF)30Ui)?GOEorGs%3ek1}~5pM41qKdQHHEDv^FUFk8AO~DZ>Pml?S z1lo!xwCRMjWqwU>@st&Mx~F&-($iO{j%D&-RQzZJyfzEzH6_55Q3*W?=t^B}?TzfSW-p+|+#amAHBWqSBW~ z$dl{|YLA`lV^psZ9)D_oh{QXUI!X4f?v$R|H{sI8oUss92L_yH0*tj8!TE#Bb_24i zu+KSTn`L`I`l-X8qIb^sAM^Ix)2-h?q3G645FoRW_)|okY-FAEu?xl0EBpRKz#0W% zxp{UwK8_bM7pU`t!gJ6nI|ntdw>wvfU2Vjt6tLTKQgTxW9OL&%-J5SJy^%mQnrt5e zHbX~c0C9t#Wq?Q3rt##T+x;aHj4!J|dht(}8|Jf>l@VaeHJ&QaFZmX&r{D&Zg_z@b zE-?2PahlrL7lT5}UqMsim60^$-UM%&5cFF5S z#ri&?bU2>RMbbccOEo^Q6J1FD%Vng243`^ zSS_2R_rHeW4amv@_su$^Og)r}QxKFud-85E{&V%e(y>Ib4`To%Hd||2tJ#0pb24R9 zqGe`ofhOS9RCAHD|1x|uG$2+00|<|h+v#afswGHDIX7*hFxw*kTm$8Nn;DKtjLrT* zEQ94Qk*yDWw!a^}Db4HT;o&r>D0I#RVD6rHk%y}*jex@zeEN4VQ{d0T@71sh#XgdO zYG}2EQ$u-mb&_EX)pdCJ%pOc=hnsX`6v?p?jC5ZN%l0P%E6lOm#+B?X`Mr>|YFtwu0 zqYB_oN^WS*+PDA#wa%GvVP0qH0_uC(yPmVEB}VKSVy?;(AZ3R=Rf!ri(i;OQLIYJveA-86dKET(z#ma&_*%-p zJ|jJq*6iU3NB$nZYM9?2^*-05fgg}j)cqbI`<`xd5VYt5l7s<3Zxa{yEFf*683yDD zKJ=c!gG zNFMhd`urG^U`Lc)Ts``;m4oE9^RCNsp{+HvKW+h=g4;4aHdQ(GC+JcIlnSl(O;?xj z22#Mb^V$pbnx>^|5GYsGIH>LOaez4%`9UERMRnJ>~aCa($pS+PqZSyLS!hyv(V zY|zV#UM(FT^*_iCnN)lvrgjB~POSq`#8A4RX_R(&th}As)CRyUFrAH0h#!R#K9r_o zo@xZJE%|yjCcBwHSW*nQ7H=%3-yU1DsK-AjihsfQa(qe>=eu`{&I-($VqUXesWuN) z;L{&Wd*kWKs2A~s^RFQekWaL*<{m^PDmQJ&lugF}EoWF|*%a`%3bWQr%U<8&oCf~Q zmGAFL2U3Y3{@2Kj0>}Nkg1N#IV-uG`_xU4@FLn6Owk9X8VWbB-BDNMvEozmj+k7~g zKxOneHEzHpm9I%sIonU1%#O0f?W{9EvE9?v6-3V-RHv{uKa1x7>|OMxAHH!Wwl-=; z1fw7i!#I46*JaVb--hR>RVYG~de?V(i6FRvzzZk+hI5x5vJ<2%`Xy42l`t+YM9g5_ z@#Ofv!OFw#sfXWR3v%)^uxGY4i-veybPu2cgdyWLiL~ugsEJnw^x^ z>2#bgQKr)$AlkRy z|5?<9p&2MCD^_8+jZgkYP(Y!ulC7RqQeT`KZD)Mww21lZB>o^}=7+{6n<{Fm%!H8% zDWLFxHtIuBY1)S&QJg5Q=+cFzDWQ{sAIr~7*d=xYDO|VY#o$#$V#N%rgBC8R8N%avh548o(7Hg1^A6YdjY(fn|l3Ym86OVkIuWz@pcjW@) zKtF#?1&X6{k4!o0s}Eke^&7qO7JB!GKgzF7Z|c1cKa}hBurJY**9I%6Q?_u`1cv8a zbB>d3&RQQIxQkqZ|%A0a2^~0MR zh?!8edY3(JlU_=U=bd-h=i)e2{QC=;n@c8$hBA0%VC&nG6jl5%Sp)%Pzz9U!sT|lc3Zykv#yfO73K9(o_*Tid0K*bq8=u!=6&Sk9lQtVy2qFY0%iU#$&6PUEIr{wkZm{G3* z{^r2;F6zAu_4cUko2gbn(t9-*9WrI(K&8FOaUJ9p`hU()KLX6u7Bh9kP^)E70R*z0 zfd1j?0pc8OSO8L_Yq?;m_koDk_)7SnqMx2j*3A6{v`%7dPwwMV)K}ARo0G)8r6U14 zv1z+Y4UU+R-Vi{+z5$Zb$+guX7C=B9WPthYO*)FGKjbKZgtqJ$PuHOK8<&l?RzuTn zh-!4Iuy5e*l4i%~SBhH1I)5FzX1(s^(xeb}{*azTspymV$Z>Ff($k2nYa&EE3U_#{ zb|y>giw1io#hx9pmJx%=;0sj;c!51QwX0kfXhb~434apn`Ih=yDmgGG9s_6*j0%*w zsYPLcCV~n{bbs%lz0I68s4iR60*P%B*X>!^ozAFcdq8p&YpfM|a}tCA!0OC+2?eMU z*cQRCYc(=4@d2600p03o;A3pnxypJ1CKOS){%W_}T%}_=$VPs{2_)W#jQloVtm6s- zVwet8AGjE2orr5<2DCor-)dD9M(2GcGj9FsWhm>qw{dzpzpMM;is~9iioiKdVwKwj zh@`R%R=$mW1gTXHhZgipOLP5jLW2qz+cAiS-r-cVls*v69^r?&zQX|(G$p#6zDb)O zL6#J#BQGw@6G5V0Rg8ZjExk%gPEv?yC7_zdrgZ`(cJN^yfZYf-I$DFbv&jw|c5i_! z0p96hPF#OKh_`=bJFin`{&%)h(#jcsbGPz-?0!~KRBRX_=Ox-bB)#3}Wda^nn8c$m zEEg5CIj~od7a*Xi95a<88#3ZdZ}BM-6kqAs&EW1Mi5znE`Vtb=vkpi_)+AGB8%NBl zdpG`45!Fc@n}za0wm`KSpbQB+c|vl&VUJ{|B7%UR$?gs1;1u?qZfxw|{Vmjfg5psS z^v{ej#Btw`x%5RWm?k_HNB=n5xz||5S4HNc5tl-AS5e`yI3e*L8FjES*$P##vtV%+_LQ8?K@* zbT;kpV{)SGxM{9T@Ks{<=OcMSsxp<)s?`(T)wq;|j1nyVa^DtTk)!t(A8bWLP69XO zY$p^*CFNwC#^cjr$e-<1F%MT*2-I#)X51Om`5fCj2dphGmSToD1YH|k?JVR!vTiTk z3x!jP+!}Qs-aHX$$uLEx)xLJw9l42m6jgpA1o;<6R{qZx*1h^5ItUI zTX?{?2z>}e5C;%4WJ{quoZneFv+hCGvm%S6;)U;jxVa!b02orS-Xh2C_!QxO?^9w zc^4~8dKhV2TaWB3JZroV&56|llngOaaqH2em9X#L_&V9&&s1xb)O2YB4s|RuJo`;f z&eU~wyz338UPp8p9?#6szIoBj%QCJ8W@UU#UZcX~#x=P{Ce10D&|q$Gp*iMZGgc0>ao(S^3l3w?5d7ai~t-pGJ=%DI$iNMS)mp9;gEh66xvhhtKA|b<{m; z?K@>zBS0A%UqT+4M)Hty@0vD^-@a|OtYzVJ^*qs39?`ow>c3jWVm~&^?|954{NRiT zGHn&M@Pv;cX1jrfqyDNgl!Q&|Y^4ap#8y^^uJLTqK17$({?WYonw!@b<|N)yJuX1w zCc~mgtcr7Vvr#g^J(;jopyrI;xVYWHZ%11<85kREyMNBHwGZyoEo6}KSp^fWtWUv- zAYaM&yvBnwt%tss*$&&JWMw}KuVd`glf@f3H;W{mN$z;US>S8pVj~?Y^z!9foXirv zHy%IB!d|9sRH5oMekT7s04yBZjSyz@I~T#eJ%Oy@$hv4l$Zk#J~vpBae_pr5VwzJE;eXVy;79@^v9jmFPQI9-ye)%YwEk0*5gq#ON}mG-C9YNv%OgV1h|B#j^@gd)ucy^eiQ zKVbA)2(yz6meWB~Zu`yS8&to#u0tkurnH<)ox$W~8G5t%4~?KS_Dipx5JvPF%2{3a zDm{KvA|Pnt@`qudxMU4ua1HLKvE>KPJwD7~^H+P0{|va0TXbHz*OhxJh2BX%*rBkZ zj>yj_cS9W+Hc1kTyRYbRuxh|e2HTMXuvHRZ0;ZMopw*{FLnTn>+998xnp(&R$$c7L z7bkuD?H)MBPK?_u%F4`4Nc1TK!v`GwNW2!z#W<6V4ke$D&&IV)%jGeJ4H92-02_0& zx3OC~t~Pl}KHUP3UN0i$(3;@c9*#Y_Ef`ty<=_4kLPaRJ?b5eY#h_e#>iOB6OOwj*7(waoGd zQ|dTGB;?Ei(@oA@-VuWKH>&ZAp4YMqHfCEHcOW=LP_+YjxTY3M!21uuyK9tE(d_BY zXk|yeD~I*j&Q1nv<%ebNCvYERwmjS&ammFX&!3goh)#q%AmOeDGqvtFl{I76jW2}Q zRB;qPYLCv(uXxM6yM7Bzwh;RKVRN-=&VIMEyMC)M8J>VFQ&WELg#HlXXIPvWaB40+@}mS2;! zSysE0kzp<1YKt*PPLDkI?t@})5Aj)s5E`t{o)s2`lkv@aj_=#y(5A4f7xj&xt)_mU zZ-7LNi>|0?t1Dk1p7Htv}FpU<0{ zx=AlynzhcDrtK$xJK{H;C3DpC@II=XK*G;zZNC1R$QFM?8zgRD2G<{0@(7pC%nF#&m*r-^?u02T_pOr*Qj+TpcQ_QyK*2SymfrJM>O~cDh4GU@V!dj@krb|dL zm^vW=TOW7vT*7B9Z@4N>rvlc1XMQ!(xpVQGu(O=ykaD?6ZIuEM>*~ zO^azRw5hCtpg*ZAj4CxX*|gTR?oHd!HD>@Jz-G?RNq(PpeX86z2U3~4YauQN3sVA2 z_NU$Ysd{ckViy{wGVDw&=?*(RC2H>DrLJe{a2#4nxy=;QG~4nSO1XS!O=;ok+1>Uw z2>9)wj_0|lQS<3lX5#bNx1j7BDa1whX4;BpeT{f+4G1hTamP{Rfv_ExG{XAN{~%vv`H*9FP|joNL_4KQGOS=7g8<&yLS?-MSPT zE}qp!+VhXRoeM})1fcF~fU`i))NT>|*=ne6r>GNQ%l@iiy@cMv~$ za#vvfbjYVz@+KNn*r#*|1Yr({E3o=Ae*Cpt2HP9GKSaS}b6i(p$RPW~b-BNa)C+c6 zf)p{gvUmvjc@}ax#NsIVfW`x?j23OETIk}BQy)E_E8S0@et>5YdcB#bb{0X#hl<-R zOY*p4y3)A-)~O*4#&`-|Bl4VujaxkW zX9sBt1u>ERLAztW$=D&SzlfXTBK_22q4A#qgnx{VEqdOFAIK5H;>yrFlb$plw&>8= znkd-fwcB#gePQH=M_W5kWt7M4`SVuCpFOByv2AelmB>3!Zt5@~<1ELF-aV+X&Z;T( zSqortg?qF&*TPZBq%h~MQVn%@lRYAOQR)k9UBDGlzZ^aTbv^@69B;gi7Qob;@7jv! zoaVNq!-?ayUv6ez&z4v(qH5E9*8xcta2)h}&U==YGKy>(xSq3g38bJew{B)PzCnVK zBA0tTrkiB4oz>&Q%li5eB5p%ru8VpfRUVQ*MD?cx4IN#U=yL7uXw@L*a~I-}yUEAL zRq_@TufKhZ9r9VD5xHry?eEsVLn7^?e`H)jqw$|w07HU##DfEG0+9-<34ZJ5p^Lg{ zM(Q+#fRyCPilyz@_{E-}4P%7J@_}Poso+QY033W=54UqFa5ydt(6GCm=U!@7KBpDl zkFA%t&}f2F^68aoUmsH(JSL83p!@ki<{%%Jo40JKm?_2-?hbkl3vA0}8<%j|S^-b! z=$O(Jo6eHfvbg#}wW;st$HDdLg&|pW^EL;HdIw&0pZ2B8d>cI!=S~$GD;-a+ zk-Kv>^Xm?q#3YGdJI954fPb8<8%0R7nJPM5I*)2AA0|X(-cwbjlFOeo)rb?SwBjW9 z=2a-?<{>7I2M&n&D=RAtW_G6Cf8mXOE#nF>V>FF4B6wgh%JgH8-*Pni!-sal@*l{+`lLFLFVv;tCsv_ zG@ZL(C2L;^L)A1DKH_(JDThX(kU}%Pljl^wqK^0cC_by1n$;bx{YB&|)()8!67{Sv(0Kwh)8)sZ-xj5WbnC~~lLT+FcFP*75pjQBR7J?^NaC;F z!qIPd$Z$mEFAcUo_=>csxjI3DfxBEjyoA0PMU%(#t%FcB-FFa#RNIP|0yHRIPR^B` zK6ZmQH)oysrO$(YIX9Ha@A*%k#LTF`KWSZ`)xrV`3kQ=i=O7Z26FI4F>5mmlTeqP_r+RX{?lBya2TJo9T^*=4*zr?7w-^>!+<$AYwtOt)^&R zQt}A$B&_q!wmFZMp}Bh?;Y#%V!PJ*Fdy?rs71obb7dd+Fak{%<<2{X)9StQR9Xu?x*e6XVS^-@$5~p$4D3KUR*dX06j|5Ktisew5!V?+nRwlIJ9_J?I}U~o)-Jk zWEy<`j>UH+$G_3ZbpU1VoDN_AE?l{OZ)b<%v8wlePnW;HUv)IC|J8k35PgAzvpQN? zj@-9Pc1lA)mR|-;^Q%X<-K(xneltcpi@6KehmW{SQ_f~jpNIU6w6na0TVD?#j0by)yUWM--AQFFh@(CalfieqdY*xVZDy~99WXjo z2Pmxw6#-lHT+f$#s}VHW&0a@(j#y_0o1PFAqdLMiI2?3Fnv5S#>AGoNH_lj*KrRw} zpT}@oZtx}7Se*!m{=(mW*m6=e>L?Rm4@o0D6BHQPp<d8U?a-+(J6|75wu(S*T zh^SqrEA{0l)#(R_9T)Q0)O8#(YmK&+{w^7e5)-iBO8w?0mfrdOk^Ex_>xIGgk)gyf z^w(INI$pIH=RKQq^;Wz&-hR+W9YxXWSvVO%;$`2qw(TFD?6hEww4l#jk0sT1UtOxm z$D`ooqZK}HI~m;nAOx!BfWbHJKFY`ALw}=cJU0upga?De6+J3ldNbF3PuWD*<>q3{ zwBPsIUylE9y%$=^O2g|Bu$cbNNyP|&W^mzO?GJ~H^8Az^gxoY;d^FES*FFLDtxPJ zmzg%?P5*{E+)IAWKU+t@<%Tt23to0`a7c@{HC0cPuJo-2Er1ND@z6f6NCBY@f}Q6EflumJmE{1AjZ=eEEXU@O`xFt^6^)lKWRyt{y^ zo$m?!nEJ&pGfe#dM+wjSV=2P220mcVX5p<&1?m*^ju$c;CikkdT}=xdi7Gx;RShJ( z4D48q5E(?Wbw!X7Q!|56-TH+nD&gO)wIcl|xV@}z2m70E zRF40v|H50}o!V)hfyigsiCybvqB7R9#Mv3E=V^f58%+^j`xm?R2_0}f99k9!$q5Sr zvnO~hU0;byY|%bG|6WbUUWSoM=GAsfK6#$ z3e6R8J>a{tc>cVt55$OCnh-z#GuWSOtx*YtL;snCuC0M4CYQGt#p+qMT2wEsK9`bV zi>uBusZQ9a=GJ`QP>{0Lxl!d^Ab$8z5EE#sr0jwGgbBh2T^9uvlV@&505+>Cg>}>d zhz7tK&;IG;-bylL2e^vdgO&^ER!a%q6HZzFHYl;CJ%1_$C%$iCeCaEC7ep%6$w$1s+pP+;* z+nc(&6(-)26C0!?mYbf^;WS)II7jagi;?-3o#6RDs zg)P)|*tp`*y6n%M><6WTn6{S%oG#VX!D`m_=83ji;vKn*?+{5ZeToTJHk+yTUnr`p zmrc~0>Pa**?hW;mKu~oz`JH+1u3gnsrvz_MEVj)UsTLU1;#g8rGU$`ZPj@H(m!iAj zy;9@Zi`G{**Yb^<;H~I>D~-5cTWCr24_&t_3%_$cT14M!1D_I68=FNaWcN@ zt5;_>V=5h&Dsa;u)0zGfoVS|!6u2Qa4ZD9ArwGyp&`CZ2jE_DZL*ilikFA5>cBz-@ z&`>^p{P>C8%|yJ8$Fh5Srz|zEOMlM2)!UQCO6F9EU_j$^y%+ea1;N3J&x3S@cTtJ) zI-4$@9*p!bn{T#I6&@uwU~)R+Jz<*5n7Q^8?qpu&ET+a}~sT(0!QOYAQkV*eg{3MtQkH z9u)#+ZqoRnGA;JEt+;{H84M(1n8mm0+*mg*iWyl@@fI0QdqfZrX5&t{rVkZDW zSBV2ZS?T1GS8ULT5i_SZsS^GpMx4Esd*_sellyjN*7Xb2;;PlD zth1U;US|L`I3EZZ(&AMm>spgsA*B1iovQ@ZxA$bMDE z{D@kpc<5)U>$X04P2;`mFYgmn<1>#Q3v=y<9fi*bX;}jksJzQ^0CrD z_S2`OcgTfSgCoM*I}@Hg!_N(!|1XZ6;=CIc(t)mA6uDq5vM-MW8KFpVA8m-XSc3CPOQ#d z&5Qxk1)=TUR_8P8aeWq6WE~SFy%9$WQ{1y$Aw|372w0$8t?9t|ykX4{RiKAFy4A0j zA1!n{o$&PgrN++cOHhHE-B_7^->-BnyzlYGreYt^;Jx zpFw_a+PqGd%@jquuR}HH)kLa}PDi{Or)&&Ex#)&l<`#6D*=k`u>?{^4W*H+dq#*qE z@0B7u%CZ&m>BWlY5@{Z8VYWYetEUMIvUZQ2)d*H*6&AKW<LRV{RPHmjnAImwGjgsFI%W-p(usY3GPkR54rOCvIDK+2`)EXx z=El&Ikm%tj9x~XXs@+HqZTod6gG8WDlTUgHhobNspuM$Z|7g`_c4{oN(b+GI3^?G_gIQcZOk<4 zxdmvM*Mj%od((>re$%3;`e0Ei;HaVr$!SU7E>RW9Wt3nL3w$$AZJF)u)N+xA>n!*E ztWii@)@ACLZAQYMmX<%K>)iq)&t8x>{qs?S=YD^9!J{c3#i=TdtvK+`=wSIjrXq36 z3ES^yhu77*iD_!rO1}3nSljp`_iyw@BI5%Y>0s893h#P8OBu|?;Zy0x%yTZXN7?V< ztb3?ucy0Rr9;Ib^h4sB?CKoIYnlktaVn{ssJofc$>N_QmBlzvfI&3|FKNZ5JF(uM+ zT7hg)ROPN$|5$6=%I{EKy4@oWyVmzTT8m1(`Eb?RuwZz_*eMxU{Xtdmd~vGrg!!CU zMd)x+9bAmQ3$=rNAP-wS`58n}n#6hh5ht(5h?DC64NlF)JzB``r~tyhNgP{#c7xr> zh%XnHb8g%8SUmOR8$My?wYod^)RKjo`W~oG@nTYZU$#`TbMv5HT<@83UL}5Qy#sRN z{-WebvzAABGC_TYM_goh(R8!hbt6x8TxMTpw<;(tWE5DC9x$_5B>PP3IbezbAY>EK zWa7CPHYPzMTj0;?GwbwavAGTM`weo9M&>I%Vn(5;@ahtYyz(hA48|@=;&1} zx-H_JbN;j^RQx`6m4sP21CH!EmuO6R3mqrj(v~E2(vn`>M_`qx4%+CU#G+CZ9y2))Y zS7_p=pY1t1L9x=o@|+kUtA7Y(sa+{O>4Gw+)S5|$j@|S>00>bH$!2O<>AWz8Vxn0%hslkegeKRI9Lsu$f8 zY$ZAQ{#?1K7G9!O_vTnGG#tVv72U^n!1Tt<(jH$vY=Qx&`T=xddyuWHN?;*-Y!!SI z#kMYZ(%Zz3sK=Q(dF3dmmTXBN-?WmIR@$8LpoHK~DV&xnW=oOZo*?RD--f!cE*j*| zab?*SyJ)IIN)|3{7xg&#g<$2CuzYMN&V$okx^aC`^fhjgC3#Z2{~r>09k{d>6o^Y= zXZ^p?M~bwb#{|rU{9s(bd^wM1&a^(irEYo~QMWpw3gb{!C5Bin>P0K62o=c7M7*}& zUek}4qgGRz7{1go_Yt?&gow>Kh{V`$Ezb;idjzVF^VH8*y7;cyx$A0IUyk;)^XxId z0Uu328=bkO{f7FuEcxQ*VG!%2)sS<;`d0Z5*Qq^VJBT1wL(K40b@$$2VaWS*U)Q$+ zN1t@YYDGv(=i>}5?ay%uJbV|1Vxh7+jR(S-)EYCX>U{1hg|5etk=D;NTpw1S`Ef~G zsG$0>KXRdtrR%5+D|x@;y<)q`z@AHTW4-B@j_J_j~OH)|={$95zMQGW-` zP%G)~dB!fTIJeIAxq&o4ZxjUkwO)O?(3%e%O}Mno7Re+&jhLUb zQh!8eFKSfvEgw6*8ixcQARni`E*=_TO&KW#_UMn8?1$O!1n!+>g^`71$&sf;t16we z!yeKGu(D`z^}*oksGG&C>EH~obUAoUp(Rb3Gxcjh{k-b?CCd+0lTN+rs)x@$=EC6 z3Bv+jaHjwTWASE--eBDzUq1D~gqJz4W|1v~2H@D=x!Oo5f9qRiuj0`}Cj}lDwisLX z0-S7AQzH5cgQ(^26|=bEyDl556TDsGI|4;?MdO+a!z)p-F`w!3uZydmkgSO~TZzyH zOqfjB*cgjbjqvik?uI{=>UpK_zV6)Y8F&^J5sdpFI1DH0wV*go6bX%nDtuJmi}S;C z2BIy7wBl3Pv2*_&HDLXY%Ip<^ z)OM_@h0sae>#Ck7q(n{Tz^!PXIJf^zTrtKFVo8BGD4p>}j7RxMR-_m)X70xmerJP4 zl9MXxz*z$nRGsI4h-J5C2+gW!OnrMuu^6imufm%k`#GWf2iSxWco8A9c?ai@dnzB2 zZ;7}Y71lN_D0E5uncE?m@b$CLy(qBh81N4sJ)#ExSyu-xh`4ZKt1q{=Rh&~}PkT)V z#*K&)ximR2g;UjCeI-KK-ui(~?O9>i{QT9Zu40p*B1Dtn&E8;9)3x7GYD%QC$-<#y ztDhwMsICp4!ip^q{s9GYpyKtw^7IG(l$S4G4I9{Kl^Rn95Z*zXAeQ}>y$y?Yl3QkU zmd?8?vl;{|JBD%weh@sc@~$k!q7#QU+N8^;%TkLILq4Kp^1r^HN!6Me-Yc-K4!Y>C zMs8P0U2DKfgf6jJc9VvZUmk87Rn(uHk2gzyh9H*w{#jn(6*CvUFA;cXL+OD_UL{^&P6 z)Wq}cBv@(`3Z!r$P2m(YMrEvtNT*Y1rEqY}kdu;m?R1Y{JWuhBQdNpkS9Be`Kx0WU z*Ycq2A&5qF+rjg)rt@ZKW7lbfh_P6RD|MHx?<2g?d;ZbnNt93?&(JZ%d3Oy-Je^dF zd^)k}&0$e`5k-%5Q5EI5i5hPkmpV1;zEmGb1D%vh zzGwS>TlRj{yl(o0-qqft9(+g%`r)Qt&_Yp0rJSNFt(drV44v4{ND2K6RDmXBO`ebV ziz4mcXcG;^6p*V|YkaappgM8egTyzW|R6Ppq>q+wsF_l%JN#SHw(>3bZO4 z9Afy=*1miqwUV&?`dk7%9(x%NKSBei=xHkjpu z?vP3g%fUsz!X&Ilx_j1VK{nu4#j|nBHrBC|+}b;howS?y9=H(vdWB8by6;l#i`qSP4CA_ru+) zRw)zvfx(oJfoo~lobo9+FzSLcY((-8mE}4%Zs?t5@wKYBp8Z@|7=G1~Wz)V7DJr3- z8y4Yt=600VPp%a@VM3EYxiGwEMZ5!?i`&-+Y+k=-s#p%?F;U^I{%C!Er)OkwPDewL z#J0DexnfpRTcE1X^y>lK!ZVcm-dVIPnek+214Gx3wJlUggY30*aZTLeCuuReA2r3t zS0<8Du>;HJk5wim1OMFXEkO+^Se}!pc(>GcTUg$!PI%PNNU+{O<$is?$ij-vC7zLw zVVoi{93mvhU6YZ;4hG#hx#H9}lRL@K*tdReHTVKLyZ37OZwdcu3$4-#6J&SDK@DqV zap_`QvW}W16sj^+o5HKVbRbxE%ldP{LI#}Xv-1?SAp<>L=hI6@g8iA#=qd+Er;o$m zUYT1H7{`vQOz@@)7Rxu)s%fzt7N7XU(Qc48rdvht0YSqp6R8$e?t5=J#@76mbNhCX& zvYaaE(h923iVkAREz`&x5a_=7F@($th}(+RR4*tfbaESduL{1LJZ`?n9NJo(j{bABzq{=R8) zUiz@FUHZANmGHq426+8D-h6fP{k%guyWW?hD>U$ru#(aq`}XQr^17b0G}Ka|9mida z|3I7d}ns?Ex&!ff`VT@McIq~1pqUkEF)^@SYlC)-3=y?#OmD62cH zu2vaEWBvn%mVaa|=KBXd{lAHa2IdqpD}spuUqbtsI}HYX@n+a literal 0 HcmV?d00001 diff --git a/packages/preview/conic-toan/0.3.4/assets/solid-geometry.png b/packages/preview/conic-toan/0.3.4/assets/solid-geometry.png new file mode 100644 index 0000000000000000000000000000000000000000..3e455b2fa4f0d696e50f74d5741fc2ec83ccc56b GIT binary patch literal 35053 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 Kx?0jS`2PR}d}qG^ literal 0 HcmV?d00001 diff --git a/packages/preview/conic-toan/0.3.4/assets/solid-of-revolution.png b/packages/preview/conic-toan/0.3.4/assets/solid-of-revolution.png new file mode 100644 index 0000000000000000000000000000000000000000..131c8962f83aca8b5d181c29c5f16346c45cfa18 GIT binary patch literal 55218 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 literal 0 HcmV?d00001 diff --git a/packages/preview/conic-toan/0.3.4/assets/statistics.png b/packages/preview/conic-toan/0.3.4/assets/statistics.png new file mode 100644 index 0000000000000000000000000000000000000000..9df24799646bc49a0aca8918c17d4179258a5ca3 GIT binary patch literal 41330 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

dpSR9OC@p-A#@WEk=D9aV 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 U1fHA#Z9Rt5r_A(=bREP04{Bc(%m4rY literal 0 HcmV?d00001 diff --git a/packages/preview/conic-toan/0.3.4/assets/variation-tables.png b/packages/preview/conic-toan/0.3.4/assets/variation-tables.png new file mode 100644 index 0000000000000000000000000000000000000000..037f3c6c39909e5a48c1ce2fc528d20edcde7680 GIT binary patch literal 22349 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 WiUu1G!fs5PGe literal 0 HcmV?d00001 diff --git a/packages/preview/conic-toan/0.3.4/baigiang.typ b/packages/preview/conic-toan/0.3.4/baigiang.typ new file mode 100644 index 0000000000..ac8fe89fd9 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/baigiang.typ @@ -0,0 +1,569 @@ +// ===================================================================== +// baigiang.typ — NHẬP MỘT FILE DUY NHẤT NÀY LÀ DÙNG ĐƯỢC TẤT CẢ +// #import "baigiang.typ": * +// ===================================================================== +#import "lib/ve.typ": * +#import "lib/hinh-phang.typ": * +#import "lib/hinh-khong-gian.typ": * +#import "lib/da-dien.typ": * +#import "lib/mat-cong.typ": * +#import "lib/oxyz-toan.typ": * +#import "lib/do-thi.typ": * +#import "lib/so-do-cay.typ": * +#import "lib/bang.typ": * +#import "lib/khao-sat.typ": * +#import "lib/bang-thong-ke.typ": * +#import "lib/bieu-do-thong-ke.typ": * +#import "lib/tron-xoay.typ": * +#import "lib/slide.typ": * +#import "lib/cau-hoi.typ": * +#import "lib/che-do.typ": * + +// ===================================================================== +// ve-voi(ctx) — BỘ HÀM VẼ ĐÃ GẮN SẴN ctx (đỡ phải gõ ctx mỗi lệnh) +// Khai báo MỘT dòng ở đầu khối `them:` rồi gọi hàm như bình thường, +// bỏ tham số ctx đầu tiên: +// +// them: ctx => { +// let (doan, diem, nhan, ve-ham) = ve-voi(ctx) // lấy hàm nào kê tên hàm đó +// doan(M, N, mau: blue, dut: true) +// ve-ham(g, mau: red) +// diem(A, mau: red, ten: $A$) +// } +// +// (Typst KHÔNG cho gọi `v.doan(M, N)` sau `let v = ve-voi(ctx)` — báo +// "cannot directly call dictionary keys as functions"; muốn giữ một biến thì +// phải bọc ngoặc `(v.doan)(M, N)`. Vậy nên cách kê tên ở trên là gọn nhất.) +// +// Cách gọi cũ `doan(ctx, M, N)` vẫn dùng được bình thường (không đổi gì). +// LƯU Ý: các hàm KHÔNG cần ctx (trung-diem, chia, hinh-chieu, giao-ham, +// ham-qua-2-diem, so-toan, mien-tron, giao/hop/bu...) cứ gọi thẳng như cũ; +// đổi khung nhìn (ctx-quay, ctx-tinh-tien) thì lấy bộ hàm mới: +// let (doan, diem) = ve-voi(ctx-quay(ctx, 30deg)) +// ===================================================================== +#let ve-voi(ctx) = ( + // ve.typ — nguyên thuỷ + toa: (..a) => toa(ctx, ..a), + toa-pt: (..a) => toa-pt(ctx, ..a), + toa-nguoc: (..a) => toa-nguoc(ctx, ..a), + goc-truc: (..a) => goc-truc(ctx, ..a), + dau-mui-ten: (..a) => dau-mui-ten(ctx, ..a), + doan: (..a) => doan(ctx, ..a), + cac-doan: (..a) => cac-doan(ctx, ..a), + nhan: (..a) => nhan(ctx, ..a), + nhan-goc: (..a) => nhan-goc(ctx, ..a), + diem: (..a) => diem(ctx, ..a), + cac-diem: (..a) => cac-diem(ctx, ..a), + mui-ten: (..a) => mui-ten(ctx, ..a), + mui-ten-2-dau: (..a) => mui-ten-2-dau(ctx, ..a), + vecto: (..a) => vecto(ctx, ..a), + duong-cong: (..a) => duong-cong(ctx, ..a), + duong-gap-khuc: (..a) => duong-gap-khuc(ctx, ..a), + da-giac: (..a) => da-giac(ctx, ..a), + duong-tron: (..a) => duong-tron(ctx, ..a), + elip: (..a) => elip(ctx, ..a), + cung: (..a) => cung(ctx, ..a), + cung-elip: (..a) => cung-elip(ctx, ..a), + xoan-oc: (..a) => xoan-oc(ctx, ..a), + goc-luong-giac: (..a) => goc-luong-giac(ctx, ..a), + goc: (..a) => goc(ctx, ..a), + goc-vuong: (..a) => goc-vuong(ctx, ..a), + ve-goc: (..a) => ve-goc(ctx, ..a), // lối TikZ: đỉnh ở GIỮA + ve-goc-vuong: (..a) => ve-goc-vuong(ctx, ..a), // lối TikZ: đỉnh ở GIỮA + danh-dau: (..a) => danh-dau(ctx, ..a), + ve-ham: (..a) => ve-ham(ctx, ..a), + to-vung: (..a) => to-vung(ctx, ..a), + to-vung-2-ham: (..a) => to-vung-2-ham(ctx, ..a), + gach-mien: (..a) => gach-mien(ctx, ..a), + gach-vung: (..a) => gach-vung(ctx, ..a), + ve-mien: (..a) => ve-mien(ctx, ..a), + duong-luon: (..a) => duong-luon(ctx, ..a), + nhan-cong: (..a) => nhan-cong(ctx, ..a), + // do-thi.typ — hệ trục & gióng + truc: (..a) => truc(ctx, ..a), + luoi: (..a) => luoi(ctx, ..a), + vach-chia: (..a) => vach-chia(ctx, ..a), + he-truc: (..a) => he-truc(ctx, ..a), + giong: (..a) => giong(ctx, ..a), + tiep-tuyen: (..a) => tiep-tuyen(ctx, ..a), + nhan-pi: (..a) => nhan-pi(ctx, ..a), + // hinh-phang.typ + da-giac-ten: (..a) => da-giac-ten(ctx, ..a), + tam-giac: (..a) => tam-giac(ctx, ..a), + duong-cao: (..a) => duong-cao(ctx, ..a), + trung-tuyen: (..a) => trung-tuyen(ctx, ..a), + phan-giac: (..a) => phan-giac(ctx, ..a), + trung-truc: (..a) => trung-truc(ctx, ..a), + tam-giac-deu: (..a) => tam-giac-deu(ctx, ..a), + tam-giac-vuong: (..a) => tam-giac-vuong(ctx, ..a), + tam-giac-can: (..a) => tam-giac-can(ctx, ..a), + tam-giac-vuong-can: (..a) => tam-giac-vuong-can(ctx, ..a), + tu-giac: (..a) => tu-giac(ctx, ..a), + hinh-binh-hanh: (..a) => hinh-binh-hanh(ctx, ..a), + hinh-chu-nhat: (..a) => hinh-chu-nhat(ctx, ..a), + hinh-thang: (..a) => hinh-thang(ctx, ..a), + duong-tron-ngoai-tiep: (..a) => duong-tron-ngoai-tiep(ctx, ..a), + duong-tron-noi-tiep: (..a) => duong-tron-noi-tiep(ctx, ..a), + duong-tron-bang-tiep: (..a) => duong-tron-bang-tiep(ctx, ..a), + ve-truc-tam: (..a) => ve-truc-tam(ctx, ..a), + tiep-tuyen-tu-diem: (..a) => tiep-tuyen-tu-diem(ctx, ..a), + tiep-tuyen-tai-diem: (..a) => tiep-tuyen-tai-diem(ctx, ..a), + // tron-xoay.typ + ve-khoi-xoay: (..a) => ve-khoi-xoay(ctx, ..a), + ve-mien-xoay: (..a) => ve-mien-xoay(ctx, ..a), + // hinh-khong-gian.typ — Oxyz + diem-oxyz: (..a) => diem-oxyz(ctx, ..a), + doan-oxyz: (..a) => doan-oxyz(ctx, ..a), + vecto-oxyz: (..a) => vecto-oxyz(ctx, ..a), + giong-oxyz: (..a) => giong-oxyz(ctx, ..a), + // da-dien.typ — engine đa diện tổng quát, mặt phẳng, thiết diện + // (da-dien / da-dien-thiet-dien TỰ tạo khung nên không kê ở đây) + ve-da-dien: (..a) => ve-da-dien(ctx, ..a), + mat-phang: (..a) => mat-phang(ctx, ..a), + mat-phang-oxyz: (..a) => mat-phang-oxyz(ctx, ..a), + mat-phang-bh: (..a) => mat-phang-bh(ctx, ..a), + ve-thiet-dien: (..a) => ve-thiet-dien(ctx, ..a), + // mat-cong.typ — nón/trụ có nét khuất tự động (mat-cong tự tạo khung, không kê) + ve-mat-cong: (..a) => ve-mat-cong(ctx, ..a), + ve-truc-3d: (..a) => ve-truc-3d(ctx, ..a), +) + + +// ===================================================================== +// ctx NGẦM — gọi hàm vẽ KHÔNG cần truyền ctx (07/2026) +// Trong thân #hinh(...) hoặc trong `them: ctx => ...` của mọi hình/đồ thị +// dựng sẵn, cứ gọi thẳng: +// +// them: ctx => { +// doan(M, N, mau: blue, dut: true) +// ve-ham(g, mau: red) +// diem(A, mau: red, ten: $A$) +// } +// +// Cách CŨ `doan(ctx, M, N)` vẫn chạy y nguyên (đối số đầu là ctx thì gọi +// thẳng) — bài soạn cũ không phải sửa gì. +// NGOẠI LỆ vẫn phải truyền ctx: các hàm TRẢ VỀ GIÁ TRỊ, không vẽ — +// toa, toa-pt, toa-nguoc, goc-truc, ctx-quay, ctx-tinh-tien. +// (Bên trong lib các hàm vẫn gọi nhau theo lối cũ nên không ảnh hưởng.) +// ===================================================================== +// ve.typ — nguyên thuỷ vẽ +/// `doan(A, B, mau: black, day: 1pt, dut: false, ten: none, tai: 0.5, huong: auto, cach: 6pt, ten-quay: false, mau-ten: auto)` +/// +/// Đoạn thẳng AB, kèm NHÃN CHÚ THÍCH đặt trên đoạn (tuỳ chọn): +/// ten : nội dung nhãn (vd $a$, [3 cm]) — none = không ghi +/// tai : vị trí nhãn theo TỈ LỆ từ A đến B (0 = tại A, 1 = tại B, .5 = giữa) +/// huong : auto = tự đặt VUÔNG GÓC phía trên đoạn (đoạn dọc thì ra trái); +/// hoặc tên hướng ("above", "below", "left", ...), hoặc vectơ (dx, dy) +/// cach : khoảng cách từ đoạn tới nhãn +#let doan = _voi-ctx(doan) +/// `cac-doan(..noi-dung, mau: black, day: 1pt, dut: false, dong: false, to: none, mui-ten: false, ten: none, tai: 0.5, huong: auto, cach: 6pt, ten-quay: false, mau-ten: auto)` +/// +/// Vẽ NHIỀU nét trong một lệnh: điểm liền nhau thành một đường gấp khúc, +/// mỗi MẢNG điểm là một nét riêng — `cac-doan((A, B), (C, D), (M, N))`. +/// Nét cần kiểu riêng thì bọc `duong(...)`. +#let cac-doan = _voi-ctx(cac-doan) +/// `nhan(P, noi-dung, huong: "above", cach: 6pt, mau: black, quay: 0deg)` +/// +/// Nhãn văn bản đặt cạnh điểm P. +/// huong: "above", "below", "left", "right", "above-left", "above-right", +/// "below-left", "below-right", "center" +/// (tên tiếng Việt cũ "tren", "duoi", "trai", "phai", ... vẫn dùng được) +/// quay: góc xoay chữ (vd 90deg, -45deg, hoặc goc-truc(...)). Mặc định 0deg. +/// Đặt chữ/công thức tại điểm P. `huong` là phía đặt chữ so với P +#let nhan = _voi-ctx(nhan) +/// `nhan-goc(..muc, mau: black, ban-kinh: 6pt)` +/// +/// Đặt nhãn cho nhiều điểm, VỊ TRÍ nhãn xác định bằng GÓC LƯỢNG GIÁC (thay cho +/// tên hướng "above"/"below"…). Mỗi mục là một tuple: +/// (P, nội-dung, goc) — bán kính & màu lấy mặc định +/// (P, nội-dung, goc, ban-kinh) — ban-kinh là ĐỘ DÀI trang (vd 8pt) +/// (P, nội-dung, goc, ban-kinh, mau) — kèm màu riêng +/// `goc` là góc lượng giác (số trần = ĐỘ, dương = ngược kim đồng hồ; nhãn đặt +#let nhan-goc = _voi-ctx(nhan-goc) +/// `diem(P, ten: none, huong: "tren", bk: 2pt, mau: black, cach: 6pt, mau-ten: black)` +/// +/// Điểm (chấm tròn) + nhãn tuỳ chọn. +/// cach: khoảng cách nhãn tới điểm · mau-ten: màu chữ (auto = màu chấm). +/// Chấm một điểm, kèm tên: `diem(A, ten: $A$, huong: "below-left")`. +/// Nhiều điểm một lệnh thì dùng `cac-diem`. +#let diem = _voi-ctx(diem) +/// `cac-diem(..noi-dung, mau: black, bk: 2pt, huong: "above", cach: 6pt, mau-ten: black)` +/// +/// Mỗi đối số vị trí là một điểm, viết theo 1 trong 4 dạng: +/// A -> chỉ chấm, không nhãn +/// (A, $A$) -> chấm + nhãn, hướng lấy theo `huong` chung +/// (A, $A$, "left") -> + hướng riêng +/// (A, $A$, "left", red) -> + màu riêng (cả chấm lẫn chữ nếu mau-ten: auto) +/// Tuỳ chọn chung: mau · bk (bán kính chấm) · huong · cach (nhãn cách chấm) +#let cac-diem = _voi-ctx(cac-diem) +/// `mui-ten(A, B, mau: black, day: 1pt, kich: 7pt, dut: false)` +/// +/// Mũi tên A -> B. +#let mui-ten = _voi-ctx(mui-ten) +/// `mui-ten-2-dau(A, B, mau: black, day: 1pt, kich: 7pt, dut: false, ten: none, huong: auto, cach: 5pt, ten-quay: false, mau-ten: auto, trong: auto, dem: 3pt, nen: white, vach: false, dai-vach: 9pt, le: 0pt)` +/// +/// không đủ dài thì tự đặt chữ ra ngoài (phía trên đoạn) +/// huong : chỗ đặt chữ khi KHÔNG nằm giữa thân +/// (auto = vuông góc phía trên đoạn, như `doan`) +/// ten-quay : true = chữ NẰM DỌC theo mũi tên (luôn đọc xuôi) +/// vach : true = kẻ vạch chặn vuông góc ở hai đầu (kiểu ghi kích thước) +/// le : lùi hai đầu mũi tên vào trong, để hở khỏi vật đang đo +#let mui-ten-2-dau = _voi-ctx(mui-ten-2-dau) +/// `vecto(A, B, ten: none, huong: "tren", mau: black, day: 1.1pt, dut: false)` +/// +/// Vectơ A -> B kèm tên đặt ở trung điểm. +/// dut: true = thân nét đứt (vectơ nằm trên cạnh khuất, kiểu hình SGK); +/// đầu mũi tên luôn nét liền. +#let vecto = _voi-ctx(vecto) +/// `duong-cong(cac-diem, mau: black, day: 1pt, dut: false, dong: false)` +/// +/// Đường gấp khúc qua dãy điểm. dut: nét đứt (vẽ cách đoạn -> đều trên đường cong). +#let duong-cong = _voi-ctx(duong-cong) +/// `duong-gap-khuc(cac-diem, mau: black, day: 1pt, dut: false, dong: false)` +/// +/// Đường gấp khúc nối lần lượt A - B - C - ... KHÁC duong-cong ở chỗ: +/// dut: true là nét đứt THẬT trên TỪNG đoạn (hợp polyline ít đoạn; +/// duong-cong đứt kiểu "bỏ đoạn xen kẽ" chỉ hợp đường nhiều mẫu). +/// dong: true nối điểm cuối về điểm đầu. +#let duong-gap-khuc = _voi-ctx(duong-gap-khuc) +/// `da-giac(cac-diem, mau: black, day: 1pt, dut: false, to: none)` +/// +/// Đa giác kín: viền + tô màu tuỳ chọn (to: màu hoặc none). +/// Đa giác khép kín. Đỉnh truyền vào dưới dạng MỘT MẢNG: +/// `da-giac((A, B, C))` — không phải `da-giac(A, B, C)`. `to:` để tô màu. +#let da-giac = _voi-ctx(da-giac) +/// `duong-tron(O, r, mau: black, day: 1pt, dut: false, to: none)` +/// +/// Đường tròn tâm O bán kính r (theo đơn vị trục x; nếu 2 trục cùng tỉ lệ +/// thì là đường tròn thật, khác tỉ lệ sẽ thành elip tương ứng). +#let duong-tron = _voi-ctx(duong-tron) +/// `elip(O, a, b, mau: black, day: 1pt, dut: false, to: none, quay: 0deg, n: 72)` +/// +/// Elip tâm O, bán trục a (ngang), b (dọc); quay: góc xoay quanh tâm +/// (dương = ngược chiều kim đồng hồ), n: số mẫu khi vẽ elip xoay. +#let elip = _voi-ctx(elip) +/// `cung(O, r, tu: 0deg, den: 180deg, mau: black, day: 1pt, dut: false, n: 48, quay: 0deg)` +/// +/// Cung tròn tâm O bán kính r từ góc `tu` đến `den` (kiểu angle, vd 30deg). +#let cung = _voi-ctx(cung) +/// `cung-elip(O, a, b, tu: 0deg, den: 180deg, mau: black, day: 1pt, dut: false, n: 48, quay: 0deg)` +/// +/// Cung elip (dùng cho hình không gian: đáy nón, trụ...). +#let cung-elip = _voi-ctx(cung-elip) +/// `xoan-oc(O, tu: 0deg, den: 360deg, r: 0.12, r-cuoi: auto, buoc: 0.16, mau: black, day: 1pt, dut: false, mui-ten: true, kich: 7pt, n: auto, ten: none, huong: "above", cach: 6pt, mau-ten: auto)` +/// +/// Xoắn ốc Archimedes tâm O: bán kính TĂNG ĐỀU theo góc, quét từ `tu` đến `den`. +/// Góc quét |den - tu| ĐƯỢC PHÉP lớn hơn 360° (nhiều vòng); +/// den > tu = ngược chiều kim đồng hồ (chiều dương), den < tu = cùng chiều kim đồng hồ. +/// r : bán kính tại góc `tu` +/// r-cuoi : bán kính tại góc `den` (auto = r + buoc · số vòng quét) +/// buoc : khoảng cách giữa hai vòng liên tiếp (dùng khi r-cuoi: auto) +#let xoan-oc = _voi-ctx(xoan-oc) +/// `goc-luong-giac(O, A, M, chieu: "duong", vong: 0, r: auto, r-cuoi: auto, buoc: auto, mau: blue, day: 0.9pt, dut: false, kich: 6pt, ten: none, so-do: false, huong: "above", cach: 6pt, mau-ten: auto, n: auto)` +/// +/// Góc lượng giác (OA, OM): xoắn ốc từ tia OA quay tới tia OM (kiểu SGK 11). +/// chieu : "duong" = ngược chiều kim đồng hồ (mặc định) · "am" = cùng chiều +/// vong : số vòng quay THÊM (0, 1, 2, ...) trước khi dừng ở tia OM +/// so-do : true = tự ghi số đo (vd -430°) cạnh mũi tên; `ten` được ưu tiên +/// Ví dụ hình "AOM = 70°, (OA, OM) = -430°": goc-luong-giac(O, A, M, chieu: "am", vong: 1) +#let goc-luong-giac = _voi-ctx(goc-luong-giac) +/// `goc(O, A, B, r: 0.45, ten: none, so-do: false, so-cung: 1, vach: 0, vach-danh-dau: auto, dai-vach: 6pt, to: none, mau: black, day: 0.8pt, cach-nhan: 1.9)` +/// +/// Góc (thường, không vuông) giữa hai tia O->A và O->B: +/// cung nhỏ + nhãn + số cung (1..3) + tô quạt + tự ghi số đo. +/// to : màu tô hình quạt (nên trong suốt, vd rgb(255, 170, 0, 70)) +/// so-do : true = tự ghi số đo góc (vd 60°) khi không đặt `ten` +/// vach : 1..3 vạch CẮT NGANG cung (ký hiệu hai góc bằng nhau, kiểu SGK). +/// Khác `so-cung` (vẽ nhiều cung đồng tâm) — `vach` chỉ vẽ MỘT cung +#let goc = _voi-ctx(goc) +/// `goc-vuong(O, A, B, r: 0.32, mau: black, day: 0.8pt)` +/// +/// Ký hiệu góc vuông tại O (giữa hai tia O->A, O->B). +/// Ký hiệu góc vuông TẠI O (giữa hai tia O→A, O→B). ĐỈNH là đối số ĐẦU. +/// Quen lối TikZ (đỉnh ở giữa) thì dùng `ve-goc-vuong(A, O, B)`. +#let goc-vuong = _voi-ctx(goc-vuong) +/// `ve-goc(A, O, B, ..tuy-chon)` +/// +/// `goc`/`goc-vuong` ở trên đặt ĐỈNH góc làm đối số ĐẦU: goc-vuong(O, A, B). +/// TikZ lại viết đỉnh ở GIỮA (pic angle = A--O--B), nên hai hàm dưới đây nhận +/// thứ tự quen thuộc đó — đỉnh là đối số THỨ HAI: +/// ve-goc(A, O, B, ...) <=> goc(O, A, B, ...) +/// ve-goc-vuong(A, O, B, ...) <=> goc-vuong(O, A, B, ...) +/// Mọi tuỳ chọn (r, ten, so-do, so-cung, vach, to, mau, day, cach-nhan…) giữ +#let ve-goc = _voi-ctx(ve-goc) +/// `ve-goc-vuong(A, O, B, ..tuy-chon)` +/// +/// Ký hiệu góc vuông TẠI O theo lối TikZ: ĐỈNH nằm GIỮA — `ve-goc-vuong(A, O, B)`. +/// Cùng bộ tuỳ chọn với `goc-vuong` (r, mau, day). +#let ve-goc-vuong = _voi-ctx(ve-goc-vuong) +/// `danh-dau(A, B, so: 1, dai: 6pt, mau: black, day: 1pt, nghieng: 0deg, cheo: false)` +/// +/// so : số vạch song song (1/2/3…) tại trung điểm đoạn. +/// nghieng: góc NGHIÊNG của vạch so với pháp tuyến đoạn (0deg = vuông góc như cũ); +/// đặt ~20–30deg cho kiểu vạch chéo "/" "//" "///". +/// cheo : true = mỗi mốc vẽ dấu CHÉO NHAU "✕" (hai vạch ±góc); khi đó `nghieng` +/// = nửa góc mở của dấu ✕ (mặc định 30deg nếu để 0deg). +#let danh-dau = _voi-ctx(danh-dau) +/// `ve-ham(f, tu: auto, den: auto, n: 150, mau: blue, day: 1.3pt, dut: false)` +/// +/// Vẽ đồ thị hàm f trên [tu, den]; tự tách nhánh khi ra ngoài cửa sổ y +/// (dùng được cho hàm có tiệm cận đứng như 1/x, tan x). +#let ve-ham = _voi-ctx(ve-ham) +/// `to-vung(f, a, b, mau: rgb(30, 100, 200, 60), n: 80)` +/// +/// Tô miền giữa đồ thị f và trục hoành trên [a, b] (mau nên có độ trong suốt). +#let to-vung = _voi-ctx(to-vung) +/// `to-vung-2-ham(f, g, a, b, mau: rgb(30, 100, 200, 60), n: 80)` +/// +/// Tô miền giữa HAI đồ thị f và g trên [a, b] (mau nên có độ trong suốt). +#let to-vung-2-ham = _voi-ctx(to-vung-2-ham) +/// `gach-mien(cac-dinh, goc: 45deg, buoc: 6.5pt, mau: red, day: 0.55pt)` +/// +/// Gạch chéo đa giác lồi theo toạ độ TOÁN. +#let gach-mien = _voi-ctx(gach-mien) +/// `gach-vung(kiem, goc: 45deg, buoc: 6.5pt, mau: red, day: 0.55pt, n: 180)` +/// +/// Gạch chéo MIỀN BẤT KÌ (biên cong tuỳ ý). +/// kiem: miền đặt tên/tổ hợp giao-hop-bu (khuyên dùng) HOẶC hàm P => true/false: +/// gach-vung(ctx, giao(A, B, bu(C))) +/// gach-vung(ctx, P => trong(P, A) and P.at(1) > 0) +/// goc: hướng vạch; buoc: khoảng cách 2 vạch; n: số mẫu mỗi vạch (biên càng mịn). +#let gach-vung = _voi-ctx(gach-vung) +/// `ve-mien(m, mau: black, day: 1pt, dut: false, to: none)` +/// +/// Vẽ biên miền (đường tròn/elip tương ứng với khai báo). +#let ve-mien = _voi-ctx(ve-mien) +/// `duong-luon(..noi-dung, mau: black, day: 1pt, dut: false, dong: false, to: none, mui-ten: false, kich: 7pt, ten: none, tai: 0.5, huong: auto, cach: 6pt, ten-quay: false, mau-ten: auto, n: 16)` +/// +/// Vẽ đường cong uốn lượn qua các điểm neo, điểm điều khiển kiểu `controls` TikZ. +/// duong-luon(A, B, C, D) // trơn tự động qua 4 neo +/// duong-luon(A, dieu-khien(c1, c2), B) // Bézier bậc ba một đoạn +/// duong-luon(A, dieu-khien(c1,c2), B, C, dong: true, to: blue.lighten(85%)) +/// Tuỳ chọn: mau · day · dut · dong (khép kín) · to (tô) · mui-ten · kich · +/// ten/tai/huong/cach/ten-quay/mau-ten (nhãn đặt theo TỈ LỆ độ dài, như cac-doan) · +#let duong-luon = _voi-ctx(duong-luon) +/// `nhan-cong(duong, chu, tu: 0, can: "trai", khoang: 0pt, co: auto, mau: black, phia: "tren", cach: 2pt, dao: false)` +/// +/// Đặt từng ký tự của `chu` DỌC THEO đường `duong` (mảng điểm toạ độ toán, vd +/// lấy từ diem-luon / diem-cung / lay-mau), tự xoay tiếp tuyến, canh theo độ dài cung. +/// duong: mảng điểm (>= 2). chu: str (khuyên dùng) hoặc content văn bản thuần. +/// tu : vị trí BẮT ĐẦU theo tỉ lệ độ dài đường (0 = đầu, 1 = cuối, .5 = giữa) +/// can : "trai" (bắt đầu tại `tu`) · "giua" (canh giữa chữ quanh `tu`) · "phai" +/// khoang: giãn cách thêm giữa các ký tự (length) +#let nhan-cong = _voi-ctx(nhan-cong) +/// `dau-mui-ten(A, B, mau: black, kich: 7pt)` +/// +/// Đầu mũi tên tại B, hướng A->B. +#let dau-mui-ten = _voi-ctx(dau-mui-ten) + +// do-thi.typ — hệ trục, lưới, vạch chia, đường gióng +/// `truc(ten-x: $x$, ten-y: $y$, ten-goc: $O$, mau: black, day: 0.9pt)` +/// +#let truc = _voi-ctx(truc) +/// `luoi(buoc: 1, mau: luma(87%), day: 0.5pt)` +/// +/// Lưới ô vuông mờ. +#let luoi = _voi-ctx(luoi) +/// `vach-chia(buoc-x: 1, buoc-y: 1, so: true, mau: black)` +/// +/// Vạch chia + số trên hai trục (bỏ qua 0). +#let vach-chia = _voi-ctx(vach-chia) +/// `he-truc(ten-x: $x$, ten-y: $y$, ten-goc: $O$, luoi-o: true, buoc-luoi: auto, vach: true, so: true, buoc-x: 1, buoc-y: 1, mau: black, day: 0.9pt, mau-luoi: luma(87%))` +/// +/// Hệ trục TRỌN GÓI: lưới + trục + vạch chia + số — một lệnh thay ba. +/// #hinh(xmin: -4, xmax: 4, ymin: -3, ymax: 3, ctx => { he-truc(ctx) ... }) +/// luoi-o: lưới ô vuông mờ · vach: vạch chia đơn vị · so: ghi số trên vạch +#let he-truc = _voi-ctx(he-truc) +/// `giong(P, ten-x: auto, ten-y: auto, huong-x: "below", huong-y: "left", mau: gray.darken(20%), mau-diem: red, diem-to: true)` +/// +/// Đường gióng từ điểm P về 2 trục (nét đứt) + nhãn toạ độ trên trục. +/// ten-x / ten-y: auto = tự ghi số; none = không ghi; hoặc nội dung tuỳ ý. +#let giong = _voi-ctx(giong) +/// `tiep-tuyen(f, x0, mau: red, day: 1.2pt, dut: false, dai: auto, cham: true, mau-cham: auto, ten-diem: none, huong-diem: "above-left", ten: none, tai: 0.9, huong-ten: auto, cach: 6pt, ten-quay: false, giong: false, h: auto)` +/// +/// TIẾP TUYẾN của đồ thị y = f(x) tại điểm có hoành độ x0 (hoặc DÃY hoành độ). +/// Hệ số góc lấy bằng đạo hàm số nên dùng được cho MỌI hàm f viết bằng closure. +/// tiep-tuyen(f, 1) // tiếp tuyến tại x = 1 +/// tiep-tuyen(f, (-1, 2), mau: green) // nhiều tiếp điểm +/// tiep-tuyen(f, 1, ten: auto) // tự ghi y = kx + m +/// tiep-tuyen(f, 1, ten: $Delta$, ten-diem: $M$) // đặt tên tuỳ ý +#let tiep-tuyen = _voi-ctx(tiep-tuyen) +/// `nhan-pi()` +/// +/// Ghi nhãn các bội của π trên trục hoành. +#let nhan-pi = _voi-ctx(nhan-pi) + +// hinh-phang.typ — tam giác, tứ giác, đường tròn đặc biệt +/// `da-giac-ten(dinh, ten: none, mau: black, day: 1.1pt, to: none, cham: true)` +/// +/// Vẽ đa giác + nhãn đỉnh tự động hướng ra ngoài. +/// ten: mảng nội dung nhãn (cùng độ dài số đỉnh) hoặc none. +#let da-giac-ten = _voi-ctx(da-giac-ten) +/// `tam-giac(A, B, C, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none, cham: true)` +/// +#let tam-giac = _voi-ctx(tam-giac) +/// `duong-cao(P, A, B, ten-chan: none, mau: red, day: 1pt, dut: false, vuong: true)` +/// +/// Đường cao hạ từ P xuống đường thẳng AB (kèm ký hiệu vuông góc). +/// ten-chan: nhãn chân đường cao (vd $H$). +#let duong-cao = _voi-ctx(duong-cao) +/// `trung-tuyen(P, A, B, ten-chan: none, mau: blue, day: 1pt, dut: false, so-vach: 1)` +/// +/// Trung tuyến từ P đến trung điểm AB (kèm 2 vạch đánh dấu bằng nhau). +#let trung-tuyen = _voi-ctx(trung-tuyen) +/// `phan-giac(O, A, B, ten-chan: none, mau: green.darken(20%), day: 1pt, dut: false, r-cung: 0.5, vach: 0, so-cung: 1, dai-vach: 6pt, lech: auto, ten-goc: none, cach-nhan: 1.9)` +/// +/// Phân giác trong góc O của tam giác OAB (kèm 2 cung đánh dấu góc bằng nhau). +/// vach : số VẠCH gạch ngang cung (0/1/2/3) — ký hiệu hai góc bằng nhau +/// so-cung : số CUNG đồng tâm vẽ ở mỗi góc (1/2/3) +/// lech : hệ số bán kính cung thứ hai so với cung thứ nhất +/// (auto = 1.18 khi KHÔNG đánh dấu, = 1 khi có vạch/nhiều cung — +/// vì hai góc bằng nhau thì phải vẽ cùng bán kính mới đúng quy ước) +#let phan-giac = _voi-ctx(phan-giac) +/// `trung-truc(A, B, dai: 1.5, mau: purple, day: 1pt, dut: true, so-vach: 1)` +/// +/// Đường trung trực của đoạn AB (đoạn vuông góc tại trung điểm, dài 2*dai). +#let trung-truc = _voi-ctx(trung-truc) +/// `tam-giac-deu(A, canh, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none, vach: 1)` +/// +/// Tam giác đều cạnh `canh`, đỉnh A ở dưới-trái, đáy nằm ngang. +/// vach: số vạch đánh dấu cạnh bằng nhau (0 = không vẽ). +#let tam-giac-deu = _voi-ctx(tam-giac-deu) +/// `tam-giac-vuong(A, a, b, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none)` +/// +/// Tam giác vuông tại A: cạnh AB ngang dài a, cạnh AC đứng dài b. +#let tam-giac-vuong = _voi-ctx(tam-giac-vuong) +/// `tam-giac-can(A, canh-day, chieu-cao, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none, vach: 1)` +/// +/// Tam giác cân tại C: đáy AB ngang dài `canh-day`, chiều cao `chieu-cao`. +#let tam-giac-can = _voi-ctx(tam-giac-can) +/// `tam-giac-vuong-can(A, canh, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none, vach: 1)` +/// +/// Tam giác vuông cân tại A, hai cạnh góc vuông dài `canh`. +#let tam-giac-vuong-can = _voi-ctx(tam-giac-vuong-can) +/// `tu-giac(A, B, C, D, ten: ($A$, $B$, $C$, $D$), mau: black, day: 1.1pt, to: none)` +/// +/// Tứ giác bất kỳ ABCD. +#let tu-giac = _voi-ctx(tu-giac) +/// `hinh-binh-hanh(A, B, C, ten: ($A$, $B$, $C$, $D$), mau: black, day: 1.1pt, to: none)` +/// +/// Hình bình hành biết 3 đỉnh liên tiếp A, B, C (D tự tính = A + C - B). +#let hinh-binh-hanh = _voi-ctx(hinh-binh-hanh) +/// `hinh-chu-nhat(A, r, c, ten: ($A$, $B$, $C$, $D$), mau: black, day: 1.1pt, to: none, goc-vg: true)` +/// +/// Hình chữ nhật: góc dưới-trái A, chiều rộng r, chiều cao c. +#let hinh-chu-nhat = _voi-ctx(hinh-chu-nhat) +/// `hinh-thang(A, a, b, c, lech: 0.6, ten: ($A$, $B$, $C$, $D$), mau: black, day: 1.1pt, to: none)` +/// +/// Hình thang: đáy lớn AB (dưới, dài a), đáy nhỏ DC (trên, dài b), +/// cao c, lech: độ lệch ngang của D so với A. +#let hinh-thang = _voi-ctx(hinh-thang) +/// `duong-tron-ngoai-tiep(..vi-tri, ten-tam: $O$, huong-tam: auto, mau: blue, day: 1pt, dut: false, to: none, ban-kinh: false, dinh-r: auto, ten-r: none, canh: false, mau-canh: black)` +/// +/// Đường tròn ngoại tiếp TAM GIÁC hoặc ĐA GIÁC. +/// duong-tron-ngoai-tiep(A, B, C) — 3 đỉnh rời (lối cũ) +/// duong-tron-ngoai-tiep((A, B, C, D, E)) — MỘT MẢNG đỉnh, đa giác tuỳ ý +/// Từ 4 đỉnh trở lên, tâm/bán kính khớp theo bình phương bé nhất (đa giác nội +/// tiếp được cho ra đúng đường tròn của nó — xem `tron-qua-diem`). +/// ten-tam : nhãn tâm (none = không ghi) +#let duong-tron-ngoai-tiep = _voi-ctx(duong-tron-ngoai-tiep) +/// `duong-tron-noi-tiep(A, B, C, ten-tam: $I$, mau: orange.darken(10%), day: 1pt, dut: false, ban-kinh: false)` +/// +/// Đường tròn nội tiếp tam giác ABC. +#let duong-tron-noi-tiep = _voi-ctx(duong-tron-noi-tiep) +/// `duong-tron-bang-tiep(A, B, C, ten-tam: $J$, ten-tiep: none, mau: green.darken(25%), day: 1pt, keo-dai: true, tiep-diem: true, ban-kinh: false)` +/// +/// Đường tròn bàng tiếp TRONG GÓC A của tam giác ABC (tiếp xúc cạnh BC và +/// phần kéo dài của AB, AC). Muốn góc B thì gọi duong-tron-bang-tiep(ctx, B, C, A). +/// ten-tam : nhãn tâm (mặc định $J$) +/// keo-dai : vẽ nét đứt kéo dài hai cạnh AB, AC tới tiếp điểm +/// tiep-diem : chấm 3 tiếp điểm +/// ban-kinh : vẽ bán kính tới cạnh BC (kèm ký hiệu vuông góc) +#let duong-tron-bang-tiep = _voi-ctx(duong-tron-bang-tiep) +/// `ve-truc-tam(A, B, C, ten: $H$, ten-chan: ($H_A$, $H_B$, $H_C$), ten-dinh: ($A$, $B$, $C$), canh: true, mau: black, mau-cao: red, day: 1.1pt, dut: false, vuong: true)` +/// +/// Ba đường cao của tam giác ABC + trực tâm H. +/// ten : nhãn trực tâm (none = không ghi) +/// ten-chan : nhãn 3 chân đường cao theo thứ tự hạ từ A, B, C +/// (none = không ghi; mặc định $H_A$, $H_B$, $H_C$) +/// vuong : vẽ ký hiệu góc vuông tại chân đường cao +/// canh : true = vẽ luôn tam giác ABC (kèm nhãn đỉnh) +#let ve-truc-tam = _voi-ctx(ve-truc-tam) +/// `tiep-tuyen-tu-diem(O, r, M, ten-tam: $O$, ten-diem: $M$, ten-tiep: ($T_1$, $T_2$), mau: black, mau-tt: red, day: 1pt)` +/// +/// Hai tiếp tuyến kẻ từ điểm M ngoài đường tròn (O; r). +#let tiep-tuyen-tu-diem = _voi-ctx(tiep-tuyen-tu-diem) +/// `tiep-tuyen-tai-diem(O, r, M, dai: auto, mau: black, day: 1pt, dut: false)` +/// +/// Dựng tiếp tuyến của đường tròn (O; r) TẠI điểm M (M nằm trên đường tròn): +/// đoạn thẳng qua M, VUÔNG GÓC với bán kính OM. +/// dai : nửa độ dài đoạn tiếp tuyến (auto = 1.4·r); đơn vị toạ độ toán. +/// mau / day / dut : kiểu nét của đoạn tiếp tuyến. +/// M không cần chính xác trên đường tròn — hướng tiếp tuyến lấy theo OM thực tế. +/// Tiếp tuyến của (O; r) TẠI điểm M: đoạn qua M vuông góc OM. +#let tiep-tuyen-tai-diem = _voi-ctx(tiep-tuyen-tai-diem) + +// tron-xoay.typ — khối tròn xoay (khoi-tron-xoay tự tạo khung, không kê ở đây) +/// `ve-khoi-xoay(f, a, b, g: none, ngang: true, k: 0.26, mau: blue, day: 1.2pt, mau-to: rgb(70, 130, 200, 60), duong-sinh: true, truc-mo: true, mat-cat: none, mau-mat-cat: rgb(235, 130, 40, 120), ten-ban-kinh: auto, ten-mat-cat: auto, n: 120)` +/// +#let ve-khoi-xoay = _voi-ctx(ve-khoi-xoay) +/// `ve-mien-xoay(f, a, b, g: none, ngang: true, mau: blue, day: 1.3pt, mau-to: rgb(70, 130, 200, 90), ten-ham: auto, ten-ham-trong: none, ten-a: auto, ten-b: auto, giong-ab: true, n: 120)` +/// +#let ve-mien-xoay = _voi-ctx(ve-mien-xoay) + +// hinh-khong-gian.typ — nhóm Oxyz +/// `diem-oxyz(t3, P, ten: none, huong: "above", bk: 2pt, mau: black)` +/// +/// Điểm 3D: chấm + nhãn. +#let diem-oxyz = _voi-ctx(diem-oxyz) +/// `doan-oxyz(t3, A, B, mau: black, day: 1pt, dut: false)` +/// +/// Đoạn / vectơ nối hai điểm 3D. +#let doan-oxyz = _voi-ctx(doan-oxyz) +/// `vecto-oxyz(t3, A, B, ten: none, huong: "tren", mau: black, day: 1.1pt, dut: false)` +/// +#let vecto-oxyz = _voi-ctx(vecto-oxyz) +/// `giong-oxyz(t3, P, mau: blue, day: 0.8pt)` +/// +/// Hộp gióng nét đứt từ gốc O đến điểm P = (a, b, c): vẽ các cạnh của +/// hình hộp chữ nhật [0,a]×[0,b]×[0,c] (trừ 3 cạnh nằm trên trục). +#let giong-oxyz = _voi-ctx(giong-oxyz) + +// da-dien.typ — khối đa diện tổng quát, mặt phẳng, thiết diện +// (da-dien, da-dien-thiet-dien tự tạo khung; các hàm TRẢ GIÁ TRỊ như +// chieu-*, khoi-*, mp-*, thiet-dien, v3-*, phan-tich-khoi KHÔNG bọc) +/// `ve-da-dien(dinh: (), mat: (), cam: auto, ten: none, huong: auto, cach: 6pt, mau-ten: black, hien-dinh: true, bk: 1.6pt, mau: black, day: 1.1pt, mau-khuat: auto, day-khuat: auto, hien-khuat: true, to: none, to-mat: none, diem: (), duong: (), mau-diem: auto, bk-diem: 1.8pt, them: none)` +/// +#let ve-da-dien = _voi-ctx(ve-da-dien) +/// `mat-phang(p, dinh3, to: auto, mau: blue.darken(15%), day: 1pt, dut: false, ten: none, ten-tai: 0, huong: "above-left", cach: 5pt, che: (), mau-che: black, day-che: 0.9pt, nhin: auto, them: none)` +/// +#let mat-phang = _voi-ctx(mat-phang) +/// `mat-phang-oxyz(t3, a, b, c, to: auto, mau: blue.darken(15%), day: 1pt, dut: false, ten: none, ten-tai: 0, huong: "above-left", cach: 5pt, ten-dinh: false, ten-abc: ($A$, $B$, $C$), hien-dinh: true, bk: 1.8pt, truc: auto, mau-che: black, day-che: 0.9pt, them: none)` +/// +#let mat-phang-oxyz = _voi-ctx(mat-phang-oxyz) +/// `mat-phang-bh(t3, tam, u, v, to: auto, mau: blue.darken(15%), day: 1pt, dut: false, ten: none, ten-tai: 1, huong: "above-right", cach: 5pt, truc: none, mau-che: black, day-che: 0.9pt, them: none)` +/// +#let mat-phang-bh = _voi-ctx(mat-phang-bh) +/// `ve-thiet-dien(dinh: (), mat: (), mp: none, cam: auto, to: auto, mau: red.darken(10%), day: 1.1pt, hien-khuat: true, mau-khuat: auto, day-khuat: auto, ten: none, huong: auto, cach: 6pt, hien-dinh: true, bk: 1.8pt, mau-ten: auto, them: none)` +/// +/// Vẽ thiết diện TRÊN một khối: tô miền + viền LIỀN ở phần nằm trên mặt +/// THẤY, ĐỨT ở phần nằm trên mặt KHUẤT (nhận ctx ⇒ kê vào ve-voi). +/// ve-thiet-dien(ctx, dinh: d, mat: m, mp: mp-qua-3-diem(P, Q, R)) +#let ve-thiet-dien = _voi-ctx(ve-thiet-dien) + +// --------------------------------------------------------------------- +// mat-cong.typ — nón / trụ có NÉT KHUẤT TỰ ĐỘNG (kể cả hai khối che nhau). +// `mat-non`, `mat-tru`, `dinh-non` TRẢ GIÁ TRỊ ⇒ KHÔNG kê ở đây. +// `mat-cong` tự tạo khung hình ⇒ cũng KHÔNG kê. +// --------------------------------------------------------------------- +/// `ve-mat-cong(..khoi, cam: auto, mau: black, day: 1.1pt, to: none, hien-khuat: true, mau-khuat: auto, day-khuat: auto, n: 48, duong: (), truc: none, truoc: none, them: none)` +/// +/// Vẽ một hoặc nhiều khối nón/trụ trục đứng, TỰ chia nét liền/đứt bằng cách +/// bắn tia về phía người nhìn — lo cả tự khuất lẫn hai khối che nhau. +/// ve-mat-cong(mat-non(r: 2, cao: 4), mat-tru(tam: (0, 2, 0), r: 2, cao: 4)) +#let ve-mat-cong = _voi-ctx(ve-mat-cong) +/// `ve-truc-3d(cam, x: 3, y: 4, z: 4, am: 0.4, dm: 0.55, ten: ($x$, $y$, $z$), ten-goc: $O$, huong-ten: ("below-left", "below-right", "left"), huong-goc: "below-right", mau: black, day: 0.9pt, cach: 5pt)` +/// +/// Vẽ hệ trục Oxyz bằng CHÍNH camera của khối ⇒ cả khung hình chung một góc +/// nghiêng (khác `oxyz`, vốn tự dựng phép chiếu xiên riêng). +#let ve-truc-3d = _voi-ctx(ve-truc-3d) diff --git a/packages/preview/conic-toan/0.3.4/lib/bang-thong-ke.typ b/packages/preview/conic-toan/0.3.4/lib/bang-thong-ke.typ new file mode 100644 index 0000000000..43ee9fac99 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/bang-thong-ke.typ @@ -0,0 +1,156 @@ +// ===================================================================== +// bang-thong-ke.typ — BẢNG TẦN SỐ (THỐNG KÊ) +// +// #import "bang-thong-ke.typ": * +// +// 4 loại bảng (bố cục NGANG — giá trị chạy theo cột, đúng kiểu SGK): +// KHÔNG ghép nhóm, ĐƠN -> bang-tan-so +// KHÔNG ghép nhóm, ĐÔI -> bang-tan-so-doi +// CÓ ghép nhóm, ĐƠN -> bang-ghep-nhom +// CÓ ghép nhóm, ĐÔI -> bang-ghep-nhom-doi +// +// "Biến đơn vị của giá trị dấu hiệu": tham số don-vi. Khi có, nhãn ô +// góc trên-trái hiện thành «tên (đơn vị)», vd: Số tiền (nghìn đồng). +// +// Ghi chú chung: +// gia-tri / tan-so : mảng nội dung hoặc số. +// moc : mảng mốc để TỰ tạo các khoảng ghép nhóm (n mốc -> n-1 khoảng). +// nhom : thay cho moc nếu muốn tự truyền sẵn nội dung từng khoảng. +// ===================================================================== + +// Nhãn ô góc: gắn thêm (đơn vị) nếu có. +#let _nhan(ten, don-vi) = if don-vi == none { ten } else { [#ten] + [ (] + [#don-vi] + [)] } + +// Một khoảng nửa mở/đóng, vd [35; 40). In-line, hợp gu SGK. +#let khoang(a, b, dong-trai: true, dong-phai: false) = { + let tl = if dong-trai { "[" } else { "(" } + let tr = if dong-phai { "]" } else { ")" } + [#tl] + [#a] + [; ] + [#b] + [#tr] +} + +// n mốc -> mảng n-1 khoảng [m0;m1), [m1;m2), ... +#let khoang-tu-moc(moc, dong-trai: true, dong-phai: false) = range(moc.len() - 1).map( + i => khoang(moc.at(i), moc.at(i + 1), dong-trai: dong-trai, dong-phai: dong-phai), +) + +// ------- LÕI DỰNG BẢNG ------- +// nhan-gia-tri : nội dung ô góc trên-trái (nhãn hàng giá trị). +// gia-tri : mảng nhãn các cột giá trị. +// hang : mảng ((nhãn-hàng, mảng-số), ...) cho các dòng tần số. +#let _bang( + nhan-gia-tri, + gia-tri, + hang, + co-chu: 11pt, + mau-vien: black, + day-vien: 0.7pt, + mau-tieu-de: none, // tô nền cột nhãn (none = không tô) + rong-nhan: auto, // bề rộng cột nhãn bên trái +) = { + let n = gia-tri.len() + set text(size: co-chu) + let o(x) = align(center + horizon, [#x]) + let nhan(x) = align(left + horizon, strong([#x])) + + let cells = () + // hàng tiêu đề: nhãn giá trị + các giá trị + cells.push(if mau-tieu-de == none { nhan(nhan-gia-tri) } else { + table.cell(fill: mau-tieu-de, nhan(nhan-gia-tri)) + }) + for v in gia-tri { cells.push(o(v)) } + // các hàng tần số + for hg in hang { + let ten = hg.at(0) + let mang = hg.at(1) + cells.push(if mau-tieu-de == none { nhan(ten) } else { + table.cell(fill: mau-tieu-de, nhan(ten)) + }) + for t in mang { cells.push(o(t)) } + } + + table( + columns: (rong-nhan,) + (auto,) * n, + inset: (x: 8pt, y: 6pt), + align: center + horizon, + stroke: day-vien + mau-vien, + ..cells, + ) +} + +// ===================== KHÔNG GHÉP NHÓM ===================== + +// ĐƠN — một dòng tần số. +#let bang-tan-so( + gia-tri: (), + tan-so: (), + ten-gia-tri: [Giá trị], + ten-tan-so: [Tần số], + don-vi: none, + ..tuy-chon, +) = _bang( + _nhan(ten-gia-tri, don-vi), + gia-tri, + ((ten-tan-so, tan-so),), + ..tuy-chon, +) + +// ĐÔI — hai dòng tần số (vd Nam / Nữ). +#let bang-tan-so-doi( + gia-tri: (), + tan-so-1: (), + tan-so-2: (), + ten-gia-tri: [Giá trị], + ten-1: [Nhóm 1], + ten-2: [Nhóm 2], + don-vi: none, + ..tuy-chon, +) = _bang( + _nhan(ten-gia-tri, don-vi), + gia-tri, + ((ten-1, tan-so-1), (ten-2, tan-so-2)), + ..tuy-chon, +) + +// ======================= CÓ GHÉP NHÓM ======================= + +// ĐƠN — một dòng tần số. Truyền moc (mốc) hoặc nhom (khoảng sẵn). +#let bang-ghep-nhom( + moc: none, + nhom: none, + tan-so: (), + ten-nhom: [Nhóm], + ten-tan-so: [Tần số], + don-vi: none, + dong-phai: false, // khoảng cuối đóng phải? + ..tuy-chon, +) = { + let cac-khoang = if nhom != none { nhom } else { khoang-tu-moc(moc, dong-phai: dong-phai) } + _bang( + _nhan(ten-nhom, don-vi), + cac-khoang, + ((ten-tan-so, tan-so),), + ..tuy-chon, + ) +} + +// ĐÔI — hai dòng tần số trên cùng bộ khoảng. +#let bang-ghep-nhom-doi( + moc: none, + nhom: none, + tan-so-1: (), + tan-so-2: (), + ten-nhom: [Nhóm], + ten-1: [Nhóm 1], + ten-2: [Nhóm 2], + don-vi: none, + dong-phai: false, + ..tuy-chon, +) = { + let cac-khoang = if nhom != none { nhom } else { khoang-tu-moc(moc, dong-phai: dong-phai) } + _bang( + _nhan(ten-nhom, don-vi), + cac-khoang, + ((ten-1, tan-so-1), (ten-2, tan-so-2)), + ..tuy-chon, + ) +} diff --git a/packages/preview/conic-toan/0.3.4/lib/bang.typ b/packages/preview/conic-toan/0.3.4/lib/bang.typ new file mode 100644 index 0000000000..a377a54e67 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/bang.typ @@ -0,0 +1,905 @@ +// ===================================================================== +// bang.typ — BẢNG BIẾN THIÊN & BẢNG XÉT DẤU +// +// #bbt( +// x: (n mốc, vd ($-oo$, $1$, $+oo$)), +// dau: 2n-1 phần tử xen kẽ [tại x0, khoảng, tại x1, ...] +// dùng chuỗi "+", "-", "0", "||", "" hoặc nội dung tuỳ ý +// (none => ẩn dòng f'), +// gia-tri: n giá trị của f tại các mốc; tại điểm gián đoạn dùng cặp +// ($-oo$, $+oo$) — giá trị 2 bên kẹp, +// huong: n-1 chiều mũi tên: "len" | "xuong" | "ngang"; +// NỬA Ô (07/2026, để 2 mũi tên cùng chiều nằm TRÊN 1 ĐƯỜNG qua +// giá trị đặt GIỮA ô): "len-duoi" (đáy→giữa) + "len-tren" +// (giữa→đỉnh); "xuong-tren" (đỉnh→giữa) + "xuong-duoi" (giữa→đáy). +// Giá trị tại mốc kề đầu mút "giữa" tự đặt chính giữa ô, +// kep: chỉ số các mốc có kẹp ‖ (tiệm cận đứng), vd (1,), +// gach: chỉ số các KHOẢNG hàm KHÔNG XÁC ĐỊNH -> gạch chéo, vd (1,) +// (khoảng k nằm giữa mốc k và k+1; huong tại khoảng đó ghi "ngang"; +// dau tại mốc kề bên ghi "||" -> kẹp ‖ vẽ CAO KÍN dòng dấu ngay +// giữa cột mốc, dải gạch phủ lấn tới tận kẹp), +// ) +// +// #bang-xet-dau(x: (...), dong: ((ten, mang-dau-2n-1), ...), gach: (...)) +// ===================================================================== +#import "ve.typ": doan-pt, da-giac-pt +#import "do-thi.typ": so-toan, so-can-thuc, cuc-tri-bac-ba, cuc-tri-huu-ti + +// Cặp vạch đứng ‖ cao `cao`. +#let kep-vach(cao, day: 0.7pt) = stack( + dir: ltr, spacing: 1.9pt, + line(angle: 90deg, length: cao, stroke: day), + line(angle: 90deg, length: cao, stroke: day), +) + +// Khối gạch chéo 45° (miền không xác định) — phần tử KHỐI, kích thước +// tuyệt đối nên đặt trong ô/grid là khít, không dính metric dòng chữ. +#let o-gach(rong, cao, buoc: 5.5pt, day: 0.5pt, mau: luma(45%)) = block( + width: rong, height: cao, clip: true, + { + let w = rong.pt() + let h = cao.pt() + let t = -h + while t < w { + doan-pt((t, h), (t + h, 0), mau: mau, day: day) + t = t + buoc.pt() + } + }, +) + +// Đổi ký hiệu dấu -> nội dung. +// Nhận cả SỐ (AI hay gõ `0` thay vì `"0"`) — quy về chuỗi rồi xử lý như cũ. +#let ky-hieu-dau(s) = { + if type(s) == int or type(s) == float { + if s == 0 { $0$ } else { $#s$ } + } else if type(s) == str { + if s == "+" { $+$ } else if s == "-" { $-$ } else if s == "0" { $0$ } + else if s == "||" { kep-vach(1.1em) } else if s == "" { none } else { s } + } else { s } +} + +// ---------- TỰ BÙ Ô TRỐNG HAI ĐẦU DÃY DẤU ---------- +// Dãy `dau` (bbt) / mảng dấu mỗi dòng (bang-xet-dau) phải có ĐÚNG 2n−1 phần +// tử, xen kẽ [tại mốc, trên khoảng, tại mốc, ...] nên HAI ĐẦU luôn là ô +// TRỐNG "". AI sinh bài rất hay quên đúng hai ô đó (viết ("+","0","-") thay +// vì ("","+","0","-","")) — trước đây lỗi ra "array index out of bounds", +// VS Code không báo trước, GV phải tự thêm tay. +// +// `_diem-day-dau` chấm điểm một dãy theo quy ước vị trí: "+"/"-" phải nằm ở +// chỉ số LẺ (trên khoảng), "0"/"||" ở chỉ số CHẴN (tại mốc). +#let _diem-day-dau(day) = { + let d = 0 + for (j, s) in day.enumerate() { + let so = type(s) == int or type(s) == float + let k = if so { str(s) } else if type(s) == str { s } else { "?" } + if k == "+" or k == "-" { d += (if calc.odd(j) { 1 } else { -1 }) } + if k == "0" or k == "||" { d += (if calc.even(j) { 1 } else { -1 }) } + } + d +} + +// Chuẩn hoá dãy dấu về đúng `m` = 2n−1 phần tử. CHỈ chèn "" vào ĐẦU/CUỐI, +// không bao giờ chèn vào giữa; chọn cách chèn khớp quy ước nhất, hoà thì ưu +// tiên chèn CÂN hai đầu. Thiếu quá 2 phần tử hoặc thừa -> báo lỗi rõ ràng. +#let _chuan-day-dau(day, m, ten: "dau") = { + if day == none { return none } + if type(day) != array { + panic("`" + ten + "` phải là MẢNG " + str(m) + " phần tử (= 2 × số mốc − 1).") + } + let l = day.len() + if l == m { return day } + let ns = " Dãy dấu xen kẽ [tại mốc, trên khoảng, tại mốc, ...], hai đầu là ô trống \"\"." + if l > m { + panic("`" + ten + "` có " + str(l) + " phần tử nhưng bảng cần đúng " + + str(m) + " (= 2 × " + str(int((m + 1) / 2)) + " mốc − 1): thừa " + + str(l - m) + " phần tử." + ns) + } + if m - l > 2 { + panic("`" + ten + "` chỉ có " + str(l) + " phần tử, bảng cần " + str(m) + + " (= 2 × " + str(int((m + 1) / 2)) + " mốc − 1): thiếu " + str(m - l) + + " phần tử nên không tự bù được." + ns) + } + let thieu = m - l + let tot = none + let diem-tot = -10000 + for k in range(thieu + 1) { + let thu = range(k).map(i => "") + day + range(thieu - k).map(i => "") + // ưu tiên điểm khớp quy ước; hoà thì ưu tiên chèn cân hai đầu + let uu = _diem-day-dau(thu) * 10 - calc.abs(k - (thieu - k)) + if uu > diem-tot { diem-tot = uu; tot = thu } + } + tot +} + +// Đổi độ dài -> số pt (nhận sẵn số thì giữ nguyên). +#let _pt-so(v) = if type(v) == length { v.pt() } else { v } + +// Mũi tên biến thiên trong ô (rong × cao). +// `y1`, `y2` (tuỳ chọn): TUNG ĐỘ đầu/cuối tính từ MÉP TRÊN ô, y hướng XUỐNG. +// Dùng để đặt đầu mút mũi tên đúng vào TIM chữ số ở hai mốc kề (lối tikz-tab) +// — `auto` thì rơi về mốc cũ (sát mép trên/dưới ô). +#let mui-ten-bbt(rong, cao, huong: "len", mau: black, day: 1pt, + y1: auto, y2: auto) = box( + width: rong, height: cao, + { + let w = rong.pt() + let h = cao.pt() + // (y đầu, y cuối) theo toạ độ trang (y hướng xuống); các hướng "-duoi"/ + // "-tren" là mũi tên NỬA Ô: đầu mút tại h/2 khớp giá trị đặt giữa ô, + // để 2 mũi tên cùng chiều ở 2 khoảng kề nhau nằm trên 1 đường thẳng. + let dau-cuoi = ( + "len": (h - 1.5, 1.5), + "len-duoi": (h - 1.5, h / 2), + "len-tren": (h / 2, 1.5), + "xuong": (1.5, h - 1.5), + "xuong-tren": (1.5, h / 2), + "xuong-duoi": (h / 2, h - 1.5), + "ngang": (h / 2, h / 2), + ).at(huong) + let ya = if y1 == auto { dau-cuoi.at(0) } else { _pt-so(y1) } + let yb = if y2 == auto { dau-cuoi.at(1) } else { _pt-so(y2) } + let (p, q) = ((1.5, ya), (w - 1.5, yb)) + doan-pt(p, q, mau: mau, day: day) + let dx = q.at(0) - p.at(0) + let dy = q.at(1) - p.at(1) + let l = calc.sqrt(dx * dx + dy * dy) + let ux = dx / l + let uy = dy / l + let px = -uy + let py = ux + let k = 6.5 + da-giac-pt( + ( + q, + (q.at(0) - k * ux + 0.38 * k * px, q.at(1) - k * uy + 0.38 * k * py), + (q.at(0) - 0.72 * k * ux, q.at(1) - 0.72 * k * uy), + (q.at(0) - k * ux - 0.38 * k * px, q.at(1) - k * uy - 0.38 * k * py), + ), + to: mau, + ) + }, +) + +// Ô giá trị: `vi` là bool (true = đỉnh, false = đáy — form cũ) hoặc +// alignment dọc (top/horizon/bottom). +#let o-gia-tri(v, vi, cao) = { + let doc = if type(vi) == bool { if vi { top } else { bottom } } else { vi } + box(height: cao, align(doc + center, v)) +} + +// Ô có kẹp ‖: giá trị trái + kẹp + giá trị phải. +// `cao` = chiều cao BĂNG nội dung (nơi đặt giá trị, khớp với mũi tên). +// `cao-kin` = chiều cao TOÀN Ô: vạch ‖ kẻ KÍN từ mép trên tới mép dưới ô +// (lối tikz-tab), còn giá trị vẫn nằm trong băng nội dung nhờ đệm `dem`. +// ⚠️ PHẢI dùng `box` (không phải `block`) để bọc đệm dọc: `block` là phần tử +// KHỐI, bề rộng mặc định NUỐT TRỌN chỗ trống của cột `auto` -> bảng phình ra +// hết bề ngang trang, các cột mốc bị bóp lại làm số có căn thức XÔ XUỐNG DÒNG. +#let o-kep(trai, phai, tren-trai, tren-phai, cao, cao-kin: auto, dem: 0pt) = { + let hk = if cao-kin == auto { cao } else { cao-kin } + grid( + columns: 3, + column-gutter: 2.6pt, + rows: hk, + box(height: hk, inset: (y: dem), o-gia-tri(trai, tren-trai, cao)), + kep-vach(hk), + box(height: hk, inset: (y: dem), o-gia-tri(phai, tren-phai, cao)), + ) +} + +// ---------- CHUẨN HOÁ MỐC & GIÁ TRỊ ---------- +// AI sinh bài hay gõ x: ("-oo", -3, -1, "+oo") và gia-tri: ("-oo", 5, -5/3, +// "+oo") — tức CHUỖI "-oo" (in ra chữ "-oo" chứ không ra −∞) và SỐ trần +// (Typst không nhận số ở vị trí nội dung). Hàm này quy về nội dung toán: +// chuỗi vô cực -> $-oo$/$+oo$, số nguyên -> $#v$, số thập phân -> so-toan +// (ra phân số đẹp: -5/3). Nội dung có sẵn ($...$) giữ NGUYÊN. +#let _chuan-o(v) = { + if type(v) == array { return v.map(_chuan-o) } + if type(v) == int { return $#v$ } + if type(v) == float { return so-toan(v) } + if type(v) != str { return v } + let t = v.trim() + if t in ("-oo", "−oo", "-inf", "-infty", "-∞", "−∞") { $-oo$ } + else if t in ("+oo", "oo", "+inf", "inf", "+infty", "infty", "+∞", "∞") { $+oo$ } + else { v } +} + +// ---------- BẢNG BIẾN THIÊN ---------- +#let bbt( + x: (), + dau: none, + gia-tri: (), + huong: (), + kep: (), + gach: (), // khoảng không xác định -> gạch chéo + ten-x: $x$, + ten-fp: $f'(x)$, + ten-f: $f(x)$, + rong-cot: 2.5cm, // bề rộng mỗi khoảng + rong-so-gach: 0.9cm, // bề rộng cột MỐC kề vùng gạch (nhỏ -> số sát mũi tên) + cao-bt: 2.3cm, // chiều cao dòng biến thiên + mau-mui-ten: blue.darken(20%), + co-chu: 11pt, +) = context { + let n = x.len() + let m = 2 * n - 1 + // "-oo"/số trần -> nội dung toán (xem _chuan-o) + let x = x.map(_chuan-o) + let gia-tri = gia-tri.map(_chuan-o) + // tự bù 2 ô trống hai đầu nếu dãy dấu bị thiếu (xem _chuan-day-dau) + let dau = _chuan-day-dau(dau, m) + // báo lỗi RÕ thay cho "array index out of bounds" khi mảng bị hụt + if gia-tri.len() < n { + panic("`gia-tri` có " + str(gia-tri.len()) + " phần tử nhưng `x` có " + + str(n) + " mốc — cần ĐÚNG " + str(n) + " giá trị (mỗi mốc một giá trị).") + } + if huong.len() < n - 1 { + panic("`huong` có " + str(huong.len()) + " phần tử nhưng `x` có " + str(n) + + " mốc — cần ĐÚNG " + str(n - 1) + " chiều mũi tên (\"len\"/\"xuong\"/\"ngang\").") + } + let inset = 5pt + // đệm NGANG nhỏ hơn đệm dọc: số nằm gần mũi tên hơn (lối tikz-tab, nơi + // mũi tên nối thẳng từ số này sang số kia) + let inset-x = 2.5pt + let cao-o = cao-bt - 2 * inset + let co-dau = dau != none + // ĐO chiều cao thật của từng giá trị -> đặt đầu mút mũi tên đúng vào TIM + // chữ số (mũi tên thẳng hàng với ký tự, lối tikz-tab). + let cao-cua(v) = measure(text(size: co-chu, v)).height + let h-tri = gia-tri.map(v => if type(v) == array { v.map(cao-cua) } else { cao-cua(v) }) + // dòng dấu cao CỐ ĐỊNH để kẹp ‖ và dải gạch phủ kín từ mép trên tới mép dưới + let cao-hang-dau = co-chu * 1.3 + 2 * inset + // ô gạch kín (tự đo đúng kích thước ô qua layout) + let o-gach-kin = table.cell(inset: 0pt, layout(sz => o-gach(sz.width, sz.height))) + // dải gạch phủ nửa ô mốc, cao `cao` + let nua-gach(cao) = layout(sz => o-gach(sz.width, cao)) + // các cột: nhãn + xen kẽ (mốc, khoảng) + // mốc kề vùng gạch -> bề rộng cố định nhỏ để nửa ô hẹp (số gần mũi tên) + let cols = (auto,) + range(m).map(j => { + if calc.odd(j) { rong-cot } + else { + let i = int(j / 2) + if ((i - 1) in gach) or (i in gach) { rong-so-gach } else { auto } + } + }) + + // ----- dòng x ----- + let hang-x = (align(horizon + center, ten-x),) + for j in range(m) { + hang-x.push( + if calc.even(j) { align(horizon + center, x.at(int(j / 2))) } else { none } + ) + } + + // ----- dòng dấu f' ----- + // Ô dấu tại MỐC: "||" -> cặp vạch ‖ kẻ KÍN từ mép trên tới mép dưới ô + // (bỏ đệm dọc của ô), như tikz-tab; dấu khác vẫn căn giữa như cũ. + let o-dau-moc(s, cao) = if s == "||" { + table.cell(inset: (x: inset-x, y: 0pt), kep-vach(cao)) + } else { align(horizon + center, ky-hieu-dau(s)) } + let hang-dau = () + if co-dau { + hang-dau.push(align(horizon + center, ten-fp)) + for j in range(m) { + if calc.odd(j) { + // khoảng: gạch kín ô hoặc ký hiệu dấu + hang-dau.push( + if int((j - 1) / 2) in gach { o-gach-kin } + else { align(horizon + center, ky-hieu-dau(dau.at(j))) } + ) + } else { + // mốc: nếu kề vùng gạch thì kẹp ‖ cao kín dòng đặt GIỮA cột + // (thẳng hàng với số ở dòng x), gạch phủ nửa ô phía vùng gạch + let i = int(j / 2) + let trai = (i - 1) in gach + let phai = i in gach + hang-dau.push( + if trai or phai { + let giua = if dau.at(j) == "||" { kep-vach(cao-hang-dau) } else { + block(height: 100%, inset: (x: 2.5pt), align(horizon + center, ky-hieu-dau(dau.at(j)))) + } + table.cell(inset: 0pt, grid( + columns: (1fr, auto, 1fr), + rows: cao-hang-dau, + if trai { nua-gach(cao-hang-dau) } else { none }, + giua, + if phai { nua-gach(cao-hang-dau) } else { none }, + )) + } else { o-dau-moc(dau.at(j), cao-hang-dau) } + ) + } + } + } + + // ----- dòng biến thiên ----- + // vị trí dọc của giá trị tại mốc i: theo ĐẦU MÚT mũi tên chạm vào mốc + // (mũi tên kết thúc/bắt đầu ở giữa ô -> giá trị đặt chính giữa "horizon"). + // Đầu mút mũi tên bên PHẢI mốc (nơi mũi tên huong.at(i) XUẤT PHÁT): + let mut-phai = ( + "len": bottom, "len-duoi": bottom, "len-tren": horizon, + "xuong": top, "xuong-tren": top, "xuong-duoi": horizon, + ) + // Đầu mút mũi tên bên TRÁI mốc (nơi mũi tên huong.at(i-1) KẾT THÚC): + let mut-trai = ( + "len": top, "len-tren": top, "len-duoi": horizon, + "xuong": bottom, "xuong-duoi": bottom, "xuong-tren": horizon, + ) + // Số bám theo mũi tên KHÔNG "ngang" (nét bằng/vùng gạch không định chiều): + // ưu tiên mũi tên trái, nếu trái là "ngang" thì theo mũi tên phải (và ngược + // lại) — nhờ vậy mốc kề vùng gạch, số đặt đúng đầu mút mũi tên thật. + let vi-tri-tai(i) = { + let ben-trai = if i > 0 { huong.at(i - 1) } else { none } + let ben-phai = if i < n - 1 { huong.at(i) } else { none } + if ben-trai != none and ben-trai != "ngang" { + mut-trai.at(ben-trai, default: bottom) + } else if ben-phai != none and ben-phai != "ngang" { + mut-phai.at(ben-phai, default: bottom) + } else { horizon } + } + // Chiều cao ĐO ĐƯỢC của giá trị tại mốc i, nhìn từ phía `ben` (mốc có kẹp ‖ + // mang HAI giá trị: trái = giá trị bên trái vạch, phải = bên phải). + let cao-tai(i, ben) = { + let h = h-tri.at(i) + if type(h) == array { if ben == "trai" { h.at(0) } else { h.at(1) } } else { h } + } + // TUNG ĐỘ (tính từ mép trên BĂNG nội dung cao `cao-o`, y hướng XUỐNG) của + // đầu mút mũi tên tại mốc i, nhìn từ phía `ben`. + // * Nếu giá trị THẬT SỰ nằm ở đầu mút đó -> ngắm đúng TIM chữ số (đo bằng + // `measure`) => mũi tên thẳng hàng với ký tự, lối tikz-tab. + // * Nếu không (vd hai mũi tên cùng "len" liên tiếp: số nằm ở ĐỈNH ô bên + // trái, mũi tên sau phải xuất phát từ ĐÁY ô) -> giữ sát mép như cũ. + let y-tai(i, ben) = { + let d = if ben == "trai" { + mut-trai.at(huong.at(i - 1), default: bottom) + } else { + mut-phai.at(huong.at(i), default: bottom) + } + let co-so = (i in kep) or (d == vi-tri-tai(i)) + let h = cao-tai(i, ben) + if d == top { + if co-so { h / 2 } else { 1.5pt } + } else if d == bottom { + if co-so { cao-o - h / 2 } else { cao-o - 1.5pt } + } else { cao-o / 2 } + } + let hang-f = (align(horizon + center, ten-f),) + for j in range(m) { + if calc.even(j) { + let i = int(j / 2) + let v = gia-tri.at(i) + let trai = (i - 1) in gach + let phai = i in gach + if i in kep { + let (tr, ph) = if type(v) == array { (v.at(0), v.at(1)) } else { (v, v) } + // ‖ kẻ KÍN toàn ô (cao-bt), giá trị vẫn nằm trong băng nội dung + hang-f.push(table.cell(inset: (x: inset-x, y: 0pt), o-kep( + tr, ph, + if i > 0 { mut-trai.at(huong.at(i - 1), default: bottom) } else { bottom }, + if i < n - 1 { mut-phai.at(huong.at(i), default: bottom) } else { bottom }, + cao-o, cao-kin: cao-bt, dem: inset, + ))) + } else if trai or phai { + // mốc kề vùng gạch: gạch phủ nửa ô tới đúng tim cột (thẳng với ‖ ở trên), + // giá trị dạt SÁT về phía mép gạch (trai -> căn trái, phai -> căn phải) + let doc = vi-tri-tai(i) + let ngang = if trai and phai { center } else if trai { left } else { right } + // inset nhỏ ở phía mép gạch, nhỏ luôn phía mũi tên để số gần cả hai + // (tên KHÁC `inset-x` ở đầu hàm để khỏi che nhầm) + let dem-x = if trai and phai { (x: 1pt) } + else if trai { (left: 2pt, right: 1pt) } + else { (left: 1pt, right: 2pt) } + let o-gia = block(width: 100%, height: 100%, inset: (..dem-x, y: inset), align(doc + ngang, v)) + hang-f.push(table.cell(inset: 0pt, + if trai and phai { + grid(columns: (1fr, auto, 1fr), rows: cao-bt, + nua-gach(cao-bt), o-gia, nua-gach(cao-bt)) + } else if trai { + grid(columns: (1fr, 1fr), rows: cao-bt, nua-gach(cao-bt), o-gia) + } else { + grid(columns: (1fr, 1fr), rows: cao-bt, o-gia, nua-gach(cao-bt)) + } + )) + } else { + hang-f.push(o-gia-tri(v, vi-tri-tai(i), cao-o)) + } + } else { + let k = int((j - 1) / 2) + hang-f.push( + if k in gach { o-gach-kin } + else { + // đầu mút ngắm đúng TIM giá trị hai mốc kề; "ngang" giữ nằm ngang. + // Mũi tên CHIẾM TRỌN bề ngang cột khoảng (ô inset ngang = 0) để nối + // liền hai số hai bên như tikz-tab — trước đây hụt 2·(5+3)pt mỗi bên + // nên nhìn như mũi tên "rời" khỏi số. + let ngang-hoa = huong.at(k) == "ngang" + table.cell(inset: (x: 0pt, y: inset), mui-ten-bbt( + rong-cot, cao-o, + huong: huong.at(k), mau: mau-mui-ten, + y1: if ngang-hoa { auto } else { y-tai(k, "phai") }, + y2: if ngang-hoa { auto } else { y-tai(k + 1, "trai") }, + )) + } + ) + } + } + + let so-hang = if co-dau { 3 } else { 2 } + set text(size: co-chu) + table( + columns: cols, + rows: if co-dau { (auto, cao-hang-dau, cao-bt) } else { (auto, cao-bt) }, + inset: (x: inset-x, y: inset), + align: center + horizon, + stroke: (x, y) => ( + left: if x <= 1 { 0.7pt } else { none }, + right: if x == m { 0.7pt } else { none }, + top: 0.7pt, + bottom: if y == so-hang - 1 { 0.7pt } else { none }, + ), + ..hang-x, + ..hang-dau, + ..hang-f, + ) +} + +// ---------- BẢNG XÉT DẤU ---------- +// dong: mảng các cặp (ten, mang-dau) — mỗi mảng dấu dài 2n-1. +#let bang-xet-dau( + x: (), + dong: (), + gach: (), // khoảng không xác định -> gạch chéo + ten-x: $x$, + rong-cot: 2.2cm, + co-chu: 11pt, +) = { + let n = x.len() + let m = 2 * n - 1 + let x = x.map(_chuan-o) + let cols = (auto,) + range(m).map(j => if calc.odd(j) { rong-cot } else { auto }) + // hàng cao cố định để kẹp ‖ và dải gạch phủ kín + let cao-hang = co-chu * 1.3 + 14pt + let o-gach-kin = table.cell(inset: 0pt, layout(sz => o-gach(sz.width, sz.height))) + let nua-gach = layout(sz => o-gach(sz.width, cao-hang)) + + let cells = (align(horizon + center, ten-x),) + for j in range(m) { + cells.push(if calc.even(j) { align(horizon + center, x.at(int(j / 2))) } else { none }) + } + for (ten, mang) in dong { + // tự bù 2 ô trống hai đầu cho từng dòng dấu + let mang = _chuan-day-dau(mang, m, ten: "mảng dấu của dòng") + cells.push(align(horizon + center, ten)) + for j in range(m) { + if calc.odd(j) { + cells.push( + if int((j - 1) / 2) in gach { o-gach-kin } + else { align(horizon + center, ky-hieu-dau(mang.at(j))) } + ) + } else { + let i = int(j / 2) + let trai = (i - 1) in gach + let phai = i in gach + cells.push( + if trai or phai { + let giua = if mang.at(j) == "||" { kep-vach(cao-hang) } else { + block(height: 100%, inset: (x: 2.5pt), align(horizon + center, ky-hieu-dau(mang.at(j)))) + } + table.cell(inset: 0pt, grid( + columns: (1fr, auto, 1fr), + rows: cao-hang, + if trai { nua-gach } else { none }, + giua, + if phai { nua-gach } else { none }, + )) + } else if mang.at(j) == "||" { + // ‖ kẻ KÍN từ mép trên tới mép dưới ô (bỏ đệm dọc) + table.cell(inset: (x: 5pt, y: 0pt), kep-vach(cao-hang)) + } else { align(horizon + center, ky-hieu-dau(mang.at(j))) } + ) + } + } + } + + set text(size: co-chu) + table( + columns: cols, + rows: cao-hang, + inset: (x: 5pt, y: 7pt), + align: center + horizon, + stroke: (x, y) => ( + left: if x <= 1 { 0.7pt } else { none }, + right: if x == m { 0.7pt } else { none }, + top: 0.7pt, + bottom: if y == dong.len() { 0.7pt } else { none }, + ), + ..cells, + ) +} + +// ---------- CÁC BẢNG DỰNG SẴN THƯỜNG GẶP ---------- +// +// FORM MỚI (07/2026) — nhập HỆ SỐ (positional), thuật toán tự tìm cực trị/ +// tiệm cận/chiều biến thiên, phủ MỌI trường hợp, nhãn đẹp qua `so-toan`: +// #bbt-bac-hai(1, -2, 3) y = x² − 2x + 3 +// #bbt-bac-ba(1, 0, -3, 1) y = x³ − 3x + 1 (2 cực trị/y'=0 kép/đơn điệu) +// #bbt-trung-phuong(1, -2, 0) y = x⁴ − 2x² (3 hoặc 1 cực trị) +// #bbt-phan-thuc(2, -1, 1, -1) y = (2x−1)/(x−1) (tự xét dấu ad−bc) +// #bbt-huu-ti(1, -1, 1, 1, -1) y = (x²−x+1)/(x−1) (2 cực trị/đơn điệu) +// FORM CŨ (named: a:, x1:, y1:, x0:, yc:, dong-bien:, ...) vẫn chạy song song. + +// eps so sánh 0 (hệ số thường là số "đẹp") +#let _eps-bbt = 0.000001 + +// BBT hàm bậc hai y = ax² + bx + c. +// Form mới: bbt-bac-hai(a, b, c). Form cũ: bbt-bac-hai(a: 1, xd: .., yd: ..). +#let bbt-bac-hai(..he-so, a: 1, xd: $(-b)/(2a)$, yd: $(-Delta)/(4a)$, ten-f: $y$, ten-fp: $y'$) = { + let hs = he-so.pos() + let (a, xd, yd) = if hs.len() == 3 { + let (a, b, c) = hs + assert(calc.abs(a) > _eps-bbt, message: "bbt-bac-hai: cần a ≠ 0") + (a, so-toan(-b / (2 * a)), so-toan(c - b * b / (4 * a))) + } else { (a, xd, yd) } + if a > 0 { + bbt( + x: ($-oo$, xd, $+oo$), + dau: ("", "-", "0", "+", ""), + gia-tri: ($+oo$, yd, $+oo$), + huong: ("xuong", "len"), + ten-f: ten-f, ten-fp: ten-fp, + ) + } else { + bbt( + x: ($-oo$, xd, $+oo$), + dau: ("", "+", "0", "-", ""), + gia-tri: ($-oo$, yd, $-oo$), + huong: ("len", "xuong"), + ten-f: ten-f, ten-fp: ten-fp, + ) + } +} + +// BBT hàm y = √(ax² + bx + c) — CHỈ CÓ form hệ số. Đặt g(x) = ax² + bx + c, +// Δ = b² − 4ac, đỉnh x₀ = −b/(2a), giá trị đỉnh y₀ = √((4ac − b²)/(4a)). +// Tự phân 3 trường hợp: +// a > 0, Δ > 0 (g có 2 nghiệm): TXĐ = (−∞, x₁] ∪ [x₂, +∞); nghịch biến rồi +// (khoảng giữa gạch chéo — ngoài TXĐ) đồng biến; giá trị 2 nghiệm = 0. +// a > 0, Δ ≤ 0 (nghiệm kép / vô nghiệm): TXĐ = ℝ; giảm rồi tăng, cực tiểu tại +// đỉnh. Δ < 0 → cực tiểu y₀ > 0, y' = 0 tại đỉnh; Δ = 0 → y = √a·|x − x₀|, +// cực tiểu 0, y' KHÔNG xác định tại đỉnh (ghi ‖). +// a < 0, Δ > 0 (g có 2 nghiệm): TXĐ = [x₁, x₂]; đồng biến rồi nghịch biến, +// cực đại y₀ tại đỉnh, giá trị 2 nghiệm = 0. (a < 0 mà Δ ≤ 0 → TXĐ rỗng.) +#let bbt-can-bac-hai-ham-bac-hai(a, b, c, ten-f: $y$, ten-fp: $y'$) = { + assert(calc.abs(a) > _eps-bbt, message: "bbt-can-bac-hai-ham-bac-hai: cần a ≠ 0") + let delta = b * b - 4 * a * c + let x-dinh = so-toan(-b / (2 * a)) + // giá trị tại đỉnh: y₀ = √((4ac − b²)/(4a)) (đúng cho cả a≷0 khi TXĐ ≠ ∅) + let y-dinh = so-can-thuc(0, 1, (4 * a * c - b * b) / (4 * a), 1) + if a > 0 { + if delta > _eps-bbt { + // 2 nghiệm: gạch khoảng giữa (ngoài TXĐ), x₁ < x₂ + let x1 = so-can-thuc(-b, -1, delta, 2 * a) + let x2 = so-can-thuc(-b, 1, delta, 2 * a) + bbt( + x: ($-oo$, x1, x2, $+oo$), + dau: ("", "-", "||", "", "||", "+", ""), + gia-tri: ($+oo$, $0$, $0$, $+oo$), + huong: ("xuong", "ngang", "len"), + gach: (1,), + ten-f: ten-f, ten-fp: ten-fp, + ) + } else if delta < -_eps-bbt { + // vô nghiệm: TXĐ = ℝ, cực tiểu y₀ tại đỉnh (y' = 0) + bbt( + x: ($-oo$, x-dinh, $+oo$), + dau: ("", "-", "0", "+", ""), + gia-tri: ($+oo$, y-dinh, $+oo$), + huong: ("xuong", "len"), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 3.2cm, + ) + } else { + // nghiệm kép: y = √a·|x − x₀|, cực tiểu 0, y' không xác định tại đỉnh (‖) + bbt( + x: ($-oo$, x-dinh, $+oo$), + dau: ("", "-", "||", "+", ""), + gia-tri: ($+oo$, $0$, $+oo$), + huong: ("xuong", "len"), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 3.2cm, + ) + } + } else { + // a < 0: TXĐ = [x₁, x₂] chỉ khi Δ > 0 + assert(delta > _eps-bbt, message: "bbt-can-bac-hai-ham-bac-hai: a < 0 thì cần Δ > 0 (nếu không TXĐ rỗng)") + let x1 = so-can-thuc(-b, 1, delta, 2 * a) + let x2 = so-can-thuc(-b, -1, delta, 2 * a) + bbt( + x: (x1, x-dinh, x2), + dau: ("", "+", "0", "-", ""), + gia-tri: ($0$, y-dinh, $0$), + huong: ("len", "xuong"), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 3.2cm, + ) + } +} + +// BBT hàm bậc ba. +// Form mới: bbt-bac-ba(a, b, c, d) — tự phân 3 trường hợp theo Δ' = b² − 3ac: +// Δ' > 0: 2 cực trị | Δ' = 0: y' = 0 kép (vẫn đơn điệu, có mốc) | Δ' < 0: đơn điệu. +// Form cũ: bbt-bac-ba(a: 1, x1:, y1:, x2:, y2:) — luôn coi là có 2 cực trị. +#let bbt-bac-ba( + ..he-so, + a: 1, + x1: $x_1$, y1: $y_(c d)$, + x2: $x_2$, y2: $y_(c t)$, + ten-f: $y$, ten-fp: $y'$, +) = { + let hs = he-so.pos() + if hs.len() == 4 { + let (a, b, c, d) = hs + assert(calc.abs(a) > _eps-bbt, message: "bbt-bac-ba: cần a ≠ 0") + let f = x => ((a * x + b) * x + c) * x + d + let delta = b * b - 3 * a * c + if delta > _eps-bbt { + // 2 cực trị — giá trị CHÍNH XÁC dạng căn thức (cuc-tri-bac-ba) + let ct = cuc-tri-bac-ba(a, b, c, d) + return bbt-bac-ba( + a: a, + x1: ct.x1, y1: ct.y1, + x2: ct.x2, y2: ct.y2, + ten-f: ten-f, ten-fp: ten-fp, + ) + } + if delta < -_eps-bbt { + // y' vô nghiệm: đơn điệu, không mốc + return if a > 0 { + bbt( + x: ($-oo$, $+oo$), dau: ("", "+", ""), + gia-tri: ($-oo$, $+oo$), huong: ("len",), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 5cm, + ) + } else { + bbt( + x: ($-oo$, $+oo$), dau: ("", "-", ""), + gia-tri: ($+oo$, $-oo$), huong: ("xuong",), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 5cm, + ) + } + } + // Δ' = 0: nghiệm kép x₀ = −b/(3a), vẫn đơn điệu nhưng ghi mốc y' = 0. + // Hai mũi tên NỬA Ô (đáy→giữa rồi giữa→đỉnh) nằm trên CÙNG 1 đường thẳng + // đi qua giá trị mốc đặt chính giữa ô. + let t = -b / (3 * a) + let y-moc = so-can-thuc(2 * b * b * b - 9 * a * b * c + 27 * a * a * d, 0, 0, 27 * a * a) + let (dau0, huong2, dau-vc) = if a > 0 { + ("+", ("len-duoi", "len-tren"), ($-oo$, $+oo$)) + } else { + ("-", ("xuong-tren", "xuong-duoi"), ($+oo$, $-oo$)) + } + return bbt( + x: ($-oo$, so-toan(t), $+oo$), + dau: ("", dau0, "0", dau0, ""), + gia-tri: (dau-vc.at(0), y-moc, dau-vc.at(1)), + huong: huong2, + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 3.2cm, + ) + } + // ----- form cũ: 2 cực trị nhập tay ----- + if a > 0 { + bbt( + x: ($-oo$, x1, x2, $+oo$), + dau: ("", "+", "0", "-", "0", "+", ""), + gia-tri: ($-oo$, y1, y2, $+oo$), + huong: ("len", "xuong", "len"), + ten-f: ten-f, ten-fp: ten-fp, + ) + } else { + bbt( + x: ($-oo$, x1, x2, $+oo$), + dau: ("", "-", "0", "+", "0", "-", ""), + gia-tri: ($+oo$, y1, y2, $-oo$), + huong: ("xuong", "len", "xuong"), + ten-f: ten-f, ten-fp: ten-fp, + ) + } +} + +// BBT hàm bậc ba đơn điệu (y' không đổi dấu). +#let bbt-bac-ba-don-dieu(a: 1, ten-f: $y$, ten-fp: $y'$) = { + if a > 0 { + bbt( + x: ($-oo$, $+oo$), + dau: ("", "+", ""), + gia-tri: ($-oo$, $+oo$), + huong: ("len",), + ten-f: ten-f, ten-fp: ten-fp, + rong-cot: 5cm, + ) + } else { + bbt( + x: ($-oo$, $+oo$), + dau: ("", "-", ""), + gia-tri: ($+oo$, $-oo$), + huong: ("xuong",), + ten-f: ten-f, ten-fp: ten-fp, + rong-cot: 5cm, + ) + } +} + +// BBT hàm trùng phương y = ax⁴ + bx² + c. +// Form mới: bbt-trung-phuong(a, b, c) — tự phân 2 trường hợp: +// a·b < 0: 3 cực trị tại ±√(−b/2a) và 0 | còn lại: 1 cực trị tại 0. +// Form cũ: bbt-trung-phuong(a: 1, x0:, yc:, y0:) — luôn coi là 3 cực trị. +#let bbt-trung-phuong( + ..he-so, + a: 1, + x0: $x_0$, // = √(-b/2a) + yc: $y_1$, // giá trị tại ±x0 + y0: $y_0$, // giá trị tại 0 + ten-f: $y$, ten-fp: $y'$, +) = { + let hs = he-so.pos() + if hs.len() == 3 { + let (a, b, c) = hs + assert(calc.abs(a) > _eps-bbt, message: "bbt-trung-phuong: cần a ≠ 0") + if a * b < -_eps-bbt { + // 3 cực trị — x0 và giá trị cực trị CHÍNH XÁC (kể cả căn thức/phân số) + return bbt-trung-phuong( + a: a, + x0: so-can-thuc(0, 1, -b / (2 * a), 1), + yc: so-can-thuc(4 * a * c - b * b, 0, 0, 4 * a), + y0: so-toan(c), + ten-f: ten-f, ten-fp: ten-fp, + ) + } + // 1 cực trị tại x = 0 (b = 0 hoặc a·b > 0) + return if a > 0 { + bbt( + x: ($-oo$, $0$, $+oo$), + dau: ("", "-", "0", "+", ""), + gia-tri: ($+oo$, so-toan(c), $+oo$), + huong: ("xuong", "len"), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 3.2cm, + ) + } else { + bbt( + x: ($-oo$, $0$, $+oo$), + dau: ("", "+", "0", "-", ""), + gia-tri: ($-oo$, so-toan(c), $-oo$), + huong: ("len", "xuong"), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 3.2cm, + ) + } + } + // ----- form cũ: 3 cực trị nhập tay ----- + if a > 0 { + bbt( + x: ($-oo$, $-$ + x0, $0$, x0, $+oo$), + dau: ("", "-", "0", "+", "0", "-", "0", "+", ""), + gia-tri: ($+oo$, yc, y0, yc, $+oo$), + huong: ("xuong", "len", "xuong", "len"), + ten-f: ten-f, ten-fp: ten-fp, + rong-cot: 2.1cm, + ) + } else { + bbt( + x: ($-oo$, $-$ + x0, $0$, x0, $+oo$), + dau: ("", "+", "0", "-", "0", "+", "0", "-", ""), + gia-tri: ($-oo$, yc, y0, yc, $-oo$), + huong: ("len", "xuong", "len", "xuong"), + ten-f: ten-f, ten-fp: ten-fp, + rong-cot: 2.1cm, + ) + } +} + +// BBT hàm phân thức y = (ax+b)/(cx+d). +// Form mới: bbt-phan-thuc(a, b, c, d) — tự tính x₀ = −d/c, y₀ = a/c, +// chiều biến thiên theo dấu ad − bc. +// Form cũ: bbt-phan-thuc(x0:, y0:, dong-bien:). +#let bbt-phan-thuc( + ..he-so, + x0: $x_0$, y0: $y_0$, + dong-bien: true, // y' > 0 trên từng khoảng? + ten-f: $y$, ten-fp: $y'$, +) = { + let hs = he-so.pos() + let (x0, y0, dong-bien) = if hs.len() == 4 { + let (a, b, c, d) = hs + assert(calc.abs(c) > _eps-bbt, message: "bbt-phan-thuc: cần c ≠ 0") + let D = a * d - b * c + assert(calc.abs(D) > _eps-bbt, message: "bbt-phan-thuc: ad − bc = 0 (hàm hằng, không có BBT)") + (so-toan(-d / c), so-toan(a / c), D > 0) + } else { (x0, y0, dong-bien) } + if dong-bien { + bbt( + x: ($-oo$, x0, $+oo$), + dau: ("", "+", "||", "+", ""), + gia-tri: (y0, ($+oo$, $-oo$), y0), + huong: ("len", "len"), + kep: (1,), + ten-f: ten-f, ten-fp: ten-fp, + rong-cot: 3.2cm, + ) + } else { + bbt( + x: ($-oo$, x0, $+oo$), + dau: ("", "-", "||", "-", ""), + gia-tri: (y0, ($-oo$, $+oo$), y0), + huong: ("xuong", "xuong"), + kep: (1,), + ten-f: ten-f, ten-fp: ten-fp, + rong-cot: 3.2cm, + ) + } +} + +// BBT hàm hữu tỉ y = (ax² + bx + c)/(dx + e) — CHỈ CÓ form hệ số. +// Viết y = px + q + r/(dx + e) với p = a/d, q = (bd − ae)/d², r = c − qe. +// Tự phân 3 trường hợp: p·r·d > 0: 2 cực trị hai bên tiệm cận đứng | +// p·r·d < 0: đơn điệu (2 khoảng) | r = 0: suy biến (đường thẳng khuyết điểm). +#let bbt-huu-ti(a, b, c, d, e, ten-f: $y$, ten-fp: $y'$) = { + assert(calc.abs(a) > _eps-bbt and calc.abs(d) > _eps-bbt, message: "bbt-huu-ti: cần a ≠ 0 và d ≠ 0") + let x0 = -e / d + let p = a / d + let q = (b * d - a * e) / (d * d) + let r = c - q * e + let f = x => (a * x * x + b * x + c) / (d * x + e) + if p * r * d > _eps-bbt { + // 2 cực trị — hoành độ & giá trị CHÍNH XÁC dạng căn thức (cuc-tri-huu-ti) + let ct = cuc-tri-huu-ti(a, b, c, d, e) + return if p > 0 { + bbt( + x: ($-oo$, ct.x1, so-toan(x0), ct.x2, $+oo$), + dau: ("", "+", "0", "-", "||", "-", "0", "+", ""), + gia-tri: ($-oo$, ct.y1, ($-oo$, $+oo$), ct.y2, $+oo$), + huong: ("len", "xuong", "xuong", "len"), + kep: (2,), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 2.1cm, + ) + } else { + bbt( + x: ($-oo$, ct.x1, so-toan(x0), ct.x2, $+oo$), + dau: ("", "-", "0", "+", "||", "+", "0", "-", ""), + gia-tri: ($+oo$, ct.y1, ($+oo$, $-oo$), ct.y2, $-oo$), + huong: ("xuong", "len", "len", "xuong"), + kep: (2,), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 2.1cm, + ) + } + } + // đơn điệu trên từng khoảng. Giới hạn tại x₀: trái = −sign(rd)·∞, + // phải = +sign(rd)·∞; riêng r = 0 (suy biến): 2 bên hữu hạn = px₀ + q. + let (trai, phai) = if calc.abs(r) < _eps-bbt { + let l = so-toan(p * x0 + q) + (l, l) + } else if r * d > 0 { ($-oo$, $+oo$) } else { ($+oo$, $-oo$) } + if p > 0 { + bbt( + x: ($-oo$, so-toan(x0), $+oo$), + dau: ("", "+", "||", "+", ""), + gia-tri: ($-oo$, (trai, phai), $+oo$), + huong: ("len", "len"), + kep: (1,), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 3.2cm, + ) + } else { + bbt( + x: ($-oo$, so-toan(x0), $+oo$), + dau: ("", "-", "||", "-", ""), + gia-tri: ($+oo$, (trai, phai), $-oo$), + huong: ("xuong", "xuong"), + kep: (1,), + ten-f: ten-f, ten-fp: ten-fp, rong-cot: 3.2cm, + ) + } +} + +// Bảng xét dấu tam thức bậc hai có 2 nghiệm (dấu theo a). +// (form hệ số: xem các bbt-* phía trên) +#let xet-dau-tam-thuc(a: 1, x1: $x_1$, x2: $x_2$, ten: $f(x)$) = { + let (ngoai, trong) = if a > 0 { ("+", "-") } else { ("-", "+") } + bang-xet-dau( + x: ($-oo$, x1, x2, $+oo$), + dong: ((ten, ("", ngoai, "0", trong, "0", ngoai, "")),), + ) +} +// (đồng bộ mount 07/2026) diff --git a/packages/preview/conic-toan/0.3.4/lib/bieu-do-thong-ke.typ b/packages/preview/conic-toan/0.3.4/lib/bieu-do-thong-ke.typ new file mode 100644 index 0000000000..35200c5bbb --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/bieu-do-thong-ke.typ @@ -0,0 +1,542 @@ +// ===================================================================== +// bieu-do-thong-ke.typ — BIỂU ĐỒ THỐNG KÊ (CT GDPT 2018) +// +// #import "bieu-do-thong-ke.typ": * +// +// 5 biểu đồ dựng sẵn (KHÔNG nhận ctx — tự dựng khung như truc-so; +// vẽ chồng qua `them: ctx => ...` như mọi hình khác): +// bieu-do-tan-so : HISTOGRAM tần số ghép nhóm — cột DÍNH SÁT nhau +// (kèm `gap-khuc: true` vẽ chồng đường gấp khúc tần số) +// da-giac-tan-so : đường gấp khúc tần số (ogive) đứng riêng +// bieu-do-cot : biểu đồ cột RỜI cho giá trị rời rạc (số hoặc chữ) +// bieu-do-hop : biểu đồ hộp (box plot) — tứ phân vị Toán 10 +// bieu-do-quat : biểu đồ quạt tròn (pie chart) — chia góc theo % +// +// 2 hàm TRẢ GIÁ TRỊ (không vẽ): +// tu-phan-vi(du-lieu) -> (min:, q1:, q2:, q3:, max:) [SGK 10] +// tu-phan-vi-ghep-nhom(moc, tan-so) -> (q1:, q2:, q3:) [SGK 11] +// +// Dữ liệu vào KHỚP với bang-thong-ke.typ: cùng `moc:` (n+1 mốc -> n nhóm) +// và `tan-so:` — một nguồn số liệu dùng được cả bảng lẫn biểu đồ. +// ===================================================================== +#import "ve.typ": * +#import "do-thi.typ": so-dep, so-toan + +// Bảng màu mặc định cho biểu đồ quạt/cột. +#let _mau-bd = ( + rgb("#4e79a7"), rgb("#f28e2b"), rgb("#e15759"), rgb("#76b7b4"), + rgb("#59a14f"), rgb("#edc949"), rgb("#b07aa1"), rgb("#9c755f"), +) + +// Bước chia "đẹp" 1/2/5·10^k cho trục tung (~muc vạch). +#let _buoc-dep(vmax, muc: 5) = { + let raw = calc.max(vmax, 1e-9) / muc + let k = calc.floor(calc.log(raw)) + let p = calc.pow(10.0, k) + let r = raw / p + let m = if r <= 1 { 1 } else if r <= 2 { 2 } else if r <= 5 { 5 } else { 10 } + m * p +} + +// Chỗ trống phải nới thêm bên PHẢI khung để đặt TÊN TRỤC HOÀNH cạnh mũi tên. +// Trước đây nhãn đặt "above-left" ngay tại đầu mũi tên nên nằm ĐÈ LÊN biểu đồ +// (trục vẽ xuyên qua chữ). Nay nhãn lui hẳn sang phải đầu mũi tên, và khung +// được nới đúng bằng bề rộng chữ để nhãn không tràn ra ngoài hình. +// TRẢ VỀ số ĐƠN VỊ TOẠ ĐỘ cần cộng thêm vào xmax. PHẢI gọi trong `context`. +#let _cho-ten-x(ten-x, w, base, co-chu: 10pt, cach: 5pt) = { + if ten-x == none { return 0 } + let s = measure(text(size: co-chu, ten-x)) + let k = calc.min((s.width + cach) / w, 0.55) + base * k / (1 - k) +} + +// Trục Ox + Oy (mũi tên), nhãn tên trục. +#let _truc-bd(ctx, x0, xb, ytop, ten-x, ten-y) = { + doan(ctx, (x0, 0), (xb, 0), day: 0.9pt) + dau-mui-ten(ctx, (x0, 0), (xb, 0)) + doan(ctx, (x0, 0), (x0, ytop), day: 0.9pt) + dau-mui-ten(ctx, (x0, 0), (x0, ytop)) + if ten-x != none { nhan(ctx, (xb, 0), ten-x, huong: "right", cach: 5pt) } + if ten-y != none { nhan(ctx, (x0, ytop), ten-y, huong: "right", cach: 7pt) } +} + +// Vạch chia + số trên trục tung (tại k·buoc) và các đường lưới ngang mờ. +#let _vach-tung(ctx, x0, xb, buoc, vmax, so: true, luoi: false) = { + let dx = 2.6pt / ctx.sx + let k = 1 + while k * buoc <= vmax * 1.02 + buoc * 0.0001 { + let y = k * buoc + if luoi { doan(ctx, (x0, y), (xb, y), mau: luma(86%), day: 0.5pt) } + doan(ctx, (x0 - dx, y), (x0 + dx, y), day: 0.8pt) + if so { nhan(ctx, (x0, y), [#so-dep(y)], huong: "left", cach: 5pt) } + k += 1 + } +} + +// Vạch chia + số trên trục hoành tại các hoành độ xs. +#let _vach-hoanh(ctx, xs, so: true) = { + let dy = 2.6pt / ctx.sy + for x in xs { + doan(ctx, (x, -dy), (x, dy), day: 0.8pt) + if so { nhan(ctx, (x, 0), [#so-dep(x)], huong: "below", cach: 5pt) } + } +} + +// ===================================================================== +// HISTOGRAM — biểu đồ tần số ghép nhóm, cột dính sát nhau. +// moc : n+1 mốc SỐ (như bang-ghep-nhom) -> n cột +// tan-so : n tần số (hoặc tần số tương đối — đổi ten-y cho khớp) +// gap-khuc: true -> vẽ chồng đường gấp khúc tần số (nối trung điểm đỉnh cột, +// kéo dài về 0 ở hai nhóm ảo ngoài cùng) +// ===================================================================== +#let bieu-do-tan-so( + moc: (), + tan-so: (), + w: 9cm, h: 5.5cm, + mau: rgb(78, 121, 167, 160), // màu tô cột + vien: rgb("#2f5d8a"), // màu viền cột + ten-x: none, // nhãn trục hoành (vd [Chiều cao (cm)]) + ten-y: [Tần số], + so-dinh: true, // ghi tần số trên đỉnh mỗi cột + so-truc: true, // số trên 2 trục + buoc-y: auto, // bước chia trục tung (auto = 1/2/5·10^k) + luoi-ngang: false, // lưới ngang mờ theo vạch trục tung + gap-khuc: false, + mau-gap-khuc: red, + co-chu: 10pt, + them: none, +) = { + let n = tan-so.len() + let m0 = moc.first() + let mn = moc.last() + let fmax = calc.max(..tan-so) + let by = if buoc-y == auto { _buoc-dep(fmax) } else { buoc-y } + let b1 = moc.at(1) - m0 + let bn = mn - moc.at(n - 1) + let x0 = m0 - b1 * 0.55 // trục tung đặt hụt sang trái nửa nhóm + let xb = mn + bn * 0.7 + let ytop = calc.max(fmax * 1.18, by) + let xa = x0 - (xb - x0) * 0.02 + context { + hinh( + w: w, h: h, + xmin: xa, xmax: xb + _cho-ten-x(ten-x, w, xb - xa, co-chu: co-chu), + ymin: -ytop * 0.16, ymax: ytop * 1.06, + co-chu: co-chu, + ctx => { + if luoi-ngang { _vach-tung(ctx, x0, xb, by, fmax, so: false, luoi: true) } + // các cột dính sát nhau + for i in range(n) { + let (a, b, f) = (moc.at(i), moc.at(i + 1), tan-so.at(i)) + da-giac(ctx, ((a, 0), (b, 0), (b, f), (a, f)), to: mau, mau: vien, day: 0.9pt) + if so-dinh and f > 0 { + nhan(ctx, ((a + b) / 2, f), [#so-dep(f)], huong: "above", cach: 4pt) + } + } + // đường gấp khúc tần số chồng lên (kéo về 0 ở 2 nhóm ảo ngoài cùng) + if gap-khuc { + let pts = ((m0 - b1 / 2, 0),) + for i in range(n) { + pts.push(((moc.at(i) + moc.at(i + 1)) / 2, tan-so.at(i))) + } + pts.push((mn + bn / 2, 0)) + duong-gap-khuc(ctx, pts, mau: mau-gap-khuc, day: 1.1pt) + for p in pts { diem(ctx, p, mau: mau-gap-khuc, bk: 1.8pt) } + } + _truc-bd(ctx, x0, xb, ytop, ten-x, ten-y) + _vach-tung(ctx, x0, xb, by, fmax, so: so-truc) + _vach-hoanh(ctx, moc, so: so-truc) + if them != none { them(ctx) } + }, + ) + } +} + +// ===================================================================== +// ĐƯỜNG GẤP KHÚC TẦN SỐ (ogive) đứng riêng — nối trung điểm các nhóm. +// keo-dai: true -> nối thêm về 0 tại trung điểm 2 nhóm ảo ngoài cùng (SGK) +// ===================================================================== +#let da-giac-tan-so( + moc: (), + tan-so: (), + w: 9cm, h: 5.5cm, + mau: rgb("#1d4ed8"), + ten-x: none, + ten-y: [Tần số], + so-dinh: true, + so-truc: true, + buoc-y: auto, + luoi-ngang: false, + keo-dai: true, + co-chu: 10pt, + them: none, +) = { + let n = tan-so.len() + let m0 = moc.first() + let mn = moc.last() + let fmax = calc.max(..tan-so) + let by = if buoc-y == auto { _buoc-dep(fmax) } else { buoc-y } + let b1 = moc.at(1) - m0 + let bn = mn - moc.at(n - 1) + let x0 = m0 - b1 * 0.55 + let xb = mn + bn * 0.7 + let ytop = calc.max(fmax * 1.18, by) + let xa = x0 - (xb - x0) * 0.02 + context { + hinh( + w: w, h: h, + xmin: xa, xmax: xb + _cho-ten-x(ten-x, w, xb - xa, co-chu: co-chu), + ymin: -ytop * 0.16, ymax: ytop * 1.06, + co-chu: co-chu, + ctx => { + if luoi-ngang { _vach-tung(ctx, x0, xb, by, fmax, so: false, luoi: true) } + let giua = range(n).map(i => ((moc.at(i) + moc.at(i + 1)) / 2, tan-so.at(i))) + let pts = if keo-dai { + ((m0 - b1 / 2, 0),) + giua + ((mn + bn / 2, 0),) + } else { giua } + duong-gap-khuc(ctx, pts, mau: mau, day: 1.1pt) + for p in pts { diem(ctx, p, mau: mau, bk: 1.9pt) } + if so-dinh { + for (i, p) in giua.enumerate() { + if tan-so.at(i) > 0 { nhan(ctx, p, [#so-dep(p.at(1))], huong: "above", cach: 5pt, mau: mau) } + } + } + _truc-bd(ctx, x0, xb, ytop, ten-x, ten-y) + _vach-tung(ctx, x0, xb, by, fmax, so: so-truc) + _vach-hoanh(ctx, moc, so: so-truc) + if them != none { them(ctx) } + }, + ) + } +} + +// ===================================================================== +// BIỂU ĐỒ CỘT RỜI — giá trị rời rạc (số HOẶC nhãn chữ), cột cách nhau. +// gia-tri: mảng số (đặt cột đúng hoành độ) hoặc mảng nội dung/chuỗi +// (cột đặt đều tại 1, 2, 3, ...) +// rong : bề rộng cột theo tỉ lệ khoảng cách nhỏ nhất giữa 2 cột +// ===================================================================== +#let bieu-do-cot( + gia-tri: (), + tan-so: (), + w: 9cm, h: 5.5cm, + mau: rgb(78, 121, 167, 160), + vien: rgb("#2f5d8a"), + ten-x: none, + ten-y: [Tần số], + rong: 0.5, + so-dinh: true, + so-truc: true, + buoc-y: auto, + luoi-ngang: false, + co-chu: 10pt, + them: none, +) = { + let n = tan-so.len() + let bang-so = gia-tri.all(v => type(v) == int or type(v) == float) + let xs = if bang-so { gia-tri } else { range(1, n + 1) } + // khoảng cách nhỏ nhất giữa 2 cột kề nhau + let kc = if n >= 2 { + calc.min(..range(n - 1).map(i => xs.at(i + 1) - xs.at(i))) + } else { 1 } + let nua = kc * rong / 2 + let fmax = calc.max(..tan-so) + let by = if buoc-y == auto { _buoc-dep(fmax) } else { buoc-y } + let x0 = xs.first() - kc * 0.8 + let xb = xs.last() + kc * 0.8 + let ytop = calc.max(fmax * 1.18, by) + let xa = x0 - (xb - x0) * 0.02 + context { + hinh( + w: w, h: h, + xmin: xa, xmax: xb + _cho-ten-x(ten-x, w, xb - xa, co-chu: co-chu), + ymin: -ytop * 0.16, ymax: ytop * 1.06, + co-chu: co-chu, + ctx => { + if luoi-ngang { _vach-tung(ctx, x0, xb, by, fmax, so: false, luoi: true) } + for i in range(n) { + let (x, f) = (xs.at(i), tan-so.at(i)) + da-giac(ctx, ((x - nua, 0), (x + nua, 0), (x + nua, f), (x - nua, f)), + to: mau, mau: vien, day: 0.9pt) + if so-dinh and f > 0 { nhan(ctx, (x, f), [#so-dep(f)], huong: "above", cach: 4pt) } + } + _truc-bd(ctx, x0, xb, ytop, ten-x, ten-y) + _vach-tung(ctx, x0, xb, by, fmax, so: so-truc) + // nhãn chân cột: số -> so-dep, chữ -> in nguyên + let dy = 2.6pt / ctx.sy + for (i, x) in xs.enumerate() { + doan(ctx, (x, -dy), (x, dy), day: 0.8pt) + if so-truc { + let nd = if bang-so { [#so-dep(x)] } else { [#gia-tri.at(i)] } + nhan(ctx, (x, 0), nd, huong: "below", cach: 5pt) + } + } + if them != none { them(ctx) } + }, + ) + } +} + +// ===================================================================== +// TỨ PHÂN VỊ (số liệu KHÔNG ghép nhóm — SGK Toán 10): +// sắp xếp tăng; Q2 = trung vị; Q1/Q3 = trung vị nửa dưới/nửa trên +// (n lẻ thì KHÔNG kể Q2 vào hai nửa). +// ===================================================================== +#let _trung-vi(d) = { + let n = d.len() + if calc.rem(n, 2) == 1 { d.at(int((n - 1) / 2)) } + else { (d.at(int(n / 2) - 1) + d.at(int(n / 2))) / 2 } +} + +#let tu-phan-vi(du-lieu) = { + let d = du-lieu.sorted() + let n = d.len() + let nd = int(calc.floor(n / 2)) + ( + min: d.first(), + q1: _trung-vi(d.slice(0, nd)), + q2: _trung-vi(d), + q3: _trung-vi(d.slice(n - nd)), + max: d.last(), + ) +} + +// Tứ phân vị của mẫu GHÉP NHÓM (SGK Toán 11): nội suy trong nhóm chứa Qk, +// Qk = L + (k·n/4 − cf)/f · (R − L). +#let tu-phan-vi-ghep-nhom(moc, tan-so) = { + let n = tan-so.sum() + let q(k) = { + let muc = k * n / 4 + let cf = 0 + for i in range(tan-so.len()) { + let f = tan-so.at(i) + if f > 0 and cf + f >= muc { + return moc.at(i) + (muc - cf) / f * (moc.at(i + 1) - moc.at(i)) + } + cf += f + } + moc.last() + } + (q1: q(1), q2: q(2), q3: q(3)) +} + +// ===================================================================== +// BIỂU ĐỒ HỘP (box plot) — nằm ngang trên trục số, gióng đứt xuống trục. +// du-lieu: mảng số thô (tự tính 5 số tóm tắt theo SGK 10), HOẶC +// tom-tat: (min, q1, q2, q3, max) — mảng 5 số hay dict cùng khoá. +// ===================================================================== +#let bieu-do-hop( + du-lieu: none, + tom-tat: none, + w: 10cm, h: 3.9cm, + mau: rgb(78, 121, 167, 90), + vien: rgb("#2f5d8a"), + ten: $x$, + so: true, // ghi 5 giá trị dưới trục + co-chu: 10pt, + them: none, +) = { + let t = if du-lieu != none { tu-phan-vi(du-lieu) } + else if type(tom-tat) == array { + (min: tom-tat.at(0), q1: tom-tat.at(1), q2: tom-tat.at(2), + q3: tom-tat.at(3), max: tom-tat.at(4)) + } else { tom-tat } + let pad = calc.max((t.max - t.min) * 0.14, 0.5) + let xa = t.min - pad + let xb = t.max + pad * 1.4 + // hộp ở trên (tâm yc), trục số ở dưới (y = ytr) + let yc = 0.42 + let nb = 0.4 // nửa chiều cao hộp + let nr = 0.2 // nửa chiều cao vạch râu min/max + let ytr = -0.78 // cao độ trục số + context { + hinh( + w: w, h: h, + xmin: xa, xmax: xb + _cho-ten-x(ten, w, xb - xa, co-chu: co-chu), + ymin: -1.62, ymax: 1.05, co-chu: co-chu, + ctx => { + // gióng nét đứt 5 đặc trưng xuống trục + for x in (t.min, t.q1, t.q2, t.q3, t.max) { + doan(ctx, (x, yc - nb), (x, ytr), mau: luma(55%), day: 0.6pt, dut: true) + } + // râu + 2 vạch đầu mút + doan(ctx, (t.min, yc), (t.q1, yc), day: 1pt) + doan(ctx, (t.q3, yc), (t.max, yc), day: 1pt) + doan(ctx, (t.min, yc - nr), (t.min, yc + nr), mau: vien, day: 1.2pt) + doan(ctx, (t.max, yc - nr), (t.max, yc + nr), mau: vien, day: 1.2pt) + // hộp Q1–Q3 + trung vị đậm + da-giac(ctx, ((t.q1, yc - nb), (t.q3, yc - nb), (t.q3, yc + nb), (t.q1, yc + nb)), + to: mau, mau: vien, day: 1.1pt) + doan(ctx, (t.q2, yc - nb), (t.q2, yc + nb), mau: vien, day: 2pt) + // trục số + vạch + 5 số + doan(ctx, (xa, ytr), (xb, ytr), day: 0.9pt) + dau-mui-ten(ctx, (xa, ytr), (xb, ytr)) + nhan(ctx, (xb, ytr), ten, huong: "right", cach: 5pt) + let dv = 2.6pt / ctx.sy + for x in (t.min, t.q1, t.q2, t.q3, t.max) { + doan(ctx, (x, ytr - dv), (x, ytr + dv), day: 0.9pt) + if so { nhan(ctx, (x, ytr), so-toan(x), huong: "below", cach: 6pt) } + } + if them != none { them(ctx) } + }) + } +} + +// ===================================================================== +// BIỂU ĐỒ QUẠT TRÒN (pie chart) — chia góc theo tỉ lệ %. +// Mỗi mục positional: (tên, số-liệu) hoặc (tên, số-liệu, màu). +// Bắt đầu từ 12 giờ, quay theo chiều kim đồng hồ (kiểu SGK). +// phan-tram: true -> ghi % trong quạt (quạt bé thì ghi kèm tên ngoài) +// ===================================================================== +#let bieu-do-quat( + ..muc, + w: 7.5cm, + phan-tram: true, + vien: white, // màu đường phân cách các quạt + co-chu: 10pt, + them: none, +) = { + let ms = muc.pos() + let tong = ms.map(m => m.at(1)).sum() + hinh( + w: w, xmin: -1.8, xmax: 1.8, ymin: -1.45, ymax: 1.45, co-chu: co-chu, + ctx => { + let g = 90deg // bắt đầu 12 giờ + for (i, m) in ms.enumerate() { + let phan = m.at(1) / tong + let g2 = g - phan * 360deg // chiều kim đồng hồ + let mq = if m.len() >= 3 { m.at(2) } else { _mau-bd.at(calc.rem(i, _mau-bd.len())) } + let pts = ((0, 0),) + diem-cung((0, 0), 1, 1, g2, g, n: calc.max(int(phan * 72), 2)) + da-giac(ctx, pts, to: mq, mau: vien, day: 1.2pt) + // nhãn tại tia phân giác của quạt + let gm = g - phan * 180deg + let (hx, hy) = (calc.cos(gm), calc.sin(gm)) + let pc = [#so-dep(calc.round(phan * 100, digits: 1))%] + let huong = if hx > 0.35 { "right" } else if hx < -0.35 { "left" } + else if hy > 0 { "above" } else { "below" } + if phan >= 0.08 { + // % trắng đậm trong quạt, tên ngoài + if phan-tram { + nhan(ctx, (hx * 0.62, hy * 0.62), text(fill: white, weight: "bold", pc), + huong: "center") + } + nhan(ctx, (hx * 1.06, hy * 1.06), [#m.at(0)], huong: huong, cach: 4pt) + } else { + // quạt bé: tên + % gộp chung bên ngoài + let nd = if phan-tram { [#m.at(0) (#pc)] } else { [#m.at(0)] } + nhan(ctx, (hx * 1.06, hy * 1.06), nd, huong: huong, cach: 4pt) + } + g = g2 + } + if them != none { them(ctx) } + }, + ) +} + +// ===================================================================== +// SỐ ĐẶC TRƯNG MẪU SỐ LIỆU (TRẢ GIÁ TRỊ, không vẽ — dùng thẳng trong lời giải) +// KHÔNG ghép nhóm: nhận mảng thô, hoặc gia-tri + tan-so: (bảng tần số). +// GHÉP NHÓM (moc, tan-so): trung bình/phương sai theo GIÁ TRỊ ĐẠI DIỆN +// (trung điểm nhóm); mốt/trung vị/tứ phân vị NỘI SUY theo SGK 11–12. +// ===================================================================== + +// Bung bảng tần số (gia-tri + tan-so) thành mảng thô; tan-so: none -> giữ nguyên. +#let _bung(du-lieu, tan-so) = { + if tan-so == none { return du-lieu } + let d = () + for (i, v) in du-lieu.enumerate() { + for _ in range(tan-so.at(i)) { d.push(v) } + } + d +} + +#let so-trung-binh(du-lieu, tan-so: none) = { + if tan-so == none { du-lieu.sum() / du-lieu.len() } + else { + range(du-lieu.len()).map(i => du-lieu.at(i) * tan-so.at(i)).sum() / tan-so.sum() + } +} + +// Mốt: TRẢ VỀ MẢNG các giá trị có tần số lớn nhất (mẫu có thể nhiều mốt). +#let mot(du-lieu, tan-so: none) = { + let (gt, ts) = if tan-so != none { (du-lieu, tan-so) } else { + let dem = (:) + for v in du-lieu { + let k = repr(v) + if k in dem { dem.insert(k, (v, dem.at(k).at(1) + 1)) } + else { dem.insert(k, (v, 1)) } + } + (dem.values().map(c => c.at(0)), dem.values().map(c => c.at(1))) + } + let fmax = calc.max(..ts) + range(gt.len()).filter(i => ts.at(i) == fmax).map(i => gt.at(i)) +} + +#let trung-vi(du-lieu, tan-so: none) = _trung-vi(_bung(du-lieu, tan-so).sorted()) + +// tu-phan-vi nay nhận thêm tan-so: (bảng tần số) — bọc lại bản gốc phía trên. +#let _tpv-tho = tu-phan-vi +#let tu-phan-vi(du-lieu, tan-so: none) = _tpv-tho(_bung(du-lieu, tan-so)) + +#let khoang-bien-thien(du-lieu, tan-so: none) = { + let d = _bung(du-lieu, tan-so) + calc.max(..d) - calc.min(..d) +} + +#let khoang-tu-phan-vi(du-lieu, tan-so: none) = { + let q = tu-phan-vi(du-lieu, tan-so: tan-so) + q.q3 - q.q1 +} + +// Phương sai s² (SGK: chia n); hieu-chinh: true -> s'² chia (n − 1). +#let phuong-sai(du-lieu, tan-so: none, hieu-chinh: false) = { + let d = _bung(du-lieu, tan-so) + let n = d.len() + let m = d.sum() / n + d.map(v => (v - m) * (v - m)).sum() / (if hieu-chinh { n - 1 } else { n }) +} + +#let do-lech-chuan(du-lieu, tan-so: none, hieu-chinh: false) = calc.sqrt( + phuong-sai(du-lieu, tan-so: tan-so, hieu-chinh: hieu-chinh), +) + +// -------------------- GHÉP NHÓM (moc: n+1 mốc, tan-so: n) -------------------- + +// Giá trị đại diện = trung điểm từng nhóm. +#let _dai-dien(moc) = range(moc.len() - 1).map(i => (moc.at(i) + moc.at(i + 1)) / 2) + +#let so-trung-binh-ghep-nhom(moc, tan-so) = so-trung-binh(_dai-dien(moc), tan-so: tan-so) + +// Mốt (SGK 11): nhóm chứa mốt = nhóm tần số LỚN NHẤT (nhiều nhóm bằng nhau +// thì lấy nhóm đầu); Mo = L + (f₁−f₀)/((f₁−f₀)+(f₁−f₂))·h, f ngoài biên = 0. +#let mot-ghep-nhom(moc, tan-so) = { + let fmax = calc.max(..tan-so) + let j = tan-so.position(f => f == fmax) + let f0 = if j > 0 { tan-so.at(j - 1) } else { 0 } + let f2 = if j < tan-so.len() - 1 { tan-so.at(j + 1) } else { 0 } + let h = moc.at(j + 1) - moc.at(j) + let ms = (fmax - f0) + (fmax - f2) + if ms == 0 { moc.at(j) + h / 2 } else { moc.at(j) + (fmax - f0) / ms * h } +} + +#let trung-vi-ghep-nhom(moc, tan-so) = tu-phan-vi-ghep-nhom(moc, tan-so).q2 + +#let phuong-sai-ghep-nhom(moc, tan-so, hieu-chinh: false) = phuong-sai( + _dai-dien(moc), tan-so: tan-so, hieu-chinh: hieu-chinh, +) + +#let do-lech-chuan-ghep-nhom(moc, tan-so, hieu-chinh: false) = calc.sqrt( + phuong-sai-ghep-nhom(moc, tan-so, hieu-chinh: hieu-chinh), +) + +// R = mốc phải nhóm CUỐI có dữ liệu − mốc trái nhóm ĐẦU có dữ liệu. +#let khoang-bien-thien-ghep-nhom(moc, tan-so) = { + let i = tan-so.position(f => f > 0) + let j = tan-so.len() - 1 - tan-so.rev().position(f => f > 0) + moc.at(j + 1) - moc.at(i) +} + +#let khoang-tu-phan-vi-ghep-nhom(moc, tan-so) = { + let q = tu-phan-vi-ghep-nhom(moc, tan-so) + q.q3 - q.q1 +} diff --git a/packages/preview/conic-toan/0.3.4/lib/cau-hoi.typ b/packages/preview/conic-toan/0.3.4/lib/cau-hoi.typ new file mode 100644 index 0000000000..14da8b09ec --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/cau-hoi.typ @@ -0,0 +1,1076 @@ +// ===================================================================== +// cau-hoi.typ — CÂU HỎI THEO ĐỊNH DẠNG ĐỀ THI 2025 +// #cau-mc(...) Trắc nghiệm nhiều phương án (tự chia cột theo độ dài) +// #cau-tf(...) Đúng — Sai 4 ý (tuỳ chọn ô tick sát lề phải) +// #cau-sa(...) Trả lời ngắn +// #cau-tl(...) Tự luận +// #cau-hd(...) Hoạt động (thẻ "HĐN", bộ đếm riêng, hình thức như TL) +// #cau-lt(...) Luyện tập (thẻ "Luyện tập N", chung bộ đếm với khung +// #luyen-tap của slide.typ) +// #cau-vdtt(...) Vận dụng thực tế (thẻ "Vận dụng N", bộ đếm riêng) +// +// Số câu tự đánh liên tục cho 4 dạng MC/TF/SA/TL; HĐ, Luyện tập, Vận dụng +// mỗi loại có bộ đếm riêng (không đổi qua bước hoạt hình). +// CÔNG TẮC ĐÁP ÁN: #bat-dap-an() / #tat-dap-an() +// KIỂU TIỀN TỐ (thẻ "Câu X", "A.", "a)"): #kieu-cau-hoi(mau: ..., hinh: ...) +// hinh: "bo-tron" (mặc định) | "chu-nhat" | "luc-giac" | "khong-to" +// ("khong-to" = không tô nền, chỉ viền + chữ màu — in không bị đen; +// riêng hồ sơ dethi: bỏ cả viền — "Câu 1." / "A." đậm cùng màu) +// hien-o: false = ẩn ô tick Đ/S (ds) + ô điền "Trả lời" (tln) toàn bài. +// -> đổi MỘT LẦN, đồng bộ toàn bài. +// CHỪA CHỖ TL (bản in): #trong-tl(cao: 5cm) / #khong-trong-tl() +// ===================================================================== +#import "slide.typ": _buoc-ht, _bd-cau, _bd-vd, _bd-hd, _bd-lt, _bd-vdtt, _bd-tat-ca, _so-moi, _ho-so, _he-so-gian, _cao-that + +#let _hien-da = state("ch-hien-da", false) + +// Điều kiện hiện đáp án: công tắc bật + (nếu có lo-da) đã đến bước lo-da. +#let _da-hien(lo-da) = _hien-da.get() and (lo-da == none or _buoc-ht.get() >= lo-da) + +#let bat-dap-an() = _hien-da.update(true) +#let tat-dap-an() = _hien-da.update(false) +// Đặt lại số thứ tự — MỖI thể loại có bộ đếm & hàm đặt lại RIÊNG: +// #dat-lai-cau() -> chỉ nhóm "Câu" (tn/ds/tln/tl); KHÔNG đụng vd/hd/lt/vdtt +// #dat-lai-cau-vd() -> Ví dụ #dat-lai-cau-hd() -> Hoạt động +// #dat-lai-cau-lt() -> Luyện tập #dat-lai-cau-vdtt() -> Vận dụng thực tế +// #dat-lai-cau-tat-ca()-> đặt lại CẢ 5 nhóm cùng lúc (hành vi cũ của dat-lai-cau) +// Không tham số hoặc `(0)` -> đánh lại từ 1; `(3)` -> đánh tiếp từ 4; ... +#let _dat-lai-bd(bd, n) = { + bd.goc.update(n) + bd.cnt.update(0) +} +#let dat-lai-cau(..so) = _dat-lai-bd(_bd-cau, so.pos().at(0, default: 0)) +#let dat-lai-cau-vd(..so) = _dat-lai-bd(_bd-vd, so.pos().at(0, default: 0)) +#let dat-lai-cau-hd(..so) = _dat-lai-bd(_bd-hd, so.pos().at(0, default: 0)) +#let dat-lai-cau-lt(..so) = _dat-lai-bd(_bd-lt, so.pos().at(0, default: 0)) +#let dat-lai-cau-vdtt(..so) = _dat-lai-bd(_bd-vdtt, so.pos().at(0, default: 0)) +#let dat-lai-cau-tat-ca(..so) = { + let n = so.pos().at(0, default: 0) + for bd in _bd-tat-ca { _dat-lai-bd(bd, n) } +} + +// ---------- Chừa chỗ trống sau câu TL/HĐ/LT/VDTT (chỉ ở BẢN IN, khi ẩn đáp án) ---------- +// Đổi ĐỒNG BỘ toàn bài, hai tuỳ chọn: +// #trong-tl(cao: 5cm) -> CHỪA chỗ làm bài sau mỗi câu: +// • câu có đặt cho-trong: dùng đúng cho-trong đó; +// • câu KHÔNG đặt cho-trong: chừa mặc định `cao`. +// #khong-trong-tl() -> KHÔNG chừa chỗ (bỏ qua cả cho-trong) — in gọn. +// Mặc định (bat: true, cao: 0pt): giữ nguyên hành vi cũ — chỉ chừa đúng +// theo cho-trong của từng câu. Ở beamer/loigiai (hiện đáp án) tuỳ chọn này +// không tác dụng vì chỗ đó dành cho lời giải. +#let _trong-tl = state("ch-trong-tl", (bat: true, cao: 0pt)) +#let trong-tl(cao: 5cm) = _trong-tl.update((bat: true, cao: cao)) +#let khong-trong-tl() = _trong-tl.update(k => (bat: false, cao: k.cao)) + +#let _xanh = rgb("#0f4c81") +#let _luc = rgb("#1e8449") +#let _do = rgb("#a93226") + +// ---------- ĐÁNH DẤU ĐÁP ÁN ĐÚNG NGAY TRONG DANH SÁCH ---------- +// Bọc phương án/phát biểu ĐÚNG bằng True(...) — đáp án đi kèm nội dung nên +// hoán vị/xáo trộn thoải mái, không lệch: +// #tn([...], ([$A$], True([$B$]), [$C$], [$D$])) -> đáp án B +// #ds([...], ([a sai], True([b đúng]), True([c]), [d])) -> (S, Đ, Đ, S) +// (bí danh tiếng Việt: Dung) +// +// LỜI GIẢI GẮN LIỀN TỪNG Ý (#ds) — cần khi công cụ trộn HOÁN VỊ các ý: +// khối lời giải viết cứng "a) … b) …" sẽ sai sau khi xáo, nên đặt lời giải +// ngay cạnh ý bằng `giai:`; lib tự đánh lại nhãn theo thứ tự SAU hoán vị. +// #ds([Cho $y = x^3 - 3x$.], ( +// True([$y' = 3x^2 - 3$], giai: [Đạo hàm của $x^n$ là $n x^(n-1)$.]), +// False([Hàm số không có cực trị], giai: [$y' = 0$ có hai nghiệm phân biệt.]), +// [Ý sai, không cần giải riêng — viết trần như cũ], +// )) +// `loi-giai:` chung vẫn dùng được — in TRƯỚC phần giải theo ý (phần dẫn nhập). +#let True(nd, giai: none) = (bg-dung: true, nd: nd, giai: giai) +#let Dung = True +#let False(nd, giai: none) = (bg-dung: false, nd: nd, giai: giai) +#let _la-y(x) = type(x) == dictionary and "nd" in x +#let _la-true(x) = type(x) == dictionary and x.at("bg-dung", default: false) +#let _bo-true(x) = if _la-y(x) { x.nd } else { x } +#let _giai-y(x) = if _la-y(x) { x.at("giai", default: none) } else { none } + +// Tách danh sách có True(...)/False(...): trả (ds: nội dung thuần, +// dung: mảng bool, giai: mảng lời giải từng ý — none nếu không khai). +#let _tach-true(ds) = ( + ds: ds.map(_bo-true), + dung: ds.map(_la-true), + giai: ds.map(_giai-y), +) + +// ---------- TỰ THÊM DẤU CHẤM CUỐI PHƯƠNG ÁN / PHÁT BIỂU ---------- +// Phương án của #tn và ý của #ds là một CÂU, đúng chính tả phải kết bằng "." +// Bộ dò dưới đây duyệt NGƯỢC cây nội dung tìm ký tự có nghĩa cuối cùng: +// • đã có . ! ? … : ; (hoặc dấu đóng ngoặc kép/ngoặc sau chúng) -> giữ nguyên +// • kết thúc bằng khối (danh sách, bảng, hình, xuống dòng...) -> BỎ QUA +// • còn lại (chữ, số, công thức toán, dấu ngoặc) -> thêm "." +// Tắt toàn bài: #kieu-cau-hoi(cham-cuoi: false); tắt một câu: `cham: false`. +#let _dau-ket = (".", "!", "?", "…", ":", ";", "。", "!", "?") +// Dấu "vỏ" bọc ngoài dấu kết: "Anh ấy đi!" / (đúng.) — nhìn xuyên qua. +#let _dau-vo = ("\"", "'", "”", "’", "»", ")", "]", "}", "›") + +// Các phần tử KHỐI: đứng cuối thì không thêm dấu chấm (chấm sẽ rơi xuống dòng). +// Lưu ý: trong markup, `- a` sinh thẳng `list.item` (chưa gom thành `list`). +#let _la-khoi(f) = ( + f == list or f == enum or f == terms or f == table or f == grid + or f == list.item or f == enum.item or f == terms.item + or f == figure or f == image or f == block or f == place + or f == parbreak or f == linebreak or f == line or f == v or f == pagebreak +) +// Phần tử "khoảng trắng" của markup (bỏ qua khi dò ngược từ cuối). +#let _f-space = [ ].func() + +// Đoạn văn bản có nghĩa CUỐI CÙNG (none = không tìm được: hình, công thức...). +#let _van-cuoi(c) = { + if c == none { return none } + let t = type(c) + if t == int or t == float { return str(c) } + if t == str { + let s = c.trim() + return if s == "" { none } else { s } + } + if t == array { + for x in c.rev() { + let r = _van-cuoi(x) + if r != none { return r } + } + return none + } + if t != content { return none } + if c.has("text") { return _van-cuoi(c.text) } + if c.has("children") { return _van-cuoi(c.children) } + if c.has("body") { return _van-cuoi(c.body) } + none +} + +// Phân loại phần tử có nghĩa CUỐI CÙNG: +// 1 = khối (danh sách/bảng/hình/ngắt dòng/công thức giữa dòng) -> KHÔNG chấm +// 0 = nội dung thường (chữ, số, công thức trong dòng) -> chấm được +// -1 = không có gì nhìn thấy (khoảng trắng, metadata, cập nhật trạng thái, +// phần tử bọc rỗng) -> đi tiếp sang phần tử trước đó +#let _loai-cuoi(c) = { + if c == none { return -1 } + let t = type(c) + if t == str { return if c.trim() == "" { -1 } else { 0 } } + if t == int or t == float { return 0 } + if t != content { return 0 } + let f = c.func() + if _la-khoi(f) { return 1 } + if f == _f-space or f == metadata { return -1 } + // công thức: TRÌNH BÀY GIỮA DÒNG ($ ... $) là khối; trong dòng luôn "thường" + if f == math.equation { + if c.at("block", default: false) { return 1 } + return if _loai-cuoi(c.body) == 1 { 1 } else { 0 } + } + if c.has("text") { return if type(c.text) == str and c.text.trim() == "" { -1 } else { 0 } } + if c.has("children") { + for x in c.children.rev() { + let r = _loai-cuoi(x) + if r >= 0 { return r } + } + return -1 + } + if c.has("body") { return _loai-cuoi(c.body) } + if c.has("child") { return _loai-cuoi(c.child) } // phần tử styled + -1 +} + +// Thêm "." nếu thiếu (giữ nguyên nội dung gốc trong mọi trường hợp còn lại). +#let cham-cau(c) = { + if c == none or c == [] { return c } + if _loai-cuoi(c) != 0 { return c } // kết bằng khối/hình hoặc rỗng -> bỏ qua + let s = _van-cuoi(c) + if s == none { return [#c#"."] } // chỉ có công thức (vd $1/2$) -> vẫn chấm + // bóc các dấu "vỏ" ngoài cùng: Đúng!" / (sai.) -> nhìn thấy ! và . + let cl = s.clusters() + let i = cl.len() - 1 + while i >= 0 and _dau-vo.contains(cl.at(i)) { i = i - 1 } + if i >= 0 and _dau-ket.contains(cl.at(i)) { return c } + [#c#"."] +} + +// ---------- THU THẬP ĐÁP ÁN CHO BẢNG ĐÁP ÁN ---------- +// Mỗi câu tn/ds/tln phát một metadata vô hình (nhãn ). Hàm bang-dap-an +// (che-do.typ) query theo document-order rồi dựng 3 bảng. loai ∈ "tn"/"ds"/"tln": +// tn -> da = chữ cái đáp án đúng ("A".."F") hoặc none. +// ds -> da = mảng bool 4 ý (true = Đ, false = S). +// tln -> da = đáp án (chuỗi/số/nội dung). +// khoa: true -> câu này KHÔNG được hoán vị phương án/ý (công cụ trộn đọc cờ này). +#let _ghi-da(loai, da, khoa: false) = [#metadata((loai: loai, da: da, khoa: khoa)) ] + +// Chữ cái đáp án đúng của câu tn (ưu tiên True(...) trong danh sách; nếu không +// có thì lấy dap-an dạng chữ "B" cũ). +#let _chu-da-tn(phuong-an, dap-an) = { + let vi = phuong-an.map(_la-true).position(d => d) + if vi != none { ("A", "B", "C", "D", "E", "F").at(vi) } else { dap-an } +} + +// ---------- Kiểu tiền tố (thẻ) — đồng bộ màu & hình dáng ---------- +// hien-o: hiện ô tick Đ/S (câu ds có o-tick) và ô điền "Trả lời" (câu tln); +// false = ẨN đồng bộ toàn bài (đề gọn, HS làm thẳng vào phiếu). +// om-hinh: chữ ÔM hình (phần dư tràn hết bề rộng dưới hình) — xem voi-hinh. +// eq-dong: ép công thức KHỐI ($ ... $ có khoảng trắng) trong phương án #tn / +// ý #ds thành công thức TRONG DÒNG — xem `_ep-trong-dong`. +#let _kieu = state("ch-kieu", (mau: rgb("#0f4c81"), hinh: "bo-tron", hien-o: true, cham: true, om-hinh: true, eq-dong: true)) + +// Đổi kiểu ở bất kỳ đâu trong tài liệu: +// #kieu-cau-hoi(mau: rgb("#e67e22"), hinh: "luc-giac", hien-o: false) +// #kieu-cau-hoi(om-hinh: false) // tắt chế độ chữ ôm hình từ đây trở đi +// #kieu-cau-hoi(eq-trong-dong: false) // giữ nguyên $ ... $ khối như cũ +// #kieu-cau-hoi(cao-that: false) // tắt cột chống chiều cao công thức +// cao-that: công thức TRONG DÒNG có khai đúng chiều cao NÉT VẼ hay không +// (xem `_chong-net` của slide.typ). Mặc định BẬT — đây là thứ chống dính chữ +// ở phân số/căn thức mà `gian-dong` không với tới được. +#let kieu-cau-hoi(mau: auto, hinh: auto, hien-o: auto, cham-cuoi: auto, om-hinh: auto, eq-trong-dong: auto, cao-that: auto) = { + _kieu.update(k => ( + mau: if mau == auto { k.mau } else { mau }, + hinh: if hinh == auto { k.hinh } else { hinh }, + hien-o: if hien-o == auto { k.at("hien-o", default: true) } else { hien-o }, + cham: if cham-cuoi == auto { k.at("cham", default: true) } else { cham-cuoi }, + om-hinh: if om-hinh == auto { k.at("om-hinh", default: true) } else { om-hinh }, + eq-dong: if eq-trong-dong == auto { k.at("eq-dong", default: true) } else { eq-trong-dong }, + )) + // Cơ chế cao-that nằm ở state RIÊNG (slide.typ) vì bai-giang/de-toan phải + // đọc được nó mà slide.typ thì không import được cau-hoi.typ. + if cao-that != auto { + _cao-that.update(c => ( + bat: cao-that, + nguong: c.at("nguong", default: 1pt), + them: c.at("them", default: 0pt), + chia: c.at("chia", default: 0.5), + hien: c.at("hien", default: false), + )) + } +} + +// Ô tick/ô điền có được hiện không (đọc trong context). +#let _hien-o() = _kieu.get().at("hien-o", default: true) + +// Tự thêm dấu chấm cuối phương án/ý hỏi không? (đọc trong context) +// cham: auto = theo cài đặt toàn bài; true/false = ép riêng câu này. +#let _cham-ds(ds, cham) = { + let bat = if cham == auto { _kieu.get().at("cham", default: true) } else { cham } + if bat { ds.map(cham-cau) } else { ds } +} + +// Thẻ tiền tố: nền màu chủ đạo, chữ trắng; 4 chế độ. +// "bo-tron" | "chu-nhat" | "luc-giac" : TÔ nền màu, chữ trắng. +// "khong-to" : KHÔNG tô nền — viền mảnh + chữ màu chủ đạo, đậm; +// in ấn không bị mảng đen (tiết kiệm mực). +// RIÊNG hồ sơ dethi: bỏ luôn viền — chỉ chữ ĐẬM cùng màu +// kèm dấu liền kề ("Câu 1.", "A." — ý ds: "a)") kiểu LaTeX; +// dấu tuỳ chỉnh qua `duoi` (mặc định "."). +#let _the(nd, mau: auto, duoi: ".") = context { + let k = _kieu.get() + let m = if mau == auto { k.mau } else { mau } + if k.hinh == "khong-to" and _ho-so.get() == "sachdethi" { + text(fill: m, weight: "bold")[#nd#duoi] + } else if k.hinh == "khong-to" { + box( + stroke: 0.8pt + m, + radius: 3.5pt, + inset: (x: 6pt, y: 2.5pt), + baseline: 1%, + text(fill: m, weight: "bold", size: 0.8em, nd), + ) + } else { + let nd2 = text(fill: white, weight: "bold", size: 0.8em, nd) + if k.hinh == "luc-giac" { + let s = measure(nd2) + let w = s.width + 15pt + let h = s.height + 5.5pt + box(width: w, height: h, baseline: 1%, { + place(polygon( + fill: m, + (h / 2, 0pt), (w - h / 2, 0pt), (w, h / 2), + (w - h / 2, h), (h / 2, h), (0pt, h / 2), + )) + place(center + horizon, nd2) + }) + } else { + box( + fill: m, + radius: if k.hinh == "chu-nhat" { 0pt } else { 3.5pt }, + inset: (x: 6pt, y: 2.5pt), + baseline: 1%, + nd2, + ) + } + } +} + +// "Câu N (x điểm)" — đầu mỗi câu hỏi. +// prefix: chữ trên thẻ ("Câu", "Bài", ...); num: auto = đếm tự động, +// hoặc số/chữ chỉ định (khi chỉ định, bộ đếm KHÔNG tăng). +#let _dau-cau(diem: none, prefix: "Câu", num: auto) = { + let so = if num == auto { _so-moi(_bd-cau) } else { [#num] } + _the([#prefix #so]) + if diem != none { + context text(weight: "bold", fill: _kieu.get().mau, size: 0.86em)[ (#diem điểm)] + } + h(6pt) +} + +// Chừa `lines` dòng trống làm bài (chỉ khi ẨN đáp án — bản in đề thi). +#let _chua-dong(lines, lo-da) = context { + if lines > 0 and not _da-hien(lo-da) { v(lines * 1.55em) } +} + +// Đóng khung cả câu khi boxed: true. +// Khoảng cách GIỮA HAI CÂU cũng nhân hệ số gian-dong: nếu không, đặt +// gian-dong: 2 thì các dòng TRONG câu giãn gấp đôi mà "Câu 2" vẫn dán sát ngay +// dưới ý cuối của câu trước — nhìn như các câu dính chùm vào nhau. +// Hệ số 1.0 cho đúng 10pt như cũ ⇒ bài cũ không đổi bố cục. +#let _khung-cau(boxed, than) = context block( + width: 100%, above: 10pt * _he-so-gian(), below: 10pt * _he-so-gian(), + stroke: if boxed { 0.7pt + luma(45%) } else { none }, + inset: if boxed { 8pt } else { 0pt }, + radius: if boxed { 4pt } else { 0pt }, + than, +) + +// (_khoi-giai/_hien-giai chuyển xuống SAU voi-hinh — nay hỗ trợ hình +// kèm LỜI GIẢI thông qua voi-hinh.) + +// ---------- ÔM HÌNH: cắt nội dung theo ĐOẠN để chữ ôm sát hình ---------- +// Bài toán: hình hẹp + lời giải DÀI ⇒ cột hình trống một khoảng lớn phía +// dưới, lãng phí giấy. Cách chữa: đặt các ĐOẠN ĐẦU cạnh hình cho vừa đúng +// chiều cao hình, phần còn lại in NGUYÊN BỀ RỘNG bên dưới hình. + +// Tách nội dung thành mảng "khối" (đoạn văn, công thức tách dòng, danh +// sách, bảng…). Dấu ngắt đoạn KHÔNG giữ lại — ghép lại bằng parbreak(). +// Nội dung ghép từ nhiều mảnh có thể LỒNG sequence trong sequence — trải +// phẳng (CHỈ trải sequence, không đụng grid/table/list… dù chúng cũng có +// trường "children"). +#let _f-seq = [].func() +#let _la-seq(c) = { + if type(c) != content { return false } + let f = c.func() + if f == _f-seq { return true } + // Dự phòng (phòng khi `[].func()` không nhận ra): có trường "children" + // mà KHÔNG phải các phần tử gom nhóm thật sự. + c.has("children") and not (f == grid or f == table or f == list or f == enum + or f == terms or f == stack or f == figure) +} +#let _phang-seq(c) = { + if _la-seq(c) { return c.children.map(_phang-seq).flatten() } + (c,) +} + +// Mỗi khối là dict `(nd: nội dung, sep: dấu ngắt đi sau nó)`. PHẢI giữ dấu +// ngắt GỐC: bản A4 đi qua `_ghep` (che-do.typ) nên mọi dấu ngắt ĐOẠN đã bị +// đổi thành ngắt DÒNG — ghép lại bằng parbreak sẽ giãn rộng ra, đổi bố cục +// mọi bài cũ. Vì vậy cũng phải CẮT ở cả linebreak, không riêng parbreak. +#let _tach-doan(c) = { + if c == none { return () } + if type(c) != content { return ((nd: c, sep: none),) } + let ds = _phang-seq(c) + let ra = () + let cum = () + for x in ds { + let f = if type(x) == content { x.func() } else { none } + if f == parbreak or f == linebreak { + if cum.len() > 0 { + ra.push((nd: cum.join(), sep: x)) + cum = () + } else if ra.len() > 0 { + // Dấu ngắt đi ngay sau một khối (vd sau công thức tách dòng): gắn + // vào khối trước, ĐỪNG bỏ đi kẻo mất một lần xuống dòng. + let u = ra.pop() + ra.push((nd: u.nd, sep: if u.sep == none { x } else { u.sep + x })) + } + } else { + cum.push(x) + if (f == block or f == grid or f == table or f == list or f == enum + or f == terms or f == figure + or (f == math.equation and x.at("block", default: false))) { + ra.push((nd: cum.join(), sep: none)) + cum = () + } + } + } + if cum.len() > 0 { ra.push((nd: cum.join(), sep: none)) } + ra +} + +// Ghép các khối lại NGUYÊN như cũ (kèm dấu ngắt gốc), bỏ dấu ngắt cuối cùng. +#let _noi-khoi(ds) = { + let ra = none + for (i, u) in ds.enumerate() { + ra = if ra == none { u.nd } else { ra + u.nd } + if i + 1 < ds.len() and u.sep != none { ra = ra + u.sep } + } + if ra == none { [] } else { ra } +} + +// Phần tử KHÔNG xuống dòng được (công thức tách dòng, ảnh, bảng): bề rộng +// tự nhiên phải lọt cột hẹp, nếu không sẽ tràn ra ngoài lề. +#let _khoi-cung(x) = { + if type(x) != content { return false } + let f = x.func() + if f == math.equation { return x.at("block", default: false) } + (f == image or f == table or f == grid or f == figure) +} + +// Khối có phần nào QUÁ RỘNG so với cột hẹp không? (gọi trong context) +#let _qua-rong(x, hep) = { + if type(x) != content { return false } + if _khoi-cung(x) { return measure(x).width > hep } + if x.has("children") { return x.children.any(y => _qua-rong(y, hep)) } + false +} + +// Số khối ĐẦU đặt vừa cạnh hình (cột rộng `hep`, cao tối đa `cao`). +// Gọi trong context (dùng measure). +#let _dem-khoi-om(khoi, hep, cao) = { + let k = 0 + while k < khoi.len() { + if _qua-rong(khoi.at(k).nd, hep) { break } + if measure(block(width: hep, _noi-khoi(khoi.slice(0, k + 1)))).height > cao { break } + k = k + 1 + } + k +} + +// Chế độ ôm có bật không: om = auto → theo kieu-cau-hoi(om-hinh:). +#let _om-bat(om) = if om == auto { _kieu.get().at("om-hinh", default: true) } else { om } + +// ---------- BỐ CỤC ĐỀ + HÌNH ---------- +// Quy tắc chung cho MỌI dạng câu (dùng qua tham số hinh: của +// cau-mc/cau-tf/cau-sa/cau-tl/... hoặc gọi trực tiếp trong thân câu): +// • hình HẸP (bề rộng ≤ ti-le × bề rộng khung): hình canh giữa CỘT PHẢI — +// cột phải ôm đúng bề rộng hình, cột trái (đề + phương án/ý hỏi) nhận +// phần còn lại; hai cột canh giữa theo chiều dọc nên độ cao cân đối. +// • hình RỘNG (BBT, bảng thống kê, hình khổ lớn): tự tách ra DÒNG RIÊNG +// canh giữa — đặt sau phần đề `de`, trước phần `duoi` (phương án/ý hỏi). +// LƯU Ý: truyền hình TRỰC TIẾP (hinh: ve-do-thi(...)), KHÔNG bọc align/block +// quanh hình — bọc thêm sẽ đo sai bề rộng. +// vi-tri ("right" | "left" | "center" | "top" | "bottom"): vị trí hình so với đề; +// • "right"/"left" — hình nằm cột phải/cột trái, chữ ôm quanh hình; +// • "center" — hình canh GIỮA trên DÒNG RIÊNG, nằm giữa phần đề và +// phần phương án/ý hỏi (dùng cho hình khổ lớn: BBT, +// bảng thống kê, đồ thị rộng); +// • "top" — hình lên TRÊN cả đề bài; +// • "bottom" — hình xuống DƯỚI cả phương án. +// be-rong: auto = ôm đúng bề rộng hình (hình rộng tự xuống dòng riêng), +// hoặc chỉ định (35%, 5cm...) = cột hình chiếm đúng bề rộng đó. +// Với "center"/"top"/"bottom" thì be-rong KHÔNG có tác dụng (hình +// đã chiếm trọn bề ngang). +// om: chữ ÔM hình — auto = theo kieu-cau-hoi(om-hinh:), mặc định BẬT; +// false = giữ lối 2 cột cũ (cột hình có thể trống nhiều phía dưới). +#let voi-hinh(de, hinh, duoi: none, ti-le: 0.46, khoang: 14pt, vi-tri: "right", be-rong: auto, om: auto) = { + assert(vi-tri in ("right", "left", "center", "top", "bottom"), + message: "vi-tri (fig-pos) chỉ nhận \"right\" | \"left\" | \"center\" | \"top\" | \"bottom\", nhận được: " + repr(vi-tri)) + let rieng = block(width: 100%, above: 8pt, below: 8pt, align(center, hinh)) + if hinh == none { + de + duoi + } else if vi-tri == "top" { + rieng + de + duoi + } else if vi-tri == "center" { + de + rieng + duoi + } else if vi-tri == "bottom" { + de + duoi + rieng + } else { + let than = { de; duoi } + let hai-cot(o-hinh) = if vi-tri == "left" { + grid( + columns: (if be-rong == auto { auto } else { be-rong }, 1fr), + column-gutter: khoang, + align: (center + horizon, left + horizon), + o-hinh, than, + ) + } else { + grid( + columns: (1fr, if be-rong == auto { auto } else { be-rong }), + column-gutter: khoang, + align: (left + horizon, center + horizon), + than, o-hinh, + ) + } + // Bố cục ÔM (gọi TRONG context): none = không cắt được hoặc không cần + // cắt (nội dung vốn đã thấp hơn hình) ⇒ quay về lối 2 cột cũ. + let bo-cuc-om(w-hinh, kich) = { + let hep = kich.width - w-hinh - khoang + if hep < 3cm { return none } + let khoi = _tach-doan(than) + if khoi.len() < 2 { return none } + let k = _dem-khoi-om(khoi, hep, measure(hinh).height) + if k == 0 or k == khoi.len() { return none } + let tren = _noi-khoi(khoi.slice(0, k)) + block(above: 0pt, below: 0pt, if vi-tri == "left" { + grid( + columns: (w-hinh, 1fr), column-gutter: khoang, + align: (center + top, left + top), + hinh, tren, + ) + } else { + grid( + columns: (1fr, w-hinh), column-gutter: khoang, + align: (left + top, center + top), + tren, hinh, + ) + }) + parbreak() + _noi-khoi(khoi.slice(k)) + } + if be-rong != auto { + context layout(kich => { + let w = if type(be-rong) == ratio { kich.width * be-rong } else if type(be-rong) == length { be-rong } else { none } + let r = if _om-bat(om) and w != none { bo-cuc-om(w, kich) } else { none } + if r == none { hai-cot(align(center + horizon, hinh)) } else { r } + }) + } else { + context layout(kich => { + let w = measure(hinh).width + if w <= ti-le * kich.width { + let r = if _om-bat(om) { bo-cuc-om(w, kich) } else { none } + if r == none { hai-cot(hinh) } else { r } + } else { + de + rieng + duoi + } + }) + } + } +} + +// Khối "Hướng dẫn giải" dùng chung cho cả 4 dạng. +// hinh: hình kèm LỜI GIẢI — bố cục 2 cột/hàng riêng như voi-hinh +// (vi-tri: "right"|"left"|"center"|"top"|"bottom"; be-rong: auto hoặc chỉ định). +#let _khoi-giai(lg, hinh: none, vi-tri: "right", be-rong: auto) = block( + width: 100%, inset: (left: 11pt, top: 4pt, bottom: 4pt), + stroke: (left: 2.5pt + _luc), above: 8pt, + voi-hinh({ + align(center, text(fill: _luc, weight: "bold", size: 0.84em)[Hướng dẫn giải]) + lg + }, hinh, vi-tri: vi-tri, be-rong: be-rong), +) + +// Hiện lời giải theo công tắc + bước `buoc`; trước bước đó KHÔNG chiếm chỗ +// (tránh phần ẩn tràn trang sinh trang trắng đệm khi trình chiếu). +#let _hien-giai(loi-giai, buoc, hinh: none, vi-tri: "right", be-rong: auto) = context { + if _hien-da.get() and loi-giai != none { + if buoc == none or _buoc-ht.get() >= buoc { + _khoi-giai(loi-giai, hinh: hinh, vi-tri: vi-tri, be-rong: be-rong) + } + } +} + +// ---------- MC: trắc nghiệm nhiều phương án ---------- +// Một phương án: thẻ chữ cái + nội dung; phương án đúng tô xanh lá. +// ---------- ÉP CÔNG THỨC KHỐI THÀNH TRONG DÒNG ---------- +// Người soạn (và nhất là AI sinh bài) rất hay gõ `$ cases(x > 0, y > 0, x + y < 3) $` +// CÓ khoảng trắng sát hai dấu $ ⇒ Typst hiểu là công thức TRÌNH BÀY GIỮA DÒNG +// (block) ⇒ phương án bị xuống dòng và canh giữa, cột phình cao, hệ 3 bpt trông +// khác hẳn hệ 2 bpt viết `$cases(...)$`. Hàm này duyệt nội dung phương án/ý và +// đổi MỌI equation `block: true` → `block: false`, cho ra đúng dáng trong dòng. +// AN TOÀN: chỉ đụng tới `math.equation`; nội dung không có công thức khối thì +// trả về NGUYÊN VẸN (so sánh `==` giữ nguyên đối tượng cũ). KHÔNG đệ quy vào +// block/grid/table/figure… (phương án là hình, bảng… giữ nguyên bố cục cũ). +#let _ep-trong-dong(c) = { + if type(c) == array { return c.map(_ep-trong-dong) } + if type(c) != content { return c } + let f = c.func() + if f == math.equation { + if c.at("block", default: false) { return math.equation(block: false, c.body) } + return c + } + if _la-seq(c) { + let ds = c.children.map(_ep-trong-dong) + return if ds.len() == 0 { c } else { ds.join() } + } + c +} + +// Có ép công thức khối thành trong dòng không? (đọc trong context) +// trong-dong: auto = theo cài đặt toàn bài; true/false = ép riêng câu này. +#let _ep-ds(ds, trong-dong) = { + let bat = if trong-dong == auto { _kieu.get().at("eq-dong", default: true) } else { trong-dong } + if bat { ds.map(_ep-trong-dong) } else { ds } +} + +#let _o-pa(chu, noi-dung, dung) = { + if dung { + box(fill: _luc.lighten(87%), radius: 3.5pt, inset: (x: 5pt, y: 3.5pt))[ + #_the(chu, mau: _luc) #noi-dung + ] + } else { + [#_the(chu) #noi-dung] + } +} + +// cot: auto = tự chọn 4 / 2 / 1 cột theo độ dài phương án dài nhất, +// hoặc số cột cố định (1, 2, 4). +// lo-da: bước lộ đáp án; loi-giai + lo-giai: lời giải và bước lộ lời giải. +// Phương án đúng: bọc True(...) trong danh sách (ưu tiên) hoặc dap-an: "B" (cũ). +#let cau-mc( + cau, phuong-an, dap-an: none, cot: auto, diem: none, hinh: none, + lo-da: none, loi-giai: none, lo-giai: auto, cham: auto, khoa-pa: false, + trong-dong: auto, + fig-pos: "right", fig-width: auto, lines: 0, num: auto, prefix: "Câu", boxed: false, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, +) = { + _ghi-da("tn", _chu-da-tn(phuong-an, dap-an), khoa: khoa-pa) + _khung-cau(boxed, { + let hinh-giai = if hinh-giai != none { hinh-giai } else { fig-giai } + let t = _tach-true(phuong-an) + let pa = t.ds + let vi-dung = t.dung.position(d => d) + let da = if vi-dung != none { ("A", "B", "C", "D", "E", "F").at(vi-dung) } else { dap-an } + voi-hinh({ _dau-cau(diem: diem, prefix: prefix, num: num); cau }, hinh, + vi-tri: fig-pos, be-rong: fig-width, duoi: { + // khoảng cách ĐỀ → phương án cũng theo hệ số gian-dong (1.0 = 6pt như cũ) + context v(6pt * _he-so-gian()) + context { + let hien = _da-hien(lo-da) and da != none + // ÉP TRONG DÒNG TRƯỚC rồi mới chấm câu: sau khi ép, `$ ... $` không còn + // là "khối" nên `cham-cau` thêm được dấu chấm — đúng như hệ 2 bpt. + let pa = _cham-ds(_ep-ds(pa, trong-dong), cham) + let cells = pa.enumerate().map(p => { + let chu = ("A", "B", "C", "D", "E", "F").at(p.at(0)) + _o-pa(chu, p.at(1), hien and chu == da) + }) + layout(kich => { + let so-cot = if cot == auto { + // đo phương án dài nhất, cộng bề rộng thẻ + khoảng đệm + let rong = calc.max(..pa.map(x => measure([#x]).width)) + 42pt + if 4 * rong <= kich.width { 4 } else if 2 * rong <= kich.width { 2 } else { 1 } + } else { cot } + grid( + columns: (1fr,) * so-cot, + // gian-dong với tới CẢ khoảng cách giữa các hàng phương án + // (hệ số 1.0 = 9pt như cũ ⇒ bài cũ không đổi bố cục). + row-gutter: 9pt * _he-so-gian(), column-gutter: 12pt, + ..cells, + ) + }) + } + }) + _chua-dong(lines, lo-da) + _hien-giai(loi-giai, if lo-giai == auto { lo-da } else { lo-giai }, + hinh: hinh-giai, vi-tri: fig-giai-pos, be-rong: fig-giai-width) + }, + ) +} + +// ---------- DÒ NHÃN THỨ TỰ GÕ TAY (chống lặp "a) a) …") ---------- +// Người soạn (hoặc AI sinh bài) hay tự gõ sẵn "a)", "1.", "(b)"… vào đầu item, +// trong khi cot-item vốn TỰ đánh nhãn ⇒ ra "a) a) Phải quay…". Bộ dò dưới đây +// đọc CHUỖI ĐẦU của từng item; item nào đã có nhãn thì cot-item bỏ nhãn tự động. +// NHẬN: a) a. a: A) (a) [b] 1) 1. (2) ii) IV. +// KHÔNG nhận: "0,5 lít" · "0.5 lít" (sau dấu là chữ số) · "Ta có" · "$x = 1$" +#let _dau-nhan = (")", ".", ":") +#let _chu-cai = "abcdefghijklmnopqrstuvwxyz" +#let _chu-so = "0123456789" +#let _so-la-ma = ("i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", "x", + "xi", "xii") + +// Chuỗi văn bản ĐẦU của một nội dung (chỉ cần vài ký tự đủ soi nhãn). +// Gặp "vật cản" (toán, hình, bảng, danh sách) thì trả "$" để chắc chắn không khớp. +#let _dau-van(c, gh: 10) = { + if c == none { return "" } + let t = type(c) + if t == int or t == float { return str(c) } + if t == str { return c } + if t == array { + let s = "" + for x in c { + s = s + _dau-van(x, gh: gh) + if s.trim(at: start).clusters().len() >= gh { break } + } + return s + } + if t != content { return "" } + let f = c.func() + if f == _f-space or f == linebreak or f == parbreak { return " " } + if f == metadata { return "" } + if _la-khoi(f) or f == math.equation { return "$" } + if c.has("text") { return if type(c.text) == str { c.text } else { "$" } } + if c.has("children") { return _dau-van(c.children, gh: gh) } + if c.has("body") { return _dau-van(c.body, gh: gh) } + if c.has("child") { return _dau-van(c.child, gh: gh) } // phần tử styled + "" +} + +// Thứ tự của nhãn gõ tay ở đầu item: 1 cho a)/1)/(a), 2 cho b)/2)… ; +// none = không thấy nhãn nào. +#let _thu-nhan-tay(c) = { + let cl = _dau-van(c).trim(at: start).clusters() + if cl.len() == 0 { return none } + let mo = (cl.at(0) == "(" or cl.at(0) == "[") + let i = if mo { 1 } else { 0 } + // thân nhãn: TOÀN chữ cái hoặc TOÀN chữ số, tối đa 4 ký tự + let than = "" + let loai = none + while i < cl.len() and than.clusters().len() < 4 { + let l = lower(cl.at(i)) + let la-so = (_chu-so.contains(l) and loai != "chu") + let la-chu = (_chu-cai.contains(l) and loai != "so") + if la-so { loai = "so" } else if la-chu { loai = "chu" } else { break } + than = than + l + i = i + 1 + } + if loai == none or i >= cl.len() { return none } + // dấu ngăn: trong ngoặc thì phải đóng ngoặc, ngoài ngoặc thì ) . : + let d = cl.at(i) + let hop-le = if mo { d == ")" or d == "]" } else { _dau-nhan.contains(d) } + if not hop-le { return none } + // Sau dấu CHẤM / HAI CHẤM bắt buộc là khoảng trắng hoặc hết chuỗi, kẻo nhận + // nhầm "0.5 lít" hay "12:30". Sau dấu ĐÓNG NGOẶC thì "a)Nội dung" vẫn tính. + i = i + 1 + let can-cach = (d == "." or d == ":") + if can-cach and i < cl.len() and cl.at(i).trim() != "" { return none } + if loai == "so" { + if than.starts-with("0") { return none } // "01." không phải nhãn + let k = int(than) + return if k >= 1 and k <= 99 { k } else { none } + } + if than.clusters().len() == 1 { + let k = _chu-cai.clusters().position(x => x == than) + return if k == none { none } else { k + 1 } + } + let k = _so-la-ma.position(x => x == than) + if k == none { none } else { k + 1 } +} + +// Quyết định BỎ nhãn tự động cho từng item: +// • MỌI item đều đã có nhãn tay -> bỏ hết (kể cả khi đánh tiếp d), e), f)). +// • chỉ VÀI item có nhãn tay -> chỉ bỏ ở item mà nhãn tay ĐÚNG thứ tự của nó +// (tránh nhận nhầm câu mở đầu bằng "A. B. C thẳng hàng"). +#let _bo-nhan-tu-dong(items) = { + let n = items.len() + let thu = items.map(_thu-nhan-tay) + let co = thu.filter(x => x != none).len() + if co == n and n > 0 { range(n).map(i => true) } else { + range(n).map(i => thu.at(i) == i + 1) + } +} + +// ---------- CHIA CỘT CÁC ITEM (dùng trong thân câu, nhất là TL) ---------- +// Tự đánh nhãn a) b) c)... và xếp các item thành nhiều cột. +// so-cot: auto => ĐO item dài nhất rồi chọn số cột lớn nhất còn vừa bề rộng +// (thử lần lượt trong `muc-cot`); hoặc số cột cố định. +// theo-cot: true => xếp DỌC theo cột (a,b,c | d,e,f); false => theo hàng. +// kieu-nhan: mẫu numbering ("a)", "1)", ...); none => không đánh nhãn. +// do-nhan-tay: true (mặc định) => item nào ĐÃ gõ sẵn "a)", "1.", "(b)"… thì +// KHÔNG đánh nhãn tự động nữa (chống lặp "a) a) …"); false => luôn đánh. +// +// LƯU Ý (beamer): các `\` NẰM TRONG một item chỉ là xuống dòng thường — +// KHÔNG tạo bước hoạt hình, vì chúng nằm trong lưới nên bộ tách bước ở cấp +// cao nhất của loi-giai không thấy. Cả item (thậm chí cả khối cot-item) hiện +// MỘT LƯỢT trong một bước. Muốn hiện dần từng cột theo bước thì bọc từng item +// bằng lo(n)[...], ví dụ: cot-item(so-cot: 2, lo(2)[...], lo(3)[...]). +// Cần hiện dần từng dòng như bình thường thì viết thẳng với `\`, đừng cho vào +// cot-item. +#let cot-item( + ..noi-dung, + so-cot: auto, + muc-cot: (4, 3, 2, 1), // các mức cột sẽ thử khi so-cot = auto (ưu tiên lớn) + dem: 42pt, // đệm mỗi cột (nhãn + khoảng cách), như cau-mc + theo-cot: true, + kieu-nhan: "a)", + do-nhan-tay: true, // dò nhãn a)/1)… gõ sẵn trong item -> không đánh chồng + cach-cot: 14pt, + cach-hang: auto, // auto = 8pt × hệ số gian-dong (1.0 ⇒ 8pt như cũ) +) = { + let items = noi-dung.pos() + let n = items.len() + if n == 0 { return } + let bo = if do-nhan-tay { _bo-nhan-tu-dong(items) } else { range(n).map(i => false) } + let nhan(i) = if kieu-nhan == none or bo.at(i) { [] } else { + [#numbering(kieu-nhan, i + 1) ] + } + let o(i) = box(width: 100%, [#nhan(i)#items.at(i)]) + context layout(kich => { + let so = if so-cot == auto { + // đo item dài nhất (kèm nhãn) + đệm để tính số cột vừa khung + let rong = calc.max(..range(n).map(i => measure([#nhan(i)#items.at(i)]).width)) + dem + let vua = muc-cot.filter(k => k * rong <= kich.width) + if vua.len() > 0 { calc.max(..vua) } else { 1 } + } else { so-cot } + let so = calc.min(so, n) + let so-hang = calc.ceil(n / so) + let cells = () + for r in range(so-hang) { + for c in range(so) { + let idx = if theo-cot { c * so-hang + r } else { r * so + c } + cells.push(if idx < n { o(idx) } else { [] }) + } + } + grid( + columns: (1fr,) * so, + column-gutter: cach-cot, + row-gutter: if cach-hang == auto { 8pt * _he-so-gian() } else { cach-hang }, + align: left + top, + ..cells, + ) + }) +} + +// ---------- TF: đúng — sai ---------- +// o-tick: true => mỗi phát biểu có 2 ô Đ/S dóng thẳng sát lề phải +// (khi hiện đáp án sẽ tick ✓ vào ô tương ứng); false => nhãn Đ/S sau câu. +// Phát biểu ĐÚNG: bọc True(...) trong danh sách (ưu tiên) hoặc +// dap-an: (false, true, ...) như cũ. +// giai: của True/False -> lời giải riêng từng ý, tự đánh nhãn theo thứ tự hiện +// tại nên hoán vị không lệch. khoa-y: true -> báo công cụ trộn ĐỪNG xáo các ý +// (dùng cho câu mà ý sau dựa vào kết quả ý trước). +#let cau-tf( + cau, cac-y, dap-an: none, diem: none, hinh: none, + lo-da: none, loi-giai: none, lo-giai: auto, o-tick: false, cham: auto, + khoa-y: false, trong-dong: auto, + fig-pos: "right", fig-width: auto, lines: 0, num: auto, prefix: "Câu", boxed: false, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, +) = { + _ghi-da("ds", if _tach-true(cac-y).dung.any(d => d) { _tach-true(cac-y).dung } else { dap-an }, + khoa: khoa-y) + _khung-cau(boxed, { + let hinh-giai = if hinh-giai != none { hinh-giai } else { fig-giai } + let t = _tach-true(cac-y) + let cac-y = t.ds + let dap-an = if t.dung.any(d => d) { t.dung } else { dap-an } + // Lời giải riêng từng ý -> ghép thành khối "a) … b) …" theo thứ tự HIỆN TẠI + let loi-giai = if t.giai.any(g => g != none) { + let nhan-y = ("a", "b", "c", "d", "e", "f") + { + if loi-giai != none { loi-giai; v(3pt) } + for (j, g) in t.giai.enumerate() { + if g != none { + block(above: 5pt, below: 5pt, { + _the(nhan-y.at(j), duoi: ")"); [ ]; g + }) + } + } + } + } else { loi-giai } + voi-hinh({ _dau-cau(diem: diem, prefix: prefix, num: num); cau }, hinh, + vi-tri: fig-pos, be-rong: fig-width, duoi: { + context v(4pt * _he-so-gian()) + context { + let hien = _da-hien(lo-da) and dap-an != none + let cac-y = _cham-ds(_ep-ds(cac-y, trong-dong), cham) + let nhan-y = ("a", "b", "c", "d", "e") + let k-mau = _kieu.get().mau + if o-tick and _hien-o() { + let o-vuong(tick, m) = box( + width: 15pt, height: 15pt, stroke: 0.8pt + luma(45%), radius: 2pt, + fill: if tick { m.lighten(80%) } else { white }, + align(center + horizon, + if tick { text(fill: m, weight: "bold", size: 9pt)[✓] }), + ) + grid( + columns: (1fr, 26pt, 26pt), + align: (left + horizon, center + horizon, center + horizon), + row-gutter: 8pt * _he-so-gian(), column-gutter: 6pt, + [], text(weight: "bold", fill: k-mau, size: 0.82em)[Đ], + text(weight: "bold", fill: k-mau, size: 0.82em)[S], + ..cac-y.enumerate().map(p => ( + [#_the(nhan-y.at(p.at(0)), duoi: ")") #p.at(1)], + o-vuong(hien and dap-an.at(p.at(0)), _luc), + o-vuong(hien and not dap-an.at(p.at(0)), _do), + )).flatten(), + ) + } else { + let gy = 6pt * _he-so-gian() // gian-dong với tới khoảng cách giữa các ý + for j in range(cac-y.len()) { + block(above: gy, below: gy, { + _the(nhan-y.at(j), duoi: ")") + [ ] + cac-y.at(j) + if hien { + h(7pt) + let dung = dap-an.at(j) + let m = if dung { _luc } else { _do } + box(fill: m.lighten(86%), radius: 3pt, inset: (x: 6pt, y: 1.5pt), + text(size: 0.78em, weight: "bold", fill: m, if dung { "Đ" } else { "S" })) + } + }) + } + } + } + }) + _chua-dong(lines, lo-da) + _hien-giai(loi-giai, if lo-giai == auto { lo-da } else { lo-giai }, + hinh: hinh-giai, vi-tri: fig-giai-pos, be-rong: fig-giai-width) + }, + ) +} + +// ---------- SA: trả lời ngắn ---------- +// Khi ẩn đáp án: hiện các ô vuông để điền (như phiếu trả lời). +// Đáp án đặt NGAY SAU đề (positional thứ 2, ưu tiên) hoặc dap-an: (cũ): +// #cau-sa([Đề...], [$6$], loi-giai: [...]) +// show-boxes/box-count: hiện/số ô điền (bí danh cũ o-dien vẫn chạy). +#let cau-sa( + cau, ..tra-loi, dap-an: none, diem: none, o-dien: 4, hinh: none, + lo-da: none, loi-giai: none, lo-giai: auto, + fig-pos: "right", fig-width: auto, show-boxes: true, box-count: auto, + lines: 0, num: auto, prefix: "Câu", boxed: false, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, +) = { + _ghi-da("tln", if tra-loi.pos().len() > 0 { tra-loi.pos().first() } else { dap-an }) + _khung-cau(boxed, { + let hinh-giai = if hinh-giai != none { hinh-giai } else { fig-giai } + let dap-an = if tra-loi.pos().len() > 0 { tra-loi.pos().first() } else { dap-an } + let so-o = if box-count == auto { o-dien } else { box-count } + voi-hinh({ _dau-cau(diem: diem, prefix: prefix, num: num); cau }, hinh, + vi-tri: fig-pos, be-rong: fig-width, duoi: { + context v(6pt * _he-so-gian()) + context { + if _da-hien(lo-da) and dap-an != none { + text(weight: "bold", fill: _luc)[Đáp án: ] + text(fill: _luc, dap-an) + } else if show-boxes and _hien-o() { + [Trả lời: ] + box(baseline: 32%, stack( + dir: ltr, spacing: 4pt, + ..range(so-o).map(k => rect(width: 21pt, height: 21pt, stroke: 0.8pt + _xanh, radius: 2pt)), + )) + } + } + }) + _chua-dong(lines, lo-da) + _hien-giai(loi-giai, if lo-giai == auto { lo-da } else { lo-giai }, + hinh: hinh-giai, vi-tri: fig-giai-pos, be-rong: fig-giai-width) + }, + ) +} + +// ---------- Thân chung cho các dạng "kiểu tự luận" (TL / HĐ / VDTT) ---------- +// dau: thẻ đầu câu; loi-giai: chỉ hiện khi bật đáp án; +// cho-trong: chừa chỗ trống khi in đề. +#let _than-tl(dau, cau, loi-giai, cho-trong, lo-da, hinh: none, + fig-pos: "right", fig-width: auto, + hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto) = context block( + width: 100%, above: 10pt * _he-so-gian(), below: 10pt * _he-so-gian(), + { + voi-hinh({ dau; cau }, hinh, vi-tri: fig-pos, be-rong: fig-width) + context { + if _da-hien(lo-da) and loi-giai != none { + _khoi-giai(loi-giai, hinh: hinh-giai, vi-tri: fig-giai-pos, be-rong: fig-giai-width) + } else { + let t = _trong-tl.get() + if t.bat { + let cao = if cho-trong > 0pt { cho-trong } else { t.cao } + if cao > 0pt { v(cao) } + } + } + } + }, +) + +// Thẻ đầu câu có nhãn + bộ đếm + màu RIÊNG (HĐ, Vận dụng). +#let _dau-rieng(nhan, bd, mau, diem) = { + _the([#nhan#_so-moi(bd)], mau: mau) + if diem != none { + text(weight: "bold", fill: mau, size: 0.86em)[ (#diem điểm)] + } + h(6pt) +} + +// Thẻ đầu câu KHÔNG đánh số + màu RIÊNG (Khám phá, Trải nghiệm, Thảo luận — +// dạng hoạt động một-lần của SGK, nhãn không mang số thứ tự). +#let _dau-nhan(nhan, mau, diem) = { + _the([#nhan], mau: mau) + if diem != none { + text(weight: "bold", fill: mau, size: 0.86em)[ (#diem điểm)] + } + h(6pt) +} + +// ---------- TL: tự luận ---------- +#let cau-tl(cau, loi-giai: none, diem: none, cho-trong: 0pt, hinh: none, lo-da: none, + fig: none, fig-pos: "right", fig-width: auto, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto) = _than-tl( + _dau-cau(diem: diem), cau, loi-giai, cho-trong, lo-da, + hinh: if hinh != none { hinh } else { fig }, + fig-pos: fig-pos, fig-width: fig-width, + hinh-giai: if hinh-giai != none { hinh-giai } else { fig-giai }, + fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, +) + +// ---------- HĐ: hoạt động (khởi động / khám phá kiến thức) ---------- +// Thẻ "HĐ1", "HĐ2"... — bộ đếm riêng, hình thức như TL (dùng được cot-item +// trong thân câu khi có nhiều ý hỏi). +#let _cam-hd = rgb("#d35400") +#let cau-hd(cau, loi-giai: none, diem: none, cho-trong: 0pt, hinh: none, lo-da: none, + fig: none, fig-pos: "right", fig-width: auto, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto) = _than-tl( + _dau-rieng([HĐ], _bd-hd, _cam-hd, diem), cau, loi-giai, cho-trong, lo-da, + hinh: if hinh != none { hinh } else { fig }, + fig-pos: fig-pos, fig-width: fig-width, + hinh-giai: if hinh-giai != none { hinh-giai } else { fig-giai }, + fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, +) + +// ---------- LT: luyện tập (củng cố lý thuyết vừa học) ---------- +// Thẻ "Luyện tập 1", "Luyện tập 2"... — DÙNG CHUNG bộ đếm với khung +// #luyen-tap (slide.typ) nên số thứ tự liên tục dù trộn hai kiểu. +#let _vang-lt = rgb("#b9770e") +#let cau-lt(cau, loi-giai: none, diem: none, cho-trong: 0pt, hinh: none, lo-da: none, + fig: none, fig-pos: "right", fig-width: auto, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto) = _than-tl( + _dau-rieng([Luyện tập~], _bd-lt, _vang-lt, diem), cau, loi-giai, cho-trong, lo-da, + hinh: if hinh != none { hinh } else { fig }, + fig-pos: fig-pos, fig-width: fig-width, + hinh-giai: if hinh-giai != none { hinh-giai } else { fig-giai }, + fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, +) + +// ---------- VDTT: vận dụng thực tế ---------- +// Thẻ "Vận dụng 1", "Vận dụng 2"... — bộ đếm riêng, hình thức như TL. +#let _tim-vdtt = rgb("#8e44ad") +#let cau-vdtt(cau, loi-giai: none, diem: none, cho-trong: 0pt, hinh: none, lo-da: none, + fig: none, fig-pos: "right", fig-width: auto, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto) = _than-tl( + _dau-rieng([Vận dụng~], _bd-vdtt, _tim-vdtt, diem), cau, loi-giai, cho-trong, lo-da, + hinh: if hinh != none { hinh } else { fig }, + fig-pos: fig-pos, fig-width: fig-width, + hinh-giai: if hinh-giai != none { hinh-giai } else { fig-giai }, + fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, +) + +// ---------- 3 HÌNH THỨC HOẠT ĐỘNG SGK — KHÔNG đánh số (Kết nối tri thức) ---- +// Nhãn "Khám phá" / "Trải nghiệm" / "Thảo luận" như thẻ HĐ nhưng KHÔNG mang số +// thứ tự (hoạt động một-lần); hình thức thân như TL (dùng được cot-item, hình, +// lời giải, lo-da…). Mỗi loại một màu riêng. +#let _luc-kham-pha = rgb("#16a085") // xanh lục (khám phá) +#let cau-kham-pha(cau, loi-giai: none, diem: none, cho-trong: 0pt, hinh: none, lo-da: none, + fig: none, fig-pos: "right", fig-width: auto, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto) = _than-tl( + _dau-nhan([Khám phá], _luc-kham-pha, diem), cau, loi-giai, cho-trong, lo-da, + hinh: if hinh != none { hinh } else { fig }, + fig-pos: fig-pos, fig-width: fig-width, + hinh-giai: if hinh-giai != none { hinh-giai } else { fig-giai }, + fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, +) + +#let _luc-trai-nghiem = rgb("#27ae60") // xanh lá (trải nghiệm) +#let cau-trai-nghiem(cau, loi-giai: none, diem: none, cho-trong: 0pt, hinh: none, lo-da: none, + fig: none, fig-pos: "right", fig-width: auto, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto) = _than-tl( + _dau-nhan([Trải nghiệm], _luc-trai-nghiem, diem), cau, loi-giai, cho-trong, lo-da, + hinh: if hinh != none { hinh } else { fig }, + fig-pos: fig-pos, fig-width: fig-width, + hinh-giai: if hinh-giai != none { hinh-giai } else { fig-giai }, + fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, +) + +#let _lam-thao-luan = rgb("#2980b9") // xanh dương (thảo luận) +#let cau-thao-luan(cau, loi-giai: none, diem: none, cho-trong: 0pt, hinh: none, lo-da: none, + fig: none, fig-pos: "right", fig-width: auto, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto) = _than-tl( + _dau-nhan([Thảo luận], _lam-thao-luan, diem), cau, loi-giai, cho-trong, lo-da, + hinh: if hinh != none { hinh } else { fig }, + fig-pos: fig-pos, fig-width: fig-width, + hinh-giai: if hinh-giai != none { hinh-giai } else { fig-giai }, + fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, +) diff --git a/packages/preview/conic-toan/0.3.4/lib/che-do.typ b/packages/preview/conic-toan/0.3.4/lib/che-do.typ new file mode 100644 index 0000000000..4d207cd50a --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/che-do.typ @@ -0,0 +1,1394 @@ +// ===================================================================== +// che-do.typ — MỘT FILE NGUỒN, BA KIỂU PDF (hồ sơ hiển thị) +// +// "dethi" : dạng đề thi/sách A4 — MC, TF, SA, TL ẩn đáp án + lời giải; +// riêng VÍ DỤ vẫn hiện lời giải. +// "loigiai" : dạng A4 — tất cả hiện lời giải, đánh dấu đáp án. +// "beamer" : trình chiếu — MỖI CÂU MỘT SLIDE: hiện đề → lời giải hiện +// dần theo từng dấu \ (xuống dòng) → cuối cùng đánh dấu đáp án. +// (Tên cũ "de-thi", "loi-giai" vẫn được chấp nhận.) +// +// Cách dùng (xem de-mau.typ): +// #let ho-so = sys.inputs.at("ho-so", default: "dethi") +// #show: de-toan.with(ho-so: ho-so, tieu-de: [...], ...) +// rồi gọi THẲNG #vd/#tn/#ds/#tln/#tl/#hd/#lt/#vdtt/#phan — các hàm tự +// nhận biết hồ sơ hiển thị, KHÔNG cần khai báo gì thêm. +// (tn = trắc nghiệm, ds = đúng sai, tln = trả lời ngắn, tl = tự luận, +// hd = hoạt động, lt = luyện tập, vdtt = vận dụng thực tế; +// tên cũ mc/tf/sa và dòng tao-cau-hoi cũ vẫn dùng được) +// ===================================================================== +#import "slide.typ": slide, muc, bai-giang, lo, vi-du, loi-giai, _ho-so, _la-sach, _buoc-ht, gian-dong, _gd, _dat-gian, _he-so-gian, _gian-ht, _bd-cau, cao-that, _cao-that, _chong-net, voi-cao-that, _vung-than +#import "cau-hoi.typ": cau-mc, cau-tf, cau-sa, cau-tl, cau-hd, cau-lt, cau-vdtt, cau-kham-pha, cau-trai-nghiem, cau-thao-luan, bat-dap-an, tat-dap-an, voi-hinh, True, Dung, _la-y + +// Gộp tham số kiểu mới / kiểu cũ: ưu tiên giá trị kiểu mới nếu được đặt. +#let _uu-tien(moi, cu) = if moi != none { moi } else { cu } + +#let _xanh = rgb("#0f4c81") +#let _luc = rgb("#1e8449") +#let _do = rgb("#a93226") +// giữ tham chiếu đến môi trường lời giải (vì tham số cùng tên sẽ che nó) +#let _mt-loi-giai = loi-giai + +// Mã đề khai ở de-toan -> lưu vào state để #bang-dap-an tự lấy (khỏi nhập tay). +#let _ma-de = state("bg-ma-de", none) + +// ===================================================================== +// HOÁN VỊ (TRỘN ĐỀ) — công tắc đặt Ở ĐẦU FILE ĐỀ +// --------------------------------------------------------------------- +// Cách dùng (khung một file ngân hàng / một đề thi): +// #let ho-so = sys.inputs.at("ho-so", default: "dethi") +// #let hoan-vi = false // <- ĐỔI THÀNH true LÀ TRỘN +// #show: de-toan.with(ho-so: ho-so, ma-de: "0101", hoan-vi: hoan-vi) +// +// NGUYÊN TẮC (chốt 08/2026): +// • Hoán vị các CÂU trong cùng MỘT NHÓM THỂ LOẠI: nhóm tn xáo với nhau, +// nhóm ds xáo với nhau, nhóm tln xáo với nhau. Câu tl GIỮ NGUYÊN thứ tự. +// "Nhóm" = dãy câu CÙNG LOẠI đứng liền nhau (chỉ cách nhau bằng dòng +// trống). Gặp #phan, một đoạn văn xen giữa, một câu tl... là hết nhóm và +// sang nhóm mới ⇒ câu KHÔNG BAO GIỜ nhảy qua tiêu đề phần. +// • Hoán vị 4 PHƯƠNG ÁN A/B/C/D của câu tn. Các Ý a/b/c/d của câu ds GIỮ +// NGUYÊN, KHÔNG hoán vị (ý sau thường dựa vào ý trước). +// • Chỉ chạy ở bản in A4 (dethi/loigiai); trình chiếu beamer không trộn. +// • dethi và loigiai cùng mầm ⇒ RA CÙNG MỘT THỨ TỰ, bảng đáp án luôn khớp. +// +// hoan-vi: false (mặc định) | true (trộn cả câu lẫn phương án) +// | "cau" (chỉ trộn thứ tự câu) | "pa" (chỉ trộn phương án) +// mam: MÃ TRỘN — auto (mặc định) = băm từ `ma-de`, nên hai mã đề 0101 / +// 0102 tự cho hai thứ tự khác nhau; đặt tay `mam: 7` để ghi đè. +// +// GIỚI HẠN đã biết: +// • Câu tn viết theo FORM CŨ `dap-an: "B"` (chữ cái) KHÔNG được trộn phương +// án — trộn sẽ lệch đáp án. Muốn trộn thì bọc phương án đúng bằng True(...). +// • `khoa-pa: true` ở một câu tn ⇒ riêng câu đó giữ nguyên thứ tự phương án. +// ===================================================================== + +// ---------- Bộ sinh số giả ngẫu nhiên TẤT ĐỊNH (LCG 32 bit) ---------- +// Typst không có random. Dùng LCG để CÙNG một mầm luôn cho CÙNG một kết quả — +// nhờ vậy bản dethi và bản loigiai (hai lần biên dịch khác nhau) trộn giống hệt. +#let _hv-m = 2147483648 +#let _hv-tiep(s) = calc.rem(1103515245 * s + 12345, _hv-m) + +// Chuẩn hoá mầm: số / chuỗi / nội dung / none -> số nguyên không âm (băm djb2). +#let _hv-mam(x) = { + if x == none or x == auto { return 20250101 } + if type(x) == int { return calc.rem(calc.abs(x), _hv-m) } + let s = if type(x) == str { x } else { repr(x) } + let h = 5381 + for c in s.clusters() { + h = calc.rem(h * 33 + str.to-unicode(c), _hv-m) + } + h +} + +// Xáo một mảng theo mầm — RÚT ngẫu nhiên không hoàn lại (khỏi gán theo chỉ số). +#let _hv-xao(mang, mam) = { + let con = mang + let kq = () + let s = _hv-tiep(_hv-mam(mam) + 1013904223) + while con.len() > 0 { + s = _hv-tiep(s) + let j = calc.rem(calc.quo(s, 65536), con.len()) + kq.push(con.at(j)) + con = con.slice(0, j) + con.slice(j + 1) + } + kq +} + +// ---------- Nhãn nhận diện câu (đánh dấu chỗ được phép xáo) ---------- +#let _hv-nhan = (tn: , ds: , tln: ) +#let _gan-tn(c) = [#c ] +#let _gan-ds(c) = [#c ] +#let _gan-tln(c) = [#c ] + +// Phần tử "trắng" giữa hai câu (dòng trống, xuống dòng, khoảng trắng, +// metadata) — bỏ qua khi gom nhóm và LUÔN giữ nguyên vị trí lúc ghép lại. +#let _hv-f-space = [ ].func() +#let _hv-trong(c) = { + if type(c) == str { return c.trim() == "" } + if type(c) != content { return false } + let f = c.func() + f == _hv-f-space or f == parbreak or f == linebreak or f == metadata +} + +// Loại của một phần tử: "tn" | "ds" | "tln" | none. +// Nếu markup bọc câu trong một sequence MỘT phần tử thì bóc thêm một lớp. +#let _hv-loai(c) = { + if type(c) != content { return none } + let l = c.at("label", default: none) + if l == _hv-nhan.tn { return "tn" } + if l == _hv-nhan.ds { return "ds" } + if l == _hv-nhan.tln { return "tln" } + if l == none and c.has("children") { + let ct = c.children.filter(x => not _hv-trong(x)) + if ct.len() == 1 { return _hv-loai(ct.first()) } + } + none +} + +// ---------- Xáo THỨ TỰ CÂU trong thân tài liệu ---------- +// Duyệt các phần tử cấp cao nhất, gom từng DÃY LIỀN NHAU cùng loại rồi hoán vị +// các phần tử của dãy đó TẠI CHỖ (vị trí nào là câu tn thì sau khi trộn vẫn là +// câu tn). Phần tử không nhận diện được giữ nguyên vị trí ⇒ nếu vì lý do nào đó +// không nhận ra câu, tài liệu vẫn dựng đúng y như khi tắt trộn. +#let _hv-xao-than(than, mam) = { + if type(than) != content or not than.has("children") { return than } + let cs = than.children + let n = cs.len() + let kq = () + let nhom = 0 + let i = 0 + while i < n { + let loai = _hv-loai(cs.at(i)) + if loai == none { + kq.push(cs.at(i)) + i = i + 1 + } else { + // gom dãy: các câu CÙNG loại, chỉ được cách nhau bằng phần tử trắng + let vt = () + let cuoi = i + let j = i + while j < n { + let lj = _hv-loai(cs.at(j)) + if lj == loai { + vt.push(j) + cuoi = j + j = j + 1 + } else if _hv-trong(cs.at(j)) { + j = j + 1 + } else { + break + } + } + nhom = nhom + 1 + let cac = vt.map(k => cs.at(k)) + let moi = if cac.len() > 1 { _hv-xao(cac, _hv-mam(mam) + nhom * 7919) } else { cac } + let d = 0 + for k in range(i, cuoi + 1) { + if vt.contains(k) { + kq.push(moi.at(d)) + d = d + 1 + } else { + kq.push(cs.at(k)) + } + } + i = cuoi + 1 + } + } + if kq.len() == 0 { than } else { kq.join() } +} + +// Trạng thái để #tn biết có xáo phương án hay không (và xáo theo mầm nào). +// bat: xáo PHƯƠNG ÁN · cau: xáo THỨ TỰ CÂU · mam: mầm chung. +// Khoá `cau` để các lệnh bố cục (#chia-2-cot, #chia-2-cot-lech) tự xáo lại +// phần thân NẰM TRONG cột — vì thân đó đã bị bọc thành MỘT phần tử nên vòng +// duyệt ở #de-toan không nhìn thấy các câu bên trong. +#let _hv = state("bg-hoan-vi", (bat: false, cau: false, mam: 0)) + +// Tách công tắc hoan-vi thành hai cờ: xáo CÂU và xáo PHƯƠNG ÁN. +#let _hv-che(h) = ( + cau: h == true or h == "cau" or h == "ca-hai", + pa: h == true or h == "pa" or h == "ca-hai", +) + +// Dùng như một show-rule RIÊNG khi tài liệu không đi qua #de-toan: +// #show: hoan-vi-de.with(true) +// #show: hoan-vi-de.with(true, mam: "0102") +// (Đi qua #de-toan thì chỉ cần tham số hoan-vi: — khỏi gọi hàm này.) +#let hoan-vi-de(bat, mam: auto, body) = { + let che = _hv-che(bat) + let m = _hv-mam(mam) + _hv.update((bat: che.pa, cau: che.cau, mam: m)) + if che.cau { _hv-xao-than(body, m) } else { body } +} + +// ---------- Tách lời giải thành từng dòng ---------- +// Nhận nội dung có dấu \ (hoặc mảng nội dung), trả về mảng các dòng. +#let tach-dong(nd) = { + if nd == none { return () } + if type(nd) == array { return nd } + if type(nd) == content and nd.has("children") { + let phan = () + let dong = () + for c in nd.children { + if c.func() == linebreak or c.func() == parbreak { + phan.push(dong.join()) + dong = () + } else { + dong.push(c) + } + } + phan.push(dong.join()) + return phan.filter(p => p != none) + } + (nd,) +} + +// ---------- Ngắt lời giải dài sang slide mới (chỉ tác dụng ở beamer) ---------- +// Đặt #sang-man trên MỘT DÒNG RIÊNG trong lời giải: +// loi-giai: [dòng 1 \ dòng 2 \ #sang-man \ dòng 3 \ ...] +// => "dòng 3" trở đi chuyển sang slide kế tiếp (nhãn "Hướng dẫn giải (tiếp)"), +// tránh lời giải tràn trang làm sinh trang đệm trắng khi hiện từng bước. +// Ở bản A4 ("dethi"/"loigiai"), dấu này được bỏ qua. +#let sang-man = metadata("bg-sang-man") + +#let _co-sang-man(d) = { + if type(d) != content { return false } + if d.func() == metadata { return d.value == "bg-sang-man" } + if d.has("children") { d.children.any(c => _co-sang-man(c)) } else { false } +} + +#let _khoang-trang = [ ].func() + +// Dòng "rỗng" — chỉ gồm khoảng trắng / dấu ngắt / metadata (không in ra gì). +#let _dong-trong(d) = { + if d == none { return true } + if type(d) != content { return false } + let f = d.func() + if f == _khoang-trang or f == linebreak or f == parbreak or f == metadata { + return true + } + if f == text { return d.text.trim() == "" } + if d.has("children") { return d.children.all(c => _dong-trong(c)) } + false +} + +// Cắt MỘT dòng tại dấu #sang-man đầu tiên -> (trước, sau, có-cắt-được). +// ⚠️ Lỗi cũ (09/08/2026): `tach-man` coi CẢ DÒNG chứa dấu là dấu ngắt rồi +// VỨT ĐI. Người soạn viết `... hết ý.` xuống dòng rồi `#sang-man \` (không có +// dấu \ ở cuối dòng chữ) thì cả câu chữ đó BIẾN MẤT khỏi trình chiếu. Nay cắt +// đôi tại dấu, giữ nguyên phần chữ hai bên. +// Chỉ cắt ở lớp con TRỰC TIẾP để không bóc mất lớp bọc (strong, emph...); +// dấu nằm sâu hơn thì trả `false` và dòng được giữ nguyên (dấu vô hình, chỉ +// mất tác dụng ngắt màn) — thà không ngắt còn hơn mất chữ. +#let _cat-sang-man(d) = { + if type(d) != content { return (d, none, false) } + if d.func() == metadata { + return if d.value == "bg-sang-man" { (none, none, true) } else { (d, none, false) } + } + if not d.has("children") { return (d, none, false) } + let truoc = () + let sau = () + let co = false + for c in d.children { + if co { sau.push(c) } + else if (type(c) == content and c.func() == metadata + and c.value == "bg-sang-man") { co = true } + else { truoc.push(c) } + } + if not co { (d, none, false) } else { (truoc.join(), sau.join(), true) } +} + +// Tách lời giải thành các màn; mỗi màn là một mảng dòng. +#let tach-man(nd) = { + let man = () + let ht = () + for d in tach-dong(nd) { + let con = d + let dau = true // lần lặp đầu: `con` còn là NGUYÊN dòng ban đầu + while con != none { + let (truoc, sau, co) = _cat-sang-man(con) + // mảnh cắt ra chỉ toàn khoảng trắng thì bỏ; dòng nguyên thì giữ y như cũ + if (dau and not co) or not _dong-trong(truoc) { ht.push(truoc) } + if not co { con = none } else { + if ht.len() > 0 { man.push(ht) } + ht = () + con = sau + } + dau = false + } + } + if ht.len() > 0 { man.push(ht) } + if man.len() == 0 { ((),) } else { man } +} + +// Ghép lại thành một khối (dùng cho bản A4) — bỏ các dấu #sang-man nhưng +// GIỮ nguyên chữ nằm cùng dòng với dấu (đi chung một đường với tach-man). +#let _ghep(nd) = { + if nd == none { return none } + let dong = () + for mk in tach-man(nd) { dong += mk } + if dong.len() == 0 { return none } + dong.join(linebreak()) +} + +// ---------- Lời giải hiện dần từng dòng (dùng trong slide) ---------- +// Khung hiện từ bước `tu`; dòng thứ i hiện ở bước tu + i. +// gian: giãn dòng trong lời giải — ĐỘ DÀI tuyệt đối (mốc 0.95em, ~150% so với +// thân slide 0.62em) để phân số, căn thức chồng tầng không dính vào nhau. +// `auto` (mặc định) = 0.95em × hệ số giãn dòng hiện hành. +// gian-dong: HỆ SỐ giãn dòng riêng cho khung này (auto = lấy hệ số toàn tài +// liệu do #bai-giang/#de-toan/#gian-dong đặt). +// do: true -> dựng SẴN mọi bước (không qua #lo) để ĐO chiều cao lúc cuối bài; +// dùng cho bộ tự ngắt màn, không phải để hiển thị. +#let giai-buoc(nd, tu: 2, nhan: [Hướng dẫn giải. ], gian: auto, gian-dong: auto, + do: false) = { + let dong = tach-dong(nd) + if dong.len() == 0 { return } + let khung = block( + width: 100%, inset: (left: 11pt, top: 5pt, bottom: 5pt), + stroke: (left: 2.5pt + _luc), above: 10pt, + context { + let k = if gian-dong == auto { _he-so-gian() } else { gian-dong } + let g = if gian == auto { 0.95em * k } else { gian } + set par(leading: g, spacing: _gd.get().at("doan", default: 1.2em) * k) + align(center, text(fill: _luc, weight: "bold", size: 0.84em, nhan)) + dong.at(0) + for i in range(1, dong.len()) { + let b = block(above: g, dong.at(i)) + if do { b } else { lo(tu + i, b) } + } + }, + ) + if do { khung } else { lo(tu, khung) } +} + +// ---------- Ghi đè giãn dòng cho MỘT câu ---------- +// gd == auto -> giữ nguyên; ngược lại nhân hệ số gd vào leading NỀN của hồ sơ. +#let _voi-gian(gd, nd) = if gd == auto { nd } else { + context { + let g = _gd.get() + // giãn CẢ khoảng cách dòng lẫn khoảng cách đoạn (dòng trống trong nội dung) + set par(leading: g.nen * gd, spacing: g.at("doan", default: 1.2em) * gd) + nd + } +} + +// ---------- Thiết lập tài liệu theo hồ sơ ---------- +// Chuẩn hoá tên hồ sơ: "de-thi" -> "dethi", "LoiGiai" -> "loigiai"... +#let _chuan-ho-so(hs) = lower(hs).replace("-", "").replace("_", "") + +#let de-toan( + ho-so: "dethi", + tieu-de: [ĐỀ KIỂM TRA], + tieu-de-ngan: none, // tên bài rút gọn ở header mọi slide (chỉ dùng ở beamer) + nen: "trang", // nền slide beamer: "trang" | "kem" | "xanh-nhat" + // | "luc-nhat" | "xam" | màu tuỳ ý (bản A4 luôn trắng) + mon: none, // vd [MÔN TOÁN 12] + thoi-gian: none, // vd "90 phút" + truong: none, // vd [SỞ GD&ĐT ... \ TRƯỜNG THPT ...] + ma-de: none, + // ----- 3 thông tin header đề thi: CHỈ hiện khi được khai báo ----- + hien-ho-ten: false, // true -> dòng "Họ và tên thí sinh: ..." + sbd: none, // hiện dòng số báo danh / lớp: + // none | false -> ẩn + // true | "sbd" -> "Số báo danh: ..." + // "lop" -> "Lớp: ..." + hien-ma-de: false, // true -> ô "Mã đề ..." (dùng giá trị ma-de nếu có) + thong-tin-hs: true, // true: đề thi thật — có "(Đề thi có N trang)" (bản dethi) + // và "(Đề thi có N trang)"; false: tài liệu bài học + ti-le-chu: 1.0, // HỆ SỐ PHÓNG CỠ CHỮ THÂN NỘI DUNG (toàn file) — + // 1.0 giữ nguyên; >1 to hơn, <1 nhỏ đi. Áp dụng cho + // cả 3 hồ sơ (beamer + A4 dethi/loigiai). + gian-dong: 1.0, // HỆ SỐ GIÃN DÒNG (toàn file) — 1.0 = mốc mặc định + // (A4 đề thi 0.65em, trình chiếu 0.62em); 1.25 = giãn + // thêm 25%; 0.9 = thu lại 10%. Tăng khi phân số/căn + // thức nhiều tầng làm hai dòng dính nhau. Đổi giữa + // bài: #gian-dong(1.4); riêng một câu: tham số + // gian-dong: của #vd/#tn/#ds/#tln/#tl/#hd/#lt/#vdtt. + mau-cong-thuc: auto, // MÀU MỌI CÔNG THỨC trong $...$ (toàn file) — auto = thừa + // kế màu chữ (đen ở thân, trắng ở tiêu đề); đặt màu cụ + // thể để nhuộm tất cả. Dùng chung cho cả 3 hồ sơ. + hoan-vi: false, // TRỘN ĐỀ — xem khối "HOÁN VỊ" ở đầu file này: + // false (mặc định) = giữ nguyên thứ tự soạn; + // true = trộn thứ tự CÂU (theo nhóm tn/ds/tln, câu + // tl giữ nguyên) VÀ trộn phương án A/B/C/D + // của câu tn (ý a/b/c/d của ds giữ nguyên); + // "cau" = chỉ trộn thứ tự câu; + // "pa" = chỉ trộn phương án. + // Chỉ tác dụng ở bản in A4 (dethi/loigiai). + mam: auto, // MÃ TRỘN: auto = băm từ `ma-de` (mã đề khác ⇒ thứ tự + // khác); hoặc số/chuỗi tuỳ ý để ghi đè. + phu-de: none, gv: none, ngay: none, + body, +) = { + let hs = _chuan-ho-so(ho-so) + _ma-de.update(ma-de) // lưu mã đề để #bang-dap-an tự đồng bộ + // ----- Hoán vị (trộn đề): chỉ ở bản in A4, beamer giữ nguyên bài giảng ----- + let _che = _hv-che(hoan-vi) + let _mam = _hv-mam(if mam == auto { ma-de } else { mam }) + _hv.update((bat: _che.pa and hs != "beamer", cau: _che.cau and hs != "beamer", mam: _mam)) + let body = if _che.cau and hs != "beamer" { _hv-xao-than(body, _mam) } else { body } + if hs == "beamer" { + bai-giang(tieu-de: tieu-de, tieu-de-ngan: tieu-de-ngan, nen: nen, + ti-le-chu: ti-le-chu, gian-dong: gian-dong, mau-cong-thuc: mau-cong-thuc, + phu-de: phu-de, gv: gv, ngay: ngay, { + bat-dap-an() + body + }) + } else { + // Ghi hồ sơ vào trạng thái để các hàm câu hỏi (vd/tn/ds/...) tự nhận + // biết đang ở bản A4; mọi #lo(n) trên bản in đều hiện đủ. + _ho-so.update("sach" + hs) + _buoc-ht.update(1000000) + set page( + paper: "a4", + margin: (x: 1.8cm, top: 1.6cm, bottom: 2.2cm), + footer: context align(center, text(size: 9pt, fill: luma(35%), { + [Trang #counter(page).display() / #counter(page).final().first()] + if ma-de != none { [ — Mã đề #ma-de] } + })), + ) + set text(size: 11pt * ti-le-chu, lang: "vi") + show math.equation: it => { + if mau-cong-thuc == auto { it } else { + set text(fill: mau-cong-thuc) + it + } + } + // 0.65em = leading mặc định của Typst -> gian-dong: 1.0 giữ y nguyên bố cục. + _dat-gian(0.65em, gian-dong) + set par(justify: true, leading: 0.65em * gian-dong, spacing: 1.2em * gian-dong) + // Công thức TRONG DÒNG khai đúng chiều cao nét vẽ (phân số, căn, chỉ số + // chồng tầng) ⇒ dòng VÀ ô lưới phương án tự nới đúng chỗ cần, khỏi phải + // đôn `gian-dong` lên 3 rồi làm trống hoác chỗ khác. Tắt: #cao-that(false). + show math.equation.where(block: false): _chong-net + if hs == "loigiai" { bat-dap-an() } else { tat-dap-an() } + // ----- đầu đề thi ----- + grid( + columns: (1fr, 1.25fr), column-gutter: 14pt, + align(center, { + if truong != none { + text(weight: "bold", size: 10pt, truong) + v(3pt) + } + // Dòng "(Đề thi có N trang)" chỉ dành cho đề thi thật — + // tài liệu kiểu bài học đặt thong-tin-hs: false là bỏ. + if thong-tin-hs { + text(size: 9pt, style: "italic")[(Đề thi có #context [#counter(page).final().first()] trang)] + } + }), + align(center, { + text(weight: "bold", size: 12.5pt, fill: _xanh, tieu-de) + if mon != none { + linebreak() + text(weight: "bold", size: 11pt, mon) + } + if thoi-gian != none { + linebreak() + text(size: 9pt, style: "italic")[Thời gian làm bài: #thoi-gian] + } + if ngay != none and hs == "dethi" { + linebreak() + text(size: 9pt, style: "italic")[Ngày kiểm tra: #ngay] + } + if hs == "loigiai" { + linebreak() + text(weight: "bold", size: 10.5pt, fill: _do)[ĐÁP ÁN VÀ LỜI GIẢI CHI TIẾT] + } + }), + ) + // ----- 3 thông tin tuỳ chọn: chỉ hiện mục nào được khai báo ----- + let hien-sbd = sbd != none and sbd != false + let co-trai = hien-ho-ten or hien-sbd + if hs == "dethi" and (co-trai or hien-ma-de) { + v(10pt) + let cot-trai = { + let dong = () + if hien-ho-ten { + dong.push([Họ và tên thí sinh: #box(width: 1fr, repeat[.])]) + } + if hien-sbd { + let nhan-sbd = if sbd == "lop" [Lớp] else [Số báo danh] + dong.push([#nhan-sbd: #box(width: 1fr, repeat[.])]) + } + dong.join(v(4pt)) + } + let o-ma-de = rect(inset: 8pt, stroke: 0.8pt, + text(weight: "bold")[Mã đề #if ma-de == none [......] else [#ma-de]]) + if co-trai and hien-ma-de { + grid(columns: (1fr, auto), column-gutter: 12pt, + cot-trai, align(horizon, o-ma-de)) + } else if co-trai { + cot-trai + } else { + align(right, o-ma-de) + } + } + v(8pt) + line(length: 100%, stroke: 0.7pt) + v(4pt) + body + } +} + +// ---------- Bộ hàm câu hỏi theo hồ sơ ---------- +// Trả về (vd, tn, ds, tln, tl, phan) có CÙNG cách gọi ở cả 3 hồ sơ +// (kèm bí danh cũ mc/tf/sa để tương thích ngược). +// Trong lời giải, mỗi dấu \ là một bước xuất hiện (chỉ tác dụng ở beamer). +// tieu-de: tiêu đề slide của câu (chỉ dùng ở beamer). +// Các slide "(tiếp)" cho những màn lời giải sau màn đầu. +// nhan-dau: nhãn của màn ĐẦU TIÊN khi màn 0 rỗng (đề dài quá nên lời giải bị +// đẩy hết sang đây) — lúc đó chưa có gì để "tiếp", ghi nhãn thường. +#let _man-tiep(man, tieu-de, nhan: [Hướng dẫn giải (tiếp). ], + nhan-dau: [Hướng dẫn giải. ], gd: auto) = { + for (i, mk) in man.slice(1).enumerate() { + let nh = if i == 0 and man.at(0).len() == 0 { nhan-dau } else { nhan } + slide(tieu-de: tieu-de, so-buoc: calc.max(1, mk.len()))[ + #_voi-gian(gd, giai-buoc(mk, tu: 1, nhan: nh, gian-dong: gd)) + ] + } +} + +// ---------- 8 DẠNG CÂU DÙNG TRỰC TIẾP Ở MỌI CHẾ ĐỘ ---------- +// Các hàm #vd/#tn/#ds/#tln/#tl/#hd/#lt/#vdtt/#phan tự đọc hồ sơ hiển thị +// (do bai-giang/de-toan ghi vào trạng thái) rồi chọn cách dựng: +// beamer -> mỗi câu một slide, lời giải hiện dần theo từng dấu \; +// A4 -> khối liên tục (dethi ẩn/loigiai hiện đáp án). +// KHÔNG cần khai báo #let (...) = tao-cau-hoi(...) nữa — import là dùng. +// Trình tự beamer: bước 1 hiện đề — bước 2.. lời giải từng dòng — đánh dấu +// đáp án ở BƯỚC CUỐI màn đầu; các màn #sang-man thành slide "(tiếp)". + +// Hình kèm LỜI GIẢI (mọi dạng câu): fig-giai (bí danh hinh-giai) + +// fig-giai-pos/fig-giai-width — bố cục 2 cột như hình kèm đề; ở beamer +// hình hiện từ bước đầu tiên của lời giải, các màn #sang-man sau muốn có +// hình thì chèn trực tiếp trong nội dung màn đó. +#let _giai-kem-hinh(nd, hg, vi-tri, be-rong, do: false) = voi-hinh( + nd, + if hg == none { none } else if do { hg } else { lo(2, hg) }, + vi-tri: vi-tri, be-rong: be-rong, +) + +// ---------- TỰ NGẮT MÀN KHI LỜI GIẢI TRÀN TRANG (chỉ beamer) ---------- +// Bệnh cũ: lời giải cao hơn thân slide thì Typst đẩy phần thừa sang TRANG SAU, +// mà mỗi bước hoạt hình lại in slide LẠI TỪ ĐẦU ⇒ bấm chuyển bước thì thấy +// xen kẽ "đề bài + mấy dòng đầu" — "phần tràn" — "đề bài + mấy dòng đầu" … +// tức ĐỀ BÀI HIỆN LẶP LẠI giữa lời giải. Nay đo trước: dòng nào làm tràn thì +// tự đẩy sang một màn "(tiếp)" y như đặt #sang-man bằng tay. +// Tắt (giữ nếp cũ) cho cả tài liệu: #tu-ngat-man(false). +#let _tu-man = state("bg-tu-man", true) +#let tu-ngat-man(bat) = _tu-man.update(bat) + +// Cắt một màn thành nhiều màn vừa trang. `dung(dòng, có-đề)` dựng thử nội dung +// slide (đã hiện hết mọi bước) để đo. Tìm nhị phân số dòng lớn nhất còn vừa. +#let _cat-vua(dong, co-de, dung, vung) = { + if dong.len() == 0 { return (dong,) } + let vua(n) = measure(block(width: vung.rong, dung(dong.slice(0, n), co-de))).height <= vung.cao + if vua(dong.len()) { return (dong,) } + // Đề bài dài, chiếm gần hết slide: slide đầu CHỈ hiện đề, lời giải sang màn + // "(tiếp)" — thà một slide đề trơn còn hơn đề bị in lại giữa lời giải. + if co-de and not vua(1) { return ((),) + _cat-vua(dong, false, dung, vung) } + let a = 1 // màn tiếp luôn giữ ít nhất 1 dòng ⇒ chắc chắn tiến + let b = dong.len() // đã biết là KHÔNG vừa + while b - a > 1 { + let m = int((a + b) / 2) + if vua(m) { a = m } else { b = m } + } + (dong.slice(0, a),) + _cat-vua(dong.slice(a), false, dung, vung) +} + +#let _cat-man-vua(man, dung) = { + if not _tu-man.get() { return man } + let vung = _vung-than() + let kq = () + for (i, mk) in man.enumerate() { + if mk.len() == 0 { kq.push(mk) } else { kq += _cat-vua(mk, i == 0, dung, vung) } + } + kq +} + +#let vd(noi-dung, loi-giai: none, loigiai: none, diem: none, hinh: none, fig: none, + fig-pos: "right", fig-width: auto, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, + gian-dong: auto, tieu-de: [Ví dụ]) = context { + let loi-giai = _uu-tien(loigiai, loi-giai) + let hinh = _uu-tien(fig, hinh) + let hg = _uu-tien(fig-giai, hinh-giai) + let gd = gian-dong + if _la-sach(_ho-so.get()) { + // Ví dụ: LUÔN hiện lời giải, kể cả trong hồ sơ "dethi". + _voi-gian(gd, { + vi-du(voi-hinh(noi-dung, hinh, vi-tri: fig-pos, be-rong: fig-width)) + if loi-giai != none { + _mt-loi-giai(voi-hinh(_ghep(loi-giai), hg, vi-tri: fig-giai-pos, be-rong: fig-giai-width)) + } + }) + } else { + let dung(dg, co-de) = if co-de { + _voi-gian(gd, [ + #vi-du(voi-hinh(noi-dung, hinh, vi-tri: fig-pos, be-rong: fig-width)) + #_giai-kem-hinh(giai-buoc(dg, nhan: [Lời giải. ], gian-dong: gd, do: true), hg, fig-giai-pos, fig-giai-width, do: true) + ]) + } else { + _voi-gian(gd, giai-buoc(dg, tu: 1, nhan: [Lời giải (tiếp). ], + gian-dong: gd, do: true)) + } + let man = _cat-man-vua(tach-man(loi-giai), dung) + // màn đầu rỗng (đề dài, lời giải đã bị đẩy hết sang màn "(tiếp)") thì + // slide này chỉ cần MỘT bước — đừng in lại y hệt lần nữa. + slide(tieu-de: tieu-de, so-buoc: if man.at(0).len() > 0 { 1 + man.at(0).len() } + else if man.len() > 1 { 1 } else { 2 })[ + #_voi-gian(gd, [ + #vi-du(voi-hinh(noi-dung, hinh, vi-tri: fig-pos, be-rong: fig-width)) + #_giai-kem-hinh(giai-buoc(man.at(0), nhan: [Lời giải. ], gian-dong: gd), hg, fig-giai-pos, fig-giai-width) + ]) + ] + _man-tiep(man, tieu-de, nhan: [Lời giải (tiếp). ], nhan-dau: [Lời giải. ], gd: gd) + } +} + +// FORM MỚI (khuyến nghị — dễ hoán vị đáp án, xáo trộn tạo mã đề): +// #tn( +// [Nội dung đề bài], +// ( [$A$], True([$B$]), [$C$], [$D$] ), // đáp án đúng bọc True(...) +// loigiai: [Lời giải chi tiết], +// fig: none, fig-pos: "right", fig-width: 35%, // hình kèm đề +// // fig-pos: "right"|"left"|"center"|"top"|"bottom" +// // ("center" = canh giữa, nằm giữa phần đề và các phương án) +// cols: 0, // 0 = tự chọn số cột; 1/2/4 = cố định +// lines: 0, // số dòng chừa làm bài (bản in đề) +// num: auto, prefix: "Câu", boxed: false, +// ) +// Form cũ (dap-an: "B", hinh:, cot:, loi-giai:) vẫn dùng được. +#let tn( + cau, phuong-an, dap-an: none, cot: auto, cols: 0, diem: none, + hinh: none, fig: none, fig-pos: "right", fig-width: auto, + loi-giai: none, loigiai: none, lines: 0, num: auto, prefix: "Câu", + boxed: false, fig-giai: none, hinh-giai: none, cham: auto, khoa-pa: false, + fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, + trong-dong: auto, + tieu-de: [Trắc nghiệm], +) = _gan-tn(context { + let lg = _uu-tien(loigiai, loi-giai) + let hinh = _uu-tien(fig, hinh) + let hg = _uu-tien(fig-giai, hinh-giai) + let gd = gian-dong + let cot = if cols != 0 { cols } else { cot } + // ----- HOÁN VỊ PHƯƠNG ÁN A/B/C/D ----- + // Chỉ xáo khi phương án đúng được bọc True(...) (form mới) — form cũ + // `dap-an: "B"` gắn đáp án vào CHỮ CÁI nên xáo sẽ lệch. `khoa-pa: true` + // giữ nguyên riêng câu này. Mầm riêng từng câu = mầm chung + số thứ tự câu + // ⇒ mỗi câu một hoán vị khác nhau, mà vẫn tất định. + let _hvt = _hv.get() + let phuong-an = if (_hvt.bat and not khoa-pa and phuong-an.len() > 1 + and phuong-an.any(_la-y)) { + let _so = _bd-cau.goc.get() + _bd-cau.cnt.get().first() + _hv-xao(phuong-an, _hvt.mam + (_so + 1) * 104729) + } else { phuong-an } + let goi(..them) = cau-mc( + cau, phuong-an, dap-an: dap-an, cot: cot, diem: diem, hinh: hinh, cham: cham, + khoa-pa: khoa-pa, trong-dong: trong-dong, + fig-pos: fig-pos, fig-width: fig-width, lines: lines, + num: num, prefix: prefix, boxed: boxed, + hinh-giai: hg, fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, ..them, + ) + if _la-sach(_ho-so.get()) { + _voi-gian(gd, goi(loi-giai: _ghep(lg))) + } else { + let dung(dg, co-de) = if co-de { + _voi-gian(gd, [ + #goi(lo-da: 1) + #_giai-kem-hinh(giai-buoc(dg, gian-dong: gd, do: true), hg, fig-giai-pos, fig-giai-width, do: true) + ]) + } else { + _voi-gian(gd, giai-buoc(dg, tu: 1, nhan: [Hướng dẫn giải (tiếp). ], + gian-dong: gd, do: true)) + } + let man = _cat-man-vua(tach-man(lg), dung) + let buoc-da = 2 + man.at(0).len() + slide(tieu-de: tieu-de, so-buoc: buoc-da)[ + #_voi-gian(gd, [ + #goi(lo-da: buoc-da) + #_giai-kem-hinh(giai-buoc(man.at(0), gian-dong: gd), hg, fig-giai-pos, fig-giai-width) + ]) + ] + _man-tiep(man, tieu-de, gd: gd) + } +}) + +// FORM MỚI: +// #ds( +// [Thân câu chung — nêu tình huống / bài toán], +// ( +// [Phát biểu a — SAI], +// True([Phát biểu b — ĐÚNG]), // ý đúng bọc True(...) +// True([Phát biểu c — ĐÚNG]), +// [Phát biểu d — SAI], +// ), +// loigiai: [Phân tích từng ý.], +// fig: none, fig-pos: "right", fig-width: 30%, +// lines: 0, num: auto, prefix: "Câu", +// ) +// Form cũ (dap-an: (false, true, true, false)) vẫn dùng được. +// Lời giải riêng từng ý (an toàn khi trộn hoán vị ý) — đặt `giai:` trong +// True(...)/Sai(...); `khoa-y: true` báo công cụ trộn đừng xáo các ý của câu. +#let ds( + cau, cac-y, dap-an: none, diem: none, + hinh: none, fig: none, fig-pos: "right", fig-width: auto, + loi-giai: none, loigiai: none, o-tick: false, lines: 0, num: auto, cham: auto, + khoa-y: false, trong-dong: auto, + prefix: "Câu", boxed: false, fig-giai: none, hinh-giai: none, + fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, + tieu-de: [Đúng — Sai], +) = _gan-ds(context { + let lg = _uu-tien(loigiai, loi-giai) + let hinh = _uu-tien(fig, hinh) + let hg = _uu-tien(fig-giai, hinh-giai) + let gd = gian-dong + let goi(..them) = cau-tf( + cau, cac-y, dap-an: dap-an, diem: diem, hinh: hinh, o-tick: o-tick, cham: cham, + khoa-y: khoa-y, trong-dong: trong-dong, + fig-pos: fig-pos, fig-width: fig-width, lines: lines, + num: num, prefix: prefix, boxed: boxed, + hinh-giai: hg, fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, ..them, + ) + if _la-sach(_ho-so.get()) { + _voi-gian(gd, goi(loi-giai: _ghep(lg))) + } else { + let dung(dg, co-de) = if co-de { + _voi-gian(gd, [ + #goi(lo-da: 1) + #_giai-kem-hinh(giai-buoc(dg, gian-dong: gd, do: true), hg, fig-giai-pos, fig-giai-width, do: true) + ]) + } else { + _voi-gian(gd, giai-buoc(dg, tu: 1, nhan: [Hướng dẫn giải (tiếp). ], + gian-dong: gd, do: true)) + } + let man = _cat-man-vua(tach-man(lg), dung) + let buoc-da = 2 + man.at(0).len() + slide(tieu-de: tieu-de, so-buoc: buoc-da)[ + #_voi-gian(gd, [ + #goi(lo-da: buoc-da) + #_giai-kem-hinh(giai-buoc(man.at(0), gian-dong: gd), hg, fig-giai-pos, fig-giai-width) + ]) + ] + _man-tiep(man, tieu-de, gd: gd) + } +}) + +// FORM MỚI — đáp án đặt NGAY SAU đề bài (positional thứ 2): +// #tln( +// [Nội dung đề bài], +// [$6$], // đáp án +// loigiai: [Lời giải.], +// fig: none, fig-pos: "right", fig-width: 30%, +// show-boxes: true, box-count: 4, // ô điền trên phiếu (bản in đề) +// lines: 0, num: auto, prefix: "Câu", +// ) +// Form cũ (dap-an: [$6$]) vẫn dùng được. +#let tln( + cau, ..tra-loi, dap-an: none, diem: none, + hinh: none, fig: none, fig-pos: "right", fig-width: auto, + loi-giai: none, loigiai: none, show-boxes: true, box-count: 4, + lines: 0, num: auto, prefix: "Câu", boxed: false, + fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, + gian-dong: auto, tieu-de: [Trả lời ngắn], +) = _gan-tln(context { + let lg = _uu-tien(loigiai, loi-giai) + let hinh = _uu-tien(fig, hinh) + let hg = _uu-tien(fig-giai, hinh-giai) + let gd = gian-dong + let dap-an = if tra-loi.pos().len() > 0 { tra-loi.pos().first() } else { dap-an } + let goi(..them) = cau-sa( + cau, dap-an: dap-an, diem: diem, hinh: hinh, + fig-pos: fig-pos, fig-width: fig-width, + show-boxes: show-boxes, box-count: box-count, lines: lines, + num: num, prefix: prefix, boxed: boxed, + hinh-giai: hg, fig-giai-pos: fig-giai-pos, fig-giai-width: fig-giai-width, ..them, + ) + if _la-sach(_ho-so.get()) { + _voi-gian(gd, goi(loi-giai: _ghep(lg))) + } else { + let dung(dg, co-de) = if co-de { + _voi-gian(gd, [ + #goi(lo-da: 1) + #_giai-kem-hinh(giai-buoc(dg, gian-dong: gd, do: true), hg, fig-giai-pos, fig-giai-width, do: true) + ]) + } else { + _voi-gian(gd, giai-buoc(dg, tu: 1, nhan: [Hướng dẫn giải (tiếp). ], + gian-dong: gd, do: true)) + } + let man = _cat-man-vua(tach-man(lg), dung) + let buoc-da = 2 + man.at(0).len() + slide(tieu-de: tieu-de, so-buoc: buoc-da)[ + #_voi-gian(gd, [ + #goi(lo-da: buoc-da) + #_giai-kem-hinh(giai-buoc(man.at(0), gian-dong: gd), hg, fig-giai-pos, fig-giai-width) + ]) + ] + _man-tiep(man, tieu-de, gd: gd) + } +}) + +// Bộ dựng chung cho 4 dạng "kiểu tự luận" (TL/HĐ/LT/VDTT). +#let _dang-tl(ham-cau, cau, loi-giai, diem, cho-trong, hinh, tieu-de, + h-pos: "right", h-width: auto, + hg: none, hg-pos: "right", hg-width: auto, gd: auto) = context { + if _la-sach(_ho-so.get()) { + _voi-gian(gd, ham-cau(cau, loi-giai: _ghep(loi-giai), diem: diem, + cho-trong: cho-trong, hinh: hinh, fig-pos: h-pos, fig-width: h-width, + hinh-giai: hg, fig-giai-pos: hg-pos, fig-giai-width: hg-width)) + } else { + let dung(dg, co-de) = if co-de { + _voi-gian(gd, [ + #ham-cau(cau, diem: diem, hinh: hinh, fig-pos: h-pos, fig-width: h-width) + #_giai-kem-hinh(giai-buoc(dg, gian-dong: gd, do: true), hg, hg-pos, hg-width, do: true) + ]) + } else { + _voi-gian(gd, giai-buoc(dg, tu: 1, nhan: [Hướng dẫn giải (tiếp). ], + gian-dong: gd, do: true)) + } + let man = _cat-man-vua(tach-man(loi-giai), dung) + // màn đầu rỗng (đề dài, lời giải đã bị đẩy hết sang màn "(tiếp)") thì + // slide này chỉ cần MỘT bước — đừng in lại y hệt lần nữa. + slide(tieu-de: tieu-de, so-buoc: if man.at(0).len() > 0 { 1 + man.at(0).len() } + else if man.len() > 1 { 1 } else { 2 })[ + #_voi-gian(gd, [ + #ham-cau(cau, diem: diem, hinh: hinh, fig-pos: h-pos, fig-width: h-width) + #_giai-kem-hinh(giai-buoc(man.at(0), gian-dong: gd), hg, hg-pos, hg-width) + ]) + ] + _man-tiep(man, tieu-de, gd: gd) + } +} + +// (loigiai:/fig:/fig-giai: là bí danh kiểu mới của loi-giai:/hinh:/hinh-giai:) +#let tl(cau, loi-giai: none, loigiai: none, diem: none, cho-trong: 0pt, hinh: none, fig: none, fig-pos: "right", fig-width: auto, fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, tieu-de: [Tự luận]) = _dang-tl(cau-tl, cau, _uu-tien(loigiai, loi-giai), diem, cho-trong, _uu-tien(fig, hinh), tieu-de, h-pos: fig-pos, h-width: fig-width, hg: _uu-tien(fig-giai, hinh-giai), hg-pos: fig-giai-pos, hg-width: fig-giai-width, gd: gian-dong) +#let hd(cau, loi-giai: none, loigiai: none, diem: none, cho-trong: 0pt, hinh: none, fig: none, fig-pos: "right", fig-width: auto, fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, tieu-de: [Hoạt động]) = _dang-tl(cau-hd, cau, _uu-tien(loigiai, loi-giai), diem, cho-trong, _uu-tien(fig, hinh), tieu-de, h-pos: fig-pos, h-width: fig-width, hg: _uu-tien(fig-giai, hinh-giai), hg-pos: fig-giai-pos, hg-width: fig-giai-width, gd: gian-dong) +#let lt(cau, loi-giai: none, loigiai: none, diem: none, cho-trong: 0pt, hinh: none, fig: none, fig-pos: "right", fig-width: auto, fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, tieu-de: [Luyện tập]) = _dang-tl(cau-lt, cau, _uu-tien(loigiai, loi-giai), diem, cho-trong, _uu-tien(fig, hinh), tieu-de, h-pos: fig-pos, h-width: fig-width, hg: _uu-tien(fig-giai, hinh-giai), hg-pos: fig-giai-pos, hg-width: fig-giai-width, gd: gian-dong) +#let vdtt(cau, loi-giai: none, loigiai: none, diem: none, cho-trong: 0pt, hinh: none, fig: none, fig-pos: "right", fig-width: auto, fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, tieu-de: [Vận dụng]) = _dang-tl(cau-vdtt, cau, _uu-tien(loigiai, loi-giai), diem, cho-trong, _uu-tien(fig, hinh), tieu-de, h-pos: fig-pos, h-width: fig-width, hg: _uu-tien(fig-giai, hinh-giai), hg-pos: fig-giai-pos, hg-width: fig-giai-width, gd: gian-dong) +// 3 hình thức hoạt động SGK — KHÔNG đánh số (Khám phá / Trải nghiệm / Thảo luận) +#let kham-pha(cau, loi-giai: none, loigiai: none, diem: none, cho-trong: 0pt, hinh: none, fig: none, fig-pos: "right", fig-width: auto, fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, tieu-de: [Khám phá]) = _dang-tl(cau-kham-pha, cau, _uu-tien(loigiai, loi-giai), diem, cho-trong, _uu-tien(fig, hinh), tieu-de, h-pos: fig-pos, h-width: fig-width, hg: _uu-tien(fig-giai, hinh-giai), hg-pos: fig-giai-pos, hg-width: fig-giai-width, gd: gian-dong) +#let trai-nghiem(cau, loi-giai: none, loigiai: none, diem: none, cho-trong: 0pt, hinh: none, fig: none, fig-pos: "right", fig-width: auto, fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, tieu-de: [Trải nghiệm]) = _dang-tl(cau-trai-nghiem, cau, _uu-tien(loigiai, loi-giai), diem, cho-trong, _uu-tien(fig, hinh), tieu-de, h-pos: fig-pos, h-width: fig-width, hg: _uu-tien(fig-giai, hinh-giai), hg-pos: fig-giai-pos, hg-width: fig-giai-width, gd: gian-dong) +#let thao-luan(cau, loi-giai: none, loigiai: none, diem: none, cho-trong: 0pt, hinh: none, fig: none, fig-pos: "right", fig-width: auto, fig-giai: none, hinh-giai: none, fig-giai-pos: "right", fig-giai-width: auto, gian-dong: auto, tieu-de: [Thảo luận]) = _dang-tl(cau-thao-luan, cau, _uu-tien(loigiai, loi-giai), diem, cho-trong, _uu-tien(fig, hinh), tieu-de, h-pos: fig-pos, h-width: fig-width, hg: _uu-tien(fig-giai, hinh-giai), hg-pos: fig-giai-pos, hg-width: fig-giai-width, gd: gian-dong) + +#let phan(ten, ngan: none) = context { + if _la-sach(_ho-so.get()) { + // Heading cấp 1 (để vào MỤC LỤC #outline + bookmark PDF), nhưng hiển thị + // y hệt khối màu cũ qua show-rule cục bộ (không ảnh hưởng heading khác). + show heading.where(level: 1): it => block( + width: 100%, fill: rgb("#eaf2f8"), inset: (x: 10pt, y: 7pt), + stroke: (left: 3pt + _xanh), above: 14pt, below: 10pt, + text(weight: "bold", fill: _xanh, it.body), + ) + heading(level: 1, ten) + } else { + muc(ten, ngan: ngan) + } +} + +// ===================================================================== +// BỐ CỤC PHẦN CÂU HỎI — #chia-2-cot · #chia-2-cot-lech (08/2026) +// --------------------------------------------------------------------- +// Hai lệnh này dùng như SHOW-RULE: ĐẶT Ở ĐÂU THÌ ÁP DỤNG TỪ DÒNG ĐÓ TRỞ +// XUỐNG (đúng lối #show: de-toan.with(...) đã quen): +// +// #show: de-toan.with(ho-so: ho-so, tieu-de: [...]) +// #show: chia-2-cot // từ đây trở xuống: 2 cột đều nhau +// #tn(...) #tn(...) ... +// +// #show: chia-2-cot-lech // trái = câu hỏi, phải = chỗ làm bài +// #show: chia-2-cot-lech.with(rong-trai: 60%) // đổi bề rộng cột trái +// +// Muốn NGỪNG chia cột (bắt buộc trước #bang-dap-an, vì lệnh đó có +// #pagebreak mà pagebreak không chạy được bên trong cột) thì đặt +// #thoi-cot() +// — phần sau dấu này trở lại nguyên khổ giấy. +// +// Cũng dùng được dạng KHỐI cho một đoạn: #chia-2-cot[ ... ]. +// +// Chỉ tác dụng ở bản in A4 (dethi/loigiai); trình chiếu beamer bỏ qua +// (mỗi câu vốn đã là một slide riêng). +// ===================================================================== + +// ---------- Dấu kết thúc vùng chia cột ---------- +#let thoi-cot() = metadata("bg-thoi-cot") + +#let _la-thoi-cot(c) = (type(c) == content and c.func() == metadata + and c.value == "bg-thoi-cot") + +// Tách thân thành (phần NẰM TRONG cột, phần SAU #thoi-cot()). +// Không có dấu #thoi-cot() ⇒ (toàn bộ thân, none). +#let _tach-thoi-cot(than) = { + if type(than) != content or not than.has("children") { return (than, none) } + let cs = than.children + let i = cs.position(_la-thoi-cot) + if i == none { return (than, none) } + let truoc = cs.slice(0, i) + let sau = cs.slice(i + 1) + ( + if truoc.len() == 0 { none } else { truoc.join() }, + if sau.len() == 0 { none } else { sau.join() }, + ) +} + +// Phần thân nằm TRONG cột đã bị bọc thành MỘT phần tử nên vòng duyệt hoán vị +// ở #de-toan không nhìn thấy các câu bên trong ⇒ xáo lại ngay tại đây. +// (Mầm vẫn là mầm chung ⇒ bản dethi và bản loigiai trộn giống hệt nhau.) +#let _cot-than(nd) = { + let hv = _hv.get() + if nd != none and hv.at("cau", default: false) { _hv-xao-than(nd, hv.mam) } else { nd } +} + +// ---------- Chỗ trống còn lại của trang & chiều cao trọn một cột ---------- +// Trả về (chỗ còn lại của TRANG HIỆN TẠI, chiều cao MỘT CỘT đầy trang). +// Phải gọi trong `context` (dùng here().position()). +#let _cao-vung() = { + let ch = page.height + if type(ch) != length { return (none, none) } + let m = page.margin + // ⚠️ `page.margin` trả về lề đã CHUẨN HOÁ thành `relative` (vd `0% + 45.35pt`) + // chứ KHÔNG phải `length` như lúc khai — cứ đòi đúng `length` là hỏng hết. + let _dai(v) = { + if type(v) == length { v } + else if type(v) == relative { ch * v.ratio + v.length } + else if type(v) == ratio { ch * v } + else { none } + } + let _le(khoa, mac-dinh) = { + if type(m) == dictionary { _dai(m.at(khoa, default: m.at("y", default: mac-dinh))) + } else if m == auto { 2.5cm } else { _dai(m) } + } + let lt = _le("top", 1.6cm) + let ld = _le("bottom", 2.2cm) + if lt == none or ld == none { return (none, none) } + let cot = ch - lt - ld + if cot <= 0pt { return (none, none) } + (calc.max(0pt, ch - here().position().y - ld), cot) +} + +// Thân gõ bằng `#for` / `#include` thường gói TẤT CẢ câu hỏi vào MỘT phần tử +// (dãy con) ⇒ không có mối nào để chèn #colbreak(). Trải phẳng các dãy con lồng +// nhau để lấy đúng từng câu; ghép lại thì y hệt thân cũ. +#let _f-day = ([a] + [b]).func() +#let _cot-la(c) = { + if type(c) == content and c.func() == _f-day and c.has("children") { + c.children.map(_cot-la).flatten() + } else { (c,) } +} + +// ---------- Tự CÂN BẰNG chiều cao các cột ---------- +// Typst KHÔNG cân bằng `columns`: cột 1 được rót đầy tới HẾT chiều cao rồi mới +// tràn sang cột 2 ⇒ thân ngắn (chưa đầy một trang) nằm gọn trong cột 1, cột 2 +// bỏ trắng — nhìn y như lệnh không chạy. Thân DÀI thì bệnh này rơi xuống TRANG +// CUỐI: cột trái chạy hết trang, cột phải trắng trơn — tốn hẳn nửa tờ giấy. +// +// Cách chữa: các trang PHÍA TRƯỚC cứ để Typst rót tự nhiên — rót tự nhiên vốn +// đã chặt nhất, nó chẻ được cả một câu dài làm đôi, mình tính tay chỉ thua — +// chỉ chia đều phần nằm trên TRANG CUỐI bằng #colbreak(). +// +// ⚠️ HAI LỐI ĐÃ THỬ VÀ BỎ, ĐỪNG QUAY LẠI: +// 1. Xếp tay cho ĐẦY từng cột (kiểu multicol của LaTeX). Mình không chẻ được +// một câu làm đôi như Typst nên cột nào cũng hụt một khoảng; bản `loigiai` +// của de-mau-da-dang PHÌNH THÊM một trang (11 → 12). +// 2. Gài `metadata` trước mỗi câu rồi `query` lại số trang cho biết chắc câu +// nào rơi xuống trang cuối. Đúng thì có đúng, nhưng mỗi vùng chia cột lại +// bắt Typst dựng thêm một lượt; đề có 3 vùng là vượt hạn 5 lượt, hiện +// "document did not converge" và dòng "(Đề thi có N trang)" ở đầu đề kẹt +// lại số cũ. Đã đối chiếu ảnh từng trang: lối ĐO dưới đây cho bố cục GIỐNG +// HỆT lối query trên toàn bộ file thử, nên không đáng đánh đổi. +// +// Phép đo sai thì sai theo hướng "đoán trang cuối bắt đầu MUỘN hơn thực tế" +// (dòng chảy thật còn có khe hở ở chỗ khối không chẻ được), tức cột trái đầy +// hơn cột phải — vẫn đẹp chán, không bao giờ đội thêm trang. +// cao-dau : chỗ trống còn lại của trang hiện tại (thân ngắn nằm gọn ở đây) +// cao-cot : chiều cao một cột đầy trang +#let _can-cot(nd, so, wcol, cao-dau, cao-cot) = { + if so < 2 or type(nd) != content or not nd.has("children") { return nd } + if type(cao-cot) != length or cao-cot <= 0pt { return nd } + // Thân gõ bằng #for gói tất cả vào MỘT phần tử ⇒ phải trải phẳng mới có mối + // để chèn #colbreak(). + let cs = nd.children.map(_cot-la).flatten() + if cs.len() < 2 { return nd } + + // ----- đo chiều cao từng câu ở bề rộng MỘT CỘT (phần tử trắng = 0) ----- + let cao = cs.map(c => if _hv-trong(c) { 0pt } else { + measure(block(width: wcol, c)).height }) + let tong-le = cao.fold(0pt, (a, b) => a + b) + if tong-le <= 0pt { return nd } + // Các phép đo RỜI không tính khoảng cách giữa hai khối ⇒ tổng của chúng nhỏ + // hơn chiều cao thật. Đo thêm cả thân rồi rải phần chênh lệch đều cho mọi câu. + let tong-that = measure(block(width: wcol, nd)).height + let he-so = if tong-that > tong-le { tong-that / tong-le } else { 1.0 } + let cao = cao.map(h => h * he-so) + let tong = tong-le * he-so + + let an = 5mm // chừa chống tràn + let hd = if type(cao-dau) == length and cao-dau > cao-cot * 0.25 { + calc.min(cao-dau, cao-cot) } else { cao-cot } + + // Chiều cao cộng dồn TRƯỚC mỗi câu (tich.at(i)); chốt cuối = tổng cả thân. + let tich = () + let cong = 0pt + for h in cao { + tich.push(cong) + cong = cong + h + } + tich.push(cong) + + // ----- câu MỞ ĐẦU trang cuối ----- + // Trang hiện tại chứa `so` cột cao `hd`, mỗi trang sau `so` cột cao `cao-cot`. + let mo = 0 + if tong > (hd - an) * so { + let suc1 = (hd - an) * so + let sucn = (cao-cot - an) * so + let so-trang-sau = calc.max(0, calc.ceil((tong - suc1) / sucn) - 1) + let h0 = suc1 + sucn * so-trang-sau + let k = range(cs.len()).position(i => tich.at(i) >= h0) + mo = if k == none { 0 } else { k } + } + + // ----- chia ĐỀU phần nằm trên trang cuối cho `so` cột ----- + let du = tich.at(cs.len()) - tich.at(mo) + if du <= 0pt { return nd } + let muc = du / so + // Trang cuối vốn đã gần đầy thì ĐỪNG đụng vào: chia ra chẳng tiết kiệm được + // bao nhiêu, mà cột chót chỉ cần phình hơn phép đo một chút là tràn sang + // trang mới — mất không một trang giấy. Chỉ can thiệp khi còn trống hẳn 15%. + if muc > (cao-cot - an) * 0.85 { return nd } + + // Chỉ được cắt ở ĐẦU một câu THẬT (câu có chiều cao). Trang cuối không đủ + // `so` câu thì chia kiểu gì cũng có cột rỗng ⇒ để nguyên. + let that = range(mo, cs.len()).filter(i => cao.at(i) > 0pt) + if that.len() < so { return nd } + // Chọn mối GẦN mốc k·muc nhất — chứ KHÔNG phải mối đầu tiên vượt mốc: trang + // cuối thường chỉ còn vài câu, cắt kiểu "vượt mốc mới cắt" là hụt luôn + // (ba câu 100/120/228 thì chẳng mối nào vượt 224 ⇒ không cắt được chỗ nào). + let moi = that.slice(1) + let cat = () + let tu = mo + 1 + for k in range(1, so) { + let dich = tich.at(mo) + muc * k + let tot = none + let lech = none + for i in moi { + if i < tu { continue } + let d = tich.at(i) - dich + let ad = if d < 0pt { -d } else { d } + if lech == none or ad < lech { lech = ad; tot = i } + } + if tot != none { cat.push(tot); tu = tot + 1 } + } + if cat.len() == 0 { return nd } + // Cắt xong mà có cột vẫn quá sức chứa thì bỏ, để Typst tự rót. + let bien = (mo,) + cat + (cs.len(),) + let cao-moi = range(bien.len() - 1).map(k => + tich.at(bien.at(k + 1)) - tich.at(bien.at(k))) + if cao-moi.fold(0pt, calc.max) > cao-cot - an { return nd } + + // ----- ghép lại, chèn #colbreak() ở các mối đã chọn ----- + let kq = () + for (i, c) in cs.enumerate() { + if cat.contains(i) { kq.push(colbreak()) } + kq.push(c) + } + kq.join() +} + +// ---------- #chia-2-cot — chia phần câu hỏi thành 2 cột đều nhau ---------- +// so : số cột (mặc định 2; đặt 3 nếu muốn ba cột) +// khoang : khe giữa hai cột +// can : true (mặc định) = tự cân chiều cao hai cột; false = để Typst rót +// đầy cột 1 trước (lối gốc của Typst) +#let chia-2-cot(so: 2, khoang: 18pt, can: true, body) = context { + if not _la-sach(_ho-so.get()) { body } else { + let (nd0, sau) = _tach-thoi-cot(body) + if nd0 != none { + let nd = _cot-than(nd0) + let (cao-dau, cao-cot) = _cao-vung() + layout(kich => { + let wcol = (kich.width - khoang * (so - 1)) / so + let hc = if cao-cot == none { kich.height } else { cao-cot } + let than = if can { _can-cot(nd, so, wcol, cao-dau, hc) } else { nd } + columns(so, gutter: khoang, than) + }) + } + if sau != none { sau } + } +} + +// ---------- #chia-2-cot-lech — cột trái câu hỏi, cột phải cho HS làm bài ---- +// Cột phải để trống, kẻ hàng ngang MỜ (kiểu vở kẻ ngang) và có vạch dọc mờ +// ngăn giữa hai cột. Số hàng kẻ tính theo chiều cao thật của phần câu hỏi +// (đo bằng measure) nên không thừa/thiếu giấy. +// rong-trai : BỀ RỘNG CỘT TRÁI — mặc định 70% khổ chữ; nhận cả tỉ lệ +// (60%) lẫn độ dài tuyệt đối (11cm). +// khoang : khe giữa hai cột +// cao-dong : khoảng cách giữa hai hàng kẻ (mặc định 9mm) +// mau, day : màu và độ dày hàng kẻ +// ke : false = cột phải để trắng trơn (không kẻ dòng) +// vach-ngan : true (mặc định) = vạch dọc mờ ngăn hai cột +// tieu-de-phai: vd [Bài làm] — in nhạt ở đầu cột phải; none = không in +#let chia-2-cot-lech( + rong-trai: 70%, + khoang: 10pt, + cao-dong: 9mm, + mau: luma(65%), + day: 0.4pt, + ke: true, + vach-ngan: true, + tieu-de-phai: none, + body, +) = context { + if not _la-sach(_ho-so.get()) { body } else { + let (nd0, sau) = _tach-thoi-cot(body) + let nd = _cot-than(nd0) + if nd != none { + layout(kich => { + let wc = kich.width + let w0 = if type(rong-trai) == ratio { wc * rong-trai + } else if type(rong-trai) == relative { wc * rong-trai.ratio + rong-trai.length + } else { rong-trai } + // chốt chặn: cột trái không hẹp quá 2cm, không nuốt hết cột phải + let wt = calc.max(2cm, calc.min(w0, wc - khoang - 1.5cm)) + let wp = wc - wt - khoang + if wp <= 0pt { + // không đủ chỗ cho cột phải ⇒ giữ nguyên lối một cột (an toàn) + nd + } else { + let cao = measure(block(width: wt, nd)).height + let so-dong = calc.min(4000, calc.max(1, int(calc.floor(cao / cao-dong)))) + let dong-ke = block( + width: 100%, height: cao-dong, above: 0pt, below: 0pt, breakable: false, + place(bottom + left, line(length: 100%, stroke: day + mau)), + ) + grid( + columns: (wt, wp), column-gutter: khoang, + block(width: 100%, nd), + block( + width: 100%, + stroke: if vach-ngan { (left: day + mau) } else { none }, + inset: (left: if vach-ngan { 7pt } else { 0pt }), + above: 0pt, below: 0pt, + { + if tieu-de-phai != none { + block(above: 0pt, below: 3pt, + text(size: 0.85em, style: "italic", fill: mau.darken(25%), tieu-de-phai)) + } + if ke { for _ in range(so-dong) { dong-ke } } + }, + ), + ) + } + }) + } + if sau != none { sau } + } +} + +// ===================================================================== +// #ke-het-trang — KẺ DÒNG LẤP ĐẦY CHỖ TRỐNG CÒN LẠI CỦA TRANG (08/2026) +// --------------------------------------------------------------------- +// Đặt ở đâu thì kẻ dòng từ chỗ đó xuống HẾT TRANG ĐÓ — số dòng tự tính, không +// phải đếm tay: +// #ke-het-trang() // nét chấm, cách 9mm +// #ke-het-trang(cao-dong: 7mm, kieu: none) // nét liền, dày dòng hơn +// #ke-het-trang(chua: 2cm) // chừa 2cm cuối trang +// #ke-het-trang(them-trang: 1) // kẻ thêm TRỌN 1 trang nữa +// +// Cách làm: `here().position().y` cho biết đang ở đâu trên trang, trừ khỏi +// `page.height` và lề dưới là ra chỗ còn lại, chia cho `cao-dong` ra số dòng. +// Vị trí của chính lệnh này KHÔNG bị đẩy đi bởi các dòng kẻ nó sinh ra (chúng +// nằm SAU nó) nên phép đo ổn định, không lặp vô hạn. +// +// Chỉ dựng ở bản in A4 (dethi/loigiai); beamer bỏ qua như #het/#bang-dap-an. +// ===================================================================== +#let ke-het-trang( + cao-dong: 9mm, // khoảng cách giữa hai dòng kẻ + mau: luma(65%), // màu nét + day: 0.4pt, // độ dày nét + kieu: "dotted", // "dotted" | "dashed" | "dash-dotted" | none (nét liền) + dai: 100%, // bề dài dòng kẻ (50% = nửa bề ngang) + chua: 0pt, // chừa thêm chỗ trống ở đáy trang + le-tren: auto, // ghi đè lề trang nếu lề khai kiểu lạ + le-duoi: auto, + them-trang: 0, // kẻ thêm bao nhiêu TRANG ĐẦY nữa sau trang hiện tại +) = context { + if not _la-sach(_ho-so.get()) { } else if type(page.height) != length { } else { + // ----- lấy lề trên / lề dưới từ thiết lập trang ----- + let m = page.margin + let _le(khoa, mac-dinh) = { + if type(m) == dictionary { m.at(khoa, default: m.at("y", default: mac-dinh)) + } else if m == auto { 2.5cm } else { m } + } + let lt = if le-tren != auto { le-tren } else { _le("top", 1.6cm) } + let ld = if le-duoi != auto { le-duoi } else { _le("bottom", 2.2cm) } + + // MỘT dòng kẻ = MỘT khối cao ĐÚNG `cao-dong`, nét vẽ sát đáy khối. + // ⚠️ ĐỪNG viết `v(cao-dong); line(...)`: `line` tự tạo một ĐOẠN VĂN nên + // Typst cộng thêm khoảng cách đoạn trên/dưới ⇒ mỗi dòng chiếm gần GẤP ĐÔI + // `cao-dong`, số dòng tính ra thừa và tràn sang trang sau. + let net = (paint: mau, thickness: day, dash: kieu) + let mot-dong = block( + width: 100%, height: cao-dong, above: 0pt, below: 0pt, breakable: false, + place(bottom + left, line(length: dai, stroke: net)), + ) + + // ----- phần còn lại của TRANG HIỆN TẠI ----- + // trừ thêm 1mm chống tràn (khoảng cách đoạn của khối đứng ngay trên lệnh + // có thể đẩy vị trí xuống một chút so với phép đo) + let con = page.height - here().position().y - ld - chua - 1mm + for _ in range(calc.max(0, int(calc.floor(con / cao-dong)))) { mot-dong } + + // ----- các trang ĐẦY tiếp theo (nếu có) ----- + if them-trang > 0 { + let cao-trang = page.height - lt - ld - chua + let n-day = calc.max(0, int(calc.floor(cao-trang / cao-dong))) + for _ in range(them-trang) { + pagebreak() + for _ in range(n-day) { mot-dong } + } + } + } +} + +// ===================================================================== +// KẾT THÚC ĐỀ + BẢNG ĐÁP ÁN (chỉ dựng ở bản in A4 dethi/loigiai) +// ===================================================================== + +// ---------- #het — dòng kết thúc đề ---------- +// Dựng khối "––––– HẾT –––––" + ghi chú (căn giữa), chỉ hiện ở bản in A4; +// ở beamer bỏ qua (không sinh slide thừa). +// #het() -> mặc định +// #het(ghi-chu: [Không được dùng tài liệu.]) +// #het(chu: [THE END], ghi-chu: none) +#let het( + chu: [HẾT], + ghi-chu: [Thí sinh không được sử dụng tài liệu.\ Giám thị coi thi không giải thích gì thêm.], +) = context { + if _la-sach(_ho-so.get()) { + v(14pt) + align(center, { + text(weight: "bold", size: 1.05em)[#("–" * 5) #h(4pt) #chu #h(4pt) #("–" * 5)] + if ghi-chu != none { + linebreak() + v(3pt) + text(style: "italic", size: 0.95em, ghi-chu) + } + }) + } +} + +// ---------- Cố gắng lấy CHUỖI từ đáp án (cho ô điền tln) ---------- +// str/số -> chuỗi; content -> đọc .text/.children/.body đệ quy; không tách +// được -> none (khi đó bảng đáp án in nguyên nội dung vào một ô). +#let _da-chuoi(c) = { + if c == none { return "" } + if type(c) == str { return c } + if type(c) == int or type(c) == float { return str(c) } + if type(c) != content { return none } + if c.has("text") { return c.text } + if c.has("children") { + let cac = c.children.map(_da-chuoi) + if cac.any(p => p == none) { return none } + return cac.join("") + } + if c.has("body") { return _da-chuoi(c.body) } + none +} + +// ---------- #bang-dap-an — 3 bảng đáp án tn / ds / tln ---------- +// Tự thu thập đáp án MỌI câu tn/ds/tln trong tài liệu (qua metadata ) +// rồi dựng bảng theo mẫu đề 2025. Mỗi loại đánh số 1..n độc lập. +// #bang-dap-an(ma-de: "0101") +// Tham số: +// ma-de : auto = LẤY TỰ ĐỘNG từ mã đề khai ở de-toan (đồng bộ, khỏi nhập +// tay); none = không in mã đề; hoặc giá trị cụ thể để ghi đè. +// tieu-de : auto = tự dựng theo ma-de; hoặc nội dung tuỳ ý; none = ẩn. +// so-o-tln : số ô mỗi đáp án trả lời ngắn (mặc định 4; tự nới nếu dài hơn). +// ngat-trang: true = sang trang mới trước bảng. +#let bang-dap-an( + ma-de: auto, + tieu-de: auto, + so-o-tln: 4, + ngat-trang: true, +) = context { + if not _la-sach(_ho-so.get()) { } else { + let ma-de = if ma-de == auto { _ma-de.get() } else { ma-de } + let items = query().map(m => m.value) + let ds-tn = items.filter(x => x.loai == "tn") + let ds-ds = items.filter(x => x.loai == "ds") + let ds-tln = items.filter(x => x.loai == "tln") + if ds-tn.len() + ds-ds.len() + ds-tln.len() > 0 { + if ngat-trang { pagebreak(weak: true) } + + // ----- Tiêu đề ----- + let td = if tieu-de == auto { + if ma-de != none [BẢNG ĐÁP ÁN MÃ ĐỀ #ma-de] else [BẢNG ĐÁP ÁN] + } else { tieu-de } + if td != none { + align(center, text(weight: "bold", size: 13pt, fill: _xanh, upper(td))) + v(8pt) + } + + let tieu-muc(nd) = block(above: 12pt, below: 7pt, + text(weight: "bold", size: 1.05em, nd)) + + // ===== 1) Trắc nghiệm nhiều lựa chọn ===== + if ds-tn.len() > 0 { + tieu-muc[Bảng đáp án các câu trắc nghiệm nhiều lựa chọn] + let o-tn(i, x) = box(stroke: 0.7pt + _xanh, radius: 2.5pt, + inset: (x: 6pt, y: 3.5pt), baseline: 30%)[ + #text(fill: _xanh, weight: "bold")[#(i + 1).]~#text(weight: "bold")[#x.da] + ] + let cot = calc.min(ds-tn.len(), 12) + grid(columns: (auto,) * cot, column-gutter: 6pt, row-gutter: 7pt, + align: center + horizon, + ..ds-tn.enumerate().map(p => o-tn(p.at(0), p.at(1)))) + } + + // ===== 2) Đúng — Sai ===== + if ds-ds.len() > 0 { + tieu-muc[Bảng đáp án các câu trắc nghiệm đúng sai] + let vong(dung) = if dung { + circle(radius: 7pt, fill: _xanh, stroke: none, + align(center + horizon, text(fill: white, weight: "bold", size: 8pt)[Đ])) + } else { + circle(radius: 7pt, fill: white, stroke: 0.9pt + luma(45%), + align(center + horizon, text(weight: "bold", size: 8pt)[S])) + } + let o-ds(i, x) = { + let bs = if type(x.da) == array { x.da } else { (false, false, false, false) } + box(stroke: 0.7pt + luma(45%), radius: 2.5pt, inset: (x: 8pt, y: 6pt), + baseline: 30%)[ + #text(fill: _do, weight: "bold")[Câu #(i + 1).]~~#h(2pt)#box(baseline: 35%, + stack(dir: ltr, spacing: 3pt, ..bs.map(vong))) + ] + } + grid(columns: (auto,) * calc.min(ds-ds.len(), 4), + column-gutter: 8pt, row-gutter: 8pt, align: left + horizon, + ..ds-ds.enumerate().map(p => o-ds(p.at(0), p.at(1)))) + } + + // ===== 3) Trả lời ngắn ===== + if ds-tln.len() > 0 { + tieu-muc[Bảng đáp án các câu trắc nghiệm trả lời ngắn] + let o-ky-tu(ky) = box(width: 17pt, height: 19pt, stroke: 0.7pt + luma(40%), + radius: 1.5pt, inset: 0pt, align(center + horizon, + if ky != none { text(size: 0.9em, ky) })) + let o-tln(i, x) = { + let s = _da-chuoi(x.da) + let noi = if s != none { + let kts = s.clusters() + let n = calc.max(so-o-tln, kts.len()) + box(baseline: 35%, stack(dir: ltr, spacing: 3pt, + ..range(n).map(k => o-ky-tu(if k < kts.len() { kts.at(k) } else { none })))) + } else { + // Không tách được thành ký tự -> in nguyên nội dung vào một ô rộng. + box(stroke: 0.7pt + luma(40%), radius: 1.5pt, inset: (x: 5pt, y: 3pt), + baseline: 30%, x.da) + } + box(stroke: 0.7pt + luma(45%), radius: 2.5pt, inset: (x: 8pt, y: 6pt), + baseline: 30%)[ + #text(fill: _do, weight: "bold")[Câu #(i + 1).]~~#h(2pt)#noi + ] + } + grid(columns: (auto,) * calc.min(ds-tln.len(), 3), + column-gutter: 8pt, row-gutter: 8pt, align: left + horizon, + ..ds-tln.enumerate().map(p => o-tln(p.at(0), p.at(1)))) + } + } + } +} + +// Bí danh tên cũ (tương thích ngược): mc = tn, tf = ds, sa = tln. +#let mc = tn +#let tf = ds +#let sa = tln + +// TƯƠNG THÍCH NGƯỢC: các file cũ vẫn gọi được +// #let (vd, tn, ds, tln, tl, hd, lt, vdtt, phan) = tao-cau-hoi(ho-so) +// (tham số ho-so nay không còn cần thiết — hồ sơ đọc từ trạng thái). +#let tao-cau-hoi(..thua) = ( + vd: vd, tn: tn, ds: ds, tln: tln, tl: tl, hd: hd, lt: lt, vdtt: vdtt, + phan: phan, mc: tn, tf: ds, sa: tln, +) diff --git a/packages/preview/conic-toan/0.3.4/lib/da-dien.typ b/packages/preview/conic-toan/0.3.4/lib/da-dien.typ new file mode 100644 index 0000000000..bb815b53cd --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/da-dien.typ @@ -0,0 +1,876 @@ +// ===================================================================== +// da-dien.typ — ENGINE ĐA DIỆN TỔNG QUÁT (07/2026) +// +// Nhập DANH SÁCH ĐỈNH 3D + DANH SÁCH MẶT (mảng chỉ số đỉnh) là vẽ được +// BẤT KÌ khối đa diện LỒI: tự chiếu, tự xác định mặt trước/mặt sau +// (back-face culling), tự vẽ nét liền / nét khuất, tự tô mặt theo độ sâu. +// +// #da-dien(..khoi-chop-deu(n: 4, R: 2, cao: 3.6)) +// #da-dien(dinh: ((0,0,0), (3,0,0), (0,3,0), (0,0,3)), +// mat: ((0,1,2), (0,1,3), (0,2,3), (1,2,3)), +// ten: ($O$, $A$, $B$, $C$)) +// +// QUY TẮC nét khuất: cạnh LIỀN nếu thuộc ít nhất một mặt hướng về người +// nhìn, ĐỨT nếu chỉ thuộc các mặt hướng ra sau. Quy tắc này CHÍNH XÁC với +// khối LỒI (chóp, lăng trụ, hộp, chóp cụt, tứ diện, bát diện… — tức toàn +// bộ hình học THPT). Khối KHÔNG lồi hoặc hai khối che nhau thì cần z-buffer, +// KHÔNG dùng được engine này. +// +// Thứ tự đỉnh trong mỗi mặt KHÔNG cần theo chiều nào: pháp tuyến được lật +// ra ngoài theo tâm khối. +// ===================================================================== +#import "ve.typ": * + +// Bí danh nội bộ: `ve-da-dien` có THAM SỐ tên `diem`/`duong`, mà tham số CHE +// mất hàm cùng tên của ve.typ. Giữ tham chiếu tới hàm vẽ ở đây để dùng bên +// trong thân hàm. (Đừng bỏ hai dòng này.) +#let _ve-diem = diem +#let _ve-duong = duong + +// ===================================================================== +// VECTƠ 3D — mọi hàm ở khối này TRẢ VỀ GIÁ TRỊ (không kê vào ve-voi) +// ===================================================================== +#let v3-cong(A, B) = (A.at(0) + B.at(0), A.at(1) + B.at(1), A.at(2) + B.at(2)) +#let v3-tru(A, B) = (A.at(0) - B.at(0), A.at(1) - B.at(1), A.at(2) - B.at(2)) +#let v3-nhan(k, A) = (k * A.at(0), k * A.at(1), k * A.at(2)) +// Tích vô hướng (dot) và tích có hướng (cross). +#let v3-vo-huong(A, B) = A.at(0) * B.at(0) + A.at(1) * B.at(1) + A.at(2) * B.at(2) +#let v3-co-huong(A, B) = ( + A.at(1) * B.at(2) - A.at(2) * B.at(1), + A.at(2) * B.at(0) - A.at(0) * B.at(2), + A.at(0) * B.at(1) - A.at(1) * B.at(0), +) +#let v3-dai(A) = calc.sqrt(calc.max(0.0, v3-vo-huong(A, A))) +#let v3-chuan(A) = { + let d = v3-dai(A) + if d < 1e-12 { A } else { v3-nhan(1 / d, A) } +} +// Trung điểm, điểm chia theo tỉ lệ t (0 = A, 1 = B), tâm (trung bình) của mảng. +#let trung-diem-3d(A, B) = v3-nhan(0.5, v3-cong(A, B)) +#let chia-3d(A, B, t) = v3-cong(A, v3-nhan(t, v3-tru(B, A))) +#let tam-3d(ds) = v3-nhan(1 / ds.len(), ds.fold((0.0, 0.0, 0.0), v3-cong)) + +// Pháp tuyến của đa giác phẳng (mảng điểm 3D) — công thức Newell, bền với +// đa giác gần suy biến và không cần biết chiều quay. +#let phap-da-giac(ds) = { + let n = ds.len() + let s = (0.0, 0.0, 0.0) + for i in range(n) { + let A = ds.at(i) + let B = ds.at(calc.rem(i + 1, n)) + s = v3-cong(s, ( + (A.at(1) - B.at(1)) * (A.at(2) + B.at(2)), + (A.at(2) - B.at(2)) * (A.at(0) + B.at(0)), + (A.at(0) - B.at(0)) * (A.at(1) + B.at(1)), + )) + } + v3-chuan(s) +} + +// ===================================================================== +// CAMERA — phép chiếu song song + HƯỚNG NHÌN tự suy ra +// Phép chiếu p là hàm tuyến tính (x, y, z) -> (u, v) toạ độ toán của khung. +// Hai hàng của ma trận chiếu là hai trục màn hình (phải, lên); tích có hướng +// của chúng chính là hướng TỪ hình VỀ PHÍA người nhìn ⇒ suy được mặt trước. +// ===================================================================== +#let _nhin(p) = { + let o = p((0, 0, 0)) + let c1 = p((1, 0, 0)) + let c2 = p((0, 1, 0)) + let c3 = p((0, 0, 1)) + let r1 = (c1.at(0) - o.at(0), c2.at(0) - o.at(0), c3.at(0) - o.at(0)) + let r2 = (c1.at(1) - o.at(1), c2.at(1) - o.at(1), c3.at(1) - o.at(1)) + v3-chuan(v3-co-huong(r1, r2)) +} + +// Bọc một phép chiếu bất kì thành camera: (p: hàm chiếu, nhin: hướng nhìn). +#let chieu-3d(p) = (p: p, nhin: _nhin(p)) + +// Chiếu XIÊN mặc định (y = chiều sâu ra sau, z lên trên, x sang phải) — +// KHỚP với phép chiếu của các hình chóp/lăng trụ dựng sẵn trong lib. +#let chieu-xien(goc: 40deg, k: 0.5) = chieu-3d(P => ( + P.at(0) + k * P.at(1) * calc.cos(goc), + P.at(2) + k * P.at(1) * calc.sin(goc), +)) + +// Chiếu KHỚP hệ trục #oxyz (Oy sang phải, Oz lên, Ox chéo xuống-trái về +// phía người nhìn). Dùng khi vẽ khối trong `oxyz(them: (ctx, t3) => ...)` +// — nhớ để k/goc trùng với oxyz. +#let chieu-oxyz(k: 0.55, goc: 35deg) = chieu-3d(P => ( + P.at(1) - k * P.at(0) * calc.cos(goc), + P.at(2) - k * P.at(0) * calc.sin(goc), +)) + +// Camera TRỰC GIAO thật theo góc Euler: quay quanh Oz góc `ngang`, nâng +// cao `cao`. Oz luôn dựng đứng trên trang. `cao` lớn = nhìn từ trên xuống. +#let chieu-truc-giao(ngang: 65deg, cao: 22deg, ti-le: 1) = { + let e = ( + calc.cos(cao) * calc.cos(ngang), + calc.cos(cao) * calc.sin(ngang), + calc.sin(cao), + ) + let r = (-calc.sin(ngang), calc.cos(ngang), 0) + let u = v3-co-huong(e, r) + ( + p: P => (ti-le * v3-vo-huong(P, r), ti-le * v3-vo-huong(P, u)), + nhin: e, + ) +} + +// auto -> chiếu xiên mặc định · hàm -> tự suy hướng nhìn · dict -> dùng luôn. +#let _cam(c) = { + if c == auto { chieu-xien() } + else if type(c) == function { chieu-3d(c) } + else { c } +} + +// ===================================================================== +// PHÂN TÍCH KHỐI — TRẢ GIÁ TRỊ (không kê vào ve-voi) +// .cam camera đã chuẩn hoá +// .d2 mảng đỉnh đã chiếu (toạ độ toán 2D) +// .sau độ sâu từng đỉnh (LỚN hơn = gần người nhìn hơn) +// .mat mảng thông tin mặt: (chi-so, phap (ra ngoài), tam, sau, truoc) +// .canh mảng cạnh duy nhất: (i, j, hien) — hien = false thì vẽ ĐỨT +// ===================================================================== +#let _canh-vong(m) = { + let n = m.len() + range(n).map(i => (m.at(i), m.at(calc.rem(i + 1, n)))) +} +#let _khoa(i, j) = if i < j { str(i) + "-" + str(j) } else { str(j) + "-" + str(i) } + +#let phan-tich-khoi(dinh, mat, cam: auto) = { + let cm = _cam(cam) + let e = cm.nhin + let C = tam-3d(dinh) + let d2 = dinh.map(cm.p) + let ttm = () + for m in mat { + let n = phap-da-giac(m.map(i => dinh.at(i))) + let T = tam-3d(m.map(i => dinh.at(i))) + // lật pháp tuyến RA NGOÀI khối (khối lồi: ra xa tâm khối) + if v3-vo-huong(n, v3-tru(T, C)) < 0 { n = v3-nhan(-1, n) } + ttm.push(( + chi-so: m, + phap: n, + tam: T, + sau: v3-vo-huong(T, e), + truoc: v3-vo-huong(n, e) > 1e-9, + )) + } + // cạnh: hiện nếu thuộc ÍT NHẤT MỘT mặt trước; mặt hở (cạnh 1 mặt) luôn hiện + let bang = (:) + for f in ttm { + for (i, j) in _canh-vong(f.chi-so) { + let k = _khoa(i, j) + let cu = bang.at(k, default: (i: calc.min(i, j), j: calc.max(i, j), hien: false, so: 0)) + bang.insert(k, (i: cu.i, j: cu.j, hien: cu.hien or f.truoc, so: cu.so + 1)) + } + } + let canh = bang.values().map(c => ( + i: c.i, + j: c.j, + hien: if c.so == 1 { true } else { c.hien }, + )) + (cam: cm, d2: d2, sau: dinh.map(P => v3-vo-huong(P, e)), mat: ttm, canh: canh) +} + +// Cửa sổ bao các điểm 2D + lề (nội bộ). +#let _cs2(pts, mg: 0.7) = { + let xs = pts.map(p => p.at(0)) + let ys = pts.map(p => p.at(1)) + ( + xmin: calc.min(..xs) - mg, xmax: calc.max(..xs) + mg, + ymin: calc.min(..ys) - mg, ymax: calc.max(..ys) + mg, + ) +} + +// Hướng đặt nhãn cho đỉnh Q: PHÂN GIÁC của KHE GÓC RỘNG NHẤT giữa các cạnh +// đi ra từ Q (ds-ke = các đỉnh kề, toạ độ toán 2D) — nhãn không đè lên cạnh +// nào. Tính trên TOẠ ĐỘ TRANG nên đúng cả khi hai trục khác tỉ lệ. +// Trả vectơ (dx, dy) trên trang (y hướng XUỐNG) — đúng dạng `huong` của `nhan`. +#let _huong-khe(ctx, Q, ds-ke) = { + let q = toa-pt(ctx, Q) + let gs = () + for R in ds-ke { + let r = toa-pt(ctx, R) + let dx = r.at(0) - q.at(0) + let dy = r.at(1) - q.at(1) + if calc.sqrt(dx * dx + dy * dy) > 1e-6 { gs.push(calc.atan2(dx, dy).deg()) } + } + if gs.len() == 0 { return (0, -1) } + let gs = gs.sorted() + let n = gs.len() + let khe = -1.0 + let giua = 0.0 + for i in range(n) { + let a = gs.at(i) + let b = if i + 1 < n { gs.at(i + 1) } else { gs.at(0) + 360 } + if b - a > khe { + khe = b - a + giua = (a + b) / 2 + } + } + let g = giua * 1deg + (calc.cos(g), calc.sin(g)) +} + +// Hướng nhãn TOẢ RA XA tâm hình chiếu (cho điểm KHÔNG nằm trên cạnh nào). +#let _huong-xa(Q, C2) = { + let dx = Q.at(0) - C2.at(0) + let dy = Q.at(1) - C2.at(1) + let l = calc.sqrt(dx * dx + dy * dy) + if l < 1e-9 { (0, -1) } else { (dx / l, -dy / l) } +} + +// --------------------------------------------------------------------- +// CHE BỞI CHÍNH KHỐI — kiểm CHÍNH XÁC (không quét mẫu, không cần z-buffer): +// tia đi từ P về phía người nhìn có cắt khối LỒI hay không. Giao đoạn tham +// số t của tia với mọi nửa không gian của khối rồi xem còn t > 0 nào không. +// Nhất quán với quy tắc nét khuất của cạnh: điểm trên một mặt TRƯỚC thì +// KHÔNG bị che; điểm trong khối hoặc trên mặt SAU thì bị che. +// --------------------------------------------------------------------- +#let _bi-khoi-che(P, e, mat-tt) = { + let lo = -1e9 + let hi = 1e9 + for f in mat-tt { + let df = v3-vo-huong(f.phap, e) + let f0 = v3-vo-huong(f.phap, P) - v3-vo-huong(f.phap, f.tam) + if calc.abs(df) < 1e-12 { + if f0 > 1e-9 { return false } // ở ngoài một mặt song song tia nhìn + } else if df > 0 { + hi = calc.min(hi, -f0 / df) + } else { + lo = calc.max(lo, -f0 / df) + } + } + calc.max(lo, 1e-6) <= hi - 1e-9 +} + +// Vẽ ĐOẠN PHỤ trong/quanh khối, TỰ phân loại nét: phần bị khối che vẽ ĐỨT, +// phần thấy vẽ LIỀN (một đoạn có thể vừa liền vừa đứt — vd đường cao SO +// xuất phát từ đỉnh S thấy được rồi chui vào trong khối). +#let _doan-tu-dong( + ctx, p, e, mat-tt, A, B, + n: 160, mau: black, day: 1pt, + hien-khuat: true, mau-khuat: auto, day-khuat: auto, dut: auto, +) = { + if dut != auto { + doan(ctx, p(A), p(B), mau: mau, day: day, dut: dut) + return + } + let moc = () + let tt = none + let dau = 0.0 + for st in range(n) { + let che = _bi-khoi-che(chia-3d(A, B, (st + 0.5) / n), e, mat-tt) + if tt == none { + tt = che + } else if che != tt { + moc.push((dau, st / n, tt)) + tt = che + dau = st / n + } + } + moc.push((dau, 1.0, tt)) + for (u, v, che) in moc { + if che and not hien-khuat { continue } + doan( + ctx, p(chia-3d(A, B, u)), p(chia-3d(A, B, v)), + mau: if not che or mau-khuat == auto { mau } else { mau-khuat }, + day: if not che or day-khuat == auto { day } else { day-khuat }, + dut: che, + ) + } +} + +// Cạnh của khối CHỨA điểm P (nếu có) — trả phần tử của `.canh`, hoặc none. +#let _canh-chua(dinh, canh, P, eps: 1e-6) = { + for c in canh { + let A = dinh.at(c.i) + let B = dinh.at(c.j) + let AB = v3-tru(B, A) + let L2 = v3-vo-huong(AB, AB) + if L2 < 1e-12 { continue } + let t = v3-vo-huong(v3-tru(P, A), AB) / L2 + if t < -eps or t > 1 + eps { continue } + let H = chia-3d(A, B, calc.max(0.0, calc.min(1.0, t))) + if v3-dai(v3-tru(P, H)) < eps { return c } + } + none +} + +// Nhận dạng một điểm 3D trần (mảng 3 số) — phân biệt với mục (P, ten, ...). +#let _la-diem3(x) = ( + type(x) == array and x.len() == 3 and type(x.at(0)) != array +) + +// ---------- Tiện ích dựng điểm (TRẢ GIÁ TRỊ) ---------- +// Điểm chia cạnh nối đỉnh thứ i và thứ j của khối (t = 0 tại i, 1 tại j). +#let diem-canh(dinh, i, j, t: 0.5) = chia-3d(dinh.at(i), dinh.at(j), t) +// Hình chiếu vuông góc của P lên ĐƯỜNG THẲNG AB. +#let hinh-chieu-3d(P, A, B) = { + let AB = v3-tru(B, A) + let L2 = v3-vo-huong(AB, AB) + if L2 < 1e-12 { A } else { + v3-cong(A, v3-nhan(v3-vo-huong(v3-tru(P, A), AB) / L2, AB)) + } +} +// Hình chiếu vuông góc của P lên MẶT PHẲNG mp = (n: pháp đơn vị, d:). +#let hinh-chieu-mp(P, mp) = v3-tru(P, v3-nhan(v3-vo-huong(mp.n, P) - mp.d, mp.n)) + +// ===================================================================== +// VẼ KHỐI ĐA DIỆN trong khung có sẵn (nhận ctx ⇒ KÊ vào ve-voi) +// dinh mảng điểm 3D · mat mảng mặt (mảng chỉ số đỉnh) +// ten mảng nhãn theo thứ tự đỉnh (none = không ghi) +// huong auto = toả ra từ tâm hình chiếu · hoặc mảng hướng từng đỉnh +// to màu tô CÁC MẶT TRƯỚC (vẽ xa -> gần) · to-mat mảng màu riêng +// diem ĐIỂM PHỤ (trung điểm cạnh, chân đường cao…): mỗi mục là +// P · (P, ten) · (P, ten, huong) · (P, ten, huong, mau). +// Điểm nằm TRÊN CẠNH: nhãn tự đặt VUÔNG GÓC với cạnh đó; điểm bị +// khối che thì `hien-khuat: false` sẽ ẩn luôn. +// duong ĐOẠN PHỤ (đường cao, hình chiếu…): mỗi mục là (A, B) hoặc +// (A, B, tuỳ-chọn) với tuỳ-chọn là dict nhận mau/day/ten/tai/huong/ +// cach/dut/hien-khuat/mau-khuat/day-khuat/vuong/r. Phần đoạn bị khối +// che TỰ vẽ đứt. `vuong: C` = ký hiệu góc vuông tại B, giữa BA và BC. +// them (ctx, p) => ... với p là hàm chiếu (x, y, z) -> điểm 2D +// ===================================================================== +#let ve-da-dien( + ctx, + dinh: (), mat: (), + cam: auto, + ten: none, huong: auto, cach: 6pt, mau-ten: black, + hien-dinh: true, bk: 1.6pt, + mau: black, day: 1.1pt, + mau-khuat: auto, day-khuat: auto, hien-khuat: true, + to: none, to-mat: none, + diem: (), duong: (), mau-diem: auto, bk-diem: 1.8pt, + them: none, +) = { + let pt = phan-tich-khoi(dinh, mat, cam: cam) + let d2 = pt.d2 + // ---- tô mặt trước, xa vẽ trước (painter's algorithm) + if to != none or to-mat != none { + let thu-tu = range(pt.mat.len()).sorted(key: i => pt.mat.at(i).sau) + for i in thu-tu { + let f = pt.mat.at(i) + if not f.truoc { continue } + let c = if to-mat == none { to } else { + let v = to-mat.at(i, default: auto) + if v == auto { to } else { v } + } + if c != none { + da-giac-pt(f.chi-so.map(k => toa-pt(ctx, d2.at(k))), to: c) + } + } + } + // ---- cạnh khuất (vẽ trước) rồi cạnh liền + for lan in (false, true) { + for c in pt.canh { + if c.hien != lan { continue } + if not c.hien and not hien-khuat { continue } + doan( + ctx, d2.at(c.i), d2.at(c.j), + mau: if c.hien or mau-khuat == auto { mau } else { mau-khuat }, + day: if c.hien or day-khuat == auto { day } else { day-khuat }, + dut: not c.hien, + ) + } + } + // ---- đoạn phụ (đường cao, hình chiếu…): tự liền/đứt theo khối + let p = pt.cam.p + let e = pt.cam.nhin + for m in duong { + let A = m.at(0) + let B = m.at(1) + let tc = if m.len() > 2 { m.at(2) } else { (:) } + let mc = tc.at("mau", default: mau) + let hk = tc.at("hien-khuat", default: hien-khuat) + _doan-tu-dong( + ctx, p, e, pt.mat, A, B, + mau: mc, day: tc.at("day", default: day), + hien-khuat: hk, + mau-khuat: tc.at("mau-khuat", default: mau-khuat), + day-khuat: tc.at("day-khuat", default: day-khuat), + dut: tc.at("dut", default: auto), + ) + // ẩn nét khuất thì bỏ luôn ký hiệu góc vuông / nhãn ở chỗ bị khối che + if "vuong" in tc and (hk or not _bi-khoi-che(B, e, pt.mat)) { + goc-vuong( + ctx, p(B), p(A), p(tc.at("vuong")), + r: tc.at("r", default: 0.3), mau: mc, day: 0.8pt, + ) + } + if tc.at("ten", default: none) != none { + let M = chia-3d(A, B, tc.at("tai", default: 0.5)) + let hg = tc.at("huong", default: auto) + if hk or not _bi-khoi-che(M, e, pt.mat) { + nhan( + ctx, p(M), tc.at("ten"), + huong: if hg == auto { _phap-tuyen(ctx, p(A), p(B)) } else { hg }, + cach: tc.at("cach", default: cach), mau: mc, + ) + } + } + } + // ---- đỉnh + nhãn + // đỉnh kề (chỉ tính theo các cạnh ĐƯỢC VẼ) để đặt nhãn vào khe trống + let ke = (:) + for c in pt.canh { + if not c.hien and not hien-khuat { continue } + ke.insert(str(c.i), ke.at(str(c.i), default: ()) + (c.j,)) + ke.insert(str(c.j), ke.at(str(c.j), default: ()) + (c.i,)) + } + for i in range(dinh.len()) { + // ẩn nét khuất thì ẩn luôn đỉnh chỉ thuộc các mặt sau + if not hien-khuat and not pt.mat.any(f => f.truoc and i in f.chi-so) { continue } + let t = if ten == none { none } else { ten.at(i, default: none) } + let hg = if huong == auto { + _huong-khe(ctx, d2.at(i), ke.at(str(i), default: ()).map(j => d2.at(j))) + } else { huong.at(i, default: "above") } + if hien-dinh { + _ve-diem(ctx, d2.at(i), ten: t, huong: hg, bk: bk, cach: cach, mau: mau, mau-ten: mau-ten) + } else if t != none { + nhan(ctx, d2.at(i), t, huong: hg, cach: cach, mau: mau-ten) + } + } + // ---- điểm phụ: nhãn tự đặt vuông góc với cạnh chứa nó + let C2 = ( + d2.map(q => q.at(0)).sum() / d2.len(), + d2.map(q => q.at(1)).sum() / d2.len(), + ) + for m in diem { + let (P, t, hg, mc) = if _la-diem3(m) { (m, none, auto, auto) } else { + (m.at(0), m.at(1, default: none), m.at(2, default: auto), m.at(3, default: auto)) + } + if not hien-khuat and _bi-khoi-che(P, e, pt.mat) { continue } + let c = _canh-chua(dinh, pt.canh, P) + let h = if hg != auto { hg } else if c != none { + _huong-khe(ctx, p(P), (d2.at(c.i), d2.at(c.j))) + } else { _huong-xa(p(P), C2) } + let m2 = if mc != auto { mc } else if mau-diem == auto { mau } else { mau-diem } + _ve-diem(ctx, p(P), ten: t, huong: h, bk: bk-diem, cach: cach, mau: m2, mau-ten: m2) + } + if them != none { them(ctx, p) } +} + +// Các điểm 3D phụ (từ `diem`/`duong`) — để cửa sổ bao luôn phần chìa ra ngoài. +#let _diem-phu(diem, duong) = { + let ds = () + for m in diem { ds.push(if _la-diem3(m) { m } else { m.at(0) }) } + for m in duong { + ds.push(m.at(0)) + ds.push(m.at(1)) + if m.len() > 2 and "vuong" in m.at(2) { ds.push(m.at(2).at("vuong")) } + } + ds +} + +// Bản TỰ TẠO KHUNG (không nhận ctx ⇒ KHÔNG kê vào ve-voi). +#let da-dien( + dinh: (), mat: (), + cam: auto, w: 7cm, le: 0.75, co-chu: 10pt, + diem: (), duong: (), + ..dt, +) = { + let cm = _cam(cam) + let cs = _cs2((dinh + _diem-phu(diem, duong)).map(cm.p), mg: le) + hinh( + w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, + co-chu: co-chu, + ctx => ve-da-dien( + ctx, dinh: dinh, mat: mat, cam: cm, diem: diem, duong: duong, ..dt, + ), + ) +} + +// ===================================================================== +// KHỐI DỰNG SẴN — TRẢ (dinh:, mat:, ten:), dùng với toán tử `..` +// #da-dien(..khoi-lang-tru-deu(n: 6, R: 1.8, cao: 3.4), to: blue.lighten(88%)) +// ===================================================================== +#let _chu = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M") +#let _ten-day(n, phay: false) = range(n).map(i => { + let t = _chu.at(calc.rem(i, _chu.len())) + eval(if phay { t + "'" } else { t }, mode: "math") +}) +// Pha mặc định của đa giác đều: đỉnh A rơi ra phía trước-trái. +#let _pha(n) = if n == 4 { 225deg } else { 210deg } +// Mảng đỉnh đa giác đều n cạnh, tâm trên trục Oz, bán kính R, cao z. +#let day-deu(n, R, z: 0, pha: auto) = { + let ph = if pha == auto { _pha(n) } else { pha } + range(n).map(i => { + let a = ph + i * 360deg / n + (R * calc.cos(a), R * calc.sin(a), z) + }) +} + +// Hình chóp: đáy là mảng điểm 3D bất kì (đa giác lồi), đỉnh S. +#let khoi-chop(day, S, ten: auto) = { + let n = day.len() + ( + dinh: day + (S,), + mat: (range(n),) + range(n).map(i => (i, calc.rem(i + 1, n), n)), + ten: if ten == auto { _ten-day(n) + ($S$,) } else { ten }, + ) +} +// Lăng trụ: đáy `day` tịnh tiến theo vectơ v (đáy trên ghi dấu phẩy). +#let khoi-lang-tru(day, v, ten: auto) = { + let n = day.len() + let tren = day.map(P => v3-cong(P, v)) + ( + dinh: day + tren, + mat: (range(n), range(n).map(i => i + n)) + + range(n).map(i => { + let j = calc.rem(i + 1, n) + (i, j, j + n, i + n) + }), + ten: if ten == auto { _ten-day(n) + _ten-day(n, phay: true) } else { ten }, + ) +} +// Chóp cụt: hai đáy CÙNG số đỉnh, tương ứng theo thứ tự. +#let khoi-chop-cut(day, day-tren, ten: auto) = { + let n = day.len() + ( + dinh: day + day-tren, + mat: (range(n), range(n).map(i => i + n)) + + range(n).map(i => { + let j = calc.rem(i + 1, n) + (i, j, j + n, i + n) + }), + ten: if ten == auto { _ten-day(n) + _ten-day(n, phay: true) } else { ten }, + ) +} +// Hình hộp tổng quát: đỉnh A + ba vectơ cạnh u, v, w. +#let khoi-hop(A, u, v, w, ten: auto) = khoi-lang-tru( + (A, v3-cong(A, u), v3-cong(v3-cong(A, u), v), v3-cong(A, v)), + w, + ten: ten, +) + +#let khoi-chop-deu(n: 4, R: 2, cao: 3.6, pha: auto, ten: auto) = khoi-chop( + day-deu(n, R, pha: pha), (0, 0, cao), ten: ten, +) +#let khoi-lang-tru-deu(n: 6, R: 1.8, cao: 3.4, pha: auto, ten: auto) = khoi-lang-tru( + day-deu(n, R, pha: pha), (0, 0, cao), ten: ten, +) +#let khoi-chop-cut-deu(n: 4, R: 2, r: 1.1, cao: 3, pha: auto, ten: auto) = khoi-chop-cut( + day-deu(n, R, pha: pha), day-deu(n, r, z: cao, pha: pha), ten: ten, +) +#let khoi-hop-chu-nhat(dai: 4, rong: 2.6, cao: 3, ten: auto) = khoi-hop( + (0, 0, 0), (dai, 0, 0), (0, rong, 0), (0, 0, cao), ten: ten, +) +#let khoi-lap-phuong(a: 3, ten: auto) = khoi-hop-chu-nhat( + dai: a, rong: a, cao: a, ten: ten, +) +// Tứ diện đều cạnh a: đáy đều bán kính a/√3, chiều cao a·√(2/3). +#let khoi-tu-dien-deu(a: 3, ten: auto) = khoi-chop( + day-deu(3, a / calc.sqrt(3)), + (0, 0, a * calc.sqrt(2 / 3)), + ten: if ten == auto { ($A$, $B$, $C$, $D$) } else { ten }, +) +// Bát diện đều cạnh a: 6 đỉnh (±s, 0, 0), (0, ±s, 0), (0, 0, ±s), s = a/√2. +#let khoi-bat-dien-deu(a: 3, ten: auto) = { + let s = a / calc.sqrt(2) + ( + dinh: ((s, 0, 0), (0, s, 0), (-s, 0, 0), (0, -s, 0), (0, 0, s), (0, 0, -s)), + mat: ( + (0, 1, 4), (1, 2, 4), (2, 3, 4), (3, 0, 4), + (0, 1, 5), (1, 2, 5), (2, 3, 5), (3, 0, 5), + ), + ten: if ten == auto { ($A$, $B$, $C$, $D$, $S$, $S'$) } else { ten }, + ) +} + +// ===================================================================== +// MẶT PHẲNG +// mp = (n: pháp tuyến ĐƠN VỊ, d: hằng số) ⇔ n · P = d +// ===================================================================== +#let mp-qua-phap(P, n) = { + let u = v3-chuan(n) + (n: u, d: v3-vo-huong(u, P)) +} +#let mp-qua-3-diem(A, B, C) = mp-qua-phap(A, v3-co-huong(v3-tru(B, A), v3-tru(C, A))) +// Mặt phẳng cắt ba trục tại (a,0,0), (0,b,0), (0,0,c): x/a + y/b + z/c = 1. +#let mp-cat-truc(a, b, c) = mp-qua-3-diem((a, 0, 0), (0, b, 0), (0, 0, c)) +// Mặt phẳng song song với mp, đi qua P. +#let mp-song-song(mp, P) = (n: mp.n, d: v3-vo-huong(mp.n, P)) + +// Điểm 2D có nằm trong đa giác LỒI (mảng điểm 2D) hay không. +#let _trong-dg2(q, dg) = { + let duong = false + let am = false + let n = dg.len() + for i in range(n) { + let A = dg.at(i) + let B = dg.at(calc.rem(i + 1, n)) + let c = ( + (B.at(0) - A.at(0)) * (q.at(1) - A.at(1)) + - (B.at(1) - A.at(1)) * (q.at(0) - A.at(0)) + ) + if c > 1e-9 { duong = true } + if c < -1e-9 { am = true } + } + not (duong and am) +} + +// Vẽ ĐỨT phần đoạn AB (3D) bị đa giác `dg3` che: nằm SAU mặt phẳng chứa +// đa giác VÀ hình chiếu rơi vào trong đa giác. Quét mẫu nên đúng với mọi +// cấu hình (đoạn cắt ngang mặt phẳng, ló ra hai đầu…). +#let _dut-bi-che(ctx, p, nhin, dg3, A, B, n: 240, mau: black, day: 0.9pt) = { + let dg2 = dg3.map(p) + let nm = phap-da-giac(dg3) + let d0 = v3-vo-huong(nm, dg3.at(0)) + // người nhìn ở phía f > 0 nếu nm cùng hướng nhìn; điểm SAU mặt phẳng: f·phia < 0 + let phia = if v3-vo-huong(nm, nhin) >= 0 { 1 } else { -1 } + let dai = () + let dau = none + for s in range(n + 1) { + let t = s / n + let P = chia-3d(A, B, t) + let f = (v3-vo-huong(nm, P) - d0) * phia + let che = f < -1e-9 and _trong-dg2(p(P), dg2) + if che and dau == none { dau = t } + if not che and dau != none { + dai.push((dau, t)) + dau = none + } + } + if dau != none { dai.push((dau, 1)) } + for (u, v) in dai { + doan(ctx, p(chia-3d(A, B, u)), p(chia-3d(A, B, v)), mau: mau, day: day, dut: true) + } +} + +// --------------------------------------------------------------------- +// Vẽ MỘT MẶT PHẲNG (đa giác phẳng 3D) — lõi chung, nhận ctx. +// p hàm chiếu (x, y, z) -> 2D (vd t3 của #oxyz) +// dinh3 mảng đỉnh 3D của đa giác biểu diễn mặt phẳng +// che mảng đoạn 3D ((A, B), ...) sẽ được vẽ ĐỨT ở phần bị mặt phẳng che +// --------------------------------------------------------------------- +#let mat-phang( + ctx, p, dinh3, + to: auto, mau: blue.darken(15%), day: 1pt, dut: false, + ten: none, ten-tai: 0, huong: "above-left", cach: 5pt, + che: (), mau-che: black, day-che: 0.9pt, + nhin: auto, + them: none, +) = { + let e = if nhin == auto { _nhin(p) } else { nhin } + let d2 = dinh3.map(p) + let mau-to = if to == auto { rgb(80, 140, 220, 46) } else { to } + if mau-to != none { da-giac-pt(d2.map(q => toa-pt(ctx, q)), to: mau-to) } + let n = d2.len() + for i in range(n) { + doan(ctx, d2.at(i), d2.at(calc.rem(i + 1, n)), mau: mau, day: day, dut: dut) + } + for (A, B) in che { + _dut-bi-che(ctx, p, e, dinh3, A, B, mau: mau-che, day: day-che) + } + if ten != none { + nhan(ctx, d2.at(calc.rem(ten-tai, n)), ten, huong: huong, cach: cach, mau: mau) + } + if them != none { them(ctx, p) } +} + +// --------------------------------------------------------------------- +// MẶT PHẲNG CẮT BA TRỤC trong hệ #oxyz — tam giác A(a,0,0) B(0,b,0) C(0,0,c), +// tự vẽ ĐỨT phần ba trục toạ độ nằm SAU mặt phẳng (bị mặt phẳng che). +// #oxyz(x: 5, y: 6, z: 5, them: (ctx, t3) => { +// mat-phang-oxyz(ctx, t3, 4, 5, 3, ten-dinh: true, ten: $(P)$) +// }) +// `truc: auto` tự lấy phạm vi (0 -> mốc cắt trục); truyền tuple 3 khoảng để +// đổi, vd truc: ((-1, 5), (-1, 6), (-1, 5)); truc: none = không xử lí nét đứt. +// --------------------------------------------------------------------- +#let mat-phang-oxyz( + ctx, t3, a, b, c, + to: auto, mau: blue.darken(15%), day: 1pt, dut: false, + ten: none, ten-tai: 0, huong: "above-left", cach: 5pt, + ten-dinh: false, ten-abc: ($A$, $B$, $C$), hien-dinh: true, bk: 1.8pt, + truc: auto, mau-che: black, day-che: 0.9pt, + them: none, +) = { + let A = (a, 0, 0) + let B = (0, b, 0) + let C = (0, 0, c) + let ds-truc = if truc == none { () } else if truc == auto { + (((0, 0, 0), A), ((0, 0, 0), B), ((0, 0, 0), C)) + } else { + ( + ((truc.at(0).at(0), 0, 0), (truc.at(0).at(1), 0, 0)), + ((0, truc.at(1).at(0), 0), (0, truc.at(1).at(1), 0)), + ((0, 0, truc.at(2).at(0)), (0, 0, truc.at(2).at(1))), + ) + } + mat-phang( + ctx, t3, (A, B, C), + to: to, mau: mau, day: day, dut: dut, + ten: ten, ten-tai: ten-tai, huong: huong, cach: cach, + che: ds-truc, mau-che: mau-che, day-che: day-che, + ) + if ten-dinh or hien-dinh { + let bo = ((A, ten-abc.at(0), "below-left"), (B, ten-abc.at(1), "below-right"), (C, ten-abc.at(2), "above-right")) + for (P, t, h) in bo { + diem( + ctx, t3(P), + ten: if ten-dinh { t } else { none }, + huong: h, bk: if hien-dinh { bk } else { 0pt }, cach: 5pt, mau: mau, + ) + } + } + if them != none { them(ctx, t3) } +} + +// --------------------------------------------------------------------- +// MẶT PHẲNG "LƠ LỬNG" dạng hình bình hành: tâm `tam`, hai nửa-vectơ u, v +// (4 đỉnh = tam ± u ± v). Ba trục toạ độ đi sau nó cũng tự vẽ đứt. +// mat-phang-bh(ctx, t3, (2, 2, 2), (2.4, 0, 0), (0, 2.4, 0), ten: $(alpha)$) +// --------------------------------------------------------------------- +#let mat-phang-bh( + ctx, t3, tam, u, v, + to: auto, mau: blue.darken(15%), day: 1pt, dut: false, + ten: none, ten-tai: 1, huong: "above-right", cach: 5pt, + truc: none, mau-che: black, day-che: 0.9pt, + them: none, +) = { + let dinh3 = ( + v3-tru(v3-tru(tam, u), v), + v3-tru(v3-cong(tam, u), v), + v3-cong(v3-cong(tam, u), v), + v3-cong(v3-tru(tam, u), v), + ) + let ds-truc = if truc == none or truc == auto { () } else { + ( + ((truc.at(0).at(0), 0, 0), (truc.at(0).at(1), 0, 0)), + ((0, truc.at(1).at(0), 0), (0, truc.at(1).at(1), 0)), + ((0, 0, truc.at(2).at(0)), (0, 0, truc.at(2).at(1))), + ) + } + mat-phang( + ctx, t3, dinh3, + to: to, mau: mau, day: day, dut: dut, + ten: ten, ten-tai: ten-tai, huong: huong, cach: cach, + che: ds-truc, mau-che: mau-che, day-che: day-che, + them: them, + ) +} + +// ===================================================================== +// THIẾT DIỆN — cắt khối LỒI bởi một mặt phẳng +// thiet-dien TRẢ VỀ mảng điểm 3D đã SẮP THEO VÒNG (rỗng nếu không cắt). +// Cách làm: lấy mọi giao điểm của mặt phẳng với các CẠNH của khối (kể cả +// đỉnh nằm đúng trên mặt phẳng), bỏ điểm trùng, rồi sắp theo GÓC quanh tâm +// trong hệ hai vectơ của mặt phẳng — thiết diện của khối lồi là đa giác lồi +// nên phép sắp theo góc cho đúng thứ tự vòng. +// ===================================================================== +#let thiet-dien(dinh, mat, mp, eps: 1e-7) = { + let f = P => v3-vo-huong(mp.n, P) - mp.d + let bang = (:) + for m in mat { + for (i, j) in _canh-vong(m) { + bang.insert(_khoa(i, j), (calc.min(i, j), calc.max(i, j))) + } + } + let ds = () + for (i, j) in bang.values() { + let A = dinh.at(i) + let B = dinh.at(j) + let fa = f(A) + let fb = f(B) + if calc.abs(fa) < eps { ds.push(A) } + if calc.abs(fb) < eps { ds.push(B) } + if calc.abs(fa) >= eps and calc.abs(fb) >= eps and fa * fb < 0 { + ds.push(chia-3d(A, B, fa / (fa - fb))) + } + } + let kq = () + for P in ds { + if not kq.any(Q => v3-dai(v3-tru(P, Q)) < 1e-6) { kq.push(P) } + } + if kq.len() < 3 { return () } + let T = tam-3d(kq) + let u = v3-chuan(v3-tru(kq.at(0), T)) + let v = v3-co-huong(mp.n, u) + kq.sorted(key: P => { + let w = v3-tru(P, T) + calc.atan2(v3-vo-huong(w, u), v3-vo-huong(w, v)).deg() + }) +} + +// Vẽ thiết diện TRÊN một khối: tô miền + viền LIỀN ở phần nằm trên mặt +// THẤY, ĐỨT ở phần nằm trên mặt KHUẤT (nhận ctx ⇒ kê vào ve-voi). +// ve-thiet-dien(ctx, dinh: d, mat: m, mp: mp-qua-3-diem(P, Q, R)) +#let ve-thiet-dien( + ctx, + dinh: (), mat: (), mp: none, + cam: auto, + to: auto, mau: red.darken(10%), day: 1.1pt, + hien-khuat: true, mau-khuat: auto, day-khuat: auto, + ten: none, huong: auto, cach: 6pt, hien-dinh: true, bk: 1.8pt, mau-ten: auto, + them: none, +) = { + let pt = phan-tich-khoi(dinh, mat, cam: cam) + let p = pt.cam.p + let dd = thiet-dien(dinh, mat, mp) + if dd.len() < 3 { return } + let d2 = dd.map(p) + let mau-to = if to == auto { rgb(220, 70, 70, 40) } else { to } + if mau-to != none { da-giac-pt(d2.map(q => toa-pt(ctx, q)), to: mau-to) } + let n = dd.len() + for i in range(n) { + let j = calc.rem(i + 1, n) + let M = trung-diem-3d(dd.at(i), dd.at(j)) + // cạnh thiết diện nằm trên mặt nào? thấy nếu có một mặt CHỨA nó là mặt trước + let hien = pt.mat.any(f => ( + calc.abs(v3-vo-huong(f.phap, M) - v3-vo-huong(f.phap, f.tam)) < 1e-5 and f.truoc + )) + if not hien and not hien-khuat { continue } + doan( + ctx, d2.at(i), d2.at(j), + mau: if hien or mau-khuat == auto { mau } else { mau-khuat }, + day: if hien or day-khuat == auto { day } else { day-khuat }, + dut: not hien, + ) + } + for i in range(n) { + let t = if ten == none { none } else { ten.at(i, default: none) } + let hg = if huong == auto { + _huong-khe(ctx, d2.at(i), (d2.at(calc.rem(i + n - 1, n)), d2.at(calc.rem(i + 1, n)))) + } else { huong.at(i, default: "above") } + if hien-dinh { + diem(ctx, d2.at(i), ten: t, huong: hg, bk: bk, cach: cach, mau: mau, mau-ten: if mau-ten == auto { mau } else { mau-ten }) + } else if t != none { + nhan(ctx, d2.at(i), t, huong: hg, cach: cach, mau: if mau-ten == auto { mau } else { mau-ten }) + } + } + if them != none { them(ctx, p) } +} + +// Khối + thiết diện trong MỘT lệnh, tự tạo khung (không nhận ctx). +#let da-dien-thiet-dien( + dinh: (), mat: (), mp: none, + cam: auto, w: 7cm, le: 0.75, co-chu: 10pt, + ten: none, huong: auto, + mau: black, day: 1.1pt, to: none, to-mat: none, + hien-khuat: true, hien-dinh: true, mau-ten: black, + diem: (), duong: (), mau-diem: auto, bk-diem: 1.8pt, + td: (:), // tuỳ chọn riêng cho thiết diện (mau, to, ten, …) + them: none, +) = { + let cm = _cam(cam) + let cs = _cs2((dinh + _diem-phu(diem, duong)).map(cm.p), mg: le) + hinh( + w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, + co-chu: co-chu, + ctx => { + ve-da-dien( + ctx, dinh: dinh, mat: mat, cam: cm, + ten: ten, huong: huong, mau: mau, day: day, to: to, to-mat: to-mat, + hien-khuat: hien-khuat, hien-dinh: hien-dinh, mau-ten: mau-ten, + diem: diem, duong: duong, mau-diem: mau-diem, bk-diem: bk-diem, + ) + if mp != none { + ve-thiet-dien(ctx, dinh: dinh, mat: mat, mp: mp, cam: cm, ..td) + } + if them != none { them(ctx, cm.p) } + }, + ) +} diff --git a/packages/preview/conic-toan/0.3.4/lib/do-thi.typ b/packages/preview/conic-toan/0.3.4/lib/do-thi.typ new file mode 100644 index 0000000000..9171225f75 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/do-thi.typ @@ -0,0 +1,1632 @@ +// ===================================================================== +// do-thi.typ — ĐỒ THỊ HÀM SỐ THPT +// Hệ trục, lưới, vạch chia, đường gióng + các đồ thị dựng sẵn: +// bậc nhất, bậc hai, bậc ba, trùng phương, phân thức b1/b1, hữu tỉ b2/b1, +// mũ, lôgarit, sin/cos/tan, căn thức. +// +// CỬA SỔ THÔNG MINH: khung nhìn tự tính từ các điểm then chốt +// (cực trị, điểm uốn, tâm đối xứng, tiệm cận, giao trục) và luôn chứa gốc O. +// +// THAM SỐ NHÃN CHUNG cho các đồ thị dựng sẵn: +// cuc-tri : auto = gióng cực trị vào 2 trục + nhãn tự động +// none = không gióng +// (x: "below", y: "left") = đổi hướng nhãn (mọi điểm) +// ((x:.., y:..), (x:.., y:..), ...) = hướng riêng từng điểm +// (thứ tự x tăng dần) +// giao-ox : none | auto | "below" | ("below", "above", ...) — nhãn giao Ox +// giao-oy : none | auto | "left" — nhãn giao Oy +// Hướng nhãn (tiếng Anh): above, below, left, right, +// above-left, above-right, below-left, below-right. +// Tiệm cận (nếu có) LUÔN được vẽ kèm nhãn trên trục. +// goc-ten : vị trí nhãn tên hàm — "above-right" (mặc định) | "above-left" +// | "below-left" | "below-right" | (x, y) toạ độ tuỳ ý +// dan-x, dan-y : co/giãn cửa sổ quanh tâm theo từng trục — +// > 1 phóng to (đơn vị lớn hơn), < 1 thu nhỏ (thấy vùng rộng hơn) +// Mỗi đồ thị dựng sẵn có `them: ctx => ...` để vẽ chồng nội dung tuỳ ý. +// ===================================================================== +#import "ve.typ": * + +// Định dạng số gọn: 2 -> "2", 2.50 -> "2.5" +#let so-dep(v) = { + let r = calc.round(v, digits: 2) + if calc.fract(r) == 0 { str(int(r)) } else { str(r) } +} + +// Nhãn toán "đẹp": nhận dạng số nguyên, phân số p/q (q ≤ 20), +// căn thức k√n (n ≤ 50), dạng a ± k√n (ví dụ 1 + √2). +// Không khớp thì trả về số thập phân. +#let so-toan(v) = { + let eps = 0.0001 + if calc.abs(v - calc.round(v)) < eps { return $#int(calc.round(v))$ } + for q in range(2, 21) { + let p = calc.round(v * q) + if calc.abs(v - p / q) < eps and calc.gcd(int(calc.abs(p)), q) == 1 { + let p = int(p) + return if p < 0 { $- #(-p) / #q$ } else { $#p / #q$ } + } + } + // phân số mẫu LỚN (vd 283/27 — giá trị cực trị bậc ba, mẫu 27a²): + // khớp CHẶT (1e-9) nên không "nhận nhầm" số vô tỉ thành phân số. + for q in range(21, 401) { + let p = calc.round(v * q) + if calc.abs(v - p / q) < 1e-9 and calc.gcd(int(calc.abs(p)), q) == 1 { + let p = int(p) + return if p < 0 { $- #(-p) / #q$ } else { $#p / #q$ } + } + } + for n in range(2, 51) { + let r = calc.sqrt(n) + for k in range(1, 6) { + if calc.abs(calc.abs(v) - k * r) < eps { + let ct = if k == 1 { $sqrt(#[#n])$ } else { $#k sqrt(#[#n])$ } + return if v < 0 { $-#ct$ } else { ct } + } + } + } + for n in range(2, 31) { + let cn = calc.sqrt(n) + if calc.abs(cn - calc.round(cn)) < eps { continue } + for k in range(1, 4) { + let ct = if k == 1 { $sqrt(#[#n])$ } else { $#k sqrt(#[#n])$ } + let du = v - k * cn // v = a + k√n ? + if calc.abs(du - calc.round(du)) < eps and calc.abs(du) > eps and calc.abs(calc.round(du)) <= 12 { + return $#int(calc.round(du)) + #ct$ + } + let du = v + k * cn // v = a - k√n ? + if calc.abs(du - calc.round(du)) < eps and calc.abs(du) > eps and calc.abs(calc.round(du)) <= 12 { + return $#int(calc.round(du)) - #ct$ + } + } + } + // dạng (m ± √n)/q — nghiệm phương trình bậc hai (vd (1+√7)/3, √3/2) + for q in range(2, 13) { + let t = v * q + let m0 = int(calc.round(t)) + for dm in range(-16, 17) { + let m = m0 + dm + let s = t - m // s = ±√n + let n = calc.round(s * s) + if n >= 2 and n <= 400 and calc.abs(s * s - n) < 0.00001 { + let cn = calc.sqrt(n) + if calc.abs(cn - calc.round(cn)) < eps { continue } // n chính phương -> hữu tỉ, đã xét + // rút thừa số chính phương: √96 -> 4√6 + let n = int(n) + let k = 1 + let j = 2 + while j * j <= n { + if calc.rem(n, j * j) == 0 { k = j } + j = j + 1 + } + let n = int(n / (k * k)) + let ct = if k == 1 { $sqrt(#[#n])$ } else { $#k sqrt(#[#n])$ } + return if m == 0 { + if s > 0 { $#ct / #q$ } else { $- #ct / #q$ } + } else if s > 0 { $(#m + #ct) / #q$ } else { $(#m - #ct) / #q$ } + } + } + } + $#so-dep(v)$ +} + +// Căn thức CHÍNH XÁC: hiển thị (P + K·√n)/Q từ các thành phần đã biết +// (P, K, Q gần-nguyên; n hữu tỉ ≥ 0). Tự chuẩn hoá n hữu tỉ (√(u/den) = +// √(u·den)/den), rút thừa số chính phương, rút gọn gcd(P, K, Q). +// Không "đẹp" được thì rơi về so-toan(giá trị số) — không tệ hơn trước. +#let so-can-thuc(P, K, n, Q) = { + let eps = 0.000001 + let gan-int(x) = calc.abs(x - calc.round(x)) < eps * calc.max(1, calc.abs(x)) + if Q == 0 or n < -eps { return $#so-dep(0)$ } // phòng hờ (không xảy ra) + let gia = (P + K * calc.sqrt(calc.max(n, 0))) / Q + // chuẩn hoá n về nguyên: (P + K√(u/den))/Q = (P·den + K√(u·den))/(Q·den) + let cap = if gan-int(n) { (calc.round(n), P, Q) } else { + let ket = none + for den in range(2, 21) { + if gan-int(n * den) { + ket = (calc.round(n * den) * den, P * den, Q * den) + break + } + } + ket + } + if cap == none { return so-toan(gia) } + let (n, P, Q) = cap + if not (gan-int(P) and gan-int(K) and gan-int(Q)) { return so-toan(gia) } + let P = int(calc.round(P)) + let K = int(calc.round(K)) + let Q = int(calc.round(Q)) + let n = int(calc.round(n)) + if Q < 0 { P = -P; K = -K; Q = -Q } + // rút thừa số chính phương: √96 -> 4√6 + if K != 0 and n > 1 { + let j = 2 + let h = 1 + while j * j <= n { + if calc.rem(n, j * j) == 0 { h = j } + j = j + 1 + } + K = K * h + n = int(n / (h * h)) + } + if n == 1 { P = P + K; K = 0 } + if K == 0 or n == 0 { + // hữu tỉ P/Q chính xác + let g = calc.max(calc.gcd(calc.abs(P), Q), 1) + let (P, Q) = (int(P / g), int(Q / g)) + return if Q == 1 { $#P$ } else if P < 0 { $- #(-P) / #Q$ } else { $#P / #Q$ } + } + let g = calc.max(calc.gcd(calc.gcd(calc.abs(P), calc.abs(K)), Q), 1) + let (P, K, Q) = (int(P / g), int(K / g), int(Q / g)) + let ct = if calc.abs(K) == 1 { $sqrt(#[#n])$ } else { $#calc.abs(K) sqrt(#[#n])$ } + if Q == 1 { + if P == 0 { if K > 0 { ct } else { $-#ct$ } } + else if K > 0 { $#P + #ct$ } else { $#P - #ct$ } + } else { + if P == 0 { if K > 0 { $#ct / #Q$ } else { $- #ct / #Q$ } } + else if K > 0 { $(#P + #ct) / #Q$ } else { $(#P - #ct) / #Q$ } + } +} + +// ---- Cực trị CHÍNH XÁC theo hệ số (dùng chung cho bang.typ + khao-sat.typ) ---- +// Bậc ba y = ax³+bx²+cx+d, Δ' = b²−3ac > 0: trả (x1:, y1:, x2:, y2:) với +// x1 < x2. x = (−b ∓ σ√Δ')/(3a), σ = sign(a); +// y = (P0 ± 2σΔ'√Δ')/(27a²), P0 = 2b³ − 9abc + 27a²d (chia đa thức f cho f'). +#let cuc-tri-bac-ba(a, b, c, d) = { + let delta = b * b - 3 * a * c + let sg = if a > 0 { 1 } else { -1 } + let P0 = 2 * b * b * b - 9 * a * b * c + 27 * a * a * d + ( + x1: so-can-thuc(-b, -sg, delta, 3 * a), + y1: so-can-thuc(P0, 2 * sg * delta, delta, 27 * a * a), + x2: so-can-thuc(-b, sg, delta, 3 * a), + y2: so-can-thuc(P0, -2 * sg * delta, delta, 27 * a * a), + ) +} +// Hữu tỉ y = (ax²+bx+c)/(dx+e) có 2 cực trị: E = ae² − bde + cd² (E/a > 0), +// x = (−e ∓ σ√(E/a))/d, σ = sign(d); tại cực trị y = (2ax+b)/d +// ⇒ y = (bd − 2ae ∓ 2aσ√(E/a))/d², x1 < x2. +#let cuc-tri-huu-ti(a, b, c, d, e) = { + let E = a * e * e - b * d * e + c * d * d + let sg = if d > 0 { 1 } else { -1 } + ( + x1: so-can-thuc(-e, -sg, E / a, d), + y1: so-can-thuc(b * d - 2 * a * e, -2 * a * sg, E / a, d * d), + x2: so-can-thuc(-e, sg, E / a, d), + y2: so-can-thuc(b * d - 2 * a * e, 2 * a * sg, E / a, d * d), + ) +} + +// Nội dung "y = px + q" rút gọn (nhãn tiệm cận xiên). +#let _pt-bac-nhat(p, q) = { + let hs = if calc.abs(p - 1) < 0.001 { $x$ } else if calc.abs(p + 1) < 0.001 { $-x$ } else { $#so-toan(p) x$ } + if calc.abs(q) < 0.001 { $y = #hs$ } else if q > 0 { $y = #hs + #so-toan(q)$ } else { $y = #hs - #so-toan(-q)$ } +} + +// ---------- Đạo hàm số & tiếp tuyến ---------- +// Đạo hàm của f tại x bằng sai phân trung tâm (đủ chính xác để vẽ hình). +// let k = dao-ham(f, 1) // hệ số góc tiếp tuyến tại x = 1 +#let dao-ham(f, x, h: auto) = { + let b = if h == auto { 0.0001 * calc.max(1, calc.abs(x)) } else { h } + (f(x + b) - f(x - b)) / (2 * b) +} + +// TIẾP TUYẾN của đồ thị y = f(x) tại điểm có hoành độ x0 (hoặc DÃY hoành độ). +// Hệ số góc lấy bằng đạo hàm số nên dùng được cho MỌI hàm f viết bằng closure. +// tiep-tuyen(f, 1) // tiếp tuyến tại x = 1 +// tiep-tuyen(f, (-1, 2), mau: green) // nhiều tiếp điểm +// tiep-tuyen(f, 1, ten: auto) // tự ghi y = kx + m +// tiep-tuyen(f, 1, ten: $Delta$, ten-diem: $M$) // đặt tên tuỳ ý +// Tham số: +// dai : auto = kéo hết bề ngang khung (tự cắt gọn theo cửa sổ); +// số = nửa độ dài đoạn tiếp tuyến theo trục Ox quanh tiếp điểm +// cham : chấm tiếp điểm · ten-diem/huong-diem: nhãn tiếp điểm +// ten : nhãn đường tiếp tuyến (auto = phương trình y = kx + m rút gọn) +// tai/huong-ten/cach/ten-quay: vị trí & kiểu nhãn (như `ten:` của `doan`) +// giong : true = kẻ đường gióng đứt từ tiếp điểm về 2 trục +#let tiep-tuyen( + ctx, f, x0, + mau: red, day: 1.2pt, dut: false, dai: auto, + cham: true, mau-cham: auto, ten-diem: none, huong-diem: "above-left", + ten: none, tai: 0.9, huong-ten: auto, cach: 6pt, ten-quay: false, + giong: false, h: auto, +) = { + for x in (if type(x0) == array { x0 } else { (x0,) }) { + let y = f(x) + let k = dao-ham(f, x, h: h) + let tt = u => y + k * (u - x) + // hai đầu đoạn tiếp tuyến: cắt gọn trong cửa sổ hoặc theo `dai` + let (X1, X2) = if dai != auto { (x - dai, x + dai) } else if calc.abs(k) > 0.0001 { + let ca = ((ctx.ymin - y) / k + x, (ctx.ymax - y) / k + x).sorted() + (calc.max(ctx.xmin, ca.at(0)), calc.min(ctx.xmax, ca.at(1))) + } else { (ctx.xmin, ctx.xmax) } + if X2 - X1 > 0.0001 { + doan( + ctx, (X1, tt(X1)), (X2, tt(X2)), + mau: mau, day: day, dut: dut, + ten: if ten == auto { _pt-bac-nhat(k, y - k * x) } else { ten }, + tai: tai, huong: huong-ten, cach: cach, ten-quay: ten-quay, mau-ten: mau, + ) + } + if giong { + doan(ctx, (x, y), (x, 0), mau: gray.darken(20%), day: 0.8pt, dut: true) + doan(ctx, (x, y), (0, y), mau: gray.darken(20%), day: 0.8pt, dut: true) + } + if cham { + diem(ctx, (x, y), bk: 2.2pt, mau: if mau-cham == auto { mau } else { mau-cham }, + ten: ten-diem, huong: huong-diem) + } + } +} + +// ---------- Hệ trục Oxy ---------- +#let truc(ctx, ten-x: $x$, ten-y: $y$, ten-goc: $O$, mau: black, day: 0.9pt) = { + mui-ten(ctx, (ctx.xmin, 0), (ctx.xmax, 0), mau: mau, day: day) + mui-ten(ctx, (0, ctx.ymin), (0, ctx.ymax), mau: mau, day: day) + nhan(ctx, (ctx.xmax, 0), ten-x, huong: "below", cach: 5pt) + nhan(ctx, (0, ctx.ymax), ten-y, huong: "left", cach: 5pt) + if ten-goc != none { nhan(ctx, (0, 0), ten-goc, huong: "below-left", cach: 4pt) } +} + +// Lưới ô vuông mờ. +#let luoi(ctx, buoc: 1, mau: luma(87%), day: 0.5pt) = { + let x = calc.ceil(ctx.xmin / buoc) * buoc + while x <= ctx.xmax + 0.0001 { + doan(ctx, (x, ctx.ymin), (x, ctx.ymax), mau: mau, day: day) + x = x + buoc + } + let y = calc.ceil(ctx.ymin / buoc) * buoc + while y <= ctx.ymax + 0.0001 { + doan(ctx, (ctx.xmin, y), (ctx.xmax, y), mau: mau, day: day) + y = y + buoc + } +} + +// Vạch chia + số trên hai trục (bỏ qua 0). +#let vach-chia(ctx, buoc-x: 1, buoc-y: 1, so: true, mau: black) = { + let t = 3pt / ctx.sy // nửa chiều dài vạch, đổi sang đơn vị toạ độ + let x = calc.ceil((ctx.xmin + 0.3) / buoc-x) * buoc-x + while x <= ctx.xmax - 0.3 { + if calc.abs(x) > 0.0001 { + doan(ctx, (x, -t), (x, t), mau: mau, day: 0.8pt) + if so { nhan(ctx, (x, 0), $#so-dep(x)$, huong: "below", cach: 4pt) } + } + x = x + buoc-x + } + let s = 3pt / ctx.sx + let y = calc.ceil((ctx.ymin + 0.3) / buoc-y) * buoc-y + while y <= ctx.ymax - 0.3 { + if calc.abs(y) > 0.0001 { + doan(ctx, (-s, y), (s, y), mau: mau, day: 0.8pt) + if so { nhan(ctx, (0, y), $#so-dep(y)$, huong: "left", cach: 4pt) } + } + y = y + buoc-y + } +} + +// Hệ trục TRỌN GÓI: lưới + trục + vạch chia + số — một lệnh thay ba. +// #hinh(xmin: -4, xmax: 4, ymin: -3, ymax: 3, ctx => { he-truc(ctx) ... }) +// luoi-o: lưới ô vuông mờ · vach: vạch chia đơn vị · so: ghi số trên vạch +#let he-truc( + ctx, + ten-x: $x$, ten-y: $y$, ten-goc: $O$, + luoi-o: true, buoc-luoi: auto, // auto = theo buoc-x + vach: true, so: true, buoc-x: 1, buoc-y: 1, + mau: black, day: 0.9pt, mau-luoi: luma(87%), +) = { + if luoi-o { + luoi(ctx, buoc: if buoc-luoi == auto { buoc-x } else { buoc-luoi }, mau: mau-luoi) + } + truc(ctx, ten-x: ten-x, ten-y: ten-y, ten-goc: ten-goc, mau: mau, day: day) + if vach { vach-chia(ctx, buoc-x: buoc-x, buoc-y: buoc-y, so: so) } +} + +// Đường gióng từ điểm P về 2 trục (nét đứt) + nhãn toạ độ trên trục. +// ten-x / ten-y: auto = tự ghi số; none = không ghi; hoặc nội dung tuỳ ý. +#let giong( + ctx, P, + ten-x: auto, ten-y: auto, + huong-x: "below", huong-y: "left", + mau: gray.darken(20%), mau-diem: red, diem-to: true, +) = { + // điểm nằm ngay trên một trục thì bỏ đường gióng (tránh đè lên trục) + if calc.abs(P.at(0)) > 0.0001 and calc.abs(P.at(1)) > 0.0001 { + doan(ctx, P, (P.at(0), 0), mau: mau, day: 0.8pt, dut: true) + doan(ctx, P, (0, P.at(1)), mau: mau, day: 0.8pt, dut: true) + } + if diem-to { diem(ctx, P, bk: 1.9pt, mau: mau-diem) } + if ten-x != none and calc.abs(P.at(0)) > 0.0001 { + let tx = if ten-x == auto { so-toan(P.at(0)) } else { ten-x } + nhan(ctx, (P.at(0), 0), tx, huong: huong-x, cach: 4pt) + } + if ten-y != none and calc.abs(P.at(1)) > 0.0001 { + let ty = if ten-y == auto { so-toan(P.at(1)) } else { ten-y } + nhan(ctx, (0, P.at(1)), ty, huong: huong-y, cach: 4pt) + } +} + +// ---------- Bộ trợ giúp cửa sổ & nhãn (dùng nội bộ) ---------- +// Cửa sổ thông minh: bao các "điểm chốt" + gốc O, thêm lề tương xứng, +// bảo đảm kích thước tối thiểu. +#let _cua-so(diem-chot, le: 0.16, rong-min: 4.2, cao-min: 3.4, goc: true) = { + let pts = diem-chot + if goc { pts = pts + ((0, 0),) } + let xs = pts.map(p => p.at(0)) + let ys = pts.map(p => p.at(1)) + let (x1, x2) = (calc.min(..xs), calc.max(..xs)) + let (y1, y2) = (calc.min(..ys), calc.max(..ys)) + let px = calc.max((x2 - x1) * le, 0.65) + let py = calc.max((y2 - y1) * le, 0.6) + let (xa, xb, ya, yb) = (x1 - px, x2 + px, y1 - py, y2 + py) + if xb - xa < rong-min { + let t = (rong-min - (xb - xa)) / 2 + xa = xa - t + xb = xb + t + } + if yb - ya < cao-min { + let t = (cao-min - (yb - ya)) / 2 + ya = ya - t + yb = yb + t + } + (xmin: xa, xmax: xb, ymin: ya, ymax: yb) +} + +// Nghiệm của f trên [a, b] bằng chia đôi (chỉ bắt nghiệm đổi dấu). +#let _nghiem(f, a, b, n: 160) = { + let kq = () + let m = lay-mau(f, a, b, n: n) + for i in range(n) { + let (x1, y1) = m.at(i) + let (x2, y2) = m.at(i + 1) + if y1 * y2 <= 0 and (y1 != 0 or y2 != 0) { + let (lo, hi) = (x1, x2) + for _ in range(36) { + let g = (lo + hi) / 2 + if f(lo) * f(g) <= 0 { hi = g } else { lo = g } + } + let x = (lo + hi) / 2 + if kq.len() == 0 or x - kq.last() > (b - a) / n * 1.5 { kq.push(x) } + } + } + kq +} + +// Lấy hướng thứ i từ tuỳ chọn none|auto|chuỗi|tuple chuỗi. +#let _huong-i(tuy-chon, i, mac-dinh) = { + if type(tuy-chon) == str { tuy-chon } else if type(tuy-chon) == array and i < tuy-chon.len() { tuy-chon.at(i) } else { mac-dinh } +} + +// Gióng điểm cực trị thứ i theo tuỳ chọn cuc-tri (xem đầu file). +// ten-x/ten-y: nhãn CHÍNH XÁC (căn thức/phân số) tính sẵn từ hệ số — dùng khi +// người dùng không tự đặt tên; auto = lấy số từ toạ độ (so-toan). +#let _giong-ct(ctx, P, tuy-chon, i, ten-x: auto, ten-y: auto) = { + if tuy-chon == none or tuy-chon == false { return } + let o = if type(tuy-chon) == array { + if i < tuy-chon.len() { tuy-chon.at(i) } else { (:) } + } else if type(tuy-chon) == dictionary { tuy-chon } else { (:) } + giong( + ctx, P, + huong-x: o.at("x", default: "below"), + huong-y: o.at("y", default: "left"), + ten-x: o.at("ten-x", default: ten-x), + ten-y: o.at("ten-y", default: ten-y), + ) +} + +// Nhãn các giao điểm với Ox (cac-x: mảng hoành độ đã tính sẵn). +// ten: mảng nhãn CHÍNH XÁC song song với cac-x (thiếu thì tự lấy so-toan). +#let _nhan-giao-ox(ctx, cac-x, tuy-chon, ten: ()) = { + if tuy-chon == none { return } + let le = (ctx.xmax - ctx.xmin) * 0.02 + let i = 0 + let j = 0 + for x in cac-x { + // giao điểm trùng gốc O thì bỏ qua hẳn + if x >= ctx.xmin + le and x <= ctx.xmax - le and calc.abs(x) > 0.001 { + diem(ctx, (x, 0), bk: 1.9pt, mau: red) + let nd = if j < ten.len() { ten.at(j) } else { so-toan(x) } + nhan(ctx, (x, 0), nd, huong: _huong-i(tuy-chon, i, "below"), cach: 4pt) + i = i + 1 + } + j = j + 1 + } +} + +// Nhãn giao điểm với Oy (y = f(0)). +#let _nhan-giao-oy(ctx, y, tuy-chon) = { + if tuy-chon == none or y == none { return } + // giao điểm trùng gốc O thì bỏ qua hẳn + if y < ctx.ymin or y > ctx.ymax or ctx.xmin > 0 or ctx.xmax < 0 or calc.abs(y) <= 0.001 { return } + diem(ctx, (0, y), bk: 1.9pt, mau: red) + nhan(ctx, (0, y), so-toan(y), huong: _huong-i(tuy-chon, 0, "left"), cach: 4pt) +} + +// Chuẩn hoá true/false cũ về auto/none. +#let _chuan-tc(t) = if t == true { auto } else if t == false { none } else { t } + +// Nhãn tên hàm đặt ở 1 trong 4 góc hoặc toạ độ (x, y) tuỳ ý. +// goc-ten: "above-right" (mặc định) | "above-left" | "below-left" +// | "below-right" | (x, y) +#let _nhan-ten(ctx, ten, goc, mau) = { + if ten == none { return } + let dx = ctx.xmax - ctx.xmin + let dy = ctx.ymax - ctx.ymin + let P = if type(goc) == array { goc } else { + ( + above-right: (ctx.xmax - dx * 0.14, ctx.ymax - dy * 0.09), + above-left: (ctx.xmin + dx * 0.14, ctx.ymax - dy * 0.09), + below-left: (ctx.xmin + dx * 0.14, ctx.ymin + dy * 0.09), + below-right: (ctx.xmax - dx * 0.14, ctx.ymin + dy * 0.09), + ).at(goc, default: (ctx.xmax - dx * 0.14, ctx.ymax - dy * 0.09)) + } + // nền trắng để đường cong không xuyên qua nhãn + nhan(ctx, P, box(fill: white, inset: (x: 2.5pt, y: 1pt), radius: 1.5pt, ten), huong: "center", mau: mau) +} + +// Co/giãn cửa sổ quanh tâm: dan > 1 phóng to (đơn vị trục lớn hơn), +// dan < 1 thu nhỏ (nhìn được vùng rộng hơn). Dùng khi đơn vị quá lớn/bé. +#let _dan(cs, dan-x, dan-y) = { + if dan-x == 1 and dan-y == 1 { return cs } + let cx = (cs.xmin + cs.xmax) / 2 + let cy = (cs.ymin + cs.ymax) / 2 + let hx = (cs.xmax - cs.xmin) / 2 / dan-x + let hy = (cs.ymax - cs.ymin) / 2 / dan-y + (xmin: cx - hx, xmax: cx + hx, ymin: cy - hy, ymax: cy + hy) +} + +// ---------- Đồ thị tổng quát ---------- +// Vẽ hàm f bất kỳ trên hệ trục hoàn chỉnh, tự tạo khung. +#let do-thi-ham( + f, + w: 8cm, h: auto, + xmin: -4, xmax: 4, ymin: -3, ymax: 3, + tu: auto, den: auto, + mau: blue, day: 1.4pt, n: 150, + ten: none, // nhãn hàm (vd $y = x^2$) + goc-ten: "above-right", // vị trí nhãn: 4 góc hoặc toạ độ (x, y) + luoi-o: false, + vach: false, // true => vạch chia bước 1 + dan-x: 1, dan-y: 1, // co/giãn cửa sổ quanh tâm (xem đầu file) + them: none, // ctx => nội dung vẽ thêm +) = { + let hh = if h == auto { w * 0.68 } else { h } + let cs = _dan((xmin: xmin, xmax: xmax, ymin: ymin, ymax: ymax), dan-x, dan-y) + hinh(w: w, h: hh, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, ctx => { + if luoi-o { luoi(ctx) } + truc(ctx) + if vach { vach-chia(ctx) } + ve-ham(ctx, f, tu: tu, den: den, n: n, mau: mau, day: day,) + _nhan-ten(ctx, ten, goc-ten, mau) + if them != none { them(ctx) } + }) +} + +// ---------- Bậc nhất: y = ax + b ---------- +#let do-thi-bac-nhat( + a, b, + w: 7cm, mau: blue, ten: auto, goc-ten: "above-right", + giao-ox: auto, giao-oy: auto, giao-truc: true, + luoi-o: false, vach: false, + dan-x: 1, dan-y: 1, + them: none, +) = { + let f = x => a * x + b + let chot = ((0, b),) + if a != 0 { chot = chot + ((-b / a, 0),) } + let cs = _dan(_cua-so(chot, rong-min: 4.6), dan-x, dan-y) + do-thi-ham( + f, + w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, + mau: mau, luoi-o: luoi-o, vach: vach, + ten: if ten == auto { none } else { ten }, + goc-ten: goc-ten, + them: ctx => { + if giao-truc { + if a != 0 { _nhan-giao-ox(ctx, (-b / a,), giao-ox) } + _nhan-giao-oy(ctx, b, giao-oy) + } + if them != none { them(ctx) } + }, + ) +} + +// ---------- Bậc hai: y = ax² + bx + c ---------- +// Cửa sổ tự tính từ đỉnh + giao trục. +#let do-thi-bac-hai( + a, b, c, + w: 7.6cm, mau: blue, ten: none, goc-ten: "above-right", + dinh: auto, // gióng đỉnh: auto | none | (x: "below", y: "left") + giao-ox: auto, giao-oy: auto, + truc-dx: false, // trục đối xứng + luoi-o: false, vach: false, + dan-x: 1, dan-y: 1, + them: none, +) = { + let f = x => a * x * x + b * x + c + let xv = -b / (2 * a) + let yv = c - b * b / (4 * a) + let dinh = _chuan-tc(dinh) + // điểm chốt: đỉnh, giao Oy, giao Ox (nếu có) + let chot = ((xv, yv), (0, c)) + let dlt = b * b - 4 * a * c + let ng = () + if dlt > 0 { + let s = calc.sqrt(dlt) / (2 * calc.abs(a)) + ng = (xv - s, xv + s) + chot = chot + ((xv - s, 0), (xv + s, 0)) + } + // cánh parabol: mở cửa sổ theo bề ngang các điểm chốt + let xs = chot.map(p => p.at(0)) + (0,) + let mien = calc.max(calc.max(..xs) - calc.min(..xs), 3.0) + let (e1, e2) = (f(xv - 0.62 * mien), f(xv + 0.62 * mien)) + let ye = if a > 0 { calc.min(e1, e2) } else { calc.max(e1, e2) } + let cs = _dan(_cua-so(chot + ((xv - 0.62 * mien, ye), (xv + 0.62 * mien, ye)), rong-min: 4.6), dan-x, dan-y) + do-thi-ham( + f, + w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, + mau: mau, ten: ten, goc-ten: goc-ten, luoi-o: luoi-o, vach: vach, + them: ctx => { + if truc-dx and calc.abs(xv) > 0.001 { + doan(ctx, (xv, cs.ymin), (xv, cs.ymax), mau: red, day: 0.8pt, dut: true) + } + _giong-ct( + ctx, (xv, yv), dinh, 0, + ten-x: so-can-thuc(-b, 0, 0, 2 * a), + ten-y: so-can-thuc(4 * a * c - b * b, 0, 0, 4 * a), + ) + _nhan-giao-ox( + ctx, ng, giao-ox, + ten: if dlt > 0 { + let sg = if a > 0 { 1 } else { -1 } + (so-can-thuc(-b, -sg, dlt, 2 * a), so-can-thuc(-b, sg, dlt, 2 * a)) + } else { () }, + ) + _nhan-giao-oy(ctx, c, giao-oy) + if them != none { them(ctx) } + }, + ) +} + +// ---------- Bậc ba: y = ax³ + bx² + cx + d ---------- +// Cửa sổ tự tính từ 2 cực trị (nếu có) hoặc điểm uốn (nếu không). +#let do-thi-bac-ba( + a, b, c, d, + w: 7.6cm, mau: blue, ten: none, goc-ten: "above-right", + cuc-tri: auto, // gióng 2 điểm cực trị (nếu có) + diem-uon: none, // gióng điểm uốn: none | auto | (x:.., y:..) + giao-ox: none, giao-oy: none, + luoi-o: false, vach: false, + dan-x: 1, dan-y: 1, + them: none, +) = { + let f = x => ((a * x + b) * x + c) * x + d + let delta = b * b - 3 * a * c + let tam = -b / (3 * a) + let cuc-tri = _chuan-tc(cuc-tri) + let (x1, x2) = (none, none) + let chot = ((tam, f(tam)),) + if delta > 0 { + let s = calc.sqrt(delta) / (3 * calc.abs(a)) + x1 = tam - s + x2 = tam + s + chot = chot + ( + (x1, f(x1)), (x2, f(x2)), + (tam - 2.1 * s, f(tam - 2.1 * s)), (tam + 2.1 * s, f(tam + 2.1 * s)), + ) + } else { + // không cực trị: bán kính quanh điểm uốn sao cho biến thiên y vừa mắt + let m = c - b * b / (3 * a) + let bien = t => calc.abs(a * t * t * t + m * t) + let r = 1.0 + while bien(r) < 4 and r < 6 { r = r * 1.25 } + while bien(r) > 7 and r > 0.9 { r = r / 1.15 } + chot = chot + ((tam - r, f(tam - r)), (tam + r, f(tam + r))) + } + // giao Ox (tìm số) chỉ khi cần nhãn + let ng = if giao-ox != none { + let bk = 1 + calc.max(calc.abs(b), calc.abs(c), calc.abs(d)) / calc.abs(a) + _nghiem(f, tam - calc.min(bk, 12), tam + calc.min(bk, 12)) + } else { () } + let cs = _dan(_cua-so(chot + ng.map(x => (x, 0))), dan-x, dan-y) + do-thi-ham( + f, + w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, + mau: mau, ten: ten, goc-ten: goc-ten, n: 180, luoi-o: luoi-o, vach: vach, + them: ctx => { + if x1 != none { + // giá trị cực trị CHÍNH XÁC (phân số/căn thức) thay vì số thập phân + let ct = cuc-tri-bac-ba(a, b, c, d) + _giong-ct(ctx, (x1, f(x1)), cuc-tri, 0, ten-x: ct.x1, ten-y: ct.y1) + _giong-ct(ctx, (x2, f(x2)), cuc-tri, 1, ten-x: ct.x2, ten-y: ct.y2) + } + _giong-ct( + ctx, (tam, f(tam)), diem-uon, 0, + ten-x: so-can-thuc(-b, 0, 0, 3 * a), + ten-y: so-can-thuc(2 * b * b * b - 9 * a * b * c + 27 * a * a * d, 0, 0, 27 * a * a), + ) + _nhan-giao-ox(ctx, ng, giao-ox) + _nhan-giao-oy(ctx, d, giao-oy) + if them != none { them(ctx) } + }, + ) +} + +// ---------- Trùng phương: y = ax⁴ + bx² + c ---------- +// Cửa sổ tự tính từ 3 cực trị (a·b < 0) hoặc 1 cực trị (0, c). +#let do-thi-trung-phuong( + a, b, c, + w: 7.6cm, mau: blue, ten: none, goc-ten: "above-right", + cuc-tri: auto, + giao-ox: none, giao-oy: none, + luoi-o: false, vach: false, + dan-x: 1, dan-y: 1, + them: none, +) = { + let f = x => a * calc.pow(x, 4) + b * x * x + c + let co-3ct = a * b < 0 + let cuc-tri = _chuan-tc(cuc-tri) + let xc = if co-3ct { calc.sqrt(-b / (2 * a)) } else { 0 } + let chot = ((0, c),) + if co-3ct { + chot = chot + ( + (xc, f(xc)), (-xc, f(xc)), + (1.45 * xc, f(1.45 * xc)), (-1.45 * xc, f(1.45 * xc)), + ) + } else { + let bien = t => calc.abs(f(t) - c) + let r = 1.0 + while bien(r) < 4 and r < 5 { r = r * 1.25 } + while bien(r) > 7 and r > 0.8 { r = r / 1.15 } + chot = chot + ((r, f(r)), (-r, f(r))) + } + let ng = if giao-ox != none { + let xs = chot.map(p => p.at(0)) + _nghiem(f, calc.min(..xs) - 0.5, calc.max(..xs) + 0.5) + } else { () } + let cs = _dan(_cua-so(chot + ng.map(x => (x, 0))), dan-x, dan-y) + do-thi-ham( + f, + w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, + mau: mau, ten: ten, goc-ten: goc-ten, n: 180, luoi-o: luoi-o, vach: vach, + them: ctx => { + if cuc-tri != none { + if co-3ct { + let Xc = so-can-thuc(0, 1, -b / (2 * a), 1) + let nXc = so-can-thuc(0, -1, -b / (2 * a), 1) + let Yc = so-can-thuc(4 * a * c - b * b, 0, 0, 4 * a) + _giong-ct(ctx, (-xc, f(xc)), cuc-tri, 0, ten-x: nXc, ten-y: Yc) + _giong-ct(ctx, (0, c), cuc-tri, 1) + _giong-ct(ctx, (xc, f(xc)), cuc-tri, 2, ten-x: Xc, ten-y: Yc) + } else { + _giong-ct(ctx, (0, c), cuc-tri, 0) + } + } + _nhan-giao-ox(ctx, ng, giao-ox) + _nhan-giao-oy(ctx, c, giao-oy) + if them != none { them(ctx) } + }, + ) +} + +// ---------- Phân thức bậc nhất/bậc nhất: y = (ax + b)/(cx + d) ---------- +// Hai nhánh hypebol; tiệm cận LUÔN vẽ + nhãn trên trục. +// Cửa sổ tự tính từ tâm đối xứng (x0, y0), giao trục và gốc O. +#let do-thi-phan-thuc( + a, b, c, d, + w: 7.6cm, mau: blue, ten: none, goc-ten: "above-right", + giao-ox: none, giao-oy: none, + huong-x0: "below-left", huong-y0: "above-left", // hướng nhãn 2 tiệm cận + luoi-o: false, vach: false, + dan-x: 1, dan-y: 1, + them: none, +) = { + let x0 = -d / c // tiệm cận đứng + let y0 = a / c // tiệm cận ngang + let f = x => (a * x + b) / (c * x + d) + let chot = ((x0, y0), (x0 - 1.7, y0 - 1.7), (x0 + 1.7, y0 + 1.7)) + if a != 0 { chot = chot + ((-b / a, 0),) } + if d != 0 { chot = chot + ((0, b / d),) } + let cs = _dan(_cua-so(chot, le: 0.2), dan-x, dan-y) + hinh( + w: w, h: w * 0.74, + xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, + ctx => { + if luoi-o { luoi(ctx) } + truc(ctx) + if vach { vach-chia(ctx) } + // tiệm cận + nhãn trên trục + doan(ctx, (x0, cs.ymin), (x0, cs.ymax), mau: red, day: 0.9pt, dut: true) + doan(ctx, (cs.xmin, y0), (cs.xmax, y0), mau: red, day: 0.9pt, dut: true) + if calc.abs(x0) > 0.001 { nhan(ctx, (x0, 0), so-can-thuc(-d, 0, 0, c), huong: huong-x0, cach: 4pt) } + if calc.abs(y0) > 0.001 { nhan(ctx, (0, y0), so-can-thuc(a, 0, 0, c), huong: huong-y0, cach: 4pt) } + // hai nhánh + let g = (cs.xmax - cs.xmin) * 0.012 + ve-ham(ctx, f, tu: cs.xmin, den: x0 - g, n: 130, mau: mau) + ve-ham(ctx, f, tu: x0 + g, den: cs.xmax, n: 130, mau: mau) + if a != 0 { _nhan-giao-ox(ctx, (-b / a,), giao-ox) } + if d != 0 { _nhan-giao-oy(ctx, b / d, giao-oy) } + _nhan-ten(ctx, ten, goc-ten, mau) + if them != none { them(ctx) } + }, + ) +} + +// ---------- Hữu tỉ bậc hai/bậc nhất: y = (ax² + bx + c)/(dx + e) ---------- +// Tự nhận 4 tình huống: (a/d > 0 hay < 0) × (có 2 cực trị hay không). +// Có cực trị -> cửa sổ bao 2 điểm cực trị (+ gióng nhãn). +// Không cực trị -> cửa sổ quanh tâm đối xứng (giao 2 tiệm cận). +// LUÔN vẽ tiệm cận đứng x = x0 (nhãn trên Ox) và tiệm cận xiên +// y = px + q (nhãn ten-tcx: auto = "y = px + q" | none | nội dung tuỳ ý). +#let do-thi-huu-ti( + a, b, c, d, e, + w: 7.6cm, mau: blue, ten: none, goc-ten: "above-right", + dan-x: 1, dan-y: 1, + cuc-tri: auto, + tam: auto, // gióng tâm đối xứng I(x0, px0+q) + nhãn: + // auto = chỉ khi không có cực trị | true/(x:..,y:..) = luôn | none = tắt + giao-ox: none, giao-oy: none, + huong-x0: "below-right", // hướng nhãn tiệm cận đứng trên Ox + ten-tcx: auto, huong-tcx: "above-left", // nhãn tiệm cận xiên + luoi-o: false, vach: false, + them: none, +) = { + let x0 = -e / d // tiệm cận đứng + let p = a / d // tiệm cận xiên y = px + q + let q = (b * d - a * e) / (d * d) + let r = c - q * e // y = px + q + r/(dx + e) + let f = x => (a * x * x + b * x + c) / (d * x + e) + let tcx = x => p * x + q + let cuc-tri = _chuan-tc(cuc-tri) + let co-ct = p != 0 and r * d * p > 0 + let hien-tam = if tam == none { false } else if tam == auto { not co-ct } else { true } + let (xt1, xt2) = (none, none) + let chot = ((x0, tcx(x0)),) + if co-ct { + let t = calc.sqrt(r * d / p) + let xs = ((-e - t) / d, (-e + t) / d).sorted() + xt1 = xs.at(0) + xt2 = xs.at(1) + let s = xt2 - xt1 + chot = chot + ( + (xt1, f(xt1)), (xt2, f(xt2)), + (xt1 - 0.55 * s, f(xt1 - 0.55 * s)), (xt2 + 0.55 * s, f(xt2 + 0.55 * s)), + ) + } else { + // không cực trị: mở quanh tâm sao cho nhánh tách rõ khỏi tiệm cận + let t = calc.max(calc.min(calc.abs(r / (2 * d)), 3), 0.9) + chot = chot + ( + (x0 - t, f(x0 - t)), (x0 + t, f(x0 + t)), + (x0 - 2.4 * t, f(x0 - 2.4 * t)), (x0 + 2.4 * t, f(x0 + 2.4 * t)), + ) + } + // giao Ox: nghiệm tử thức (nếu cần nhãn) + let ng = if giao-ox != none and b * b - 4 * a * c > 0 { + let s = calc.sqrt(b * b - 4 * a * c) / (2 * calc.abs(a)) + (-b / (2 * a) - s, -b / (2 * a) + s) + } else { () } + let cs = _dan(_cua-so(chot + ng.map(x => (x, 0)), le: 0.14), dan-x, dan-y) + hinh( + w: w, h: w * 0.78, + xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, + ctx => { + if luoi-o { luoi(ctx) } + truc(ctx) + if vach { vach-chia(ctx) } + // tiệm cận đứng (khi gióng tâm thì nhãn x0 do gióng đảm nhiệm, tránh ghi 2 lần) + doan(ctx, (x0, cs.ymin), (x0, cs.ymax), mau: red, day: 0.9pt, dut: true) + if calc.abs(x0) > 0.001 and not hien-tam { nhan(ctx, (x0, 0), so-can-thuc(-e, 0, 0, d), huong: huong-x0, cach: 4pt) } + // tiệm cận xiên (cắt gọn trong cửa sổ) + let (X1, X2) = if calc.abs(p) > 0.0001 { + let ca = ((cs.ymin - q) / p, (cs.ymax - q) / p).sorted() + (calc.max(cs.xmin, ca.at(0)), calc.min(cs.xmax, ca.at(1))) + } else { (cs.xmin, cs.xmax) } + doan(ctx, (X1, tcx(X1)), (X2, tcx(X2)), mau: red, day: 0.9pt, dut: true) + if ten-tcx != none { + let nd = if ten-tcx == auto { _pt-bac-nhat(p, q) } else { ten-tcx } + let Xn = X1 + 0.88 * (X2 - X1) + nhan(ctx, (Xn, tcx(Xn)), nd, huong: huong-tcx, cach: 5pt, mau: red) + } + // hai nhánh + let g = (cs.xmax - cs.xmin) * 0.012 + ve-ham(ctx, f, tu: cs.xmin, den: x0 - g, n: 140, mau: mau) + ve-ham(ctx, f, tu: x0 + g, den: cs.xmax, n: 140, mau: mau) + // cực trị (nếu có) + if xt1 != none { + let ct = cuc-tri-huu-ti(a, b, c, d, e) + _giong-ct(ctx, (xt1, f(xt1)), cuc-tri, 0, ten-x: ct.x1, ten-y: ct.y1) + _giong-ct(ctx, (xt2, f(xt2)), cuc-tri, 1, ten-x: ct.x2, ten-y: ct.y2) + } + // tâm đối xứng I = giao 2 tiệm cận + if hien-tam { + let o = if type(tam) == dictionary { tam } else { (:) } + giong( + ctx, (x0, tcx(x0)), + huong-x: o.at("x", default: "below"), + huong-y: o.at("y", default: "left"), + ten-x: o.at("ten-x", default: so-can-thuc(-e, 0, 0, d)), + ten-y: o.at("ten-y", default: so-can-thuc(b * d - 2 * a * e, 0, 0, d * d)), + ) + } + _nhan-giao-ox( + ctx, ng, giao-ox, + ten: if ng.len() == 2 { + let dl = b * b - 4 * a * c + let sg = if a > 0 { 1 } else { -1 } + (so-can-thuc(-b, -sg, dl, 2 * a), so-can-thuc(-b, sg, dl, 2 * a)) + } else { () }, + ) + if e != 0 { _nhan-giao-oy(ctx, c / e, giao-oy) } + _nhan-ten(ctx, ten, goc-ten, mau) + if them != none { them(ctx) } + }, + ) +} + +// ---------- Mũ: y = aˣ (a > 0, a ≠ 1) ---------- +#let do-thi-mu(a, w: 7cm, mau: blue, ten: auto, goc-ten: auto, diem-db: true, luoi-o: false, vach: false, them: none) = { + // cơ số: nguyên -> số nguyên; còn lại -> số đẹp (phân số/căn) trong ngoặc + let cs = if calc.abs(a - calc.round(a)) < 0.001 { $#int(calc.round(a))$ } else { $(#so-toan(a))$ } + let nd = if ten == auto { $y = #cs^x$ } else { ten } + // a > 1 tăng (góc trống trên-trái) · a < 1 giảm (trên-phải) + let gt = if goc-ten == auto { if a > 1 { "above-left" } else { "above-right" } } else { goc-ten } + do-thi-ham( + x => calc.pow(a, x), + w: w, xmin: -2.9, xmax: 2.9, ymin: -1.2, ymax: 5.2, + mau: mau, ten: nd, goc-ten: gt, n: 160, luoi-o: luoi-o, vach: vach, + them: ctx => { + if diem-db { + giong(ctx, (0, 1), ten-x: none) + giong(ctx, (1, a)) + } + if them != none { them(ctx) } + }, + ) +} + +// ---------- Lôgarit: y = log_a(x) ---------- +#let do-thi-log(a, w: 7cm, mau: blue, ten: auto, goc-ten: auto, diem-db: true, luoi-o: false, vach: false, them: none) = { + let cs = if calc.abs(a - calc.round(a)) < 0.001 { $#int(calc.round(a))$ } else { so-toan(a) } + let nd = if ten == auto { $y = log_#cs x$ } else { ten } + // a > 1 tăng (góc trống dưới-phải) · a < 1 giảm (dưới-trái) + let gt = if goc-ten == auto { if a > 1 { "below-right" } else { "below-left" } } else { goc-ten } + do-thi-ham( + x => calc.ln(x) / calc.ln(a), + w: w, xmin: -1.4, xmax: 6, ymin: -3.1, ymax: 3.1, + tu: 0.035, den: 5.8, + mau: mau, ten: nd, goc-ten: gt, n: 220, luoi-o: luoi-o, vach: vach, + them: ctx => { + if diem-db { + giong(ctx, (1, 0), ten-y: none) + giong(ctx, (a, 1)) + } + if them != none { them(ctx) } + }, + ) +} + +// ---------- Lượng giác ---------- +// Ghi nhãn các bội của π trên trục hoành. +#let nhan-pi(ctx) = { + let cap = ( + (-2 * calc.pi, $-2pi$), (-calc.pi, $-pi$), (-calc.pi / 2, $-pi/2$), + (calc.pi / 2, $pi/2$), (calc.pi, $pi$), (2 * calc.pi, $2pi$), + ) + for (v, t) in cap { + if v > ctx.xmin + 0.3 and v < ctx.xmax - 0.3 { + let s = 3pt / ctx.sy + doan(ctx, (v, -s), (v, s), day: 0.8pt) + nhan(ctx, (v, 0), t, huong: "below", cach: 5pt) + } + } +} + +#let do-thi-sin(w: 10cm, mau: blue, ten: $y = sin x$, goc-ten: "above-right", luoi-o: false, them: none) = do-thi-ham( + x => calc.sin(x), + w: w, h: w * 0.34, + xmin: -6.9, xmax: 6.9, ymin: -1.7, ymax: 1.7, + tu: -6.6, den: 6.6, + mau: mau, ten: ten, goc-ten: goc-ten, n: 220, luoi-o: luoi-o, + them: ctx => { + nhan-pi(ctx) + nhan(ctx, (0, 1), $1$, huong: "above-left", cach: 3pt) + nhan(ctx, (0, -1), $-1$, huong: "below-left", cach: 3pt) + if them != none { them(ctx) } + }, +) + +#let do-thi-cos(w: 10cm, mau: blue, ten: $y = cos x$, goc-ten: "above-right", luoi-o: false, them: none) = do-thi-ham( + x => calc.cos(x), + w: w, h: w * 0.34, + xmin: -6.9, xmax: 6.9, ymin: -1.7, ymax: 1.7, + tu: -6.6, den: 6.6, + mau: mau, ten: ten, goc-ten: goc-ten, n: 220, luoi-o: luoi-o, + them: ctx => { + nhan-pi(ctx) + nhan(ctx, (0, 1), $1$, huong: "above-right", cach: 3pt) + if them != none { them(ctx) } + }, +) + +#let do-thi-tan(w: 8cm, mau: blue, ten: $y = tan x$, goc-ten: "above-right", luoi-o: false, them: none) = { + let p2 = calc.pi / 2 + do-thi-ham( + x => calc.tan(x), + w: w, h: w * 0.8, + xmin: -4.6, xmax: 4.6, ymin: -3.6, ymax: 3.6, + tu: -4.55, den: 4.55, + mau: mau, ten: ten, goc-ten: goc-ten, n: 260, luoi-o: luoi-o, + them: ctx => { + for k in (-3, -1, 1, 3) { + doan(ctx, (k * p2, -3.6), (k * p2, 3.6), mau: red, day: 0.8pt, dut: true) + } + nhan-pi(ctx) + if them != none { them(ctx) } + }, + ) +} + +// ---------- Côtang: y = cot x (tiệm cận đứng x = kπ) ---------- +// cot x = cos x / sin x; nhánh tự tách tại các tiệm cận (giống tan). +#let do-thi-cot(w: 8cm, mau: blue, ten: $y = cot x$, goc-ten: "above-left", luoi-o: false, them: none) = { + let pi = calc.pi + let cot = x => { + let s = calc.sin(x) + if calc.abs(s) < 0.000001 { 1000000 } else { calc.cos(x) / s } + } + do-thi-ham( + cot, + w: w, h: w * 0.8, + xmin: -4.6, xmax: 4.6, ymin: -3.6, ymax: 3.6, + tu: -4.55, den: 4.55, + mau: mau, ten: ten, goc-ten: goc-ten, n: 260, luoi-o: luoi-o, + them: ctx => { + // tiệm cận x = ±π (x = 0 trùng trục Oy nên không vẽ lại) + for k in (-1, 1) { + doan(ctx, (k * pi, -3.6), (k * pi, 3.6), mau: red, day: 0.8pt, dut: true) + } + nhan-pi(ctx) + if them != none { them(ctx) } + }, + ) +} + +// ---------- VẼ NHANH: chỉ cần công thức + màu ---------- +// Tự lấy mẫu hàm số và chọn cửa sổ y hợp lý (kể cả khi hàm tăng rất nhanh). +// #ve-do-thi(x => x*x*x - 3*x, mau: red) +// #ve-do-thi(x => calc.sin(2*x), mau: purple, ten: $y = sin 2x$) +// Lưu ý: hàm phải xác định trên [xmin, xmax] (hoặc trên [tu, den] nếu đặt); +// với hàm phân thức có tiệm cận, dùng #do-thi-phan-thuc / #do-thi-huu-ti. +#let ve-do-thi( + f, + mau: blue, + w: 8cm, + xmin: -4, xmax: 4, + tu: auto, den: auto, + ten: none, goc-ten: "above-right", + vach: false, luoi-o: false, + gioi-han-y: 12, // bề cao tối đa của cửa sổ nhìn theo trục y + giao-ox: none, giao-oy: none, + day: 1.4pt, + them: none, +) = { + let a = if tu == auto { xmin } else { tu } + let b = if den == auto { xmax } else { den } + let ys = lay-mau(f, a, b, n: 160).map(p => p.at(1)).sorted() + let lo = ys.first() + let hi = ys.last() + // gộp trục hoành vào cửa sổ nếu không làm cửa sổ quá cao + if lo > 0 and hi <= gioi-han-y { lo = 0 } + if hi < 0 and -lo <= gioi-han-y { hi = 0 } + // hàm biến thiên quá mạnh: thu cửa sổ quanh trung vị (đồ thị tự cắt nhánh) + if hi - lo > gioi-han-y { + let tv = ys.at(calc.floor(ys.len() / 2)) + lo = calc.max(lo, tv - gioi-han-y / 2) + hi = calc.min(hi, tv + gioi-han-y / 2) + } + let dem = (hi - lo) * 0.1 + 0.35 + do-thi-ham( + f, + w: w, xmin: xmin, xmax: xmax, + ymin: lo - dem, ymax: hi + dem, + tu: a, den: b, + mau: mau, day: day, n: 180, + ten: ten, goc-ten: goc-ten, vach: vach, luoi-o: luoi-o, + them: them, + ) +} + +// ===================================================================== +// NHIỀU ĐỒ THỊ TRÊN CÙNG MỘT HỆ TRỤC +// Mỗi hàm gói bằng #ham(...); truyền hàm trần x => ... cũng được. +// #do-thi-nhieu-ham( +// ham(x => x*x - 2, mau: blue, ten: $y = x^2 - 2$), +// ham(x => x, mau: red, ten: $y = x$, dut: true), +// xmin: -3, xmax: 3, giao-diem: auto, +// ) +// ===================================================================== +// Gói thông số một hàm. +// tai : auto = nhãn đặt gần đầu phải của đường; hoặc hoành độ x cụ thể +// huong-ten : hướng nhãn so với điểm đặt +#let ham( + f, + mau: blue, day: 1.3pt, dut: false, + ten: none, tai: auto, huong-ten: "above", + tu: auto, den: auto, +) = ( + f: f, mau: mau, day: day, dut: dut, + ten: ten, tai: tai, huong-ten: huong-ten, tu: tu, den: den, +) + +#let do-thi-nhieu-ham( + ..cac-ham, + w: 8cm, h: auto, + xmin: -4, xmax: 4, ymin: -3, ymax: 3, + vach: false, luoi-o: false, + giao-diem: none, // auto = chấm đỏ tại giao điểm của từng cặp đồ thị + dan-x: 1, dan-y: 1, + them: none, +) = { + let ds = cac-ham.pos().map(hs => if type(hs) == function { ham(hs) } else { hs }) + let cs = _dan((xmin: xmin, xmax: xmax, ymin: ymin, ymax: ymax), dan-x, dan-y) + let hh = if h == auto { w * 0.68 } else { h } + hinh(w: w, h: hh, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, ctx => { + if luoi-o { luoi(ctx) } + truc(ctx) + if vach { vach-chia(ctx) } + for hs in ds { + let a = if hs.tu == auto { cs.xmin } else { hs.tu } + let b = if hs.den == auto { cs.xmax } else { hs.den } + ve-ham(ctx, hs.f, tu: a, den: b, mau: hs.mau, day: hs.day, dut: hs.dut) + if hs.ten != none { + // vị trí nhãn: hoành độ chỉ định, hoặc điểm trong cửa sổ gần đầu phải + let P = none + if hs.tai != auto { + P = (hs.tai, (hs.f)(hs.tai)) + } else { + let le = (cs.ymax - cs.ymin) * 0.08 + let i = 40 + while i >= 0 and P == none { + let x = a + (b - a) * (0.06 + 0.88 * i / 40) + let y = (hs.f)(x) + if y > cs.ymin + le and y < cs.ymax - le { P = (x, y) } + i = i - 1 + } + } + if P != none { nhan(ctx, P, hs.ten, huong: hs.huong-ten, mau: hs.mau, cach: 7pt) } + } + } + if giao-diem == auto { + for i in range(ds.len()) { + for j in range(i + 1, ds.len()) { + let fi = ds.at(i).f + let fj = ds.at(j).f + for x in _nghiem(x => fi(x) - fj(x), cs.xmin, cs.xmax) { + let y = fi(x) + if y > cs.ymin and y < cs.ymax { diem(ctx, (x, y), bk: 2pt, mau: red) } + } + } + } + } + if them != none { them(ctx) } + }) +} + +// ===================================================================== +// MIỀN NGHIỆM BPT / HỆ BPT BẬC NHẤT HAI ẨN +// Quy ước SGK: GẠCH phần KHÔNG là miền nghiệm; miền nghiệm để trắng +// (hoặc tô nhạt bằng to-mien). +// #mien-nghiem(bpt(4, 5, -8, dau: "<"), xmin: -4, xmax: 4) +// #mien-nghiem( +// bpt(3, -2, -9, dau: "<=", mau: red), +// bpt(3, -5, -18, dau: ">=", mau: green.darken(20%)), +// to-mien: rgb(30, 100, 200, 40), +// ) +// ===================================================================== +// Một BPT: a·x + b·y (dau) c. dau: "<=", ">=", "<", ">" +// mau : màu đường thẳng; mau-gach: auto = cùng màu (nhạt hơn) +// goc-gach : GÓC gạch chéo RIÊNG cho bpt này; auto = theo goc-gach chung +// của mien-nghiem (giống lối mau-gach). buoc-gach: bước gạch riêng +// ten : nhãn đường thẳng (vd $d: 4x + 5y = -8$); ten-tai: 0..1 vị trí +// dọc theo đoạn nhìn thấy tại điểm đặt nhãn +// nghieng-ten : true (mặc định) = chữ NẰM NGHIÊNG theo chiều đường thẳng +// (tự đọc xuôi, không lộn ngược); false = chữ nằm ngang như cũ +// huong-ten : PHÍA đặt nhãn tại điểm đó — "above" hoặc "below". Khi nghiêng, +// đây là phía trên/dưới so với đường (theo pháp tuyến); khi không +// nghiêng vẫn nhận mọi hướng cũ ("above"/"below"/"left"/tuple…) +// cach-ten : khoảng cách từ đường tới nhãn +#let bpt( + a, b, c, + dau: "<=", + mau: red, day: 1.1pt, + mau-gach: auto, goc-gach: auto, buoc-gach: auto, + ten: none, ten-tai: 0.78, huong-ten: "above", + nghieng-ten: true, cach-ten: 3pt, +) = ( + a: a, b: b, c: c, dau: dau, mau: mau, day: day, + mau-gach: mau-gach, goc-gach: goc-gach, buoc-gach: buoc-gach, + ten: ten, ten-tai: ten-tai, huong-ten: huong-ten, + nghieng-ten: nghieng-ten, cach-ten: cach-ten, +) + +// Đoạn nhìn thấy của đường a·x + b·y = c trong cửa sổ (none nếu ngoài). +#let _doan-cua-so(ctx, a, b, c) = { + let gd = () + let canh = ( + ((ctx.xmin, ctx.ymin), (ctx.xmax, ctx.ymin)), + ((ctx.xmax, ctx.ymin), (ctx.xmax, ctx.ymax)), + ((ctx.xmax, ctx.ymax), (ctx.xmin, ctx.ymax)), + ((ctx.xmin, ctx.ymax), (ctx.xmin, ctx.ymin)), + ) + for (P, Q) in canh { + let fp = a * P.at(0) + b * P.at(1) - c + let fq = a * Q.at(0) + b * Q.at(1) - c + if fp * fq <= 0 and fp != fq { + let s = fp / (fp - fq) + let G = (P.at(0) + s * (Q.at(0) - P.at(0)), P.at(1) + s * (Q.at(1) - P.at(1))) + if gd.len() == 0 or khoang-cach(gd.at(0), G) > 0.0001 { gd.push(G) } + } + } + if gd.len() >= 2 { (gd.at(0), gd.at(1)) } else { none } +} + +#let mien-nghiem( + ..cac-bpt, + w: 7cm, h: auto, + xmin: -5, xmax: 5, ymin: -4, ymax: 4, + to-mien: none, // màu tô miền nghiệm (nên trong suốt), none = bỏ + giao-truc: none, // auto = ghi số giao điểm của các đường với 2 trục + buoc-gach: 6.5pt, goc-gach: 45deg, + vach: false, luoi-o: false, + them: none, +) = { + let ds = cac-bpt.pos() + let hh = if h == auto { w * (ymax - ymin) / (xmax - xmin) } else { h } + hinh(w: w, h: hh, xmin: xmin, xmax: xmax, ymin: ymin, ymax: ymax, ctx => { + let khung = ((xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)) + if luoi-o { luoi(ctx) } + // tô miền nghiệm: giao các nửa mặt phẳng + if to-mien != none { + let mn = khung + for d in ds { + // chuẩn hoá về dạng ≤: miền nghiệm là a·x + b·y ≤ c + let (a, b, c) = if d.dau in ("<=", "<") { (d.a, d.b, d.c) } else { (-d.a, -d.b, -d.c) } + mn = cat-nua-mp(mn, a, b, c) + } + if mn.len() >= 3 { da-giac-pt(mn.map(P => toa-pt(ctx, P)), to: to-mien) } + } + // gạch phần loại bỏ của từng BPT + for d in ds { + let (a, b, c) = if d.dau in ("<=", "<") { (d.a, d.b, d.c) } else { (-d.a, -d.b, -d.c) } + let loai = cat-nua-mp(khung, -a, -b, -c) // nửa mặt phẳng a·x + b·y ≥ c + let mg = if d.mau-gach == auto { d.mau.lighten(25%) } else { d.mau-gach } + // góc/bước gạch RIÊNG của từng bpt (auto = theo giá trị chung của mien-nghiem) + let gg = if d.at("goc-gach", default: auto) == auto { goc-gach } else { d.goc-gach } + let bg = if d.at("buoc-gach", default: auto) == auto { buoc-gach } else { d.buoc-gach } + if loai.len() >= 3 { + gach-mien(ctx, loai, goc: gg, buoc: bg, mau: mg) + } + } + truc(ctx) + if vach { vach-chia(ctx) } + // đường biên (đứt nếu BPT ngặt) + nhãn + for d in ds { + let dc = _doan-cua-so(ctx, d.a, d.b, d.c) + if dc != none { + let (A, B) = dc + doan(ctx, A, B, mau: d.mau, day: d.day, dut: d.dau in ("<", ">")) + if d.ten != none { + let P = chia(A, B, d.ten-tai) + let cach = d.at("cach-ten", default: 3pt) + if d.at("nghieng-ten", default: true) { + // chữ nằm nghiêng theo chiều đường; PHÍA above/below theo pháp tuyến. + // Đặt SÁT đường: lệch = cach + nửa bề DÀY chữ (vuông góc đường) — + // KHÔNG dùng offset của `nhan` (offset đó theo nửa khung ĐÃ XOAY, với + // nhãn dài + đường xiên thì khung rất to nên nhãn bị đẩy xa đường). + let xuoi = toa-pt(ctx, A).at(0) <= toa-pt(ctx, B).at(0) + let ang = if xuoi { goc-truc(ctx, A, B) } else { goc-truc(ctx, B, A) } + let n = _phap-tuyen(ctx, A, B) // pháp tuyến hướng LÊN (trang) + if d.huong-ten in ("below", "duoi") { n = (-n.at(0), -n.at(1)) } + context { + let nd = text(fill: d.mau, d.ten) + let nd2 = rotate(ang, reflow: true, nd) + let s = measure(nd2) // khung sau khi xoay (canh giữa) + let off = cach + measure(nd).height / 2 // bề dày chữ, vuông góc đường + let p = toa(ctx, P) + place( + dx: p.at(0) - s.width / 2 + n.at(0) * off, + dy: p.at(1) - s.height / 2 + n.at(1) * off, + nd2, + ) + } + } else { + nhan(ctx, P, d.ten, huong: d.huong-ten, mau: d.mau, cach: cach) + } + } + } + if giao-truc == auto { + if d.a != 0 { _nhan-giao-ox(ctx, (d.c / d.a,), auto) } + if d.b != 0 { _nhan-giao-oy(ctx, d.c / d.b, auto) } + } + } + if them != none { them(ctx) } + }) +} + +// ===================================================================== +// DIỆN TÍCH HÌNH PHẲNG GIỚI HẠN BỞI HAI ĐỒ THỊ +// g mặc định là trục hoành (y = 0). +// a, b: auto = tự lấy giao điểm ngoài cùng của f và g trong [tu, den]; +// hoặc chỉ định đoạn [a, b] cụ thể. +// Miền tô tự tách tại các giao điểm ở giữa nên tô đúng cả khi f − g đổi dấu. +// #dien-tich-2-ham(x => x*x, g: x => x + 2) // tự tìm giao +// #dien-tich-2-ham(x => calc.sin(x), a: 0, b: calc.pi) // trên [a, b] +// ===================================================================== +// Điểm đặt nhãn: điểm trên đồ thị gần mép phải mà còn nằm trong cửa sổ. +#let _diem-nhan(cs, f, a, b) = { + let le = (cs.ymax - cs.ymin) * 0.08 + let i = 40 + while i >= 0 { + let x = a + (b - a) * (0.06 + 0.88 * i / 40) + let y = f(x) + if y > cs.ymin + le and y < cs.ymax - le { return (x, y) } + i = i - 1 + } + none +} + +#let dien-tich-2-ham( + f, g: x => 0.0, + a: auto, b: auto, + tu: -5, den: 5, // phạm vi tìm giao điểm khi a, b: auto + w: 8cm, h: auto, + mau-f: blue, mau-g: red, day: 1.3pt, + ten-f: none, ten-g: none, + huong-ten-f: "above", huong-ten-g: "below", + mau-to: rgb(70, 90, 200, 70), + ten-a: auto, ten-b: auto, // nhãn cận trên Ox; none = bỏ + giong-ab: true, // đường đứt x = a, x = b + cham-giao: true, // chấm đỏ tại giao điểm + keo-dai: 0.22, // vẽ đồ thị dài thêm ra 2 phía (tỉ lệ đoạn) + dan-x: 1, dan-y: 1, + them: none, +) = { + let hieu = x => f(x) - g(x) + let giao = _nghiem(hieu, tu, den) + let aa = if a == auto { if giao.len() > 0 { giao.first() } else { tu } } else { a } + let bb = if b == auto { if giao.len() > 0 { giao.last() } else { den } } else { b } + let m = (bb - aa) * keo-dai + 0.3 + // cửa sổ: bao miền tô trên [aa, bb]; đồ thị ngoài đoạn bị cắt theo cửa sổ + let chot = lay-mau(f, aa, bb, n: 60) + lay-mau(g, aa, bb, n: 60) + let cs = _dan(_cua-so(chot + ((aa - m, 0), (bb + m, 0))), dan-x, dan-y) + let hh = if h == auto { w * 0.68 } else { h } + hinh(w: w, h: hh, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, ctx => { + // các đoạn con tách tại giao điểm nằm trong (aa, bb) + let moc = (aa,) + giao.filter(x => x > aa + 0.0001 and x < bb - 0.0001) + (bb,) + for i in range(moc.len() - 1) { + to-vung-2-ham(ctx, f, g, moc.at(i), moc.at(i + 1), mau: mau-to) + } + truc(ctx) + ve-ham(ctx, f, tu: aa - m, den: bb + m, mau: mau-f, day: day) + ve-ham(ctx, g, tu: aa - m, den: bb + m, mau: mau-g, day: day) + if giong-ab { + for (x, t) in ((aa, ten-a), (bb, ten-b)) { + let (y1, y2) = (calc.min(f(x), g(x), 0), calc.max(f(x), g(x), 0)) + if y2 - y1 > 0.0001 { + doan(ctx, (x, y1), (x, y2), mau: gray.darken(20%), day: 0.8pt, dut: true) + } + if t != none and calc.abs(x) > 0.001 { + nhan(ctx, (x, 0), if t == auto { so-toan(x) } else { t }, + huong: if calc.max(f(x), g(x)) < 0.0001 { "above" } else { "below" }, cach: 4pt) + } + } + } + if cham-giao { + for x in giao.filter(x => x >= aa - 0.0001 and x <= bb + 0.0001) { + diem(ctx, (x, f(x)), bk: 2pt, mau: red) + } + } + if ten-f != none { + let P = _diem-nhan(cs, f, aa - m, bb + m) + if P != none { nhan(ctx, P, ten-f, huong: huong-ten-f, mau: mau-f, cach: 7pt) } + } + if ten-g != none { + let P = _diem-nhan(cs, g, aa - m, bb + m) + if P != none { nhan(ctx, P, ten-g, huong: huong-ten-g, mau: mau-g, cach: 7pt) } + } + if them != none { them(ctx) } + }) +} + +// ---------- Căn thức: y = √x ---------- +#let do-thi-can(w: 7cm, mau: blue, ten: $y = sqrt(x)$, goc-ten: "above-right", luoi-o: false, vach: false, them: none) = do-thi-ham( + x => calc.sqrt(x), + w: w, xmin: -1.2, xmax: 6.2, ymin: -1, ymax: 3.2, + tu: 0, den: 6, + mau: mau, ten: ten, goc-ten: goc-ten, n: 160, luoi-o: luoi-o, vach: vach, + them: ctx => { + giong(ctx, (1, 1)) + giong(ctx, (4, 2)) + if them != none { them(ctx) } + }, +) + +// ===================================================================== +// TRỤC SỐ — biểu diễn đoạn / khoảng / nửa khoảng (một hoặc nhiều) +// Mỗi khoảng là tuple: (a, b, kieu) hoặc (a, b, kieu, mau) +// a, b : số HOẶC "-oo" / "+oo" (vô cực) — a < b +// kieu : chuỗi 2 kí tự đầu mút trái+phải, chọn trong: +// "[]" đoạn · "()" khoảng · "[)" · "(]" nửa khoảng +// Quy ước (SGK): GIỮ trống vùng nghiệm, GẠCH CHÉO phần loại bỏ; +// [ ] = lấy đầu mút (đặc), ( ) = không lấy (rỗng). +// #truc-so((-2, 3, "[)")) +// #truc-so((-2, 1, "()"), (3, "+oo", "[)")) +// dau: "ngoac" (mặc định) | "cham" (chấm đặc/rỗng) +// ===================================================================== +#let _vc(v) = type(v) == str // v là vô cực ("-oo"/"+oo") ? + +#let truc-so( + ..khoang, + w: 10cm, h: 1.5cm, // h: chiều cao khung cố định (hết khoảng trống dọc) + min: auto, max: auto, + ten: $x$, + mau: rgb("#1d4ed8"), // màu đoạn nghiệm + gach: true, // gạch chéo phần loại bỏ + mau-gach: red, + dau: "ngoac", // kiểu đầu mút: "ngoac" | "cham" + moc-phu: (), // mốc phụ chỉ để ghi số (không thuộc nghiệm) + so: true, + co-chu: 13pt, // cỡ chữ số tại mốc + cao-gach: 2mm, // chiều cao dải gạch chéo (đo trên trang) +) = { + let ks = khoang.pos() + // các đầu mút hữu hạn -> cửa sổ tự động + let huu-han = () + for k in ks { + if not _vc(k.at(0)) { huu-han.push(k.at(0)) } + if not _vc(k.at(1)) { huu-han.push(k.at(1)) } + } + for m in moc-phu { huu-han.push(if type(m) == array { m.at(0) } else { m }) } + let lo = if huu-han.len() > 0 { calc.min(..huu-han) } else { -4 } + let hi = if huu-han.len() > 0 { calc.max(..huu-han) } else { 4 } + let pad = calc.max((hi - lo) * 0.2, 1.5) + let xa = if min == auto { lo - pad } else { min } + let xb = if max == auto { hi + pad } else { max } + hinh(w: w, h: h, xmin: xa, xmax: xb, ymin: -1.1, ymax: 1.1, co-chu: co-chu, ctx => { + // ---- gạch chéo phần loại bỏ (bù của hợp các khoảng) ---- + if gach { + let segs = ks.map(k => { + let a = if _vc(k.at(0)) { xa } else { k.at(0) } + let b = if _vc(k.at(1)) { xb } else { k.at(1) } + (calc.min(a, b), calc.max(a, b)) + }).sorted(key: s => s.at(0)) + let merged = () + for s in segs { + if merged.len() == 0 or s.at(0) > merged.last().at(1) + 0.000001 { + merged.push(s) + } else { + let last = merged.pop() + merged.push((last.at(0), calc.max(last.at(1), s.at(1)))) + } + } + let unc = () + let cur = xa + for s in merged { + if s.at(0) > cur + 0.000001 { unc.push((cur, s.at(0))) } + cur = calc.max(cur, s.at(1)) + } + if cur < xb - 0.000001 { unc.push((cur, xb)) } + // dải gạch cao CỐ ĐỊNH trên trang (không phình theo cửa sổ toạ độ) + let hb = cao-gach / 2 / ctx.sy + for (u1, u2) in unc { + gach-mien( + ctx, ((u1, -hb), (u2, -hb), (u2, hb), (u1, hb)), + goc: 45deg, buoc: 6pt, mau: mau-gach, day: 0.5pt, + ) + } + } + // ---- trục (mũi tên 2 đầu) + nhãn x ---- + doan(ctx, (xa, 0), (xb, 0), day: 1pt) + dau-mui-ten(ctx, (xa, 0), (xb, 0)) + dau-mui-ten(ctx, (xb, 0), (xa, 0)) + nhan(ctx, (xb, 0), ten, huong: "below", cach: 6pt) + // ---- vạch + số tại đầu mút và mốc phụ ---- + let t = 3pt / ctx.sy + let ghi-moc(x) = { + doan(ctx, (x, -t), (x, t), day: 0.9pt) + if so { nhan(ctx, (x, 0), so-toan(x), huong: "below", cach: 6pt) } + } + for k in ks { + if not _vc(k.at(0)) { ghi-moc(k.at(0)) } + if not _vc(k.at(1)) { ghi-moc(k.at(1)) } + } + for m in moc-phu { ghi-moc(if type(m) == array { m.at(0) } else { m }) } + // ---- đoạn nghiệm + đầu mút ---- + let dau-mut(x, mo, ben) = { + // ben: "trai" | "phai" (quyết định chiều ngoặc / hướng) + let m = mau + if dau == "cham" { + let p = toa(ctx, (x, 0)) + if mo { + place(dx: p.at(0) - 3pt, dy: p.at(1) - 3pt, + circle(radius: 3pt, fill: white, stroke: 1pt + m)) + } else { + place(dx: p.at(0) - 3pt, dy: p.at(1) - 3pt, + circle(radius: 3pt, fill: m, stroke: none)) + } + } else { + let sym = if ben == "trai" { if mo { "(" } else { "[" } } + else { if mo { ")" } else { "]" } } + nhan(ctx, (x, 0), text(size: 1.45em, weight: "bold", fill: m)[#sym], huong: "center") + } + } + for k in ks { + let (a, b, kieu) = (k.at(0), k.at(1), k.at(2)) + let m = if k.len() >= 4 { k.at(3) } else { mau } + let xl = if _vc(a) { xa } else { a } + let xr = if _vc(b) { xb } else { b } + doan(ctx, (xl, 0), (xr, 0), mau: m, day: 2.6pt) + if not _vc(a) { + let mo = kieu.at(0) == "(" + if dau == "cham" { dau-mut(a, mo, "trai") } + else { + let sym = if mo { "(" } else { "[" } + nhan(ctx, (a, 0), text(size: 1.45em, weight: "bold", fill: m)[#sym], huong: "center") + } + } + if not _vc(b) { + let mo = kieu.at(1) == ")" + if dau == "cham" { dau-mut(b, mo, "phai") } + else { + let sym = if mo { ")" } else { "]" } + nhan(ctx, (b, 0), text(size: 1.45em, weight: "bold", fill: m)[#sym], huong: "center") + } + } + } + }) +} + +// ===================================================================== +// HYPERBOL x²/a² − y²/b² = 1 (nhập bán trục thực a, ảo b) +// Mặc định vẽ kèm: 2 tiệm cận y = ±(b/a)x, đỉnh A₁A₂, tiêu điểm F₁F₂. +// ===================================================================== +#let hyperbol( + a, b, + w: 8.4cm, + mau: blue, day: 1.4pt, + ten: auto, goc-ten: auto, + tiem-can: true, dinh: true, tieu-diem: true, + ten-dinh: ($A_1$, $A_2$), ten-tieu: ($F_1$, $F_2$), + luoi-o: false, vach: false, + them: none, +) = { + let eexp = t => calc.pow(2.718281828459045, t) + let ch = t => (eexp(t) + eexp(-t)) / 2 + let sh = t => (eexp(t) - eexp(-t)) / 2 + let c = calc.sqrt(a * a + b * b) + let X = calc.max(a * 2.4, c * 1.12) + let Y = (b / a) * X * 0.9 + let T = calc.ln(X / a + calc.sqrt((X / a) * (X / a) - 1)) + let nhanh = s => range(0, 121).map(i => { + let tt = -T + 2 * T * i / 120 + (s * a * ch(tt), b * sh(tt)) + }) + let nd = if ten == auto { + $x^2 / #so-dep(a * a) - y^2 / #so-dep(b * b) = 1$ + } else { ten } + let gt = if goc-ten == auto { (X * 0.3, Y * 0.82) } else { goc-ten } + hinh(w: w, xmin: -X, xmax: X, ymin: -Y, ymax: Y, ctx => { + if luoi-o { luoi(ctx) } + if tiem-can { + let xl = Y * a / b + doan(ctx, (-xl, -Y), (xl, Y), mau: gray.darken(15%), day: 0.9pt, dut: true) + doan(ctx, (-xl, Y), (xl, -Y), mau: gray.darken(15%), day: 0.9pt, dut: true) + } + truc(ctx) + if vach { vach-chia(ctx) } + duong-cong(ctx, nhanh(1), mau: mau, day: day) + duong-cong(ctx, nhanh(-1), mau: mau, day: day) + if dinh { + diem(ctx, (-a, 0), ten: ten-dinh.at(0), huong: "below", bk: 1.9pt, mau: red) + diem(ctx, (a, 0), ten: ten-dinh.at(1), huong: "below", bk: 1.9pt, mau: red) + } + if tieu-diem { + diem(ctx, (-c, 0), ten: ten-tieu.at(0), huong: "above", bk: 1.9pt) + diem(ctx, (c, 0), ten: ten-tieu.at(1), huong: "above", bk: 1.9pt) + } + _nhan-ten(ctx, nd, gt, mau) + if them != none { them(ctx) } + }) +} + +// ===================================================================== +// PARABOL y² = 2px (nhập tham số tiêu p ≠ 0; p > 0 mở phải, p < 0 mở trái) +// Mặc định vẽ kèm: đỉnh O, tiêu điểm F(p/2; 0), đường chuẩn x = −p/2. +// ===================================================================== +#let parabol( + p, + w: 8cm, + mau: blue, day: 1.4pt, + ten: auto, goc-ten: auto, + tieu-diem: true, duong-chuan: true, dinh: true, + ten-tieu: $F$, + luoi-o: false, vach: false, + them: none, +) = { + let s = if p > 0 { 1 } else { -1 } + let Xt = 4.2 + let Y = calc.sqrt(2 * calc.abs(p) * Xt) + let xdc = -p / 2 // đường chuẩn + let xf = p / 2 // tiêu điểm + let xs = (0, s * Xt, xdc, xf) + let xa = calc.min(..xs) - 0.9 + let xb = calc.max(..xs) + 0.9 + let ya = -Y - 0.6 + let yb = Y + 0.6 + let cur = range(0, 121).map(i => { + let yy = -Y + 2 * Y * i / 120 + (yy * yy / (2 * p), yy) + }) + let nd = if ten == auto { $y^2 = #so-dep(2 * p) x$ } else { ten } + // p > 0 mở phải -> nhãn bên trái trống; p < 0 mở trái -> nhãn bên phải + let gt = if goc-ten == auto { if s > 0 { "above-left" } else { "above-right" } } else { goc-ten } + hinh(w: w, xmin: xa, xmax: xb, ymin: ya, ymax: yb, ctx => { + if luoi-o { luoi(ctx) } + if duong-chuan { + doan(ctx, (xdc, ya + 0.2), (xdc, yb - 0.2), mau: gray.darken(15%), day: 1pt, dut: true) + nhan(ctx, (xdc, yb - 0.2), $Delta$, huong: if s > 0 { "above-left" } else { "above-right" }, cach: 3pt) + } + truc(ctx) + if vach { vach-chia(ctx) } + duong-cong(ctx, cur, mau: mau, day: day) + if dinh { diem(ctx, (0, 0), bk: 1.9pt, mau: red) } + if tieu-diem { + diem(ctx, (xf, 0), ten: ten-tieu, huong: "below", bk: 1.9pt, mau: red) + } + _nhan-ten(ctx, nd, gt, mau) + if them != none { them(ctx) } + }) +} + +// ===================================================================== +// ELIP x²/a² + y²/b² = 1 (nhập 2 bán trục a, b như hyperbol) +// Mặc định vẽ kèm: 4 đỉnh (±a; 0), (0; ±b) và 2 tiêu điểm trên trục lớn +// (a > b: F(±c; 0); b > a: F(0; ±c); c = √|a² − b²|). a = b -> đường tròn. +// ===================================================================== +#let duong-elip( + a, b, + w: 8cm, + mau: blue, day: 1.4pt, + ten: auto, goc-ten: "above-right", + dinh: true, tieu-diem: true, + ten-tieu: ($F_1$, $F_2$), + luoi-o: false, vach: false, + them: none, +) = { + let c = calc.sqrt(calc.abs(a * a - b * b)) + let mg = 0.85 + let X = a + mg + let Y = b + mg + let nd = if ten == auto { $x^2 / #so-dep(a * a) + y^2 / #so-dep(b * b) = 1$ } else { ten } + hinh(w: w, xmin: -X, xmax: X, ymin: -Y, ymax: Y, ctx => { + if luoi-o { luoi(ctx) } + truc(ctx) + if vach { vach-chia(ctx) } + elip(ctx, (0, 0), a, b, mau: mau, day: day) + if dinh { + diem(ctx, (-a, 0), ten: $A_1$, huong: "below-left", bk: 1.9pt, mau: red) + diem(ctx, (a, 0), ten: $A_2$, huong: "below-right", bk: 1.9pt, mau: red) + diem(ctx, (0, b), ten: $B_2$, huong: "above-left", bk: 1.9pt, mau: red) + diem(ctx, (0, -b), ten: $B_1$, huong: "below-left", bk: 1.9pt, mau: red) + } + if tieu-diem and c > 0.001 { + if a >= b { + diem(ctx, (-c, 0), ten: ten-tieu.at(0), huong: "above", bk: 1.9pt) + diem(ctx, (c, 0), ten: ten-tieu.at(1), huong: "above", bk: 1.9pt) + } else { + diem(ctx, (0, -c), ten: ten-tieu.at(0), huong: "right", bk: 1.9pt) + diem(ctx, (0, c), ten: ten-tieu.at(1), huong: "right", bk: 1.9pt) + } + } + _nhan-ten(ctx, nd, goc-ten, mau) + if them != none { them(ctx) } + }) +} +// (đồng bộ mount 07/2026) diff --git a/packages/preview/conic-toan/0.3.4/lib/hinh-khong-gian.typ b/packages/preview/conic-toan/0.3.4/lib/hinh-khong-gian.typ new file mode 100644 index 0000000000..5929960dd8 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/hinh-khong-gian.typ @@ -0,0 +1,925 @@ +// ===================================================================== +// hinh-khong-gian.typ — HÌNH KHÔNG GIAN THPT +// Hình chóp, lăng trụ, hình hộp, lập phương, nón, trụ, cầu, trục Oxyz. +// Nét khuất tự động vẽ đứt theo đúng quy ước SGK. +// +// Mỗi hình đều có tham số `them`: hàm (ctx, dinh) => nội dung, cho phép +// vẽ THÊM điểm/đoạn/thiết diện lên hình bằng chính các đỉnh có sẵn. +// vd: them: (ctx, d) => { diem(ctx, chia(d.S, d.A, 0.5), ten: $M$) } +// ===================================================================== +#import "ve.typ": * +#import "hinh-phang.typ": huong-ra, trong-tam + +// Phép chiếu song song đơn giản: (x, y, z) -> mặt phẳng. +// x: sang phải, y: chiều sâu (ra sau), z: lên trên. +#let chieu(P, k: 0.45, goc: 40deg) = ( + P.at(0) + k * P.at(1) * calc.cos(goc), + P.at(2) + k * P.at(1) * calc.sin(goc), +) + +// Gọi callback `them` nếu có. +#let goi-them(them, ctx, dinh) = { + if them != none { them(ctx, dinh) } +} + +// ---------- HÌNH CHÓP TAM GIÁC S.ABC ---------- +// duong-cao: none | "tam" (chân H trong tam giác) | "dinh-a" (SA vuông đáy) +#let hinh-chop-tam-giac( + w: 7cm, + ten: ($S$, $A$, $B$, $C$), + duong-cao: none, + ten-chan: $H$, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -0.6, xmax: 5.0, ymin: -0.5, ymax: 5.0, ctx => { + let d = ( + A: (0.0, 1.1), + B: (3.1, 0.0), + C: (4.3, 1.9), + S: (2.1, 4.4), + ) + // nét liền + doan(ctx, d.A, d.B, mau: mau, day: day) + doan(ctx, d.S, d.A, mau: mau, day: day) + doan(ctx, d.S, d.B, mau: mau, day: day) + doan(ctx, d.S, d.C, mau: mau, day: day) + doan(ctx, d.B, d.C, mau: mau, day: day) + // nét khuất + doan(ctx, d.A, d.C, mau: mau, day: day, dut: true) + // đường cao + if duong-cao == "tam" { + let H = (2.35, 1.15) + d.insert("H", H) + doan(ctx, d.S, H, mau: mau-phu, day: 1pt, dut: true) + goc-vuong(ctx, H, d.S, d.B, r: 0.28, mau: mau-phu) + diem(ctx, H, ten: ten-chan, huong: "duoi", bk: 1.6pt, mau: mau-phu) + } else if duong-cao == "dinh-a" { + goc-vuong(ctx, d.A, d.S, d.B, r: 0.3, mau: mau-phu) + } + // nhãn đỉnh + let G = trong-tam(d.A, d.B, d.C, d.S) + for (P, t) in ((d.S, d.A, d.B, d.C).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) +}) + +// ---------- HÌNH CHÓP TỨ GIÁC S.ABCD (đáy hình bình hành / vuông) ---------- +// duong-cheo: vẽ 2 đường chéo đáy (đứt) + tâm O. +// duong-cao: none | "tam" (SO vuông đáy, chóp đều) | "dinh-a" (SA vuông đáy) + +#let hinh-chop-tu-giac-thuong( + w: 7.6cm, + ten: ($S$, $A$, $B$, $C$, $D$), + duong-cheo: false, + duong-cao: none, + ten-tam: $O$, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -1, xmax: 5, ymin: -0.5, ymax: 7, ctx => { + let d = ( + A: (-1, 3), + B: (5, 3), + C: (4, 1), + D: (0, 0), // = A + C - B (hình bình hành) + S: (2, 7), + ) + let O = trung-diem(d.A, d.C) + // nét liền + doan(ctx, d.A, d.B, mau: mau, day: day, dut: true) + doan(ctx, d.B, d.C, mau: mau, day: day) + doan(ctx, d.S, d.A, mau: mau, day: day) + doan(ctx, d.S, d.B, mau: mau, day: day) + doan(ctx, d.S, d.C, mau: mau, day: day) + // nét khuất + doan(ctx, d.A, d.D, mau: mau, day: day) + doan(ctx, d.D, d.C, mau: mau, day: day) + doan(ctx, d.S, d.D, mau: mau, day: day) + if duong-cheo or duong-cao == "tam" { + d.insert("O", O) + doan(ctx, d.A, d.C, mau: mau, day: 0.9pt, dut: true) + doan(ctx, d.B, d.D, mau: mau, day: 0.9pt, dut: true) + diem(ctx, O, ten: ten-tam, huong: "duoi", bk: 1.6pt) + } + if duong-cao == "tam" { + doan(ctx, d.S, O, mau: mau-phu, day: 1pt, dut: true) + goc-vuong(ctx, O, d.S, d.C, r: 0.3, mau: mau-phu) + } else if duong-cao == "dinh-a" { + goc-vuong(ctx, d.A, d.S, d.B, r: 0.3, mau: mau-phu) + } + // nhãn đỉnh + let G = trong-tam(d.A, d.B, d.C, d.D, d.S) + for (P, t) in ((d.S, d.A, d.B, d.C, d.D).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) +}) + +#let hinh-chop-tu-giac( + w: 7.6cm, + ten: ($S$, $A$, $B$, $C$, $D$), + duong-cheo: false, + duong-cao: none, + ten-tam: $O$, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: 0, xmax: 9, ymin: -0.5, ymax: 6, ctx => { + let d = ( + A: (3, 2), + B: (9, 2), + C: (6, 0), + D: (0, 0), // = A + C - B (hình bình hành) + S: (4.5, 6), + ) + let O = trung-diem(d.A, d.C) + // nét liền + doan(ctx, d.A, d.B, mau: mau, day: day, dut: true) + doan(ctx, d.B, d.C, mau: mau, day: day) + doan(ctx, d.S, d.A, mau: mau, day: day, dut: true) + doan(ctx, d.S, d.B, mau: mau, day: day) + doan(ctx, d.S, d.C, mau: mau, day: day) + // nét khuất + doan(ctx, d.A, d.D, mau: mau, day: day, dut: true) + doan(ctx, d.D, d.C, mau: mau, day: day) + doan(ctx, d.S, d.D, mau: mau, day: day) + if duong-cheo or duong-cao == "tam" { + d.insert("O", O) + doan(ctx, d.A, d.C, mau: mau, day: 0.9pt, dut: true) + doan(ctx, d.B, d.D, mau: mau, day: 0.9pt, dut: true) + diem(ctx, O, ten: ten-tam, huong: "duoi", bk: 1.6pt) + } + if duong-cao == "tam" { + doan(ctx, d.S, O, mau: mau-phu, day: 1pt, dut: true) + goc-vuong(ctx, O, d.S, d.C, r: 0.3, mau: mau-phu) + } else if duong-cao == "dinh-a" { + goc-vuong(ctx, d.A, d.S, d.B, r: 0.3, mau: mau-phu) + } + // nhãn đỉnh + let G = trong-tam(d.A, d.B, d.C, d.D, d.S) + for (P, t) in ((d.S, d.A, d.B, d.C, d.D).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) +}) + +#let hinh-chop-day-hinh-thang( + w: 7.6cm, + ten: ($S$, $A$, $B$, $C$, $D$), + duong-cheo: false, + duong-cao: none, + ten-tam: $O$, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: 0, xmax: 9, ymin: -0.5, ymax: 6, ctx => { + let d = ( + A: (2, 2), + B: (9, 2), + C: (3.5, 0), + D: (0, 0), // = A + C - B (hình bình hành) + S: (2, 6), + ) + let O = trung-diem(d.A, d.C) + // nét liền + doan(ctx, d.A, d.B, mau: mau, day: day, dut: true) + doan(ctx, d.B, d.C, mau: mau, day: day) + doan(ctx, d.S, d.A, mau: mau, day: day, dut: true) + doan(ctx, d.S, d.B, mau: mau, day: day) + doan(ctx, d.S, d.C, mau: mau, day: day) + // nét khuất + doan(ctx, d.A, d.D, mau: mau, day: day, dut: true) + doan(ctx, d.D, d.C, mau: mau, day: day) + doan(ctx, d.S, d.D, mau: mau, day: day) + if duong-cheo or duong-cao == "tam" { + d.insert("O", O) + doan(ctx, d.A, d.C, mau: mau, day: 0.9pt, dut: true) + doan(ctx, d.B, d.D, mau: mau, day: 0.9pt, dut: true) + diem(ctx, O, ten: ten-tam, huong: "duoi", bk: 1.6pt) + } + if duong-cao == "tam" { + doan(ctx, d.S, O, mau: mau-phu, day: 1pt, dut: true) + goc-vuong(ctx, O, d.S, d.C, r: 0.3, mau: mau-phu) + } else if duong-cao == "dinh-a" { + goc-vuong(ctx, d.A, d.S, d.B, r: 0.3, mau: mau-phu) + } + // nhãn đỉnh + let G = trong-tam(d.A, d.B, d.C, d.D, d.S) + for (P, t) in ((d.S, d.A, d.B, d.C, d.D).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) +}) + +// ---------- HÌNH CHÓP TAM GIÁC ĐỀU S.ABC ---------- +// S thẳng trên trọng tâm O của đáy; SO vuông góc đáy (nét đứt). +// trung-tuyen: vẽ thêm trung tuyến AM của đáy đi qua O. +#let hinh-chop-tam-giac-deu( + w: 7cm, + ten: ($S$, $A$, $B$, $C$), + ten-tam: $O$, + ten-trung-diem: $M$, // tên trung điểm BC (khi trung-tuyen: true) + trung-tuyen: false, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -0.6, xmax: 6.0, ymin: -0.5, ymax: 5.3, ctx => { + let d = (A: (0.0, 2), B: (1.5, 0.0), C: (6, 2)) + let O = trong-tam(d.A, d.B, d.C) + let S = (O.at(0), O.at(1) + 3.7) + d.insert("O", O) + d.insert("S", S) + // cạnh đáy + doan(ctx, d.A, d.B, mau: mau, day: day) + doan(ctx, d.B, d.C, mau: mau, day: day) + doan(ctx, d.A, d.C, mau: mau, day: day, dut: true) + // cạnh bên + doan(ctx, S, d.A, mau: mau, day: day) + doan(ctx, S, d.B, mau: mau, day: day) + doan(ctx, S, d.C, mau: mau, day: day) + // đường cao SO + if trung-tuyen { + let M = trung-diem(d.B, d.C) + d.insert("M", M) + doan(ctx, d.A, M, mau: mau, day: 0.9pt, dut: true) + diem(ctx, M, ten: ten-trung-diem, huong: "phai", bk: 1.5pt) + } + doan(ctx, S, O, mau: mau-phu, day: 1pt, dut: true) + goc-vuong(ctx, O, S, d.A, r: 0.26, mau: mau-phu) + diem(ctx, O, ten: ten-tam, huong: "duoi", bk: 1.5pt) + let G = trong-tam(d.A, d.B, d.C, S) + for (P, t) in ((S, d.A, d.B, d.C).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) +}) + +// ---------- HÌNH CHÓP TỨ GIÁC ĐỀU S.ABCD ---------- +// (đáy hình vuông, SO vuông góc đáy tại tâm O — vẽ sẵn 2 đường chéo) +#let hinh-chop-tu-giac-deu(w: 7.6cm, ten: ($S$, $A$, $B$, $C$, $D$), ten-tam: $O$, them: none) = { + hinh-chop-tu-giac(w: w, ten: ten, ten-tam: ten-tam, duong-cao: "tam", duong-cheo: true, them: them) +} + +// ---------- HÌNH CHÓP TAM DIỆN VUÔNG O.ABC ---------- +// OA, OB, OC đôi một vuông góc (vẽ kiểu "góc phòng", 3 dấu vuông tại O). +#let hinh-chop-tam-dien-vuong( + w: 6.6cm, + ten: ($O$, $A$, $B$, $C$), + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -2.9, xmax: 3.3, ymin: -2.7, ymax: 3.4, ctx => { + let d = (O: (0.0, 0.0), A: (-2.1, -1.9), B: (2.7, -0.4), C: (0.0, 2.8)) + doan(ctx, d.O, d.A, mau: mau, day: day, dut: true) + doan(ctx, d.O, d.B, mau: mau, day: day, dut: true) + doan(ctx, d.O, d.C, mau: mau, day: day, dut: true) + doan(ctx, d.A, d.B, mau: mau, day: day) + doan(ctx, d.B, d.C, mau: mau, day: day) + doan(ctx, d.C, d.A, mau: mau, day: day) + goc-vuong(ctx, d.O, d.A, d.B, r: 0.30, mau: mau-phu) + goc-vuong(ctx, d.O, d.B, d.C, r: 0.38, mau: mau-phu) + goc-vuong(ctx, d.O, d.A, d.C, r: 0.46, mau: mau-phu) + diem(ctx, d.O, ten: ten.at(0), huong: "tren-phai", bk: 1.5pt) + diem(ctx, d.A, ten: ten.at(1), huong: "duoi-trai", bk: 1.5pt) + diem(ctx, d.B, ten: ten.at(2), huong: "phai", bk: 1.5pt) + diem(ctx, d.C, ten: ten.at(3), huong: "tren", bk: 1.5pt) + goi-them(them, ctx, d) +}) + +// ---------- CHÓP ĐÁY TAM GIÁC VUÔNG, CẠNH BÊN VUÔNG GÓC ĐÁY ---------- +// Đáy ABC vuông tại B; SA vuông góc với đáy (S thẳng trên A). +#let hinh-chop-day-tam-giac-vuong( + w: 6.8cm, + ten: ($S$, $A$, $B$, $C$), + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -0.7, xmax: 4.6, ymin: -0.6, ymax: 5.9, ctx => { + let d = (A: (0.0, 2), B: (3.4, 0.0), C: (4.6, 2), S: (0.0, 5.5)) + // đáy + doan(ctx, d.A, d.B, mau: mau, day: day) + doan(ctx, d.B, d.C, mau: mau, day: day) + doan(ctx, d.A, d.C, mau: mau, day: day, dut: true) + // cạnh bên + doan(ctx, d.S, d.A, mau: mau, day: day) + doan(ctx, d.S, d.B, mau: mau, day: day) + doan(ctx, d.S, d.C, mau: mau, day: day) + // SA vuông đáy + đáy vuông tại B + goc-vuong(ctx, d.A, d.S, d.B, r: 0.3, mau: mau-phu) + goc-vuong(ctx, d.B, d.A, d.C, r: 0.3, mau: mau-phu) + let G = trong-tam(d.A, d.B, d.C, d.S) + for (P, t) in ((d.S, d.A, d.B, d.C).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) +}) + +// ---------- CHÓP ĐÁY HÌNH CHỮ NHẬT, CẠNH BÊN VUÔNG GÓC ĐÁY ---------- +// Đáy ABCD hình chữ nhật; SA vuông góc đáy (S thẳng trên A). +#let hinh-chop-day-chu-nhat( + w: 7.6cm, + ten: ($S$, $A$, $B$, $C$, $D$), + duong-cheo: false, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -3, xmax: 6.0, ymin: -2, ymax: 5.5, ctx => { + let d = (A: (0.0, 0.0), B: (6, 0.0), C: (3, -2), D: (-3, -2), S: (0.0, 4.8)) + // đáy: nét liền phía trước, nét khuất phía sau + doan(ctx, d.A, d.B, mau: mau, day: day, dut: true) + doan(ctx, d.B, d.C, mau: mau, day: day) + doan(ctx, d.A, d.D, mau: mau, day: day, dut: true) + doan(ctx, d.D, d.C, mau: mau, day: day) + // cạnh bên + doan(ctx, d.S, d.A, mau: mau, day: day, dut: true) + doan(ctx, d.S, d.B, mau: mau, day: day) + doan(ctx, d.S, d.C, mau: mau, day: day) + doan(ctx, d.S, d.D, mau: mau, day: day) + if duong-cheo { + doan(ctx, d.A, d.C, mau: mau, day: 0.9pt, dut: true) + doan(ctx, d.B, d.D, mau: mau, day: 0.9pt, dut: true) + } + // SA vuông với AB và AD + goc-vuong(ctx, d.A, d.S, d.B, r: 0.3, mau: mau-phu) + goc-vuong(ctx, d.A, d.S, d.D, r: 0.42, mau: mau-phu) + let G = trong-tam(d.A, d.B, d.C, d.D, d.S) + for (P, t) in ((d.S, d.A, d.B, d.C, d.D).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) +}) + +// ---------- HÌNH HỘP CHỮ NHẬT / LẬP PHƯƠNG ABCD.A'B'C'D' ---------- +// Đáy ABCD, đỉnh trên A'B'C'D'. dai, cao: kích thước; sau: độ "lùi" phối cảnh. +#let hinh-hop( + w: 7.6cm, + dai: 3.6, cao: 2.6, sau: 1.5, nghieng: -0.7, + ten: ($A$, $B$, $C$, $D$, $A'$, $B'$, $C'$, $D'$), + duong-cheo: false, // đường chéo không gian AC' (đứt) + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = { + let dx = -sau * 0.75 + let dy = -sau * 0.62 + hinh( + w: w, + xmin: -dx - 2, xmax: dai - dx , + ymin: -dy - 2, ymax: cao - dy, + ctx => { + let d = ( + A: (0.0, 0.0), + B: (dai, 0.0), + C: (dai + dx, dy), + D: (dx, dy), + ) + d.insert("A1", (d.A.at(0) + nghieng, d.A.at(1) + cao)) + d.insert("B1", (d.B.at(0) + nghieng, d.B.at(1) + cao)) + d.insert("C1", (d.C.at(0) + nghieng, d.C.at(1) + cao)) + d.insert("D1", (d.D.at(0) + nghieng, d.D.at(1) + cao)) + // nét liền: mặt trước, mặt phải, mặt trên + duong-cong(ctx, (d.A1, d.B1, d.C1, d.D1), mau: mau, day: day, dong: true) + + + doan(ctx, d.B, d.C, mau: mau, day: day) + doan(ctx, d.D, d.C, mau: mau, day: day) + doan(ctx, d.D, d.D1, mau: mau, day: day) + doan(ctx, d.B, d.B1, mau: mau, day: day) + doan(ctx, d.C, d.C1, mau: mau, day: day) + // nét khuất: 3 cạnh tại đỉnh A + doan(ctx, d.A, d.D, mau: mau, day: day, dut: true) + doan(ctx, d.A, d.B, mau: mau, day: day, dut: true) + doan(ctx, d.A, d.A1, mau: mau, day: day, dut: true) + + if duong-cheo { + doan(ctx, d.A, d.C1, mau: mau-phu, day: 1pt, dut: true) + } + let G = trong-tam(d.A, d.C1) + for (P, t) in ((d.A, d.B, d.C, d.D, d.A1, d.B1, d.C1, d.D1).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) + }, + ) +} + +// Hình lập phương cạnh chuẩn. +#let hinh-lap-phuong(w: 7cm, ten: ($A$, $B$, $C$, $D$, $A'$, $B'$, $C'$, $D'$), duong-cheo: false, them: none) = { + hinh-hop(w: w, dai: 2.8, cao: 2.8, sau: 1.5, nghieng: 0, ten: ten, duong-cheo: duong-cheo, them: them) +} + +// Hình lập phương cạnh chuẩn. +#let hinh-hop-chu-nhat(w: 7cm, ten: ($A$, $B$, $C$, $D$, $A'$, $B'$, $C'$, $D'$), duong-cheo: false, them: none) = { + hinh-hop(w: w, dai: 4, cao: 2.8, sau: 1.5, nghieng: 0, ten: ten, duong-cheo: duong-cheo, them: them) +} + + +// ---------- LĂNG TRỤ TAM GIÁC ABC.A'B'C' (đứng) ---------- +#let hinh-lang-tru-tam-giac( + w: 6.6cm, + cao: 3.0, + ten: ($A$, $B$, $C$, $A'$, $B'$, $C'$), + mau: black, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -0.6, xmax: 4.4, ymin: -0.5, ymax: cao + 1.9, ctx => { + let d = ( + A: (0.0, 0.0), + B: (3.4, -0.0), + C: (2.4, 1.3), + ) + d.insert("A1", (d.A.at(0), d.A.at(1) + cao)) + d.insert("B1", (d.B.at(0), d.B.at(1) + cao)) + d.insert("C1", (d.C.at(0), d.C.at(1) + cao)) + // đáy trên: nét liền + da-giac(ctx, (d.A1, d.B1, d.C1), mau: mau, day: day) + // cạnh bên + doan(ctx, d.A, d.A1, mau: mau, day: day) + doan(ctx, d.B, d.B1, mau: mau, day: day) + doan(ctx, d.C, d.C1, mau: mau, day: day, dut: true) + // đáy dưới + doan(ctx, d.A, d.B, mau: mau, day: day) + doan(ctx, d.A, d.C, mau: mau, day: day, dut: true) + doan(ctx, d.B, d.C, mau: mau, day: day, dut: true) + let G = trong-tam(d.A, d.B, d.C, d.A1, d.B1, d.C1) + for (P, t) in ((d.A, d.B, d.C, d.A1, d.B1, d.C1).zip(ten)) { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } + goi-them(them, ctx, d) +}) + +// ---------- HÌNH NÓN ---------- +#let hinh-non( + w: 5.6cm, + r: 1.6, cao: 3.4, + ten-dinh: $S$, ten-tam: $O$, ten-bk: $r$, + truc: true, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -r - 0.7, xmax: r + 0.7, ymin: -r * 0.42 - 0.5, ymax: cao + 0.6, ctx => { + let ry = r * 0.32 + let S = (0.0, cao) + let O = (0.0, 0.0) + let d = (S: S, O: O, T1: (-r, 0.0), T2: (r, 0.0)) + // đáy: nửa trước liền, nửa sau đứt + cung-elip(ctx, O, r, ry, tu: 180deg, den: 360deg, mau: mau, day: day) + cung-elip(ctx, O, r, ry, tu: 0deg, den: 180deg, mau: mau, day: day, dut: true) + // hai đường sinh + doan(ctx, S, d.T1, mau: mau, day: day) + doan(ctx, S, d.T2, mau: mau, day: day) + if truc { + doan(ctx, S, O, mau: mau-phu, day: 1pt, dut: true) + doan(ctx, O, d.T2, mau: mau-phu, day: 1pt) + goc-vuong(ctx, O, S, d.T2, r: 0.24, mau: mau-phu) + nhan(ctx, (r / 2, 0), ten-bk, huong: "duoi", mau: mau-phu) + diem(ctx, O, ten: ten-tam, huong: "duoi-trai", bk: 1.5pt) + } + diem(ctx, S, ten: ten-dinh, huong: "tren", bk: 1.5pt) + goi-them(them, ctx, d) +}) + +// ---------- HÌNH TRỤ ---------- +#let hinh-tru( + w: 5.6cm, + r: 1.5, cao: 3.2, + ten-tam: ($O$, $O'$), ten-bk: $r$, + truc: true, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -r - 0.7, xmax: r + 0.7, ymin: -r * 0.42 - 0.4, ymax: cao + r * 0.42 + 0.5, ctx => { + let ry = r * 0.3 + let O = (0.0, 0.0) + let O1 = (0.0, cao) + let d = (O: O, O1: O1) + // đáy dưới: nửa trước liền, nửa sau đứt + cung-elip(ctx, O, r, ry, tu: 180deg, den: 360deg, mau: mau, day: day) + cung-elip(ctx, O, r, ry, tu: 0deg, den: 180deg, mau: mau, day: day, dut: true) + // đáy trên: liền hoàn toàn + cung-elip(ctx, O1, r, ry, tu: 0deg, den: 360deg, mau: mau, day: day) + // hai cạnh bên + doan(ctx, (-r, 0), (-r, cao), mau: mau, day: day) + doan(ctx, (r, 0), (r, cao), mau: mau, day: day) + if truc { + doan(ctx, O, O1, mau: mau-phu, day: 1pt, dut: true) + doan(ctx, O1, (r, cao), mau: mau-phu, day: 1pt) + nhan(ctx, (r / 2, cao), ten-bk, huong: "tren", mau: mau-phu) + diem(ctx, O, ten: ten-tam.at(0), huong: "trai", bk: 1.5pt) + diem(ctx, O1, ten: ten-tam.at(1), huong: "trai", bk: 1.5pt) + } + goi-them(them, ctx, d) +}) + +// ---------- HÌNH CẦU ---------- +#let hinh-cau( + w: 5.6cm, + r: 2.0, + ten-tam: $O$, ten-bk: $R$, + ban-kinh: true, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = hinh(w: w, xmin: -r - 0.5, xmax: r + 0.5, ymin: -r - 0.5, ymax: r + 0.5, ctx => { + let O = (0.0, 0.0) + let ry = r * 0.32 + duong-tron(ctx, O, r, mau: mau, day: day) + // xích đạo: nửa trước liền, nửa sau đứt + cung-elip(ctx, O, r, ry, tu: 180deg, den: 360deg, mau: mau, day: 0.9pt) + cung-elip(ctx, O, r, ry, tu: 0deg, den: 180deg, mau: mau, day: 0.9pt, dut: true) + diem(ctx, O, ten: ten-tam, huong: "duoi-trai", bk: 1.5pt) + if ban-kinh { + let M = (r * calc.cos(40deg), r * calc.sin(40deg)) + doan(ctx, O, M, mau: mau-phu, day: 1pt) + diem(ctx, M, ten: none, bk: 1.5pt, mau: mau-phu) + nhan(ctx, trung-diem(O, M), ten-bk, huong: "tren-trai", mau: mau-phu) + } + goi-them(them, ctx, (O: O)) +}) + +// ---------- HỆ TRỤC TOẠ ĐỘ Oxyz ---------- +#let truc-oxyz( + w: 6cm, + ten: ($x$, $y$, $z$), + ten-goc: $O$, + don-vi: false, // vẽ 3 điểm đơn vị i, j, k + mau: black, day: 1pt, + them: none, +) = hinh(w: w, xmin: -2.2, xmax: 2.6, ymin: -1.9, ymax: 2.6, ctx => { + let O = (0.0, 0.0) + // Ox: chéo xuống trái (hướng về người nhìn), Oy: phải, Oz: lên + let X = (-1.55, -1.55) + let Y = (2.3, 0.0) + let Z = (0.0, 2.3) + mui-ten(ctx, O, X, mau: mau, day: day) + mui-ten(ctx, O, Y, mau: mau, day: day) + mui-ten(ctx, O, Z, mau: mau, day: day) + nhan(ctx, X, ten.at(0), huong: "duoi-trai") + nhan(ctx, Y, ten.at(1), huong: "duoi") + nhan(ctx, Z, ten.at(2), huong: "phai") + diem(ctx, O, ten: ten-goc, huong: "tren-phai", bk: 1.5pt) + if don-vi { + vecto(ctx, O, (-0.62, -0.62), ten: $arrow(i)$, huong: "duoi", mau: red) + vecto(ctx, O, (0.9, 0), ten: $arrow(j)$, huong: "tren", mau: red) + vecto(ctx, O, (0, 0.9), ten: $arrow(k)$, huong: "trai", mau: red) + } + goi-them(them, ctx, (O: O)) +}) + +// ===================================================================== +// HỆ TRỤC Oxyz CÓ TOẠ ĐỘ THẬT — điểm, vectơ, hộp gióng theo đơn vị +// Phép chiếu: Oy sang phải, Oz lên trên, Ox chéo xuống-trái về người nhìn. +// #oxyz(x: 5, y: 8, z: 8, them: (ctx, t3) => { +// diem-oxyz(ctx, t3, (5, 8, 8), ten: $B$, huong: "above-right") +// giong-oxyz(ctx, t3, (5, 8, 8)) +// vecto-oxyz(ctx, t3, (0, 0, 0), (5, 8, 8), mau: purple) +// }) +// `them` nhận (ctx, t3): t3 đổi (x, y, z) -> điểm 2D, dùng được với MỌI +// hàm vẽ phẳng: diem(ctx, t3((1,2,3)), ...), doan(ctx, t3(A), t3(B), ...). +// ===================================================================== +// Điểm 3D: chấm + nhãn. +#let diem-oxyz(ctx, t3, P, ten: none, huong: "above", bk: 2pt, mau: black) = { + diem(ctx, t3(P), ten: ten, huong: huong, bk: bk, mau: mau) +} + +// Đoạn / vectơ nối hai điểm 3D. +#let doan-oxyz(ctx, t3, A, B, mau: black, day: 1pt, dut: false) = { + doan(ctx, t3(A), t3(B), mau: mau, day: day, dut: dut) +} +#let vecto-oxyz(ctx, t3, A, B, ten: none, huong: "tren", mau: black, day: 1.1pt, dut: false) = { + vecto(ctx, t3(A), t3(B), ten: ten, huong: huong, mau: mau, day: day, dut: dut) +} + +// Hộp gióng nét đứt từ gốc O đến điểm P = (a, b, c): vẽ các cạnh của +// hình hộp chữ nhật [0,a]×[0,b]×[0,c] (trừ 3 cạnh nằm trên trục). +#let giong-oxyz(ctx, t3, P, mau: blue, day: 0.8pt) = { + let (a, b, c) = (P.at(0), P.at(1), P.at(2)) + let canh = () + if a != 0 and b != 0 { + canh += (((a, 0, 0), (a, b, 0)), ((0, b, 0), (a, b, 0))) + } + if c != 0 { + if a != 0 or b != 0 { canh += (((a, b, 0), (a, b, c)),) } + if a != 0 { canh += (((0, 0, c), (a, 0, c)), ((a, 0, 0), (a, 0, c))) } + if b != 0 { canh += (((0, 0, c), (0, b, c)), ((0, b, 0), (0, b, c))) } + if a != 0 and b != 0 { + canh += (((a, 0, c), (a, b, c)), ((0, b, c), (a, b, c))) + } + } + for (A, B) in canh { + doan(ctx, t3(A), t3(B), mau: mau, day: day, dut: true) + } +} + +// Các mốc bội của buoc trong [a, b], bỏ 0 (vạch chia/số/lưới của oxyz). +#let _moc-oxyz(a, b, buoc) = { + let kq = () + let t = calc.ceil(a / buoc - 0.0001) * buoc + while t <= b + 0.0001 { + if calc.abs(t) > 0.0001 { kq.push(t) } + t = t + buoc + } + kq +} + +// Phạm vi một trục: n -> (0, n) | số âm -> (n, 0) | tuple (mn, mx) giữ nguyên. +#let _mien-oxyz(v) = if type(v) == array { + (v.at(0), v.at(1)) +} else if v < 0 { (v, 0) } else { (0, v) } + +// Định dạng số gọn cho nhãn trục: 2.0 -> "2", 0.5 -> "0.5". +#let _so-oxyz(t) = if calc.abs(t - calc.round(t)) < 0.0001 { + str(int(calc.round(t))) +} else { str(t) } + +#let oxyz( + w: 7cm, + x: 4, y: 5, z: 4, // phạm vi trục: n = [0, n] | số âm = [n, 0] + // | tuple (mn, mx) = hai phía, vd x: (-2, 3) + am: 0.8, // kéo dài thêm trục quá phạm vi (phía không mũi tên) + ten: ($x$, $y$, $z$), ten-goc: $O$, + don-vi: true, // vẽ 3 vectơ đơn vị i, j, k + mau-don-vi: (red, purple, green.darken(30%)), + vach: false, // vạch chia đơn vị trên 3 trục + so: false, // ghi số tại các vạch (bỏ qua 0) + buoc: 1, // bước của vạch chia / số / lưới + luoi: (), // lưới trên mặt phẳng toạ độ: true = cả 3, + // hoặc tuple chọn: ("xy",), ("xy", "xz", "yz") + mau-luoi: luma(84%), + k: 0.55, goc: 35deg, // hệ số & góc chiếu của trục Ox + mau: black, day: 0.9pt, + co-chu: 10pt, + them: none, // (ctx, t3) => nội dung vẽ thêm +) = { + let t3 = P => ( + P.at(1) - k * P.at(0) * calc.cos(goc), + P.at(2) - k * P.at(0) * calc.sin(goc), + ) + let dm = 0.9 // lề cho mũi tên + nhãn + // phạm vi từng trục (chấp nhận số âm / tuple 2 phía) + let (xa, xb) = _mien-oxyz(x) + let (ya, yb) = _mien-oxyz(y) + let (za, zb) = _mien-oxyz(z) + // cửa sổ: bao 8 đỉnh hộp phạm vi và đầu mút các trục + let chot = ( + (xb + am + dm, 0, 0), (xa - am, 0, 0), + (0, yb + am + dm, 0), (0, ya - am, 0), + (0, 0, zb + am + dm), (0, 0, za - am), + ) + for cx in (xa, xb) { + for cy in (ya, yb) { + for cz in (za, zb) { chot.push((cx, cy, cz)) } + } + } + let chot = chot.map(t3) + let xs = chot.map(p => p.at(0)) + let ys = chot.map(p => p.at(1)) + let (x1, x2) = (calc.min(..xs) - 0.35, calc.max(..xs) + 0.35) + let (y1, y2) = (calc.min(..ys) - 0.35, calc.max(..ys) + 0.35) + hinh(w: w, xmin: x1, xmax: x2, ymin: y1, ymax: y2, co-chu: co-chu, ctx => { + // lưới trên các mặt phẳng toạ độ (vẽ TRƯỚC để nằm dưới trục) + let mp = if luoi == true { ("xy", "xz", "yz") } else if luoi == none or luoi == false { () } else if type(luoi) == str { (luoi,) } else { luoi } + for m in mp { + if m == "xy" { + for i in _moc-oxyz(xa, xb, buoc) { doan(ctx, t3((i, ya, 0)), t3((i, yb, 0)), mau: mau-luoi, day: 0.5pt) } + for j in _moc-oxyz(ya, yb, buoc) { doan(ctx, t3((xa, j, 0)), t3((xb, j, 0)), mau: mau-luoi, day: 0.5pt) } + } else if m == "xz" { + for i in _moc-oxyz(xa, xb, buoc) { doan(ctx, t3((i, 0, za)), t3((i, 0, zb)), mau: mau-luoi, day: 0.5pt) } + for l in _moc-oxyz(za, zb, buoc) { doan(ctx, t3((xa, 0, l)), t3((xb, 0, l)), mau: mau-luoi, day: 0.5pt) } + } else if m == "yz" { + for j in _moc-oxyz(ya, yb, buoc) { doan(ctx, t3((0, j, za)), t3((0, j, zb)), mau: mau-luoi, day: 0.5pt) } + for l in _moc-oxyz(za, zb, buoc) { doan(ctx, t3((0, ya, l)), t3((0, yb, l)), mau: mau-luoi, day: 0.5pt) } + } + } + mui-ten(ctx, t3((xa - am, 0, 0)), t3((xb + am + dm, 0, 0)), mau: mau, day: day) + mui-ten(ctx, t3((0, ya - am, 0)), t3((0, yb + am + dm, 0)), mau: mau, day: day) + mui-ten(ctx, t3((0, 0, za - am)), t3((0, 0, zb + am + dm)), mau: mau, day: day) + nhan(ctx, t3((xb + am + dm, 0, 0)), ten.at(0), huong: "below-left", cach: 5pt) + nhan(ctx, t3((0, yb + am + dm, 0)), ten.at(1), huong: "below", cach: 5pt) + nhan(ctx, t3((0, 0, zb + am + dm)), ten.at(2), huong: "left", cach: 5pt) + nhan(ctx, t3((0, 0, 0)), ten-goc, huong: "below-right", cach: 4pt) + // vạch chia đơn vị + số trên 3 trục (vạch vuông góc với trục trên trang) + if vach or so { + let bo3 = ( + ((1, 0, 0), xa, xb, tm => t3((tm, 0, 0)), "below"), + ((0, 1, 0), ya, yb, tm => t3((0, tm, 0)), "below"), + ((0, 0, 1), za, zb, tm => t3((0, 0, tm)), "left"), + ) + for (u, lo, hi, f, hg) in bo3 { + let o2 = toa-pt(ctx, t3((0, 0, 0))) + let u2 = toa-pt(ctx, t3(u)) + let (dx, dy) = (u2.at(0) - o2.at(0), u2.at(1) - o2.at(1)) + let l = calc.sqrt(dx * dx + dy * dy) + let (nx, ny) = (-dy / l, dx / l) + for tm in _moc-oxyz(lo, hi, buoc) { + let p = toa-pt(ctx, f(tm)) + if vach { + doan-pt( + (p.at(0) - 2.2 * nx, p.at(1) - 2.2 * ny), + (p.at(0) + 2.2 * nx, p.at(1) + 2.2 * ny), + mau: mau, day: 0.8pt, + ) + } + if so { nhan(ctx, f(tm), $#_so-oxyz(tm)$, huong: hg, cach: 4pt) } + } + } + } + if don-vi { + vecto(ctx, t3((0, 0, 0)), t3((1, 0, 0)), ten: $arrow(i)$, huong: "trai", mau: mau-don-vi.at(0), day: 1.2pt) + vecto(ctx, t3((0, 0, 0)), t3((0, 1, 0)), ten: $arrow(j)$, huong: "tren", mau: mau-don-vi.at(1), day: 1.2pt) + vecto(ctx, t3((0, 0, 0)), t3((0, 0, 1)), ten: $arrow(k)$, huong: "trai", mau: mau-don-vi.at(2), day: 1.2pt) + } + if them != none { them(ctx, t3) } + }) +} + +// ===================================================================== +// LỤC GIÁC ĐỀU & CHÓP CỤT ĐỀU — engine chiếu + phân loại nét khuất +// Đáy đa giác đều NẰM NGANG (z = const), nhìn chếch từ trước-trên. +// Nét khuất (đáy sau + cạnh bên tới đỉnh sau) TỰ vẽ đứt theo hình chiếu. +// #hinh-chop-luc-giac-deu() — chóp lục giác đều S.ABCDEF +// #hinh-lang-tru-luc-giac-deu() — lăng trụ lục giác đều ABCDEF.A'…F' +// #hinh-chop-cut-deu(n: 3|4|6) — chóp cụt đều tam/tứ/lục giác +// ===================================================================== +// Chiếu điểm 3D (x, y, z) -> điểm 2D trên trang (y = chiều sâu ra sau). +#let _pr(P) = ( + P.at(0) + 0.5 * P.at(1) * calc.cos(40deg), + P.at(2) + 0.5 * P.at(1) * calc.sin(40deg), +) + +// Đỉnh đa giác đều n cạnh, tâm trục Oz, bán kính R, độ cao z, lệch pha. +#let _dinh-deu(n, R, z, pha) = range(n).map(i => { + let a = pha + i * 360deg / n + (R * calc.cos(a), R * calc.sin(a), z) +}) + +// Pha mặc định: đỉnh A rơi vào phía trước-trái, các cạnh trước hiện rõ. +// n = 6 dùng 210° (như n = 3): các đỉnh toả đều 2 phía, D và E (hay D', E') +// không dồn sát nhau bên phải như pha 180° cũ. +#let _pha-deu(n) = if n == 3 { 210deg } else if n == 4 { 225deg } else { 210deg } + +// Phân loại nét khuất của đáy: trả (canh-sau, dinh-sau). +// P2: đỉnh đáy đã chiếu; y3: độ sâu 3D tương ứng. Đáy lồi -> hai chuỗi +// giữa đỉnh trái-nhất và phải-nhất; chuỗi có độ sâu lớn hơn là "sau" (khuất). +#let _khuat(P2, y3) = { + let n = P2.len() + let L = 0 + let R = 0 + for i in range(n) { + if P2.at(i).at(0) < P2.at(L).at(0) { L = i } + if P2.at(i).at(0) > P2.at(R).at(0) { R = i } + } + let ch1 = () + let i = L + while i != R { ch1.push(i); i = calc.rem(i + 1, n) } + ch1.push(R) + let ch2 = () + let j = L + while j != R { ch2.push(j); j = calc.rem(j + n - 1, n) } + ch2.push(R) + let tb(ch) = ch.map(k => y3.at(k)).sum() / ch.len() + let sau = if tb(ch1) >= tb(ch2) { ch1 } else { ch2 } + let canh-sau = () + for t in range(sau.len() - 1) { canh-sau.push((sau.at(t), sau.at(t + 1))) } + (canh-sau: canh-sau, dinh-sau: sau.slice(1, sau.len() - 1)) +} +#let _la-khuat(canh, i, j) = canh.any(c => (c.at(0) == i and c.at(1) == j) or (c.at(0) == j and c.at(1) == i)) + +// Cửa sổ bao các điểm 2D + lề. +#let _cua-so-2d(pts, mg: 0.7) = { + let xs = pts.map(p => p.at(0)) + let ys = pts.map(p => p.at(1)) + (xmin: calc.min(..xs) - mg, xmax: calc.max(..xs) + mg, ymin: calc.min(..ys) - mg, ymax: calc.max(..ys) + mg) +} + +// ---------- CHÓP LỤC GIÁC ĐỀU S.ABCDEF ---------- +#let hinh-chop-luc-giac-deu( + w: 7cm, + R: 2.0, cao: 3.8, + ten: ($S$, $A$, $B$, $C$, $D$, $E$, $F$), + ten-tam: $O$, + duong-cao: true, + mau: black, mau-phu: red, day: 1.1pt, + them: none, +) = { + let n = 6 + let B3 = _dinh-deu(n, R, 0, _pha-deu(n)) + let S3 = (0, 0, cao) + let O3 = (0, 0, 0) + let B2 = B3.map(_pr) + let S2 = _pr(S3) + let O2 = _pr(O3) + let kl = _khuat(B2, B3.map(P => P.at(1))) + let cs = _cua-so-2d(B2 + (S2,)) + hinh(w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, ctx => { + // cạnh đáy: sau đứt + for i in range(n) { + let j = calc.rem(i + 1, n) + doan(ctx, B2.at(i), B2.at(j), mau: mau, day: day, dut: _la-khuat(kl.canh-sau, i, j)) + } + // cạnh bên: tới đỉnh sau thì đứt + for i in range(n) { + doan(ctx, S2, B2.at(i), mau: mau, day: day, dut: kl.dinh-sau.contains(i)) + } + if duong-cao { + doan(ctx, S2, O2, mau: mau-phu, day: 1pt, dut: true) + goc-vuong(ctx, O2, S2, B2.at(0), r: 0.26, mau: mau-phu) + diem(ctx, O2, ten: ten-tam, huong: "below", bk: 1.5pt, mau: mau-phu) + } + let G = trong-tam(..B2, S2) + diem(ctx, S2, ten: ten.at(0), huong: huong-ra(S2, G)) + for i in range(n) { diem(ctx, B2.at(i), ten: ten.at(i + 1), huong: huong-ra(B2.at(i), G)) } + if them != none { them(ctx, (S: S3, O: O3, day: B3)) } + }) +} + +// ---------- LĂNG TRỤ LỤC GIÁC ĐỀU ABCDEF.A'B'C'D'E'F' ---------- +#let hinh-lang-tru-luc-giac-deu( + w: 7cm, + R: 1.9, cao: 3.4, + ten: ($A$, $B$, $C$, $D$, $E$, $F$, $A'$, $B'$, $C'$, $D'$, $E'$, $F'$), + mau: black, day: 1.1pt, + them: none, +) = { + let n = 6 + let pha = _pha-deu(n) + let B3 = _dinh-deu(n, R, 0, pha) + let T3 = _dinh-deu(n, R, cao, pha) + let B2 = B3.map(_pr) + let T2 = T3.map(_pr) + let kl = _khuat(B2, B3.map(P => P.at(1))) + let cs = _cua-so-2d(B2 + T2) + hinh(w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, ctx => { + // đáy trên: liền hết + for i in range(n) { + let j = calc.rem(i + 1, n) + doan(ctx, T2.at(i), T2.at(j), mau: mau, day: day) + } + // cạnh bên + for i in range(n) { + doan(ctx, B2.at(i), T2.at(i), mau: mau, day: day, dut: kl.dinh-sau.contains(i)) + } + // đáy dưới: sau đứt + for i in range(n) { + let j = calc.rem(i + 1, n) + doan(ctx, B2.at(i), B2.at(j), mau: mau, day: day, dut: _la-khuat(kl.canh-sau, i, j)) + } + let Gb = trong-tam(..B2) + let Gt = trong-tam(..T2) + for i in range(n) { diem(ctx, B2.at(i), ten: ten.at(i), huong: huong-ra(B2.at(i), Gt)) } + for i in range(n) { diem(ctx, T2.at(i), ten: ten.at(i + n), huong: huong-ra(T2.at(i), Gb)) } + if them != none { them(ctx, (duoi: B3, tren: T3)) } + }) +} + +// ---------- CHÓP CỤT ĐỀU (tam/tứ/lục giác) ABCD….A'B'C'D'… ---------- +// n: 3 | 4 | 6. R: bán kính đáy lớn (dưới), r: bán kính đáy nhỏ (trên). +#let hinh-chop-cut-luc-giac-deu( + n: 6, + w: 7cm, + R: 2.3, r: 1.25, cao: 3.0, + ten: auto, // auto: A,B,… (đáy dưới) + phẩy (đáy trên); hoặc mảng 2n nội dung + mau: black, day: 1.1pt, + truc: false, ten-tam: ($O$, $O'$), + mau-phu: red, + them: none, +) = { + let pha = _pha-deu(n) + let B3 = _dinh-deu(n, R, 0, pha) + let T3 = _dinh-deu(n, r, cao, pha) + let B2 = B3.map(_pr) + let T2 = T3.map(_pr) + let kl = _khuat(B2, B3.map(P => P.at(1))) + let chu = ("A", "B", "C", "D", "E", "F", "G", "H") + let ten2 = if ten == auto { + range(n).map(i => eval(chu.at(i), mode: "math")) + range(n).map(i => eval(chu.at(i) + "'", mode: "math")) + } else { ten } + let O3 = (0, 0, 0) + let O13 = (0, 0, cao) + let cs = _cua-so-2d(B2 + T2) + hinh(w: w, xmin: cs.xmin, xmax: cs.xmax, ymin: cs.ymin, ymax: cs.ymax, ctx => { + // đáy trên: liền hết + for i in range(n) { + let j = calc.rem(i + 1, n) + doan(ctx, T2.at(i), T2.at(j), mau: mau, day: day) + } + // cạnh bên + for i in range(n) { + doan(ctx, B2.at(i), T2.at(i), mau: mau, day: day, dut: kl.dinh-sau.contains(i)) + } + // đáy dưới: sau đứt + for i in range(n) { + let j = calc.rem(i + 1, n) + doan(ctx, B2.at(i), B2.at(j), mau: mau, day: day, dut: _la-khuat(kl.canh-sau, i, j)) + } + if truc { + let O2 = _pr(O3) + let O12 = _pr(O13) + doan(ctx, O2, O12, mau: mau-phu, day: 1pt, dut: true) + diem(ctx, O2, ten: ten-tam.at(0), huong: "below", bk: 1.1pt, mau: mau-phu) + diem(ctx, O12, ten: ten-tam.at(1), huong: "above", bk: 1.1pt, mau: mau-phu) + } + let Gb = trong-tam(..B2) + let Gt = trong-tam(..T2) + for i in range(n) { diem(ctx, B2.at(i), ten: ten2.at(i), huong: huong-ra(B2.at(i), Gt)) } + for i in range(n) { diem(ctx, T2.at(i), ten: ten2.at(i + n), huong: huong-ra(T2.at(i), Gb)) } + if them != none { them(ctx, (duoi: B3, tren: T3)) } + }) +} + +// Bí danh chóp cụt đều theo số cạnh đáy (cùng bộ tham số ở trên): +// #hinh-chop-cut-deu(n: 3|4|6) — gọi chung +// #hinh-chop-cut-tam-giac-deu() — ABC.A'B'C' +// #hinh-chop-cut-tu-giac-deu() — ABCD.A'B'C'D' +#let hinh-chop-cut-deu = hinh-chop-cut-luc-giac-deu +#let hinh-chop-cut-tam-giac-deu(..th) = hinh-chop-cut-luc-giac-deu(n: 3, ..th) +#let hinh-chop-cut-tu-giac-deu(..th) = hinh-chop-cut-luc-giac-deu(n: 4, ..th) diff --git a/packages/preview/conic-toan/0.3.4/lib/hinh-phang.typ b/packages/preview/conic-toan/0.3.4/lib/hinh-phang.typ new file mode 100644 index 0000000000..b4b2408e0d --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/hinh-phang.typ @@ -0,0 +1,498 @@ +// ===================================================================== +// hinh-phang.typ — HÌNH HỌC PHẲNG THPT +// Tam giác + các đường đặc biệt, tứ giác, đường tròn nội/ngoại tiếp, +// tiếp tuyến, giao hai đường tròn, đường tròn lượng giác... +// Mọi hàm nhận ctx từ #hinh (xem ve.typ). Toạ độ nên dùng 2 trục cùng tỉ lệ +// (để h: auto trong #hinh). +// ===================================================================== +#import "ve.typ": * + +// Hướng nhãn tự động: đẩy nhãn ra xa điểm G (thường là trọng tâm hình). +#let huong-ra(P, G) = { + let d = calc.atan2(P.at(0) - G.at(0), P.at(1) - G.at(1)).deg() + if d < 0 { d = d + 360 } + let idx = calc.rem(calc.floor((d + 22.5) / 45), 8) + ("phai", "tren-phai", "tren", "tren-trai", "trai", "duoi-trai", "duoi", "duoi-phai").at(idx) +} + +// Trọng tâm của dãy điểm. +#let trong-tam(..pts) = { + let ds = pts.pos() + let n = ds.len() + ( + ds.map(P => P.at(0)).sum() / n, + ds.map(P => P.at(1)).sum() / n, + ) +} + +// Vẽ đa giác + nhãn đỉnh tự động hướng ra ngoài. +// ten: mảng nội dung nhãn (cùng độ dài số đỉnh) hoặc none. +#let da-giac-ten(ctx, dinh, ten: none, mau: black, day: 1.1pt, to: none, cham: true) = { + da-giac(ctx, dinh, mau: mau, day: day, to: to) + let G = trong-tam(..dinh) + if ten != none { + for (P, t) in dinh.zip(ten) { + if cham { + diem(ctx, P, ten: t, huong: huong-ra(P, G)) + } else { + nhan(ctx, P, t, huong: huong-ra(P, G)) + } + } + } +} + +// ---------- Tam giác ---------- +#let tam-giac(ctx, A, B, C, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none, cham: true) = { + da-giac-ten(ctx, (A, B, C), ten: ten, mau: mau, day: day, to: to, cham: cham) +} + +// Đường cao hạ từ P xuống đường thẳng AB (kèm ký hiệu vuông góc). +// ten-chan: nhãn chân đường cao (vd $H$). +#let duong-cao(ctx, P, A, B, ten-chan: none, mau: red, day: 1pt, dut: false, vuong: true) = { + let H = hinh-chieu(P, A, B) + doan(ctx, P, H, mau: mau, day: day, dut: dut) + if vuong { + let Q = if khoang-cach(H, A) > khoang-cach(H, B) { A } else { B } + goc-vuong(ctx, H, P, Q, mau: mau) + } + if ten-chan != none { + diem(ctx, H, ten: ten-chan, huong: huong-ra(H, trong-tam(P, A, B))) + } +} + +// Trung tuyến từ P đến trung điểm AB (kèm 2 vạch đánh dấu bằng nhau). +#let trung-tuyen(ctx, P, A, B, ten-chan: none, mau: blue, day: 1pt, dut: false, so-vach: 1) = { + let M = trung-diem(A, B) + doan(ctx, P, M, mau: mau, day: day, dut: dut) + danh-dau(ctx, A, M, so: so-vach, mau: mau) + danh-dau(ctx, M, B, so: so-vach, mau: mau) + if ten-chan != none { + diem(ctx, M, ten: ten-chan, huong: huong-ra(M, trong-tam(P, A, B))) + } +} + +// Phân giác trong góc O của tam giác OAB (kèm 2 cung đánh dấu góc bằng nhau). +// vach : số VẠCH gạch ngang cung (0/1/2/3) — ký hiệu hai góc bằng nhau +// so-cung : số CUNG đồng tâm vẽ ở mỗi góc (1/2/3) +// lech : hệ số bán kính cung thứ hai so với cung thứ nhất +// (auto = 1.18 khi KHÔNG đánh dấu, = 1 khi có vạch/nhiều cung — +// vì hai góc bằng nhau thì phải vẽ cùng bán kính mới đúng quy ước) +// ten-goc : nhãn ghi ở CẢ HAI góc (vd $alpha$) — none = không ghi +// Ví dụ: phan-giac(A, B, C, vach: 1) // mỗi góc một vạch +// phan-giac(A, B, C, so-cung: 2) // mỗi góc hai cung đồng tâm +#let phan-giac( + ctx, O, A, B, + ten-chan: none, mau: green.darken(20%), day: 1pt, dut: false, + r-cung: 0.5, vach: 0, so-cung: 1, dai-vach: 6pt, lech: auto, + ten-goc: none, cach-nhan: 1.9, +) = { + let oa = khoang-cach(O, A) + let ob = khoang-cach(O, B) + let D = chia(A, B, oa / (oa + ob)) + doan(ctx, O, D, mau: mau, day: day, dut: dut) + let k = if lech != auto { lech } else if vach > 0 or so-cung > 1 { 1 } else { 1.18 } + goc(ctx, O, A, D, r: r-cung, mau: mau, day: day, + vach: vach, so-cung: so-cung, dai-vach: dai-vach, + ten: ten-goc, cach-nhan: cach-nhan) + goc(ctx, O, D, B, r: r-cung * k, mau: mau, day: day, + vach: vach, so-cung: so-cung, dai-vach: dai-vach, + ten: ten-goc, cach-nhan: cach-nhan) + if ten-chan != none { + diem(ctx, D, ten: ten-chan, huong: huong-ra(D, trong-tam(O, A, B))) + } +} + +// Đường trung trực của đoạn AB (đoạn vuông góc tại trung điểm, dài 2*dai). +#let trung-truc(ctx, A, B, dai: 1.5, mau: purple, day: 1pt, dut: true, so-vach: 1) = { + let M = trung-diem(A, B) + let d = khoang-cach(A, B) + let ux = (B.at(0) - A.at(0)) / d + let uy = (B.at(1) - A.at(1)) / d + let P1 = (M.at(0) - dai * uy, M.at(1) + dai * ux) + let P2 = (M.at(0) + dai * uy, M.at(1) - dai * ux) + doan(ctx, P1, P2, mau: mau, day: day, dut: dut) + goc-vuong(ctx, M, B, P1, mau: mau) + danh-dau(ctx, A, M, so: so-vach, mau: mau) + danh-dau(ctx, M, B, so: so-vach, mau: mau) +} + +// ---------- Tam giác đặc biệt ---------- +// Tam giác đều cạnh `canh`, đỉnh A ở dưới-trái, đáy nằm ngang. +// vach: số vạch đánh dấu cạnh bằng nhau (0 = không vẽ). +#let tam-giac-deu(ctx, A, canh, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none, vach: 1) = { + let B = (A.at(0) + canh, A.at(1)) + let C = (A.at(0) + canh / 2, A.at(1) + canh * calc.sqrt(3) / 2) + da-giac-ten(ctx, (A, B, C), ten: ten, mau: mau, day: day, to: to) + if vach > 0 { + danh-dau(ctx, A, B, so: vach, mau: mau) + danh-dau(ctx, B, C, so: vach, mau: mau) + danh-dau(ctx, C, A, so: vach, mau: mau) + } +} + +// Tam giác vuông tại A: cạnh AB ngang dài a, cạnh AC đứng dài b. +#let tam-giac-vuong(ctx, A, a, b, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none) = { + let B = (A.at(0) + a, A.at(1)) + let C = (A.at(0), A.at(1) + b) + da-giac-ten(ctx, (A, B, C), ten: ten, mau: mau, day: day, to: to) + goc-vuong(ctx, A, B, C, mau: mau) +} + +// Tam giác cân tại C: đáy AB ngang dài `canh-day`, chiều cao `chieu-cao`. +#let tam-giac-can(ctx, A, canh-day, chieu-cao, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none, vach: 1) = { + let B = (A.at(0) + canh-day, A.at(1)) + let C = (A.at(0) + canh-day / 2, A.at(1) + chieu-cao) + da-giac-ten(ctx, (A, B, C), ten: ten, mau: mau, day: day, to: to) + if vach > 0 { + danh-dau(ctx, C, A, so: vach, mau: mau) + danh-dau(ctx, C, B, so: vach, mau: mau) + } +} + +// Tam giác vuông cân tại A, hai cạnh góc vuông dài `canh`. +#let tam-giac-vuong-can(ctx, A, canh, ten: ($A$, $B$, $C$), mau: black, day: 1.1pt, to: none, vach: 1) = { + tam-giac-vuong(ctx, A, canh, canh, ten: ten, mau: mau, day: day, to: to) + if vach > 0 { + let B = (A.at(0) + canh, A.at(1)) + let C = (A.at(0), A.at(1) + canh) + danh-dau(ctx, A, B, so: vach, mau: mau) + danh-dau(ctx, A, C, so: vach, mau: mau) + } +} + +// ---------- Tứ giác thường gặp ---------- +// Tứ giác bất kỳ ABCD. +#let tu-giac(ctx, A, B, C, D, ten: ($A$, $B$, $C$, $D$), mau: black, day: 1.1pt, to: none) = { + da-giac-ten(ctx, (A, B, C, D), ten: ten, mau: mau, day: day, to: to) +} + +// Hình bình hành biết 3 đỉnh liên tiếp A, B, C (D tự tính = A + C - B). +#let hinh-binh-hanh(ctx, A, B, C, ten: ($A$, $B$, $C$, $D$), mau: black, day: 1.1pt, to: none) = { + let D = (A.at(0) + C.at(0) - B.at(0), A.at(1) + C.at(1) - B.at(1)) + da-giac-ten(ctx, (A, B, C, D), ten: ten, mau: mau, day: day, to: to) +} + +// Hình chữ nhật: góc dưới-trái A, chiều rộng r, chiều cao c. +#let hinh-chu-nhat(ctx, A, r, c, ten: ($A$, $B$, $C$, $D$), mau: black, day: 1.1pt, to: none, goc-vg: true) = { + let B = (A.at(0) + r, A.at(1)) + let C = (A.at(0) + r, A.at(1) + c) + let D = (A.at(0), A.at(1) + c) + da-giac-ten(ctx, (A, B, C, D), ten: ten, mau: mau, day: day, to: to) + if goc-vg { goc-vuong(ctx, A, B, D, mau: mau) } +} + +// Hình thang: đáy lớn AB (dưới, dài a), đáy nhỏ DC (trên, dài b), +// cao c, lech: độ lệch ngang của D so với A. +#let hinh-thang(ctx, A, a, b, c, lech: 0.6, ten: ($A$, $B$, $C$, $D$), mau: black, day: 1.1pt, to: none) = { + let B = (A.at(0) + a, A.at(1)) + let D = (A.at(0) + lech, A.at(1) + c) + let C = (D.at(0) + b, D.at(1)) + da-giac-ten(ctx, (A, B, C, D), ten: ten, mau: mau, day: day, to: to) +} + +// ---------- Đường tròn liên quan tam giác / đa giác ---------- +// Khoảng cách từ P tới ĐOẠN thẳng AB (không phải tới đường thẳng AB). +#let _cach-doan(P, A, B) = { + let ux = B.at(0) - A.at(0) + let uy = B.at(1) - A.at(1) + let d2 = ux * ux + uy * uy + if d2 < 1e-12 { return khoang-cach(P, A) } + let t = calc.max(0, calc.min(1, ((P.at(0) - A.at(0)) * ux + (P.at(1) - A.at(1)) * uy) / d2)) + khoang-cach(P, (A.at(0) + t * ux, A.at(1) + t * uy)) +} + +// Khoảng cách từ P tới cạnh GẦN NHẤT của đa giác (mảng đỉnh, khép kín). +#let _cach-canh(P, dinh) = calc.min( + ..range(dinh.len()).map(i => _cach-doan(P, dinh.at(i), dinh.at(calc.rem(i + 1, dinh.len())))), +) + +// 8 hướng đặt nhãn + vectơ tương ứng trong TOẠ ĐỘ TOÁN (y hướng lên). +#let _tam-huong = ( + ("phai", (1, 0)), ("tren-phai", (0.7, 0.7)), ("tren", (0, 1)), ("tren-trai", (-0.7, 0.7)), + ("trai", (-1, 0)), ("duoi-trai", (-0.7, -0.7)), ("duoi", (0, -1)), ("duoi-phai", (0.7, -0.7)), +) + +// Hướng đặt nhãn tâm sao cho chữ KHÔNG rơi lên cạnh nào của đa giác. +#let _huong-thoang(O, dinh, r) = { + let d = _tam-huong.map(((ten, v)) => ( + ten, + _cach-canh((O.at(0) + 0.3 * r * v.at(0), O.at(1) + 0.3 * r * v.at(1)), dinh), + )) + let tot = calc.max(..d.map(x => x.at(1))) + d.find(x => x.at(1) == tot).at(0) +} + +// Đường tròn ngoại tiếp TAM GIÁC hoặc ĐA GIÁC. +// duong-tron-ngoai-tiep(A, B, C) — 3 đỉnh rời (lối cũ) +// duong-tron-ngoai-tiep((A, B, C, D, E)) — MỘT MẢNG đỉnh, đa giác tuỳ ý +// Từ 4 đỉnh trở lên, tâm/bán kính khớp theo bình phương bé nhất (đa giác nội +// tiếp được cho ra đúng đường tròn của nó — xem `tron-qua-diem`). +// ten-tam : nhãn tâm (none = không ghi) +// huong-tam : auto = tự chọn phía THOÁNG nhất, không đè lên cạnh đa giác +// ban-kinh : vẽ bán kính (nét đứt) tới một đỉnh; đỉnh được chọn TỰ ĐỘNG +// sao cho bán kính không nằm đè lên cạnh (vd tam giác vuông: +// tâm ở giữa cạnh huyền, bán kính tới hai đầu huyền là trùng +// cạnh — hàm tự tránh, chọn đỉnh góc vuông) +// dinh-r : chỉ số đỉnh muốn vẽ bán kính tới (auto = tự chọn như trên) +// ten-r : nhãn ghi trên bán kính (vd $R$) +// canh : true = vẽ luôn đa giác (mặc định false, thường vẽ sẵn rồi) +#let duong-tron-ngoai-tiep( + ctx, ..vi-tri, + ten-tam: $O$, huong-tam: auto, + mau: blue, day: 1pt, dut: false, to: none, + ban-kinh: false, dinh-r: auto, ten-r: none, + canh: false, mau-canh: black, +) = { + let ds = vi-tri.pos() + let dinh = if ds.len() == 1 { ds.at(0) } else { ds } + if dinh.len() < 3 { panic("duong-tron-ngoai-tiep: cần ít nhất 3 đỉnh") } + let (O, r) = tron-qua-diem(dinh) + duong-tron(ctx, O, r, mau: mau, day: day, dut: dut, to: to) + if canh { da-giac(ctx, dinh, mau: mau-canh, day: 1.1pt) } + if ban-kinh { + // đỉnh nào có bán kính nằm THOÁNG nhất (không trùng cạnh đa giác) + let i = if dinh-r != auto { dinh-r } else { + let d = dinh.map(V => _cach-canh(trung-diem(O, V), dinh)) + let tot = calc.max(..d) + d.position(x => x == tot) + } + let V = dinh.at(i) + doan(ctx, O, V, mau: mau, day: 0.8pt, dut: true, + ten: ten-r, mau-ten: mau) + } + if ten-tam != none { + diem(ctx, O, ten: ten-tam, + huong: if huong-tam == auto { _huong-thoang(O, dinh, r) } else { huong-tam }) + } +} + +// Đường tròn nội tiếp tam giác ABC. +#let duong-tron-noi-tiep(ctx, A, B, C, ten-tam: $I$, mau: orange.darken(10%), day: 1pt, dut: false, ban-kinh: false) = { + let (I, r) = tam-noi-tiep(A, B, C) + duong-tron(ctx, I, r, mau: mau, day: day, dut: dut) + if ten-tam != none { diem(ctx, I, ten: ten-tam, huong: "tren") } + if ban-kinh { + let H = hinh-chieu(I, A, B) + doan(ctx, I, H, mau: mau, day: 0.8pt, dut: true) + goc-vuong(ctx, H, I, A, r: 0.22, mau: mau) + } +} + +// ---------- Trực tâm ---------- +// Ba đường cao của tam giác ABC + trực tâm H. +// ten : nhãn trực tâm (none = không ghi) +// ten-chan : nhãn 3 chân đường cao theo thứ tự hạ từ A, B, C +// (none = không ghi; mặc định $H_A$, $H_B$, $H_C$) +// vuong : vẽ ký hiệu góc vuông tại chân đường cao +// canh : true = vẽ luôn tam giác ABC (kèm nhãn đỉnh) +#let ve-truc-tam( + ctx, A, B, C, + ten: $H$, + ten-chan: ($H_A$, $H_B$, $H_C$), + ten-dinh: ($A$, $B$, $C$), + canh: true, + mau: black, mau-cao: red, day: 1.1pt, dut: false, vuong: true, +) = { + if canh { tam-giac(ctx, A, B, C, ten: ten-dinh, mau: mau, day: day) } + let H = truc-tam(A, B, C) + for (i, (P, X, Y)) in ((A, B, C), (B, C, A), (C, A, B)).enumerate() { + let K = hinh-chieu(P, X, Y) + // kéo dài tới chân đường cao (tam giác tù thì chân nằm ngoài cạnh) + doan(ctx, P, K, mau: mau-cao, day: 0.9pt, dut: dut) + // nối dài cạnh nếu chân đường cao rơi ra ngoài đoạn XY + let t = ( + ((K.at(0) - X.at(0)) * (Y.at(0) - X.at(0)) + + (K.at(1) - X.at(1)) * (Y.at(1) - X.at(1))) + / calc.pow(khoang-cach(X, Y), 2) + ) + if t < 0 { doan(ctx, X, K, mau: mau, day: 0.7pt, dut: true) } + if t > 1 { doan(ctx, Y, K, mau: mau, day: 0.7pt, dut: true) } + if vuong { goc-vuong(ctx, K, P, if t < 0.5 { Y } else { X }, r: 0.22, mau: mau-cao) } + if ten-chan != none { + diem(ctx, K, bk: 1.6pt, mau: mau-cao, ten: ten-chan.at(i), + huong: huong-ra(K, trong-tam(A, B, C))) + } + } + if ten != none { + diem(ctx, H, mau: mau-cao, ten: ten, huong: huong-ra(H, trong-tam(A, B, C))) + } else { + diem(ctx, H, mau: mau-cao) + } +} + +// ---------- Đường tròn bàng tiếp ---------- +// Đường tròn bàng tiếp TRONG GÓC A của tam giác ABC (tiếp xúc cạnh BC và +// phần kéo dài của AB, AC). Muốn góc B thì gọi duong-tron-bang-tiep(ctx, B, C, A). +// ten-tam : nhãn tâm (mặc định $J$) +// keo-dai : vẽ nét đứt kéo dài hai cạnh AB, AC tới tiếp điểm +// tiep-diem : chấm 3 tiếp điểm +// ban-kinh : vẽ bán kính tới cạnh BC (kèm ký hiệu vuông góc) +#let duong-tron-bang-tiep( + ctx, A, B, C, + ten-tam: $J$, ten-tiep: none, + mau: green.darken(25%), day: 1pt, + keo-dai: true, tiep-diem: true, ban-kinh: false, +) = { + let (J, r) = tam-bang-tiep(A, B, C) + let a = khoang-cach(B, C) + let b = khoang-cach(C, A) + let c = khoang-cach(A, B) + let s = (a + b + c) / 2 + let X = chia(B, C, (s - c) / a) // tiếp điểm trên cạnh BC + let Y = chia(A, B, s / c) // trên tia AB (ngoài B) + let Z = chia(A, C, s / b) // trên tia AC (ngoài C) + if keo-dai { + doan(ctx, B, Y, mau: mau, day: 0.7pt, dut: true) + doan(ctx, C, Z, mau: mau, day: 0.7pt, dut: true) + } + duong-tron(ctx, J, r, mau: mau, day: day) + if tiep-diem { + for (i, P) in (X, Y, Z).enumerate() { + diem(ctx, P, bk: 1.6pt, mau: mau, + ten: if ten-tiep == none { none } else { ten-tiep.at(i) }, + huong: huong-ra(P, J)) + } + } + if ban-kinh { + doan(ctx, J, X, mau: mau, day: 0.8pt, dut: true) + goc-vuong(ctx, X, J, B, r: 0.22, mau: mau) + } + if ten-tam != none { diem(ctx, J, mau: mau, ten: ten-tam, huong: huong-ra(J, A)) } +} + +// ---------- Tiếp tuyến ---------- +// Hai tiếp tuyến kẻ từ điểm M ngoài đường tròn (O; r). +#let tiep-tuyen-tu-diem( + ctx, O, r, M, + ten-tam: $O$, ten-diem: $M$, ten-tiep: ($T_1$, $T_2$), + mau: black, mau-tt: red, day: 1pt, +) = { + duong-tron(ctx, O, r, mau: mau, day: day) + let (T1, T2) = tiep-diem(O, r, M) + doan(ctx, M, T1, mau: mau-tt, day: day) + doan(ctx, M, T2, mau: mau-tt, day: day) + doan(ctx, O, T1, mau: mau, day: 0.8pt, dut: true) + doan(ctx, O, T2, mau: mau, day: 0.8pt, dut: true) + doan(ctx, O, M, mau: mau, day: 0.8pt, dut: true) + goc-vuong(ctx, T1, O, M, r: 0.24, mau: mau-tt) + goc-vuong(ctx, T2, O, M, r: 0.24, mau: mau-tt) + if ten-tam != none { diem(ctx, O, ten: ten-tam, huong: "duoi") } + if ten-diem != none { diem(ctx, M, ten: ten-diem, huong: "phai") } + if ten-tiep != none { + diem(ctx, T1, ten: ten-tiep.at(0), huong: huong-ra(T1, O)) + diem(ctx, T2, ten: ten-tiep.at(1), huong: huong-ra(T2, O)) + } +} + +// ---------- Tiếp tuyến TẠI một điểm trên đường tròn ---------- +// Dựng tiếp tuyến của đường tròn (O; r) TẠI điểm M (M nằm trên đường tròn): +// đoạn thẳng qua M, VUÔNG GÓC với bán kính OM. +// dai : nửa độ dài đoạn tiếp tuyến (auto = 1.4·r); đơn vị toạ độ toán. +// mau / day / dut : kiểu nét của đoạn tiếp tuyến. +// M không cần chính xác trên đường tròn — hướng tiếp tuyến lấy theo OM thực tế. +/// Tiếp tuyến của (O; r) TẠI điểm M: đoạn qua M vuông góc OM. +/// `tiep-tuyen-tai-diem(O, r, M, dai: auto, mau: black, day: 1pt, dut: false)`. +#let tiep-tuyen-tai-diem(ctx, O, r, M, dai: auto, mau: black, day: 1pt, dut: false) = { + let dx = M.at(0) - O.at(0) + let dy = M.at(1) - O.at(1) + let l = calc.sqrt(dx * dx + dy * dy) + if l < 1e-9 { panic("tiep-tuyen-tai-diem: M trùng tâm O") } + // vectơ đơn vị VUÔNG GÓC với OM (hướng tiếp tuyến) + let tx = -dy / l + let ty = dx / l + let d = if dai == auto { r * 1.4 } else { dai } + let P1 = (M.at(0) - d * tx, M.at(1) - d * ty) + let P2 = (M.at(0) + d * tx, M.at(1) + d * ty) + doan(ctx, P1, P2, mau: mau, day: day, dut: dut) +} + +// Giao điểm ĐƯỜNG THẲNG AB với ĐƯỜNG TRÒN (O; r). +// duong : cặp điểm (A, B) xác định đường thẳng +// tron : cặp (O, r) — tâm và bán kính +// TRẢ VỀ MẢNG điểm giao, sắp theo chiều A -> B: +// () rỗng nếu không cắt · một điểm (P,) nếu TIẾP XÚC · (P1, P2) nếu cắt. +// let gd = giao-duong-thang-duong-tron((A, B), (O, r)) +#let giao-duong-thang-duong-tron(duong, tron) = { + let (A, B) = duong + let (O, r) = tron + let dx = B.at(0) - A.at(0) + let dy = B.at(1) - A.at(1) + let l = calc.sqrt(dx * dx + dy * dy) + if l < 1e-9 { panic("giao-duong-thang-duong-tron: A trùng B") } + let ux = dx / l + let uy = dy / l + // chân đường vuông góc H từ O xuống AB + let t = (O.at(0) - A.at(0)) * ux + (O.at(1) - A.at(1)) * uy + let H = (A.at(0) + t * ux, A.at(1) + t * uy) + let d2 = calc.pow(khoang-cach(O, H), 2) + let r2 = r * r + if d2 > r2 + 1e-9 { return () } // không cắt + let h = calc.sqrt(calc.max(0, r2 - d2)) + if h < 1e-9 { return ((H.at(0), H.at(1)),) } // tiếp xúc + ( + (H.at(0) - h * ux, H.at(1) - h * uy), + (H.at(0) + h * ux, H.at(1) + h * uy), + ) +} + +// Giao điểm hai đường tròn (O1;r1), (O2;r2): trả về (P1, P2). +#let giao-hai-duong-tron(O1, r1, O2, r2) = { + let d = khoang-cach(O1, O2) + let a = (d * d + r1 * r1 - r2 * r2) / (2 * d) + let h = calc.sqrt(r1 * r1 - a * a) + let ux = (O2.at(0) - O1.at(0)) / d + let uy = (O2.at(1) - O1.at(1)) / d + let H = (O1.at(0) + a * ux, O1.at(1) + a * uy) + ( + (H.at(0) - h * uy, H.at(1) + h * ux), + (H.at(0) + h * uy, H.at(1) - h * ux), + ) +} + +// Giao điểm hai đường thẳng AB và CD (giả sử không song song). +#let giao-duong-thang(A, B, C, D) = { + let x1 = A.at(0); let y1 = A.at(1) + let x2 = B.at(0); let y2 = B.at(1) + let x3 = C.at(0); let y3 = C.at(1) + let x4 = D.at(0); let y4 = D.at(1) + let m = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) + ( + ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / m, + ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / m, + ) +} + +// ---------- Đường tròn lượng giác (hình hoàn chỉnh, tự tạo khung) ---------- +// goc: góc của điểm M trên đường tròn; hien: hiển thị hình chiếu sin/cos. +#let duong-tron-luong-giac( + w: 7cm, + so-do: 55deg, // số đo góc của điểm M + ten-goc: $alpha$, + hien-chieu: true, + mau-tron: blue, + mau-goc: red, +) = hinh(w: w, xmin: -1.55, xmax: 1.55, ymin: -1.5, ymax: 1.5, ctx => { + // hai trục + mui-ten(ctx, (-1.4, 0), (1.4, 0), day: 0.9pt) + mui-ten(ctx, (0, -1.35), (0, 1.35), day: 0.9pt) + nhan(ctx, (1.4, 0), $x$, huong: "duoi") + nhan(ctx, (0, 1.35), $y$, huong: "phai") + duong-tron(ctx, (0, 0), 1, mau: mau-tron, day: 1.2pt) + // các điểm đặc biệt + nhan(ctx, (1, 0), $1$, huong: "duoi-phai", cach: 4pt) + nhan(ctx, (-1, 0), $-1$, huong: "duoi-trai", cach: 4pt) + nhan(ctx, (0, 1), $1$, huong: "tren-trai", cach: 4pt) + nhan(ctx, (0, -1), $-1$, huong: "duoi-trai", cach: 4pt) + diem(ctx, (0, 0), ten: $O$, huong: "duoi-trai", bk: 1.6pt) + // điểm M và bán kính OM + let M = (calc.cos(so-do), calc.sin(so-do)) + doan(ctx, (0, 0), M, mau: mau-goc, day: 1.2pt) + diem(ctx, M, ten: $M$, huong: huong-ra(M, (0, 0)), mau: mau-goc) + goc(ctx, (0, 0), (1, 0), M, r: 0.3, ten: ten-goc, mau: mau-goc) + if hien-chieu { + doan(ctx, M, (M.at(0), 0), mau: gray, day: 0.8pt, dut: true) + doan(ctx, M, (0, M.at(1)), mau: gray, day: 0.8pt, dut: true) + diem(ctx, (M.at(0), 0), ten: $cos alpha$, huong: "duoi", bk: 1.6pt, mau: mau-goc) + diem(ctx, (0, M.at(1)), ten: $sin alpha$, huong: "trai", bk: 1.6pt, mau: mau-goc) + } +}) diff --git a/packages/preview/conic-toan/0.3.4/lib/khao-sat.typ b/packages/preview/conic-toan/0.3.4/lib/khao-sat.typ new file mode 100644 index 0000000000..3f48628ce6 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/khao-sat.typ @@ -0,0 +1,354 @@ +// ===================================================================== +// khao-sat.typ — KHẢO SÁT & VẼ ĐỒ THỊ TỰ ĐỘNG TỪ HỆ SỐ +// +// 5 hàm, chỉ cần nhập hệ số (như bbt-*/do-thi-*) là xổ ra TRỌN lời giải +// (tập xác định, đạo hàm, giới hạn, chiều biến thiên, cực trị / tiệm cận, +// bảng biến thiên) kèm ĐỒ THỊ — bao quát mọi trường hợp của mỗi hàm: +// +// #khao-sat-ve-do-thi-ham-bac-hai(a, b, c) y = ax² + bx + c +// #khao-sat-ve-do-thi-ham-bac-ba(a, b, c, d) y = ax³ + bx² + cx + d +// #khao-sat-ve-do-thi-ham-trung-phuong(a, b, c) y = ax⁴ + bx² + c +// #khao-sat-ve-do-thi-ham-phan-thuc(a, b, c, d) y = (ax + b)/(cx + d) +// #khao-sat-ve-do-thi-ham-huu-ti(a, b, c, d, e) y = (ax² + bx + c)/(dx + e) +// +// Tham số chung: +// tieu-de: auto -> dòng đậm "Khảo sát sự biến thiên và vẽ đồ thị của +// hàm số y = ..."; none -> bỏ tiêu đề; hoặc nội dung tuỳ ý. +// w: bề rộng đồ thị (mặc định 7.6cm). giua: true -> canh giữa BBT + đồ thị. +// ===================================================================== +#import "do-thi.typ": so-toan, so-dep, so-can-thuc, cuc-tri-bac-ba, cuc-tri-huu-ti, do-thi-bac-hai, do-thi-bac-ba, do-thi-trung-phuong, do-thi-phan-thuc, do-thi-huu-ti +#import "bang.typ": bbt-bac-hai, bbt-bac-ba, bbt-trung-phuong, bbt-phan-thuc, bbt-huu-ti + +#let _eps = 0.000001 +#let _ninf = $-oo$ +#let _pinf = $+oo$ + +// ----- Đơn thức |co|·x^mu (không kèm dấu) ----- +#let _don-thuc(co, mu) = { + let mag = calc.abs(co) + let bien = if mu == 0 { none } else if mu == 1 { $x$ } else { $x^#mu$ } + if bien == none { so-toan(mag) } + else if calc.abs(mag - 1) < _eps { bien } + else { $#so-toan(mag) #bien$ } +} + +// ----- Đa thức: danh sách (hệ số, số mũ) giảm dần -> nội dung math ----- +#let _da-thuc(cac) = { + let cac = cac.filter(t => calc.abs(t.at(0)) > _eps) + if cac.len() == 0 { return $0$ } + let acc = $0$ + for (i, t) in cac.enumerate() { + let (co, mu) = t + let than = _don-thuc(co, mu) + if i == 0 { + acc = if co < 0 { $-#than$ } else { than } + } else { + acc = if co < 0 { $#acc - #than$ } else { $#acc + #than$ } + } + } + acc +} + +// ----- Khoảng (a; b) — a, b là nội dung math; dùng TRONG MARKUP: #_kh(..) ----- +#let _kh(a, b) = $(#a ; #b)$ + +// ----- Dòng tiêu đề "Khảo sát ... y = " ----- +#let _tieu-de(tieu-de, bt) = { + if tieu-de == none { return none } + let nd = if tieu-de == auto { [Khảo sát sự biến thiên và vẽ đồ thị của hàm số $y = #bt$] } else { tieu-de } + [*#nd* ] +} + +// ----- Khối canh giữa (hoặc để nguyên) cho BBT/đồ thị ----- +#let _o(giua, than) = if giua { align(center, than) } else { than } + +// ===================================================================== +// 1) BẬC HAI: y = ax² + bx + c +// ===================================================================== +#let khao-sat-ve-do-thi-ham-bac-hai(a, b, c, tieu-de: auto, w: 7.6cm, giua: false) = { + assert(calc.abs(a) > _eps, message: "bac-hai: cần a ≠ 0") + let bt = _da-thuc(((a, 2), (b, 1), (c, 0))) + let xd = -b / (2 * a) // hoành độ đỉnh + let yd = c - b * b / (4 * a) // tung độ đỉnh + let dlt = b * b - 4 * a * c + let Xd = so-toan(xd) + let Yd = so-toan(yd) + let cbt = if a > 0 { + [Hàm số nghịch biến trên khoảng #_kh(_ninf, Xd) và đồng biến trên khoảng #_kh(Xd, _pinf)#"."] + } else { + [Hàm số đồng biến trên khoảng #_kh(_ninf, Xd) và nghịch biến trên khoảng #_kh(Xd, _pinf)#"."] + } + let ct = if a > 0 { + [Hàm số đạt cực tiểu tại $x = #Xd$, $y_("CT") = #Yd$; không có cực đại.] + } else { + [Hàm số đạt cực đại tại $x = #Xd$, $y_("CĐ") = #Yd$; không có cực tiểu.] + } + let gh = if a > 0 { + [$limits(lim)_(x -> -oo) y = +oo$; $limits(lim)_(x -> +oo) y = +oo$.] + } else { + [$limits(lim)_(x -> -oo) y = -oo$; $limits(lim)_(x -> +oo) y = -oo$.] + } + let giao-ox = if dlt > _eps { + let s = calc.sqrt(dlt) / (2 * calc.abs(a)) + [cắt trục hoành tại hai điểm $(#so-toan(xd - s) ; 0)$ và $(#so-toan(xd + s) ; 0)$] + } else if dlt < -_eps { + [không cắt trục hoành] + } else { + [tiếp xúc trục hoành tại $(#Xd ; 0)$] + } + [ + #_tieu-de(tieu-de, bt) + - Tập xác định: $D = RR$. + - Sự biến thiên: + + Đạo hàm: $y' = #_da-thuc(((2 * a, 1), (b, 0)))$; $y' = 0 <=> x = #Xd$. + + Giới hạn: #gh + + Chiều biến thiên: #cbt + + Cực trị: #ct + + Bảng biến thiên: + #_o(giua, bbt-bac-hai(a, b, c)) + - Đồ thị: + + Đỉnh $I(#Xd ; #Yd)$, trục đối xứng $x = #Xd$. + + Đồ thị cắt trục tung tại $(0; #so-toan(c))$ và #giao-ox. + #_o(giua, do-thi-bac-hai(a, b, c, w: w)) + ] +} + +// ===================================================================== +// 2) BẬC BA: y = ax³ + bx² + cx + d (Δ' = b² − 3ac) +// ===================================================================== +#let khao-sat-ve-do-thi-ham-bac-ba(a, b, c, d, tieu-de: auto, w: 7.6cm, giua: false) = { + assert(calc.abs(a) > _eps, message: "bac-ba: cần a ≠ 0") + let bt = _da-thuc(((a, 3), (b, 2), (c, 1), (d, 0))) + let f = x => ((a * x + b) * x + c) * x + d + let delta = b * b - 3 * a * c + let xu = -b / (3 * a) // hoành độ điểm uốn + let yu = f(xu) + let gh = if a > 0 { + [$limits(lim)_(x -> -oo) y = -oo$; $limits(lim)_(x -> +oo) y = +oo$.] + } else { + [$limits(lim)_(x -> -oo) y = +oo$; $limits(lim)_(x -> +oo) y = -oo$.] + } + let dao = _da-thuc(((3 * a, 2), (2 * b, 1), (c, 0))) + let cbt = [] + let ct = [] + let dao-them = [] + if delta > _eps { + // hai cực trị — hoành độ & giá trị CHÍNH XÁC dạng căn thức + let ctr = cuc-tri-bac-ba(a, b, c, d) + let U = ctr.x1 + let V = ctr.x2 + let Fu = ctr.y1 + let Fv = ctr.y2 + dao-them = [ $y' = 0 <=> x = #U$ hoặc $x = #V$.] + if a > 0 { + cbt = [Hàm số đồng biến trên các khoảng #_kh(_ninf, U) và #_kh(V, _pinf)#"; "nghịch biến trên khoảng #_kh(U, V)#"."] + ct = [Hàm số đạt cực đại tại $x = #U$, $y_("CĐ") = #Fu$; đạt cực tiểu tại $x = #V$, $y_("CT") = #Fv$.] + } else { + cbt = [Hàm số nghịch biến trên các khoảng #_kh(_ninf, U) và #_kh(V, _pinf)#"; "đồng biến trên khoảng #_kh(U, V)#"."] + ct = [Hàm số đạt cực tiểu tại $x = #U$, $y_("CT") = #Fu$; đạt cực đại tại $x = #V$, $y_("CĐ") = #Fv$.] + } + } else if delta < -_eps { + // đơn điệu, y' vô nghiệm + dao-them = [ Vì $Delta' = #so-toan(delta) < 0$ nên $y'$ không đổi dấu.] + cbt = if a > 0 { + [Hàm số đồng biến trên khoảng #_kh(_ninf, _pinf)#"."] + } else { + [Hàm số nghịch biến trên khoảng #_kh(_ninf, _pinf)#"."] + } + ct = [Hàm số không có cực trị.] + } else { + // Δ' = 0: nghiệm kép, vẫn đơn điệu + dao-them = [ $y' = 0 <=> x = #so-toan(xu)$ (nghiệm kép), $y'$ không đổi dấu.] + cbt = if a > 0 { + [Hàm số đồng biến trên khoảng #_kh(_ninf, _pinf)#"."] + } else { + [Hàm số nghịch biến trên khoảng #_kh(_ninf, _pinf)#"."] + } + ct = [Hàm số không có cực trị.] + } + [ + #_tieu-de(tieu-de, bt) + - Tập xác định: $D = RR$. + - Sự biến thiên: + + Đạo hàm: $y' = #dao$.#dao-them + + Giới hạn: #gh + + Chiều biến thiên: #cbt + + Cực trị: #ct + + Bảng biến thiên: + #_o(giua, bbt-bac-ba(a, b, c, d)) + - Đồ thị: + + Đạo hàm cấp hai: $y'' = #_da-thuc(((6 * a, 1), (2 * b, 0)))$; $y'' = 0 <=> x = #so-toan(xu)$. Điểm uốn $I(#so-toan(xu) ; #so-toan(yu))$. + + Đồ thị cắt trục tung tại $(0; #so-toan(d))$. + #_o(giua, do-thi-bac-ba(a, b, c, d, w: w)) + ] +} + +// ===================================================================== +// 3) TRÙNG PHƯƠNG: y = ax⁴ + bx² + c (a·b < 0 -> 3 cực trị) +// ===================================================================== +#let khao-sat-ve-do-thi-ham-trung-phuong(a, b, c, tieu-de: auto, w: 7.6cm, giua: false) = { + assert(calc.abs(a) > _eps, message: "trung-phuong: cần a ≠ 0") + let bt = _da-thuc(((a, 4), (b, 2), (c, 0))) + let f = x => a * calc.pow(x, 4) + b * x * x + c + let dao = _da-thuc(((4 * a, 3), (2 * b, 1))) + let gh = if a > 0 { + [$limits(lim)_(x -> -oo) y = +oo$; $limits(lim)_(x -> +oo) y = +oo$.] + } else { + [$limits(lim)_(x -> -oo) y = -oo$; $limits(lim)_(x -> +oo) y = -oo$.] + } + let cbt = [] + let ct = [] + let dao-them = [] + if a * b < -_eps { + // 3 cực trị: x = 0, x = ±xc + let Xc = so-can-thuc(0, 1, -b / (2 * a), 1) + let nXc = so-can-thuc(0, -1, -b / (2 * a), 1) + let yc = so-can-thuc(4 * a * c - b * b, 0, 0, 4 * a) + let y0 = so-toan(c) + dao-them = [ $y' = 0 <=> x = 0$ hoặc $x = plus.minus #Xc$.] + if a > 0 { + cbt = [Hàm số nghịch biến trên các khoảng #_kh(_ninf, nXc) và #_kh($0$, Xc)#"; "đồng biến trên các khoảng #_kh(nXc, $0$) và #_kh(Xc, _pinf)#"."] + ct = [Hàm số đạt cực tiểu tại $x = plus.minus #Xc$, $y_("CT") = #yc$; đạt cực đại tại $x = 0$, $y_("CĐ") = #y0$.] + } else { + cbt = [Hàm số đồng biến trên các khoảng #_kh(_ninf, nXc) và #_kh($0$, Xc)#"; "nghịch biến trên các khoảng #_kh(nXc, $0$) và #_kh(Xc, _pinf)#"."] + ct = [Hàm số đạt cực đại tại $x = plus.minus #Xc$, $y_("CĐ") = #yc$; đạt cực tiểu tại $x = 0$, $y_("CT") = #y0$.] + } + } else { + // 1 cực trị tại x = 0 + dao-them = [ $y' = 0 <=> x = 0$.] + if a > 0 { + cbt = [Hàm số nghịch biến trên khoảng #_kh(_ninf, $0$) và đồng biến trên khoảng #_kh($0$, _pinf)#"."] + ct = [Hàm số đạt cực tiểu tại $x = 0$, $y_("CT") = #so-toan(c)$; không có cực đại.] + } else { + cbt = [Hàm số đồng biến trên khoảng #_kh(_ninf, $0$) và nghịch biến trên khoảng #_kh($0$, _pinf)#"."] + ct = [Hàm số đạt cực đại tại $x = 0$, $y_("CĐ") = #so-toan(c)$; không có cực tiểu.] + } + } + [ + #_tieu-de(tieu-de, bt) + - Tập xác định: $D = RR$. + - Sự biến thiên: + + Đạo hàm: $y' = #dao$.#dao-them + + Giới hạn: #gh + + Chiều biến thiên: #cbt + + Cực trị: #ct + + Bảng biến thiên: + #_o(giua, bbt-trung-phuong(a, b, c)) + - Đồ thị: + + Đồ thị cắt trục tung tại $(0; #so-toan(c))$; là hàm số chẵn nên nhận trục $O y$ làm trục đối xứng. + #_o(giua, do-thi-trung-phuong(a, b, c, w: w)) + ] +} + +// ===================================================================== +// 4) PHÂN THỨC BẬC NHẤT/BẬC NHẤT: y = (ax + b)/(cx + d) +// ===================================================================== +#let khao-sat-ve-do-thi-ham-phan-thuc(a, b, c, d, tieu-de: auto, w: 7.6cm, giua: false) = { + assert(calc.abs(c) > _eps, message: "phan-thuc: cần c ≠ 0") + let D = a * d - b * c + assert(calc.abs(D) > _eps, message: "phan-thuc: ad − bc = 0 (hàm hằng)") + let tu = _da-thuc(((a, 1), (b, 0))) + let mau = _da-thuc(((c, 1), (d, 0))) + let bt = $(#tu)/(#mau)$ + let x0 = -d / c // TCĐ + let y0 = a / c // TCN + let X0 = so-toan(x0) + let Y0 = so-toan(y0) + let dong = D > 0 + let dao = if dong { + $y' = #so-toan(D)/(#mau)^2 > 0, forall x != #X0$ + } else { + $y' = #so-toan(D)/(#mau)^2 < 0, forall x != #X0$ + } + let cbt = if dong { + [Hàm số đồng biến trên từng khoảng #_kh(_ninf, X0) và #_kh(X0, _pinf)#"."] + } else { + [Hàm số nghịch biến trên từng khoảng #_kh(_ninf, X0) và #_kh(X0, _pinf)#"."] + } + let trai = if dong { _ninf } else { _pinf } + let phai = if dong { _pinf } else { _ninf } + [ + #_tieu-de(tieu-de, bt) + - Tập xác định: $D = RR without {#X0}$. + - Sự biến thiên: + + Chiều biến thiên: #dao. #cbt + + Cực trị: Hàm số không có cực trị. + + Tiệm cận: + $limits(lim)_(x -> #X0^-) y = #trai$, $limits(lim)_(x -> #X0^+) y = #phai$ nên đường thẳng $x = #X0$ là tiệm cận đứng. + $limits(lim)_(x -> plus.minus oo) y = #Y0$ nên đường thẳng $y = #Y0$ là tiệm cận ngang. + + Bảng biến thiên: + #_o(giua, bbt-phan-thuc(a, b, c, d)) + - Đồ thị: + + Đồ thị nhận điểm $I(#X0 ; #Y0)$ (giao hai tiệm cận) làm tâm đối xứng. + #_o(giua, do-thi-phan-thuc(a, b, c, d, w: w)) + ] +} + +// ===================================================================== +// 5) HỮU TỈ BẬC HAI/BẬC NHẤT: y = (ax² + bx + c)/(dx + e) +// Viết y = px + q + r/(dx + e); p = a/d, q = (bd − ae)/d², r = c − qe. +// ===================================================================== +#let khao-sat-ve-do-thi-ham-huu-ti(a, b, c, d, e, tieu-de: auto, w: 7.6cm, giua: false) = { + assert(calc.abs(a) > _eps and calc.abs(d) > _eps, message: "huu-ti: cần a ≠ 0 và d ≠ 0") + let tu = _da-thuc(((a, 2), (b, 1), (c, 0))) + let mau = _da-thuc(((d, 1), (e, 0))) + let bt = $(#tu)/(#mau)$ + let x0 = -e / d + let p = a / d + let q = (b * d - a * e) / (d * d) + let r = c - q * e + let f = x => (a * x * x + b * x + c) / (d * x + e) + let X0 = so-toan(x0) + let tcx = _da-thuc(((p, 1), (q, 0))) // y = px + q + let co-ct = p != 0 and r * d * p > _eps + // y' = p + k/(dx+e)² với k = −rd + let k = -r * d + let dao = if k > _eps { $y' = #so-toan(p) + #so-toan(k)/(#mau)^2$ } + else if k < -_eps { $y' = #so-toan(p) - #so-toan(-k)/(#mau)^2$ } + else { $y' = #so-toan(p)$ } + let cbt = [] + let ct = [] + let dao-them = [] + if co-ct { + // hoành độ & giá trị cực trị CHÍNH XÁC dạng căn thức + let ctr = cuc-tri-huu-ti(a, b, c, d, e) + let U = ctr.x1 + let V = ctr.x2 + let Fu = ctr.y1 + let Fv = ctr.y2 + dao-them = [ $y' = 0 <=> x = #U$ hoặc $x = #V$.] + if p > 0 { + cbt = [Hàm số đồng biến trên các khoảng #_kh(_ninf, U) và #_kh(V, _pinf)#"; "nghịch biến trên các khoảng #_kh(U, X0) và #_kh(X0, V)#"."] + ct = [Hàm số đạt cực đại tại $x = #U$, $y_("CĐ") = #Fu$; đạt cực tiểu tại $x = #V$, $y_("CT") = #Fv$.] + } else { + cbt = [Hàm số nghịch biến trên các khoảng #_kh(_ninf, U) và #_kh(V, _pinf)#"; "đồng biến trên các khoảng #_kh(U, X0) và #_kh(X0, V)#"."] + ct = [Hàm số đạt cực tiểu tại $x = #U$, $y_("CT") = #Fu$; đạt cực đại tại $x = #V$, $y_("CĐ") = #Fv$.] + } + } else { + dao-them = [ (không đổi dấu trên tập xác định).] + if p > 0 { + cbt = [Hàm số đồng biến trên từng khoảng #_kh(_ninf, X0) và #_kh(X0, _pinf)#"."] + } else { + cbt = [Hàm số nghịch biến trên từng khoảng #_kh(_ninf, X0) và #_kh(X0, _pinf)#"."] + } + ct = [Hàm số không có cực trị.] + } + let trai = if r * d > 0 { _ninf } else { _pinf } + let phai = if r * d > 0 { _pinf } else { _ninf } + [ + #_tieu-de(tieu-de, bt) + - Tập xác định: $D = RR without {#X0}$. + - Sự biến thiên: + + Chiều biến thiên: #dao.#dao-them #cbt + + Cực trị: #ct + + Tiệm cận: + $limits(lim)_(x -> #X0^-) y = #trai$, $limits(lim)_(x -> #X0^+) y = #phai$ nên đường thẳng $x = #X0$ là tiệm cận đứng. + $limits(lim)_(x -> plus.minus oo) [y - (#tcx)] = 0$ nên đường thẳng $y = #tcx$ là tiệm cận xiên. + + Bảng biến thiên: + #_o(giua, bbt-huu-ti(a, b, c, d, e)) + - Đồ thị: + + Đồ thị nhận giao hai tiệm cận làm tâm đối xứng. + #_o(giua, do-thi-huu-ti(a, b, c, d, e, w: w)) + ] +} +// (đồng bộ mount 07/2026) diff --git a/packages/preview/conic-toan/0.3.4/lib/mat-cong.typ b/packages/preview/conic-toan/0.3.4/lib/mat-cong.typ new file mode 100644 index 0000000000..46aee9091a --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/mat-cong.typ @@ -0,0 +1,471 @@ +// ===================================================================== +// mat-cong.typ — ENGINE MẶT CONG (nón · trụ) CÓ NÉT KHUẤT TỰ ĐỘNG (08/2026) +// +// Bổ khuyết cho `da-dien.typ`: engine kia chỉ lo được KHỐI ĐA DIỆN LỒI và +// KHÔNG xử lý được hai khối che nhau. Ở đây dùng MỘT cơ chế duy nhất cho cả +// hai việc — BẮN TIA từ điểm đang xét về phía người nhìn; tia gặp lòng bất +// kì khối nào (kể cả chính nó) thì đoạn đó là NÉT KHUẤT. +// +// #mat-cong( +// mat-non(r: 2, cao: 4, mau: red), +// mat-tru(tam: (0, 2, 0), r: 2, cao: 4, mau: blue), +// cam: chieu-truc-giao(ngang: 15deg, cao: 22deg), +// truc: (x: 2.6, y: 4.4, z: 4.6), +// ) +// +// TRỤC KHỐI ĐẶT NGHIÊNG ĐƯỢC: `nghieng:` là góc giữa trục khối và Oz, +// `huong:` là hướng ngả. Khi trục nghiêng thì TOÀN THÂN nghiêng theo — mặt +// đáy luôn VUÔNG GÓC với trục, đường sinh nghiêng cùng trục: +// +// #mat-cong(mat-tru(r: 1.2, cao: 5, nghieng: 70deg)) // trụ nằm nghiêng +// #mat-cong(mat-non(r: 1.6, cao: 4.5, nghieng: 90deg)) // nón nằm ngang +// +// Mọi công thức đều viết theo KHUNG RIÊNG (u, v, w) của khối — w là trục, +// (u, v) là hai phương trong mặt đáy — nên trục đứng chỉ là trường hợp riêng. +// +// Phép thử che khuất là GIẢI TÍCH, KHÔNG quét mẫu: giao của tia với mặt đáy +// là phương trình bậc nhất, với mặt bên là bậc hai; trạng thái trong/ngoài +// chỉ đổi tại các nghiệm đó nên chỉ cần xét trung điểm giữa hai mốc liên +// tiếp. Sai số duy nhất còn lại là ở mắt lưới của đường được vẽ (`n:`). +// +// GIỚI HẠN: đường sinh BIÊN (silhouette) CỐ Ý không để chính khối của nó +// che — tia bắn từ đúng đường biên là tiếp tuyến, xét ở đó sẽ chập chờn làm +// nét biên lúc liền lúc đứt. Chưa có mặt cầu. +// ===================================================================== +#import "ve.typ": * +#import "da-dien.typ": v3-cong, v3-tru, v3-nhan, v3-dai, v3-vo-huong, v3-co-huong, v3-chuan, _cam, chieu-3d, chieu-xien, chieu-oxyz, chieu-truc-giao + +// ===================================================================== +// KHUNG RIÊNG CỦA KHỐI — (u, v) trong mặt đáy, w dọc trục +// ===================================================================== +// Camera MẶC ĐỊNH của engine này là chiếu TRỰC GIAO, KHÔNG phải `chieu-xien` +// như `da-dien.typ`. Lý do: chiếu xiên làm đường tròn nằm ngang chiếu ra elip +// NGHIÊNG trong khi trục khối vẫn dựng đứng — đáy nghiêng mà trục đứng, nhìn +// vô lý. Chiếu trực giao cho elip đáy có trục lớn NẰM NGANG, khối vẽ ra NGAY +// NGẮN đúng lối sách giáo khoa. Muốn lối cũ thì khai rõ `cam: chieu-xien()`. +#let _cam-mc(c) = if c == auto { + chieu-truc-giao(ngang: 15deg, cao: 22deg) +} else { _cam(c) } + +#let _khung(w0) = { + let w = v3-chuan(w0) + // chọn vectơ mồi KHÔNG song song với w, kẻo tích có hướng ra vectơ không + let moi = if calc.abs(w.at(2)) > 0.9 { (1.0, 0.0, 0.0) } else { (0.0, 0.0, 1.0) } + let u = v3-chuan(v3-co-huong(moi, w)) + (u, v3-co-huong(w, u), w) +} + +// Nhận CẢ kiểu angle (`30deg`) LẪN số trần (`30` = 30 ĐỘ) — cùng lối +// `toa-cuc` của ve.typ. Không có dòng này thì `nghieng: 210` bị Typst hiểu +// là 210 RADIAN, khối quay đi đâu không biết mà KHÔNG hề báo lỗi. +#let _goc-do(a) = if type(a) == angle { a } else { a * 1deg } + +// Trục khối suy từ hai góc: `nghieng` = góc so với Oz, `huong` = hướng ngả. +#let _truc-tu-goc(nghieng0, huong0) = { + let nghieng = _goc-do(nghieng0) + let huong = _goc-do(huong0) + ( + calc.sin(nghieng) * calc.cos(huong), + calc.sin(nghieng) * calc.sin(huong), + calc.cos(nghieng), + ) +} + +#let _khoi(loai, tam, r, cao, nghieng, huong, truc, mau, to) = { + let w0 = if truc == auto { _truc-tu-goc(nghieng, huong) } else { truc } + let (u, v, w) = _khung(w0) + (loai: loai, tam: tam, r: r, cao: cao, u: u, v: v, w: w, mau: mau, to: to) +} + +// ===================================================================== +// MÔ TẢ KHỐI — TRẢ GIÁ TRỊ (KHÔNG kê vào ve-voi) +// tam : tâm mặt đáy (đáy DƯỚI nếu trục đứng) +// r, cao : bán kính đáy, chiều cao ĐO DỌC TRỤC +// nghieng : góc giữa trục khối và Oz (0 = đứng, 90° = nằm ngang) +// huong : hướng ngả của trục, đo trong mặt phẳng Oxy +// truc : ghi đè thẳng bằng vectơ 3D (auto = suy từ nghieng/huong) +// ===================================================================== +#let mat-non( + tam: (0, 0, 0), r: 2, cao: 4, + nghieng: 0deg, huong: 0deg, truc: auto, + mau: auto, to: auto, +) = _khoi("non", tam, r, cao, nghieng, huong, truc, mau, to) + +#let mat-tru( + tam: (0, 0, 0), r: 2, cao: 4, + nghieng: 0deg, huong: 0deg, truc: auto, + mau: auto, to: auto, +) = _khoi("tru", tam, r, cao, nghieng, huong, truc, mau, to) + +// Đỉnh nón (cũng là tâm đáy trên của trụ). +#let dinh-non(k) = v3-cong(k.tam, v3-nhan(k.cao, k.w)) + +// Mảng điểm 3D của một ĐƯỜNG TRÒN tâm `tam`, bán kính r, nằm trong mặt phẳng +// vuông góc với `truc` — đưa vào `duong:` của `ve-mat-cong` để vẽ thiết diện, +// đường tròn phụ… mà vẫn được tự chia liền/đứt. TRẢ GIÁ TRỊ (không kê). +#let tron-ngang(tam, r, truc: (0, 0, 1), n: 48) = { + let (u, v, w) = _khung(truc) + range(n + 1).map(i => { + let t = 360deg * (i / n) + v3-cong(tam, v3-cong(v3-nhan(r * calc.cos(t), u), v3-nhan(r * calc.sin(t), v))) + }) +} + +// Điểm P có nằm HẲN trong lòng khối không? +// `lui` CO KHỐI LẠI một chút — KHÔNG phải để làm đẹp mà là BẮT BUỘC: +// đường tròn được vẽ bằng đa giác n cạnh, nên TRUNG ĐIỂM DÂY CUNG (điểm mà +// `_duong-tu-dong` đem đi thử) thụt vào trong vành đúng bằng độ võng +// `r·(1 − cos(180°/n))`. Tia bắn từ đó xuyên qua một LÁT MỎNG của chính khối +// rồi ra ngay ⇒ phép thử giải tích bắt đúng lát đó và báo KHUẤT oan, làm cả +// vành đáy thành nét đứt. Vì vậy `ve-mat-cong` tính `lui` theo `n` rồi gắn +// vào từng khối. (Bản quét mẫu cũ vô tình NHẢY QUA lát này nên không lộ lỗi.) +#let _trong-khoi(k, P) = { + let lui = k.at("lui", default: 1e-4) + let q = v3-tru(P, k.tam) + let z = v3-vo-huong(q, k.w) // toạ độ DỌC TRỤC + if z < lui or z > k.cao - lui { return false } + let d = v3-dai(v3-tru(q, v3-nhan(z, k.w))) // khoảng cách tới trục + let bk = if k.loai == "non" { k.r * (1 - z / k.cao) } else { k.r } + d < bk - lui +} + +// Các MỐC s > 0 mà trạng thái trong/ngoài khối k CÓ THỂ đổi dọc tia P + s·e: +// hai mặt đáy (mặt phẳng) + mặt bên (phương trình bậc hai). +// ⚠️ ĐỪNG quay lại lối QUÉT MẪU đều: bước quét luôn có thể dài hơn dây cung +// đi qua khối ⇒ tia "nhảy qua" khối và nét khuất bị bỏ sót. Đã vấp đúng lỗi +// đó (đường sinh trụ nằm trong lòng nón mà vẫn vẽ liền): bước 1.13 trong khi +// dây cung chỉ 0.5. +#let _moc-tia(k, P, e) = { + let q = v3-tru(P, k.tam) + let z0 = v3-vo-huong(q, k.w) + let ez = v3-vo-huong(e, k.w) + let a = v3-tru(q, v3-nhan(z0, k.w)) // phần vuông góc trục + let b = v3-tru(e, v3-nhan(ez, k.w)) + let moc = () + if calc.abs(ez) > 1e-12 { + moc.push(-z0 / ez) + moc.push((k.cao - z0) / ez) + } + // mặt bên: |a + s·b|² = bán kính(s)² + let he = if k.loai == "tru" { + (v3-vo-huong(b, b), 2 * v3-vo-huong(a, b), v3-vo-huong(a, a) - k.r * k.r) + } else { + let m = k.r / k.cao + let a0 = k.r - m * z0 + let k1 = m * ez + ( + v3-vo-huong(b, b) - k1 * k1, + 2 * (v3-vo-huong(a, b) + a0 * k1), + v3-vo-huong(a, a) - a0 * a0, + ) + } + let (A, B, C) = he + if calc.abs(A) > 1e-12 { + let D = B * B - 4 * A * C + if D > 0 { + let sq = calc.sqrt(D) + moc.push((-B - sq) / (2 * A)) + moc.push((-B + sq) / (2 * A)) + } + } else if calc.abs(B) > 1e-12 { + moc.push(-C / B) + } + moc +} + +// Điểm P có bị khối nào che không: bắn tia P + s·e về phía người nhìn. +// CHÍNH XÁC (không quét mẫu): trạng thái trong/ngoài chỉ đổi tại các mốc +// của `_moc-tia`, nên chỉ cần xét TRUNG ĐIỂM giữa hai mốc liên tiếp. +// `bo` = chỉ số khối được BỎ QUA (không tự che). +#let _bi-che(P, e, ds-khoi, bo: -1) = { + let moc = (1e-6,) + for j in range(ds-khoi.len()) { + if j == bo { continue } + for s in _moc-tia(ds-khoi.at(j), P, e) { + if s > 1e-6 { moc.push(s) } + } + } + let moc = moc.sorted() + for i in range(moc.len() - 1) { + let Q = v3-cong(P, v3-nhan((moc.at(i) + moc.at(i + 1)) / 2, e)) + for j in range(ds-khoi.len()) { + if j != bo and _trong-khoi(ds-khoi.at(j), Q) { return true } + } + } + false +} + +// ===================================================================== +// VẼ MỘT ĐƯỜNG 3D, TỰ CHIA LIỀN / ĐỨT +// Mỗi mắt lưới xét ở TRUNG ĐIỂM rồi gom các mắt liên tiếp cùng trạng thái +// (cùng lối với `_doan-tu-dong` của da-dien.typ). +// ===================================================================== +#let _duong-tu-dong( + ctx, p, e, ds-khoi, pts, + mau: black, day: 1.1pt, hien-khuat: true, + mau-khuat: auto, day-khuat: auto, bo: -1, +) = { + let m = pts.len() + if m < 2 { return } + let trang = range(m - 1).map(i => { + let M = v3-nhan(0.5, v3-cong(pts.at(i), pts.at(i + 1))) + _bi-che(M, e, ds-khoi, bo: bo) + }) + let dau = 0 + let i = 1 + while i <= m - 1 { + let het = i == m - 1 + if het or trang.at(i) != trang.at(dau) { + let cuoi = if het { m - 1 } else { i } + let che = trang.at(dau) + if not che or hien-khuat { + duong-cong( + ctx, range(dau, cuoi + 1).map(j => p(pts.at(j))), + mau: if not che or mau-khuat == auto { mau } else { mau-khuat }, + day: if not che or day-khuat == auto { day } else { day-khuat }, + dut: che, + ) + } + dau = i + } + i = i + 1 + } +} + +// ===================================================================== +// HÌNH HỌC CỦA TỪNG KHỐI — viết theo KHUNG RIÊNG (u, v, w) +// ===================================================================== +// Điểm trên đường tròn bán kính R quanh trục, cách đáy `h` dọc trục. +#let _diem-vanh(k, R, t, h) = v3-cong( + v3-cong(k.tam, v3-nhan(h, k.w)), + v3-cong(v3-nhan(R * calc.cos(t), k.u), v3-nhan(R * calc.sin(t), k.v)), +) +#let _cung-vanh(k, R, h, tu, den, n: 48) = range(n + 1).map(i => { + _diem-vanh(k, R, tu + (den - tu) * (i / n), h) +}) + +// Hiệu hai điểm 2D · tích có hướng 2D. +#let _tru2(a, b) = (a.at(0) - b.at(0), a.at(1) - b.at(1)) +#let _cheo2(a, b) = a.at(0) * b.at(1) - a.at(1) * b.at(0) + +// Góc ứng với điểm GẦN người nhìn nhất trên vành: độ sâu tỉ lệ với +// cos t·(u·e) + sin t·(v·e), lớn nhất tại atan2(u·e, v·e). +#let _goc-gan(k, e) = calc.atan2(v3-vo-huong(k.u, e), v3-vo-huong(k.v, e)) + +// Hai góc ứng với ĐƯỜNG SINH BIÊN của khối. +// • trụ : pháp tuyến mặt bên vuông góc tia nhìn ⇔ _goc-gan ± 90°. +// • nón : điều kiện hình chiếu đường sinh TIẾP XÚC elip đáy — +// (W×U)·sin t − (W×V)·cos t = −r·(U×V)/cao, +// với U, V, W là ảnh của khung riêng (u, v, w) qua phép chiếu. +// (Kiểm chứng với trục đứng + chieu-oxyz: ra đúng +// cos t + k·cos(goc)·sin t = −r·k·sin(goc)/cao.) +#let _goc-bien(k, p, e) = { + let g0 = _goc-gan(k, e) + if k.loai != "non" { return (g0 - 90deg, g0 + 90deg) } + let o = p((0, 0, 0)) + let U = _tru2(p(k.u), o) + let V = _tru2(p(k.v), o) + let W = _tru2(p(k.w), o) + let a = _cheo2(W, U) + let b = -_cheo2(W, V) + let bien-do = calc.sqrt(a * a + b * b) + if bien-do < 1e-12 { return (g0 - 90deg, g0 + 90deg) } + let ve-phai = -k.r * _cheo2(U, V) / (k.cao * bien-do) + let dt = calc.acos(calc.max(-1.0, calc.min(1.0, ve-phai))) + let ph = calc.atan2(b, a) + (ph - dt, ph + dt) +} + +// Các đường phải vẽ của một khối: mảng (pts, tu-che). `tu-che = false` +// nghĩa là đường BIÊN — không để chính khối đó che. +#let _duong-khoi(k, p, e, n: 48) = { + let (t1, t2) = _goc-bien(k, p, e) + // ÉP KIỂU int: calc.round và phép chia của Typst trả FLOAT, mà `range` + // chỉ nhận số nguyên ("expected integer, found float"). + let ns = calc.max(2, int(n / 2)) + let ds = ((_cung-vanh(k, k.r, 0, 0deg, 360deg, n: n), true),) + if k.loai == "non" { + let S = dinh-non(k) + for t in (t1, t2) { + let B = _diem-vanh(k, k.r, t, 0) + ds.push(( + range(ns + 1).map(i => v3-cong(B, v3-nhan(i / ns, v3-tru(S, B)))), + false, + )) + } + } else { + ds.push((_cung-vanh(k, k.r, k.cao, 0deg, 360deg, n: n), true)) + for t in (t1, t2) { + let A = _diem-vanh(k, k.r, t, 0) + ds.push(( + range(ns + 1).map(i => v3-cong(A, v3-nhan(k.cao * i / ns, k.w))), + false, + )) + } + } + ds +} + +// Đường bao ngoài (dùng để tô màu): nón = cung đáy thấy được + đỉnh; +// trụ = cung đáy phía trước + cung nắp phía sau. +#let _bao-khoi(k, p, e, n: 48) = { + let (t1, t2) = _goc-bien(k, p, e) + if k.loai == "non" { + _cung-vanh(k, k.r, 0, t1, t2, n: n) + (dinh-non(k),) + } else { + // BẮT BUỘC bọc ngoặc: `+` ở ĐẦU dòng nối bị Typst đọc thành dấu dương + // một ngôi ("cannot apply unary '+' to array"). + ( + _cung-vanh(k, k.r, 0, t1, t2, n: n) + + _cung-vanh(k, k.r, k.cao, t2, t1 + 360deg, n: n) + ) + } +} + +// Các điểm bao của khối (để dựng cửa sổ khung hình). Lấy 12 hướng quanh mỗi +// vành chứ không phải 4 — hình chiếu của đường tròn là elip XIÊN nên bốn +// điểm theo trục toạ độ hụt mất phần chìa ra hai bên. +#let _chot-khoi(k, m: 12) = { + let ds = () + for h in (0, k.cao) { + let bk = if k.loai == "non" and h == k.cao { 0.0 } else { k.r } + for i in range(m) { ds.push(_diem-vanh(k, bk, 360deg * (i / m), h)) } + } + ds +} +#let _chot-tat-ca(ds) = { + let kq = () + for k in ds { kq = kq + _chot-khoi(k) } + kq +} + +// ===================================================================== +// HỆ TRỤC Oxyz VẼ BẰNG CHÍNH CAMERA CỦA KHỐI +// Lý do phải có: `oxyz` của hinh-khong-gian.typ tự dựng phép chiếu XIÊN +// riêng của nó. Trong phép chiếu xiên, đường tròn nằm ngang chiếu ra elip +// NGHIÊNG trong khi Oz vẫn dựng đứng — nhìn vô lý. Vẽ trục bằng chính +// camera của khối thì cả khung hình chung MỘT góc nhìn. Với +// `chieu-truc-giao(cao:)` thì elip đáy của khối ĐỨNG có trục lớn NẰM NGANG +// và trục khối THẲNG ĐỨNG — đúng như nhìn vật thật. +// NHẬN ctx ⇒ PHẢI kê vào `ve-voi` và khối `_voi-ctx`. +// ===================================================================== +#let ve-truc-3d( + ctx, cam, + x: 3, y: 4, z: 4, + am: 0.4, // kéo dài thêm về phía âm + dm: 0.55, // chừa chỗ cho mũi tên + nhãn ở đầu dương + ten: ($x$, $y$, $z$), ten-goc: $O$, + huong-ten: ("below-left", "below-right", "left"), + huong-goc: "below-right", + mau: black, day: 0.9pt, cach: 5pt, +) = { + let p = _cam(cam).p + let bo3 = ( + ((1, 0, 0), x, ten.at(0), huong-ten.at(0)), + ((0, 1, 0), y, ten.at(1), huong-ten.at(1)), + ((0, 0, 1), z, ten.at(2), huong-ten.at(2)), + ) + for (u, L, t, hg) in bo3 { + let B = v3-nhan(L + am + dm, u) + mui-ten(ctx, p(v3-nhan(-am, u)), p(B), mau: mau, day: day) + if t != none { nhan(ctx, p(B), t, huong: hg, cach: cach) } + } + if ten-goc != none { + nhan(ctx, p((0, 0, 0)), ten-goc, huong: huong-goc, cach: 4pt) + } +} + +// Hai đầu mút ba trục (để cửa sổ khung hình bao được cả hệ trục). +#let _chot-truc(tr) = { + let am = tr.at("am", default: 0.4) + let dm = tr.at("dm", default: 0.55) + let kq = () + for (khoa, u) in (("x", (1, 0, 0)), ("y", (0, 1, 0)), ("z", (0, 0, 1))) { + let L = tr.at(khoa, default: if khoa == "x" { 3 } else { 4 }) + kq.push(v3-nhan(-am, u)) + kq.push(v3-nhan(L + am + dm, u)) + } + kq +} + +// ===================================================================== +// VE-MAT-CONG — NHẬN ctx ⇒ PHẢI kê vào `ve-voi` và khối `_voi-ctx` +// ===================================================================== +#let ve-mat-cong( + ctx, + ..khoi, + cam: auto, + mau: black, day: 1.1pt, to: none, + hien-khuat: true, mau-khuat: auto, day-khuat: auto, + n: 48, + duong: (), + truc: none, // dict tuỳ chọn của `ve-truc-3d`, vd truc: (x: 3, y: 5, z: 5) + truoc: none, // vẽ TRƯỚC khối · them: vẽ SAU khối + them: none, +) = { + let ds0 = khoi.pos() + if ds0.len() == 0 { return } + // gắn `lui` cho từng khối: phải LỚN HƠN độ võng dây cung của lưới n cạnh, + // xem giải thích ở `_trong-khoi`. Hệ số 1.6 là biên an toàn. + let ds = ds0.map(k => k + ( + lui: calc.max(1e-4, 1.6 * k.r * (1 - calc.cos(180deg / n))), + )) + let cm = _cam-mc(cam) + let p = cm.p + let e = cm.nhin + // 0) hệ trục + lớp vẽ nền — nằm DƯỚI khối + if truc != none { ve-truc-3d(ctx, cm, ..truc) } + if truoc != none { truoc(ctx, p) } + // 1) tô màu trước, để nét không bị màu đè lên + for k in ds { + let mt = if k.to == auto { to } else { k.to } + if mt != none { + da-giac-pt(_bao-khoi(k, p, e, n: n).map(P => toa-pt(ctx, p(P))), to: mt) + } + } + // 2) rồi mới kẻ nét, tự chia liền / đứt + for j in range(ds.len()) { + let k = ds.at(j) + let mk = if k.mau == auto { mau } else { k.mau } + for (pts, tu-che) in _duong-khoi(k, p, e, n: n) { + _duong-tu-dong( + ctx, p, e, ds, pts, + mau: mk, day: day, hien-khuat: hien-khuat, + mau-khuat: mau-khuat, day-khuat: day-khuat, + bo: if tu-che { -1 } else { j }, + ) + } + } + // 3) các đường vẽ thêm (thiết diện, đường tròn phụ…) — cũng tự chia liền/đứt. + // Mỗi mục là một DICT: (pts: mảng điểm 3D, mau: …, day: …, hien-khuat: …) + for m in duong { + _duong-tu-dong( + ctx, p, e, ds, m.pts, + mau: m.at("mau", default: mau), + day: m.at("day", default: day), + hien-khuat: m.at("hien-khuat", default: hien-khuat), + mau-khuat: mau-khuat, day-khuat: day-khuat, + ) + } + if them != none { them(ctx, p) } +} + +// ===================================================================== +// MAT-CONG — TỰ tạo khung hình (KHÔNG kê vào ve-voi) +// Mọi tuỳ chọn khác đều chảy thẳng xuống `ve-mat-cong` qua sink `..khoi`. +// ===================================================================== +#let mat-cong(..khoi, cam: auto, w: 7cm, le: 0.75, co-chu: 10pt) = { + let cm = _cam-mc(cam) + let tr = khoi.named().at("truc", default: none) + let chot = _chot-tat-ca(khoi.pos()) + (if tr == none { () } else { _chot-truc(tr) }) + let pts = chot.map(cm.p) + let xs = pts.map(q => q.at(0)) + let ys = pts.map(q => q.at(1)) + hinh( + w: w, + xmin: calc.min(..xs) - le, xmax: calc.max(..xs) + le, + ymin: calc.min(..ys) - le, ymax: calc.max(..ys) + le, + co-chu: co-chu, + ctx => ve-mat-cong(ctx, ..khoi, cam: cm), + ) +} diff --git a/packages/preview/conic-toan/0.3.4/lib/oxyz-toan.typ b/packages/preview/conic-toan/0.3.4/lib/oxyz-toan.typ new file mode 100644 index 0000000000..c4fec33ca7 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/oxyz-toan.typ @@ -0,0 +1,544 @@ +// ===================================================================== +// oxyz-toan.typ — TÍNH TOÁN trong không gian toạ độ Oxyz +// Toàn bộ hàm ở file này TRẢ VỀ GIÁ TRỊ (số / điểm / dict) hoặc NỘI DUNG +// toán đã định dạng ⇒ KHÔNG kê vào `ve-voi` / `_voi-ctx` của baigiang.typ. +// +// Quy ước kiểu dữ liệu +// điểm / vectơ : (x, y, z) +// mặt phẳng : (a: , b: , c: , d: ) ⇔ ax + by + cz + d = 0 +// (cũng nhận dạng (n: , d: ) của da-dien.typ) +// đường thẳng : (P: điểm đi qua, u: vectơ chỉ phương) +// mặt cầu : (I: tâm, R: bán kính) +// +// Nhóm hàm `hien-*` trả về NỘI DUNG toán CHÍNH XÁC (căn thức, phân số), +// nhóm còn lại trả số thực để tính tiếp / vẽ hình. +// ===================================================================== +#import "da-dien.typ": * +#import "do-thi.typ": so-toan, so-dep, so-can-thuc + +#let _e0 = 1e-9 + +#let _gan-int(x, eps: 1e-9) = calc.abs(x - calc.round(x)) < eps * calc.max(1, calc.abs(x)) + +#let _gcd2(a, b) = { + let (a, b) = (calc.abs(int(a)), calc.abs(int(b))) + while b != 0 { let r = calc.rem(a, b); a = b; b = r } + a +} + +// Nhân dãy hệ số với số hữu tỉ nhỏ nhất để thành NGUYÊN rồi chia ước chung; +// dấu chuẩn hoá: hệ số khác 0 đầu tiên > 0. Không nguyên hoá được -> none. +#let _rut-gon(ds) = { + let ok = none + for q in range(1, 61) { + let t = ds.map(v => v * q) + if t.all(v => _gan-int(v, eps: 1e-7)) { + ok = t.map(v => int(calc.round(v))) + break + } + } + if ok == none { return none } + let g = 0 + for v in ok { g = _gcd2(g, v) } + if g == 0 { return ok } + let r = ok.map(v => int(v / g)) + let am = false + for v in r { + if v != 0 { am = v < 0; break } + } + if am { r = r.map(v => -v) } + r +} + +// Số -> CHUỖI toán (nguyên, phân số p/q rút gọn, cùng lắm là thập phân 3 chữ số). +// Lượt mẫu LỚN (tới 5000, khớp chặt 1e-11) để tâm ngoại tiếp/trực tâm — vốn là +// phân số mẫu vài trăm — vẫn hiện CHÍNH XÁC thay vì số thập phân. +#let _ms(v) = { + if _gan-int(v) { return str(int(calc.round(v))) } + for q in range(2, 101) { + let p = v * q + if _gan-int(p) { + let p = int(calc.round(p)) + if _gcd2(p, q) == 1 { return str(p) + "/" + str(q) } + } + } + for q in range(101, 5001) { + let p = v * q + if _gan-int(p, eps: 1e-11) { + let p = int(calc.round(p)) + if _gcd2(p, q) == 1 { return str(p) + "/" + str(q) } + } + } + let r = calc.round(v, digits: 3) + if calc.fract(r) == 0 { str(int(r)) } else { str(r) } +} + +// Có phải chuỗi số NGUYÊN (để biết cần bọc ngoặc hay không). +#let _la-nguyen(v) = _gan-int(v) + +// Vế trái "2x - 3y + z - 5" từ hệ số + tên biến ("" = hạng tử tự do). +#let _ve-trai(hs, bien) = { + let s = "" + for (v, b) in hs.zip(bien) { + if calc.abs(v) < 1e-12 { continue } + let am = v < 0 + let a = calc.abs(v) + let t = if b == "" { + _ms(a) + } else if _la-nguyen(a) and calc.round(a) == 1 { + b + } else if _la-nguyen(a) { + _ms(a) + b + } else { + "(" + _ms(a) + ")" + b + } + if s == "" { + s = if am { "-" + t } else { t } + } else { + s = s + (if am { " - " } else { " + " }) + t + } + } + if s == "" { s = "0" } + s +} + +// Bọc ngoặc khi số âm hoặc là phân số (dùng làm mẫu số, hệ số đứng riêng). +#let _ms-ngoac(v) = { + let s = _ms(v) + if v < 0 or not _la-nguyen(v) { "(" + s + ")" } else { s } +} + +// ===================================================================== +// 1. VECTƠ +// (các phép cơ bản v3-cong, v3-tru, v3-nhan, v3-chuan… có sẵn ở da-dien.typ) +// ===================================================================== + +// Toạ độ vectơ AB = B - A. Gọi 1 đối số thì trả về chính vectơ đó. +#let vecto-3d(..a) = { + let p = a.pos() + if p.len() == 1 { p.at(0) } else { v3-tru(p.at(1), p.at(0)) } +} + +#let tich-vo-huong(u, v) = v3-vo-huong(u, v) +#let tich-co-huong(u, v) = v3-co-huong(u, v) +// Tích hỗn tạp [u, v, w] = (u × v) · w (định thức cấp ba). +#let tich-hon-tap(u, v, w) = v3-vo-huong(v3-co-huong(u, v), w) + +#let do-dai-vecto(u) = v3-dai(u) + +// cos của góc giữa hai vectơ (giá trị thực, đã chặn trong [-1; 1]). +#let cos-goc-vecto(u, v) = { + let m = v3-dai(u) * v3-dai(v) + if m < _e0 { return 0.0 } + calc.max(-1.0, calc.min(1.0, v3-vo-huong(u, v) / m)) +} +// Góc giữa hai vectơ (0° -> 180°), trả về kiểu angle. +#let goc-vecto(u, v) = calc.acos(cos-goc-vecto(u, v)) + +#let cung-phuong(u, v) = v3-dai(v3-co-huong(u, v)) < 1e-7 * calc.max(1, v3-dai(u) * v3-dai(v)) +#let vuong-goc(u, v) = calc.abs(v3-vo-huong(u, v)) < 1e-7 * calc.max(1, v3-dai(u) * v3-dai(v)) +#let dong-phang(u, v, w) = calc.abs(tich-hon-tap(u, v, w)) < 1e-7 + +// ===================================================================== +// 2. ĐIỂM — khoảng cách, điểm đặc biệt của tam giác +// (trung-diem-3d, chia-3d, tam-3d đã có ở da-dien.typ) +// ===================================================================== +#let khoang-cach-3d(A, B) = v3-dai(v3-tru(B, A)) +#let do-dai-doan-3d(A, B) = khoang-cach-3d(A, B) + +#let trong-tam-3d(..P) = { + let ds = P.pos() + v3-nhan(1 / ds.len(), ds.fold((0.0, 0.0, 0.0), v3-cong)) +} + +// Tâm đường tròn NGOẠI tiếp tam giác ABC (toạ độ tâm tỉ cự). +#let tam-ngoai-tiep-3d(A, B, C) = { + let a2 = v3-vo-huong(v3-tru(C, B), v3-tru(C, B)) + let b2 = v3-vo-huong(v3-tru(C, A), v3-tru(C, A)) + let c2 = v3-vo-huong(v3-tru(B, A), v3-tru(B, A)) + let al = a2 * (b2 + c2 - a2) + let be = b2 * (c2 + a2 - b2) + let ga = c2 * (a2 + b2 - c2) + let s = al + be + ga + if calc.abs(s) < _e0 { return trong-tam-3d(A, B, C) } + v3-nhan(1 / s, v3-cong(v3-cong(v3-nhan(al, A), v3-nhan(be, B)), v3-nhan(ga, C))) +} + +// Trực tâm — hệ thức Euler: H = A + B + C - 2·O. +#let truc-tam-3d(A, B, C) = v3-tru( + v3-cong(v3-cong(A, B), C), + v3-nhan(2, tam-ngoai-tiep-3d(A, B, C)), +) + +// Tâm đường tròn NỘI tiếp: I = (a·A + b·B + c·C)/(a + b + c). +#let tam-noi-tiep-3d(A, B, C) = { + let a = khoang-cach-3d(B, C) + let b = khoang-cach-3d(C, A) + let c = khoang-cach-3d(A, B) + let s = a + b + c + if s < _e0 { return A } + v3-nhan(1 / s, v3-cong(v3-cong(v3-nhan(a, A), v3-nhan(b, B)), v3-nhan(c, C))) +} + +// Tâm đường tròn BÀNG tiếp trong góc A. +#let tam-bang-tiep-3d(A, B, C) = { + let a = khoang-cach-3d(B, C) + let b = khoang-cach-3d(C, A) + let c = khoang-cach-3d(A, B) + let s = -a + b + c + if calc.abs(s) < _e0 { return A } + v3-nhan(1 / s, v3-cong(v3-cong(v3-nhan(-a, A), v3-nhan(b, B)), v3-nhan(c, C))) +} + +// ===================================================================== +// 3. DIỆN TÍCH — THỂ TÍCH +// ===================================================================== +#let dien-tich-tam-giac-3d(A, B, C) = 0.5 * v3-dai(v3-co-huong(v3-tru(B, A), v3-tru(C, A))) +#let dien-tich-hbh-3d(A, B, C) = v3-dai(v3-co-huong(v3-tru(B, A), v3-tru(C, A))) +#let the-tich-tu-dien(A, B, C, D) = calc.abs( + tich-hon-tap(v3-tru(B, A), v3-tru(C, A), v3-tru(D, A)), +) / 6 +#let the-tich-hinh-hop(A, B, C, D) = calc.abs( + tich-hon-tap(v3-tru(B, A), v3-tru(C, A), v3-tru(D, A)), +) +#let bon-diem-dong-phang(A, B, C, D) = dong-phang( + v3-tru(B, A), v3-tru(C, A), v3-tru(D, A), +) +#let ban-kinh-ngoai-tiep-3d(A, B, C) = { + let s = dien-tich-tam-giac-3d(A, B, C) + if s < _e0 { return 0.0 } + (khoang-cach-3d(B, C) * khoang-cach-3d(C, A) * khoang-cach-3d(A, B)) / (4 * s) +} +#let ban-kinh-noi-tiep-3d(A, B, C) = { + let p = (khoang-cach-3d(B, C) + khoang-cach-3d(C, A) + khoang-cach-3d(A, B)) / 2 + if p < _e0 { return 0.0 } + dien-tich-tam-giac-3d(A, B, C) / p +} + +// ===================================================================== +// 4. MẶT PHẲNG ax + by + cz + d = 0 +// ===================================================================== + +// Nhận cả dạng (a,b,c,d) lẫn dạng (n: đơn vị, d:) của da-dien.typ. +#let _hs-mp(m) = { + if "a" in m { (m.a, m.b, m.c, m.d) } else { (m.n.at(0), m.n.at(1), m.n.at(2), -m.d) } +} +#let phap-tuyen-mp(m) = { + let h = _hs-mp(m) + (h.at(0), h.at(1), h.at(2)) +} +// Đổi sang dạng (n: , d: ) để VẼ bằng mat-phang-bh / hinh-chieu-mp của da-dien. +#let mp-de-ve(m) = { + let h = _hs-mp(m) + let n = v3-chuan((h.at(0), h.at(1), h.at(2))) + let l = v3-dai((h.at(0), h.at(1), h.at(2))) + (n: n, d: if l < _e0 { 0.0 } else { -h.at(3) / l }) +} + +#let mat-phang-qua-phap(A, n) = { + let h = _rut-gon((n.at(0), n.at(1), n.at(2), -v3-vo-huong(n, A))) + let h = if h == none { (n.at(0), n.at(1), n.at(2), -v3-vo-huong(n, A)) } else { h } + (a: h.at(0), b: h.at(1), c: h.at(2), d: h.at(3)) +} +#let mat-phang-qua-3-diem(A, B, C) = mat-phang-qua-phap( + A, v3-co-huong(v3-tru(B, A), v3-tru(C, A)), +) +// Mặt phẳng đoạn chắn: x/p + y/q + z/r = 1. +#let mat-phang-doan-chan(p, q, r) = mat-phang-qua-3-diem((p, 0, 0), (0, q, 0), (0, 0, r)) +#let mat-phang-song-song(m, A) = mat-phang-qua-phap(A, phap-tuyen-mp(m)) +#let mat-phang-trung-truc(A, B) = mat-phang-qua-phap(trung-diem-3d(A, B), v3-tru(B, A)) +// Mặt phẳng qua A và vuông góc đường thẳng d. +#let mat-phang-vuong-goc-duong(A, d) = mat-phang-qua-phap(A, d.u) +// Mặt phẳng qua A, chứa hai phương u, v. +#let mat-phang-qua-2-phuong(A, u, v) = mat-phang-qua-phap(A, v3-co-huong(u, v)) + +// Thay toạ độ điểm vào vế trái: ax + by + cz + d. +#let the-vao-mp(A, m) = { + let h = _hs-mp(m) + h.at(0) * A.at(0) + h.at(1) * A.at(1) + h.at(2) * A.at(2) + h.at(3) +} +#let khoang-cach-diem-mp(A, m) = { + let n = phap-tuyen-mp(m) + let l = v3-dai(n) + if l < _e0 { return 0.0 } + calc.abs(the-vao-mp(A, m)) / l +} +#let hinh-chieu-len-mp(A, m) = { + let n = phap-tuyen-mp(m) + let l2 = v3-vo-huong(n, n) + if l2 < _e0 { return A } + v3-tru(A, v3-nhan(the-vao-mp(A, m) / l2, n)) +} +#let doi-xung-qua-mp(A, m) = { + let n = phap-tuyen-mp(m) + let l2 = v3-vo-huong(n, n) + if l2 < _e0 { return A } + v3-tru(A, v3-nhan(2 * the-vao-mp(A, m) / l2, n)) +} +// Khung tứ giác biểu diễn mặt phẳng để VẼ bằng mat-phang-bh của da-dien.typ: +// let (T, u, v) = khung-mp(P, tam: A, r: 2.2) +// mat-phang-bh(ctx, t3, T, u, v) +#let khung-mp(m, tam: auto, r: 2) = { + let n = v3-chuan(phap-tuyen-mp(m)) + let T = hinh-chieu-len-mp(if tam == auto { (0, 0, 0) } else { tam }, m) + let t = if calc.abs(n.at(2)) < 0.9 { (0, 0, 1) } else { (1, 0, 0) } + let u = v3-nhan(r, v3-chuan(v3-co-huong(n, t))) + let v = v3-nhan(r, v3-chuan(v3-co-huong(n, u))) + (T, u, v) +} + +#let goc-2-mp(m1, m2) = calc.acos( + calc.min(1.0, calc.abs(cos-goc-vecto(phap-tuyen-mp(m1), phap-tuyen-mp(m2)))), +) +#let vi-tri-2-mp(m1, m2) = { + let (n1, n2) = (phap-tuyen-mp(m1), phap-tuyen-mp(m2)) + if not cung-phuong(n1, n2) { return "cắt nhau" } + let (h1, h2) = (_hs-mp(m1), _hs-mp(m2)) + let k = 0.0 + for i in range(3) { + if calc.abs(h2.at(i)) > _e0 { k = h1.at(i) / h2.at(i) } + } + if calc.abs(h1.at(3) - k * h2.at(3)) < 1e-7 { "trùng nhau" } else { "song song" } +} + +// Phương trình mặt phẳng: nội dung $2x - 3y + z - 5 = 0$ +#let pt-mat-phang(m, an: ("x", "y", "z")) = { + let h = _hs-mp(m) + let r = _rut-gon(h) + let h = if r == none { h } else { r } + eval(_ve-trai(h, (an.at(0), an.at(1), an.at(2), "")) + " = 0", mode: "math") +} + +// ===================================================================== +// 5. ĐƯỜNG THẲNG (P: điểm, u: vectơ chỉ phương) +// ===================================================================== +// Vectơ chỉ phương "đẹp": nhân lên thành nguyên, chia ước chung, thành phần +// khác 0 đầu tiên dương. Không nguyên hoá được thì giữ nguyên. +#let vtcp-dep(u) = { + let r = _rut-gon(u) + if r == none { u } else { r } +} +#let duong-thang-qua-vtcp(A, u) = (P: A, u: u) +#let duong-thang-qua-2-diem(A, B) = (P: A, u: v3-tru(B, A)) +#let duong-thang-vuong-goc-mp(A, m) = (P: A, u: phap-tuyen-mp(m)) + +#let khoang-cach-diem-duong(A, d) = { + let l = v3-dai(d.u) + if l < _e0 { return khoang-cach-3d(A, d.P) } + v3-dai(v3-co-huong(v3-tru(A, d.P), d.u)) / l +} +#let hinh-chieu-len-duong(A, d) = { + let l2 = v3-vo-huong(d.u, d.u) + if l2 < _e0 { return d.P } + v3-cong(d.P, v3-nhan(v3-vo-huong(v3-tru(A, d.P), d.u) / l2, d.u)) +} +#let doi-xung-qua-duong(A, d) = v3-tru(v3-nhan(2, hinh-chieu-len-duong(A, d)), A) +#let goc-2-duong(d1, d2) = calc.acos(calc.min(1.0, calc.abs(cos-goc-vecto(d1.u, d2.u)))) +#let goc-duong-mp(d, m) = calc.asin( + calc.min(1.0, calc.abs(cos-goc-vecto(d.u, phap-tuyen-mp(m)))), +) + +#let vi-tri-2-duong(d1, d2) = { + let w = v3-tru(d2.P, d1.P) + if cung-phuong(d1.u, d2.u) { + if v3-dai(v3-co-huong(w, d1.u)) < 1e-7 { "trùng nhau" } else { "song song" } + } else if calc.abs(tich-hon-tap(d1.u, d2.u, w)) < 1e-7 { "cắt nhau" } else { "chéo nhau" } +} +#let khoang-cach-2-duong(d1, d2) = { + let n = v3-co-huong(d1.u, d2.u) + if v3-dai(n) < 1e-7 { return khoang-cach-diem-duong(d2.P, d1) } + calc.abs(v3-vo-huong(n, v3-tru(d2.P, d1.P))) / v3-dai(n) +} +// Giao điểm đường thẳng với mặt phẳng — song song / nằm trong thì trả none. +#let giao-duong-mp(d, m) = { + let n = phap-tuyen-mp(m) + let t0 = v3-vo-huong(n, d.u) + if calc.abs(t0) < 1e-9 { return none } + v3-cong(d.P, v3-nhan(-the-vao-mp(d.P, m) / t0, d.u)) +} +// Giao điểm hai đường thẳng (chéo nhau / song song -> none). +#let giao-2-duong(d1, d2) = { + if vi-tri-2-duong(d1, d2) != "cắt nhau" { return none } + let w = v3-tru(d2.P, d1.P) + let n = v3-co-huong(d1.u, d2.u) + let t = v3-vo-huong(v3-co-huong(w, d2.u), n) / v3-vo-huong(n, n) + v3-cong(d1.P, v3-nhan(t, d1.u)) +} +// Giao tuyến hai mặt phẳng (song song / trùng -> none). +#let giao-2-mp(m1, m2) = { + let (n1, n2) = (phap-tuyen-mp(m1), phap-tuyen-mp(m2)) + let u = v3-co-huong(n1, n2) + if v3-dai(u) < 1e-7 { return none } + let (h1, h2) = (_hs-mp(m1), _hs-mp(m2)) + let (k1, k2) = (-h1.at(3), -h2.at(3)) + let (a11, a12) = (v3-vo-huong(n1, n1), v3-vo-huong(n1, n2)) + let a22 = v3-vo-huong(n2, n2) + let de = a11 * a22 - a12 * a12 + let al = (k1 * a22 - k2 * a12) / de + let be = (k2 * a11 - k1 * a12) / de + (P: v3-cong(v3-nhan(al, n1), v3-nhan(be, n2)), u: vtcp-dep(u)) +} + +// Phương trình THAM SỐ: cases(x = ..., y = ..., z = ...) +#let pt-tham-so(d, t: "t", an: ("x", "y", "z")) = { + let dong = range(3).map(i => ( + an.at(i) + " = " + _ve-trai((d.P.at(i), d.u.at(i)), ("", t)) + )) + eval("cases(" + dong.join(", ") + ")", mode: "math") +} +// Phương trình CHÍNH TẮC (thành phần chỉ phương bằng 0 thì tách ra sau dấu phẩy). +#let pt-chinh-tac(d, an: ("x", "y", "z")) = { + let ph = () + let le = () + for i in range(3) { + if calc.abs(d.u.at(i)) < _e0 { + le.push(an.at(i) + " = " + _ms(d.P.at(i))) + } else { + ph.push( + "(" + _ve-trai((1, -d.P.at(i)), (an.at(i), "")) + ")/" + _ms-ngoac(d.u.at(i)), + ) + } + } + let s = if ph.len() >= 2 { (ph.join(" = "),) + le } else { ph + le } + eval(s.join(", "), mode: "math") +} + +// ===================================================================== +// 6. MẶT CẦU (I: tâm, R: bán kính) +// ===================================================================== +#let mat-cau(I, R) = (I: I, R: calc.abs(R)) +#let mat-cau-duong-kinh(A, B) = (I: trung-diem-3d(A, B), R: khoang-cach-3d(A, B) / 2) +#let mat-cau-tam-tiep-xuc-mp(I, m) = (I: I, R: khoang-cach-diem-mp(I, m)) +// Mặt cầu qua 4 điểm không đồng phẳng (đồng phẳng -> none). +#let mat-cau-qua-4-diem(A, B, C, D) = { + let q = P => v3-vo-huong(P, P) + let ha = (v3-tru(B, A), v3-tru(C, A), v3-tru(D, A)) + let ve = ((q(B) - q(A)) / 2, (q(C) - q(A)) / 2, (q(D) - q(A)) / 2) + let de = tich-hon-tap(ha.at(0), ha.at(1), ha.at(2)) + if calc.abs(de) < 1e-9 { return none } + let _cot-j(j) = { + let m = ha.map(r => r) + let k = range(3).map(i => { + let r = m.at(i) + range(3).map(t => if t == j { ve.at(i) } else { r.at(t) }) + }) + tich-hon-tap(k.at(0), k.at(1), k.at(2)) + } + let I = (_cot-j(0) / de, _cot-j(1) / de, _cot-j(2) / de) + (I: I, R: khoang-cach-3d(I, A)) +} +#let vi-tri-mp-mat-cau(m, S) = { + let k = khoang-cach-diem-mp(S.I, m) + if k > S.R + 1e-7 { "không cắt" } else if k > S.R - 1e-7 { "tiếp xúc" } else { "cắt nhau" } +} +// Đường tròn giao tuyến của mặt phẳng và mặt cầu -> (I: tâm, R: bán kính). +#let duong-tron-giao(m, S) = { + let k = khoang-cach-diem-mp(S.I, m) + if k > S.R + 1e-9 { return none } + (I: hinh-chieu-len-mp(S.I, m), R: calc.sqrt(calc.max(0.0, S.R * S.R - k * k))) +} + +// Phương trình mặt cầu dạng CHÍNH TẮC: (x - a)^2 + (y - b)^2 + (z - c)^2 = R^2 +#let pt-mat-cau(S, an: ("x", "y", "z")) = { + let ve = range(3).map(i => { + let t = _ve-trai((1, -S.I.at(i)), (an.at(i), "")) + if t.contains(" ") { "(" + t + ")^2" } else { t + "^2" } + }) + let r2 = S.R * S.R + eval(ve.join(" + ") + " = " + _ms(r2), mode: "math") +} +// Phương trình mặt cầu dạng KHAI TRIỂN: x^2+y^2+z^2 - 2ax - 2by - 2cz + d = 0 +#let pt-mat-cau-khai-trien(S, an: ("x", "y", "z")) = { + let d0 = v3-vo-huong(S.I, S.I) - S.R * S.R + let dau = _ve-trai( + (-2 * S.I.at(0), -2 * S.I.at(1), -2 * S.I.at(2), d0), + (an.at(0), an.at(1), an.at(2), ""), + ) + let dau = if dau == "0" { "" } else if dau.starts-with("-") { + " - " + dau.slice(1) + } else { " + " + dau } + eval( + an.at(0) + "^2 + " + an.at(1) + "^2 + " + an.at(2) + "^2" + dau + " = 0", + mode: "math", + ) +} + +// ===================================================================== +// 7. HIỂN THỊ — nội dung toán CHÍNH XÁC (căn thức, phân số) +// ===================================================================== +// Số CHÍNH XÁC nếu là nguyên / phân số (mẫu tới 5000), ngược lại là thập phân +// 3 chữ số. KHÔNG "đoán" căn thức như so-toan (dễ ra dạng đẹp mà SAI với số vô +// tỉ); cần căn thức chính xác thì dùng hien-can / hien-do-dai / hien-dien-tich-*. +#let hien-so(v) = eval(_ms(v), mode: "math") +// Giá trị gần đúng (mặc định 2 chữ số thập phân). +#let hien-gan-dung(v, chu-so: 2) = { + let r = calc.round(v, digits: chu-so) + eval(if calc.fract(r) == 0 { str(int(r)) } else { str(r) }, mode: "math") +} +// Căn bậc hai CHÍNH XÁC của số nguyên n (tự rút thừa số chính phương). +#let hien-can(n) = so-can-thuc(0, 1, n, 1) +// Tên điểm/vectơ: chuỗi "AB" -> $A B$ (math của Typst coi chuỗi nhiều chữ cái +// là MỘT biến nên phải tách ra); truyền thẳng nội dung toán cũng được. +#let _ten-toan(t) = if type(t) == str { eval(t.clusters().join(" "), mode: "math") } else { t } +#let _bo-ba(u) = eval("(" + u.map(v => _ms(v)).join("; ") + ")", mode: "math") + +// Toạ độ điểm: A(1; 2; -3) +#let hien-diem(A, ten: none) = { + let s = _bo-ba(A) + if ten == none { s } else { $#_ten-toan(ten)#s$ } +} +// Toạ độ vectơ: arrow(u) = (1; 2; -3) +#let hien-vecto(u, ten: none) = { + let s = _bo-ba(u) + if ten == none { s } else { $arrow(#_ten-toan(ten)) = #s$ } +} +#let hien-goc(g, chu-so: 1) = { + let d = g.deg() + let s = if _gan-int(d, eps: 1e-6) { + str(int(calc.round(d))) + } else { so-dep(calc.round(d, digits: chu-so)) } + eval(s + " degree", mode: "math") +} +// Độ dài vectơ / khoảng cách hai điểm — dạng căn thức chính xác. +#let hien-do-dai(u) = so-can-thuc(0, 1, v3-vo-huong(u, u), 1) +#let hien-khoang-cach(A, B) = hien-do-dai(v3-tru(B, A)) +// cos góc giữa hai vectơ — chính xác: (u·v)/(|u||v|) = t√(N)/N +#let hien-cos-goc(u, v) = { + let t = v3-vo-huong(u, v) + let n = v3-vo-huong(u, u) * v3-vo-huong(v, v) + if n < _e0 { return $0$ } + so-can-thuc(0, t, n, n) +} +#let hien-khoang-cach-diem-mp(A, m) = { + let h = _hs-mp(m) + let r = _rut-gon(h) + let h = if r == none { h } else { r } + let n = h.at(0) * h.at(0) + h.at(1) * h.at(1) + h.at(2) * h.at(2) + if n < _e0 { return $0$ } + let t = calc.abs(h.at(0) * A.at(0) + h.at(1) * A.at(1) + h.at(2) * A.at(2) + h.at(3)) + so-can-thuc(0, t, n, n) +} +#let hien-khoang-cach-diem-duong(A, d) = { + let n2 = v3-vo-huong(d.u, d.u) + if n2 < _e0 { return hien-khoang-cach(A, d.P) } + let c = v3-co-huong(v3-tru(A, d.P), d.u) + so-can-thuc(0, 1, v3-vo-huong(c, c) * n2, n2) +} +#let hien-khoang-cach-2-duong(d1, d2) = { + let n = v3-co-huong(d1.u, d2.u) + let n2 = v3-vo-huong(n, n) + if n2 < 1e-12 { return hien-khoang-cach-diem-duong(d2.P, d1) } + let t = v3-vo-huong(n, v3-tru(d2.P, d1.P)) + so-can-thuc(0, 1, t * t / n2, 1) +} +// Diện tích hình bình hành dựng trên hai vectơ = √M (chính xác). +#let hien-dien-tich-hbh(A, B, C) = { + let c = v3-co-huong(v3-tru(B, A), v3-tru(C, A)) + so-can-thuc(0, 1, v3-vo-huong(c, c), 1) +} +// Diện tích tam giác = √M/2 (chính xác). +#let hien-dien-tich-tam-giac(A, B, C) = { + let c = v3-co-huong(v3-tru(B, A), v3-tru(C, A)) + so-can-thuc(0, 1, v3-vo-huong(c, c), 2) +} +#let hien-the-tich-tu-dien(A, B, C, D) = so-toan(the-tich-tu-dien(A, B, C, D)) diff --git a/packages/preview/conic-toan/0.3.4/lib/slide.typ b/packages/preview/conic-toan/0.3.4/lib/slide.typ new file mode 100644 index 0000000000..bb4c088dcb --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/slide.typ @@ -0,0 +1,1005 @@ +// ===================================================================== +// slide.typ — ENGINE TRÌNH CHIẾU THUẦN TYPST (16:9) +// +// Cách dùng: +// #show: bai-giang.with( +// tieu-de: [CHƯƠNG I. HÀM SỐ], gv: "Thầy/Cô ...", ... +// ) +// #muc[1. Định nghĩa] +// #slide(tieu-de: [Khái niệm])[ ... nội dung ... ] +// +// Khung nội dung: #dinh-nghia[...], #dinh-ly[...], #vi-du[...], +// #loi-giai[...], #chu-y[...], #ghi-nho[...], #nhan-xet[...], #luyen-tap[...] +// Bố cục: #chia-cot(trai, phai, ti-le: (3fr, 2fr)) +// ===================================================================== + +// Bảng màu (đọc qua state để mọi hàm dùng chung). +#let _mau = state("bg-mau", ( + chinh: rgb("#0f4c81"), // xanh đậm chủ đạo + nhan: rgb("#e67e22"), // cam nhấn + nen: rgb("#f7f9fc"), +)) +#let _thong-tin = state("bg-thong-tin", (tieu-de: [], gv: none, ngan: none)) +#let _muc-ht = state("bg-muc", none) +#let _buoc-ht = state("bg-buoc", 1) // bước hoạt hình hiện tại +#let _dem-slide = counter("bg-slide") // số thứ tự slide (không tính bước) + +// ----- Hồ sơ hiển thị: "beamer" (trình chiếu) hoặc bản in A4 ----- +// "sach-dethi" : A4, ví dụ hiện lời giải, 4 loại câu ẩn đáp án. +// "sach-loigiai": A4, mọi thứ hiện lời giải + đánh dấu đáp án. +#let _ho-so = state("bg-ho-so", "beamer") +#let _chuan-hs(hs) = lower(hs).replace("-", "").replace("_", "") +#let _la-sach(hs) = _chuan-hs(hs) != "beamer" // khác beamer = bản in A4 + +// ----- KÍCH THƯỚC THÂN SLIDE 16:9 ----- +// Rút thành hằng số để `slide()` và bộ TỰ NGẮT MÀN (che-do.typ) dùng CHUNG +// một bộ số — đổi lề ở đây là chỗ đo cũng đổi theo, không lệch nhau. +// `presentation-16-9` của Typst = 297mm × 167,06mm. +#let _kho-slide = (rong: 841.89pt, cao: 473.55pt) +#let _le-slide = ( + tren: 73pt, // chừa thanh tiêu đề slide + tren-tron: 26pt, // slide không có tiêu đề + duoi: 34pt, // chừa thanh điều hướng + ngang: 28pt, + du: 8pt, // hụt an toàn khi đo (đo bao giờ cũng có sai số) +) +// Vùng chữ thật sự của một slide — dùng để biết lời giải có tràn trang không. +#let _vung-than(co-tieu-de: true) = ( + rong: _kho-slide.rong - 2 * _le-slide.ngang, + cao: _kho-slide.cao - _le-slide.duoi - _le-slide.du + - (if co-tieu-de { _le-slide.tren } else { _le-slide.tren-tron }), +) + +// ----- GIÃN DÒNG (khoảng cách giữa các dòng) ----- +// `nen` : leading NỀN của hồ sơ đang dùng (bai-giang/de-toan ghi vào); +// `he-so` : HỆ SỐ NHÂN do người dùng đặt — 1.0 = mốc mặc định, +// 1.3 = giãn thêm 30%, 0.9 = thu lại 10%. +// Cách đặt: +// #bai-giang(gian-dong: 1.25) / #de-toan(gian-dong: 1.25) — toàn tài liệu +// #gian-dong(1.4) — đổi giữa bài (áp cho các slide/khung sau đó) +// #tn(..., gian-dong: 1.4) — ghi đè RIÊNG một câu +// Dùng khi phân số / căn thức / chỉ số chồng tầng làm hai dòng dính nhau. +// `doan` = khoảng cách GIỮA HAI ĐOẠN (khi nội dung có DÒNG TRỐNG). Phải giãn +// theo cùng hệ số với `leading`, kẻo dòng nối bằng `\` (cùng một đoạn) thì +// giãn ra mà chỗ cách bằng dòng trống (sang đoạn mới) lại y nguyên — đó là lý +// do trước đây #voi-gian-dong "chỉ ăn đoạn đầu". +// 1.2em là mốc mặc định của Typst ⇒ he-so = 1.0 giữ nguyên bố cục mọi bài cũ. +#let _doan-nen = 1.2em +#let _gd = state("bg-gian-dong", (nen: 0.62em, doan: _doan-nen, he-so: 1.0)) +#let _dat-gian(nen, k, doan: _doan-nen) = _gd.update((nen: nen, doan: doan, he-so: k)) +#let gian-dong(k) = _gd.update(g => ( + nen: g.nen, doan: g.at("doan", default: _doan-nen), he-so: k)) +// Ba helper dưới đây CHỈ gọi được trong `context`. +#let _he-so-gian() = _gd.get().he-so +#let _gian-ht() = { + let g = _gd.get() + g.nen * g.he-so +} +#let _gian-doan() = { + let g = _gd.get() + g.at("doan", default: _doan-nen) * g.he-so +} +// Áp hệ số giãn dòng cho MỘT KHỐI nội dung bất kì (phần còn lại không đổi): +// #voi-gian-dong(1.4)[ ... đoạn nhiều phân số ... ] +// Giãn CẢ khoảng cách dòng lẫn khoảng cách đoạn. +#let voi-gian-dong(k, body) = context { + let g = _gd.get() + set par(leading: g.nen * k, spacing: g.at("doan", default: _doan-nen) * k) + body +} + +// ----- CHIỀU CAO THẬT CỦA CÔNG THỨC TRONG DÒNG (chống dính chữ) ----- +// VÌ SAO CẦN: Typst đóng khung công thức TRONG DÒNG theo SỐ ĐO PHÔNG CHỮ +// (cap-height → đường chân chữ, ≈6.83pt ở cỡ 10pt) chứ KHÔNG theo nét vẽ thật. +// `measure($1/2$)` và `measure($0,5$)` vì thế ra CÙNG chiều cao, trong khi phân +// số vẽ ra cao ≈12pt và TRÀN cả trên lẫn dưới khung. Hệ quả: +// • trong một đoạn: dòng không cao lên ⇒ tử số dòng dưới chạm mẫu dòng trên; +// • trong ô của `grid` (phương án #tn, ý #ds, `cot-item`): ô cũng lấy chiều +// cao HỤT đó ⇒ phân số đè sang hàng trên, mà `gian-dong` (chỉ chạm +// par.leading/par.spacing) KHÔNG với tới được. +// Trước đây phải bù bằng `gian-dong: 3` — giãn ĐỀU cả bài, chỗ cần thì vừa, +// chỗ chữ thường thì trống hoác. +// +// CÁCH CHỮA: đo nét vẽ thật (top-edge/bottom-edge: "bounds" — xem `co-net` của +// ve.typ) rồi chèn một CỘT CHỐNG vô hình RỘNG 0 ngay trước công thức, cao đúng +// phần nét nhô lên/thò xuống. Cột chống là hộp trong dòng nên Typst tính nó vào +// chiều cao dòng VÀ chiều cao ô ⇒ dòng/ô tự nới ĐÚNG chỗ cần, chữ thường không +// đổi một pt nào. Vì rộng 0 nên `measure(...).width` (chỗ `cot: auto` chọn số +// cột) cũng không đổi. +// CỐ Ý dùng cột chống chứ KHÔNG bọc công thức trong `box`: bọc box sẽ chặn +// Typst ngắt dòng giữa công thức dài. +// bat : bật/tắt toàn bộ cơ chế. +// nguong : nét tràn dưới mức này thì bỏ qua (khỏi chèn cột chống vô ích). +// them : nới thêm bấy nhiêu ở MỖI phía (chỉ dùng khi muốn thoáng hơn nữa). +#let _cao-that = state("bg-cao-that", (bat: true, nguong: 1pt, them: 0pt, chia: 0.5, hien: false)) + +// #cao-that(false) — tắt từ đây trở đi; #cao-that() — bật lại; +// #cao-that(them: 1pt) — nới thêm 1pt mỗi phía cho mọi công thức có tràn nét. +// Dùng SINK để nhận `bat` ở dạng ĐỐI SỐ VỊ TRÍ mà vẫn gọi được #cao-that(). +// hien: true — CHẨN ĐOÁN, in phần `thieu` (pt) màu đỏ ngay sau mỗi công thức. +// Không thấy số đỏ nào hiện ra ⇒ bản lib đang chạy KHÔNG phải bản này. +#let cao-that(..a, nguong: auto, them: auto, chia: auto, hien: auto) = { + let bat = if a.pos().len() > 0 { a.pos().first() } else { true } + _cao-that.update(c => ( + bat: bat, + nguong: if nguong == auto { c.at("nguong", default: 1pt) } else { nguong }, + them: if them == auto { c.at("them", default: 0pt) } else { them }, + chia: if chia == auto { c.at("chia", default: 0.5) } else { chia }, + hien: if hien == auto { c.at("hien", default: false) } else { hien }, + )) +} + +// (tren, duoi) = phần NÉT VẼ nhô lên trên / thò xuống dưới đường chân chữ. +// CHỈ gọi trong `context`. +#let _do-net(nd) = ( + measure({ set text(top-edge: "bounds", bottom-edge: "baseline"); nd }).height, + measure({ set text(top-edge: "baseline", bottom-edge: "bounds"); nd }).height, +) + +// ⚠️ KHÔNG tách được nét vẽ thành (trên, dưới) một cách đáng tin. Đã thử và +// SAI: với công thức CAO (nhất là khi tài liệu bật `math.display` cho công +// thức trong dòng — kiểu \displaystyle của LaTeX, người dùng rất hay dùng để +// phân số to đẹp), hai phép đo một phía trả về CÙNG một số (đo được 17.31 / +// 17.31 cho `$1/2$`), tức mỗi phép đo đã gồm cả hai phía ⇒ cộng lại là nới +// GẤP ĐÔI. Đừng quay lại lối đó. +// +// LỐI ĐÚNG — TỰ HIỆU CHỈNH, chỉ dùng phép đo CẢ DÒNG (đáng tin): +// du = nét vẽ dòng "mẫu + công thức" trừ nét vẽ dòng "mẫu" → chỗ ink CẦN +// hop = khung dòng "mẫu + công thức" trừ khung dòng "mẫu" → chỗ Typst CHO +// thieu = du − hop → phần TRÀN +// `thieu` chính là phần đè sang hàng trên/dòng trên. Cách này không phụ thuộc +// vào việc Typst tính khung công thức thế nào, nên đúng với cả math.display, +// cả phông chữ khác, cả cỡ chữ khác. +#let _mau-chu = [Ág] + +#let _cao-ink(nd) = measure({ + set text(top-edge: "bounds", bottom-edge: "bounds") + nd +}).height + +// Cột chống vô hình + chính công thức. Thiếu ít hơn `nguong` ⇒ TRẢ NGUYÊN. +// `chia` = phần `thieu` dồn xuống DƯỚI đường chân chữ (0.5 = chia đôi, hợp với +// phân số vì tử tràn lên còn mẫu tràn xuống gần bằng nhau). +#let _chong-net(nd) = context { + let c = _cao-that.get() + if not c.at("bat", default: true) { nd } else { + let mau = _mau-chu + let ca = [#mau#nd] + let du = _cao-ink(ca) - _cao-ink(mau) + let hop = measure(ca).height - measure(mau).height + let thieu = calc.max(0pt, du - hop) + let ng = c.at("nguong", default: 1pt) + let them = c.at("them", default: 0pt) + let chia = c.at("chia", default: 0.5) + // Chỉ chèn cột chống khi thật sự tràn; không tràn thì KHÔNG thêm gì cả. + if thieu > ng { + box(width: 0pt, height: measure(nd).height + thieu + 2 * them, + baseline: thieu * chia + them) + } + nd + // Chẩn đoán: #cao-that(hien: true) -> in số `thieu` màu đỏ sau công thức. + if c.at("hien", default: false) { + text(size: 6pt, fill: red, weight: "bold")[ (#calc.round(thieu.pt(), digits: 2))] + } + } +} + +// Áp cơ chế cho MỘT khối bất kì (bai-giang/de-toan đã tự áp cho cả tài liệu): +// #voi-cao-that[ ... ] +#let voi-cao-that(body) = { + show math.equation.where(block: false): _chong-net + body +} + +// ----- Bộ đếm "đóng băng" theo slide ----- +// Slide hoạt hình được in thành nhiều trang lặp; nếu đếm kiểu thường thì +// "Ví dụ 1" sẽ thành "Ví dụ 2, 3..." qua từng bước. Cách giải quyết: +// goc = tổng tích luỹ đến HẾT slide trước (state), +// cnt = đếm trong slide, được ĐẶT LẠI ở mỗi bước lặp, +// số hiển thị = goc + cnt => không đổi giữa các bước. +#let _bd-cau = (goc: state("bg-goc-cau", 0), cnt: counter("bg-cnt-cau")) +#let _bd-vd = (goc: state("bg-goc-vd", 0), cnt: counter("bg-cnt-vd")) +#let _bd-lt = (goc: state("bg-goc-lt", 0), cnt: counter("bg-cnt-lt")) +#let _bd-hd = (goc: state("bg-goc-hd", 0), cnt: counter("bg-cnt-hd")) +#let _bd-vdtt = (goc: state("bg-goc-vdtt", 0), cnt: counter("bg-cnt-vdtt")) +#let _bd-tat-ca = (_bd-cau, _bd-vd, _bd-lt, _bd-hd, _bd-vdtt) + +// Tăng bộ đếm và hiển thị số hiện tại. +#let _so-moi(bd) = { + bd.cnt.step() + context [#(bd.goc.get() + bd.cnt.get().first())] +} + +// ===================================================================== +// TRANG BÌA — 5 kiểu (kieu-bia: 1..5 hoặc tên) +// 1 "toi-gian" Tối giản & Thanh lịch +// 2 "tre-trung" Trẻ trung & Sáng tạo +// 3 "co-dien" Chuẩn mực Học thuật Cổ điển +// 4 "chuyen-nghiep" Chuyên nghiệp & Khoa học +// 5 "ky-thuat" Tiêu chuẩn cho Tài liệu Kỹ thuật +// Mỗi kiểu có tông màu MẶC ĐỊNH riêng; nếu người dùng truyền mau-chinh / +// mau-nhan (khác auto) thì màu đó GHI ĐÈ tông mặc định. +// ===================================================================== + +// Tông màu mặc định của từng kiểu bìa: (chính, nhấn). +#let _bia-mau(kieu) = ( + (chinh: rgb("#0f4c81"), nhan: rgb("#e67e22")), // 1 tối giản — navy + cam (mặc định, giữ tương thích) + (chinh: rgb("#5b2a86"), nhan: rgb("#ff5d73")), // 2 trẻ trung — tím + hồng san hô + (chinh: rgb("#14213d"), nhan: rgb("#c8a24b")), // 3 cổ điển — navy + vàng đồng + (chinh: rgb("#0f4c81"), nhan: rgb("#17a2b8")), // 4 chuyên nghiệp — xanh + lam ngọc + (chinh: rgb("#263238"), nhan: rgb("#ef6c00")), // 5 kỹ thuật — xám lam + cam +).at(kieu - 1) + +// Chuẩn hoá kieu-bia: nhận số 1..5 hoặc chuỗi tên. +#let _bia-so(k) = { + if type(k) == int { return calc.max(1, calc.min(5, k)) } + let s = lower(str(k)).replace("-", "").replace("_", "").replace(" ", "") + ( + toigian: 1, thanhlich: 1, minimal: 1, + tretrung: 2, sangtao: 2, creative: 2, + codien: 3, hocthuat: 3, classic: 3, + chuyennghiep: 4, khoahoc: 4, pro: 4, + kythuat: 5, tailieu: 5, tech: 5, + ).at(s, default: 1) +} + +// Logo: PHẢI truyền image(...) — không nhận chuỗi đường dẫn. Vì image() gọi +// trong file này (bên trong package) sẽ tìm ảnh CẠNH package, không phải cạnh +// file bài giảng của người dùng. Bọc image(...) trong file người dùng thì Typst +// mới tìm đúng thư mục. Truyền chuỗi => panic có hướng dẫn. +#let _bia-logo(logo, cao) = if logo != none { + if type(logo) == str { + panic("logo phải bọc image(...): dùng logo: image(\"" + logo + "\") thay cho chuỗi \"" + logo + "\" (ảnh đặt cùng thư mục file .typ của bạn).") + } + box(height: cao, logo) +} + +// Gom các trường phụ có giá trị thành mảng (nhan, gia-tri). +#let _bia-truong(mon, lop, gv, ngay) = { + let ds = () + if mon != none { ds.push(("Môn", mon)) } + if lop != none { ds.push(("Lớp", lop)) } + if gv != none { ds.push(("Giáo viên", gv)) } + if ngay != none { ds.push(("Ngày", ngay)) } + ds +} + +#let _co(x) = x != none + +// ---------- Kiểu 1: Tối giản & Thanh lịch ---------- +#let _bia-1(c, n, don-vi, logo, tieu-de, phu-de, mon, lop, gv, ngay) = { + place(rect(width: 100%, height: 100%, fill: white)) + place(bottom, rect(width: 100%, height: 5pt, fill: n)) + place(top + right, dx: -2.4cm, dy: 2.2cm, _bia-logo(logo, 1.9cm)) + place(left + horizon, dx: 2.6cm, block(width: 19.5cm, { + rect(width: 42pt, height: 4pt, fill: n) + v(18pt) + if _co(don-vi) { + text(size: 12.5pt, tracking: 2.5pt, fill: luma(45%), upper(don-vi)) + v(14pt) + } + text(fill: c, weight: "bold", size: 40pt, tieu-de) + if _co(phu-de) { v(10pt); text(size: 19pt, fill: luma(38%), phu-de) } + v(22pt) + line(length: 6.5cm, stroke: 0.6pt + luma(55%)) + v(16pt) + let ds = _bia-truong(mon, lop, gv, ngay) + if ds.len() > 0 { + text(size: 14pt, fill: c.darken(5%), ds.map(p => + [#text(fill: luma(50%))[#p.at(0): ]#p.at(1)]).join(text(fill: n)[#h(10pt)•#h(10pt)])) + } + })) +} + +// ---------- Kiểu 2: Trẻ trung & Sáng tạo ---------- +#let _bia-2(c, n, don-vi, logo, tieu-de, phu-de, mon, lop, gv, ngay) = { + place(rect(width: 100%, height: 100%, + fill: gradient.linear(c, n, angle: 40deg))) + // Khối hình học trang trí (bán trong suốt). + place(top + right, dx: 2.5cm, dy: -3cm, + circle(radius: 4.4cm, fill: white.transparentize(80%))) + place(bottom + left, dx: -2.6cm, dy: 2.6cm, + circle(radius: 3.4cm, fill: white.transparentize(85%))) + place(top + left, dx: 3.2cm, dy: 2.4cm, + box(rotate(18deg, rect(width: 26pt, height: 26pt, radius: 6pt, + fill: white.transparentize(70%))))) + place(bottom + right, dx: -3.6cm, dy: -2.4cm, + box(rotate(12deg, rect(width: 34pt, height: 34pt, radius: 8pt, + fill: white.transparentize(78%))))) + place(top + right, dx: -2.2cm, dy: 2cm, _bia-logo(logo, 1.7cm)) + place(center + horizon, align(center, block(width: 21cm, { + if _co(don-vi) { + box(fill: white.transparentize(78%), radius: 20pt, inset: (x: 16pt, y: 7pt), + text(fill: white, size: 13pt, weight: "medium", tracking: 1pt, upper(don-vi))) + v(20pt) + } + text(fill: white, weight: "bold", size: 42pt, tieu-de) + if _co(phu-de) { + v(12pt) + text(fill: white.transparentize(12%), size: 21pt, phu-de) + } + v(26pt) + let ds = _bia-truong(mon, lop, gv, ngay) + if ds.len() > 0 { + grid(columns: ds.len(), column-gutter: 12pt, + ..ds.map(p => box(fill: white.transparentize(82%), radius: 10pt, + inset: (x: 14pt, y: 9pt), align(center, { + text(fill: white.transparentize(20%), size: 10pt, upper(p.at(0))) + linebreak() + text(fill: white, size: 14pt, weight: "bold", p.at(1)) + })))) + } + }))) +} + +// ---------- Kiểu 3: Chuẩn mực Học thuật Cổ điển ---------- +#let _bia-3(c, n, don-vi, logo, tieu-de, phu-de, mon, lop, gv, ngay) = { + place(rect(width: 100%, height: 100%, fill: rgb("#f6f1e6"))) + // Khung viền kép cổ điển. + place(center + horizon, rect(width: 100% - 1.6cm, height: 100% - 1.6cm, + stroke: 1.4pt + c)) + place(center + horizon, rect(width: 100% - 2.0cm, height: 100% - 2.0cm, + stroke: 0.6pt + n)) + place(center + horizon, align(center, block(width: 20cm, { + _bia-logo(logo, 2.1cm) + if _co(logo) { v(14pt) } + if _co(don-vi) { + text(size: 14pt, tracking: 3pt, fill: c, upper(don-vi)) + v(6pt) + } + // Đường phân cách trang trí ✦ + grid(columns: (1fr, auto, 1fr), column-gutter: 12pt, align: horizon, + line(length: 100%, stroke: 0.6pt + n), + text(fill: n, size: 13pt, "✦"), + line(length: 100%, stroke: 0.6pt + n)) + v(18pt) + text(fill: c, weight: "bold", size: 38pt, + font: ("Charis SIL", "Noto Serif", "Libertinus Serif"), tieu-de) + if _co(phu-de) { + v(12pt) + text(size: 19pt, style: "italic", fill: c.lighten(15%), phu-de) + } + v(18pt) + grid(columns: (1fr, auto, 1fr), column-gutter: 12pt, align: horizon, + line(length: 100%, stroke: 0.6pt + n), + text(fill: n, size: 13pt, "✦"), + line(length: 100%, stroke: 0.6pt + n)) + v(20pt) + let ds = _bia-truong(mon, lop, gv, ngay) + if ds.len() > 0 { + text(size: 14pt, fill: c, ds.map(p => + [#emph[#p.at(0):] #p.at(1)]).join(h(18pt))) + } + }))) +} + +// ---------- Kiểu 4: Chuyên nghiệp & Khoa học ---------- +#let _bia-4(c, n, don-vi, logo, tieu-de, phu-de, mon, lop, gv, ngay) = { + place(rect(width: 100%, height: 100%, fill: white)) + // Dải màu dọc bên trái (~38%). + place(left + top, rect(width: 38%, height: 100%, + fill: gradient.linear(c, c.darken(30%), angle: 90deg))) + place(left + top, dx: 38%, rect(width: 6pt, height: 100%, fill: n)) + // Nội dung panel trái. + place(left + horizon, dx: 1.6cm, block(width: 8cm, { + _bia-logo(logo, 2cm) + if _co(logo) { v(20pt) } + if _co(don-vi) { + text(fill: white, size: 15pt, weight: "bold", don-vi) + v(16pt) + } + let ds = _bia-truong(mon, lop, gv, ngay) + for p in ds { + text(fill: white.transparentize(45%), size: 10.5pt, upper(p.at(0))) + linebreak() + text(fill: white, size: 14pt, weight: "medium", p.at(1)) + v(9pt) + } + })) + // Nội dung bên phải. + place(left + horizon, dx: 38% + 1.4cm, block(width: 13cm, { + rect(width: 46pt, height: 5pt, fill: n) + v(16pt) + text(fill: c, weight: "bold", size: 34pt, tieu-de) + if _co(phu-de) { + v(12pt) + text(fill: luma(35%), size: 19pt, phu-de) + } + })) +} + +// ---------- Kiểu 5: Tiêu chuẩn cho Tài liệu Kỹ thuật ---------- +#let _bia-5(c, n, don-vi, logo, tieu-de, phu-de, mon, lop, gv, ngay) = { + place(rect(width: 100%, height: 100%, fill: white)) + // Thanh đầu trang. + place(top, block(width: 100%, height: 1.7cm, fill: c, + align(horizon, pad(x: 1.6cm, grid(columns: (1fr, auto), align: horizon + left, + text(fill: white, size: 14pt, weight: "bold", tracking: 1pt, + if _co(don-vi) { upper(don-vi) } else { [TÀI LIỆU] }), + _bia-logo(logo, 1.1cm)))))) + place(top, dy: 1.7cm, rect(width: 100%, height: 4pt, fill: n)) + // Nhãn loại tài liệu + tiêu đề. + place(left + horizon, dx: 1.7cm, dy: -1.2cm, block(width: 21cm, { + box(fill: n, inset: (x: 10pt, y: 4pt), radius: 3pt, + text(fill: white, size: 11pt, weight: "bold", tracking: 2pt, + font: ("DejaVu Sans Mono", "Consolas"), "TAI-LIEU / DOC")) + v(16pt) + text(fill: c, weight: "bold", size: 36pt, tieu-de) + if _co(phu-de) { + v(10pt) + text(fill: luma(35%), size: 19pt, phu-de) + } + v(22pt) + // Bảng siêu dữ liệu. + let ds = _bia-truong(mon, lop, gv, ngay) + if ds.len() > 0 { + block(stroke: (left: 3pt + n), inset: (left: 14pt, y: 2pt), + grid(columns: (auto, auto), column-gutter: 16pt, row-gutter: 7pt, + ..ds.map(p => ( + text(fill: luma(45%), size: 12.5pt, + font: ("DejaVu Sans Mono", "Consolas"), upper(p.at(0)) + " :"), + text(fill: c, size: 13.5pt, weight: "medium", p.at(1)), + )).flatten())) + } + })) + // Thanh chân trang. + place(bottom, block(width: 100%, height: 0.8cm, fill: c.lighten(8%), + align(horizon, pad(x: 1.6cm, text(fill: white.transparentize(25%), size: 9.5pt, + font: ("DejaVu Sans Mono", "Consolas"), + if _co(ngay) { [\/\/ #ngay] } else { [\/\/ bài giảng · typst] }))))) +} + +// Dispatch trang bìa theo kiểu. +#let _ve-bia(kieu, c, n, don-vi, logo, tieu-de, phu-de, mon, lop, gv, ngay) = { + let f = (_bia-1, _bia-2, _bia-3, _bia-4, _bia-5).at(kieu - 1) + block(width: 100%, height: 100%, + f(c, n, don-vi, logo, tieu-de, phu-de, mon, lop, gv, ngay)) +} + +// ---------- Thiết lập tổng ---------- +// (bai-giang: điểm vào chính của hệ thống trình chiếu) +#let bai-giang( + tieu-de: [BÀI GIẢNG TOÁN], + tieu-de-ngan: none, // tên bài rút gọn hiện trên DẢI ĐẦU TRANG của mọi + // slide (beamer); none => dùng nguyên tieu-de + phu-de: none, + gv: none, + don-vi: none, + ngay: none, + lop: none, // Lớp/khối hiện trên BÌA (vd "Lớp 12A1") + mon: none, // Môn/chương hiện trên BÌA (vd "Đại số & Giải tích") + logo: none, // Logo trường trên BÌA: đường dẫn ảnh HOẶC nội dung ảnh + kieu-bia: 1, // KIỂU TRANG BÌA: 1..5 hoặc tên + // 1 "toi-gian" 2 "tre-trung" 3 "co-dien" + // 4 "chuyen-nghiep" 5 "ky-thuat" + mau-chinh: auto, // auto = dùng TÔNG MẶC ĐỊNH của kieu-bia; đặt màu để GHI ĐÈ + mau-nhan: auto, // auto = dùng tông nhấn mặc định của kieu-bia + nen: "trang", // NỀN SLIDE (beamer): "trang" | "kem" | "xanh-nhat" + // | "luc-nhat" | "xam" — hoặc màu tuỳ ý: rgb("#fdf8ee") + // (chọn tông SÁNG để hình vẽ/bảng nét sẫm còn rõ) + // Charis SIL đứng ĐẦU: font của SIL, thiết kế riêng cho dấu CHỒNG tiếng Việt + // (Ể Ổ Ữ Ẩ...). Với chữ HOA (tiêu đề đề thi) nó vẫn giữ dấu hỏi/ngã NGUYÊN CỠ + // và chồng THẲNG trên dấu mũ; Noto Serif thu nhỏ + đẩy lệch phải, còn + // Libertinus/Times vẽ hook nhỏ nằm ngang -> nhìn như mất dấu. + phong: ("Charis SIL", "Noto Serif", "Libertinus Serif", "Times New Roman"), + co-chu: 19pt, + ti-le-chu: 1.0, // HỆ SỐ PHÓNG CỠ CHỮ THÂN NỘI DUNG (toàn file): + // 1.0 = giữ nguyên; 1.1 = to hơn 10%; 0.9 = nhỏ đi 10%. + // Nhân vào cỡ chữ nền -> mọi phần thân + khung dùng em + // (định nghĩa/ví dụ/lời giải...) co giãn theo; các thanh + // tiêu đề/header/footer (pt tuyệt đối) giữ nguyên. + gian-dong: 1.0, // HỆ SỐ GIÃN DÒNG thân nội dung (toàn file): + // 1.0 = mốc mặc định (A4 0.6em, trình chiếu 0.62em); + // 1.25 = giãn thêm 25%; 0.9 = thu lại 10%. Tăng khi + // phân số/căn thức nhiều tầng làm hai dòng dính nhau. + // Đổi giữa bài: #gian-dong(1.4); riêng một câu: + // #vd/#tn/#ds/... (gian-dong: 1.4). + mau-cong-thuc: auto, // MÀU MỌI CÔNG THỨC/KÍ HIỆU trong $...$ (toàn file): + // auto (mặc định) = thừa kế màu chữ xung quanh — thân + // bài màu đen nên công thức hiển thị ĐEN, còn công thức + // nằm trong tiêu đề/bìa (chữ trắng) vẫn TRẮNG. Đặt một + // màu cụ thể, vd rgb("#0f4c81"), để nhuộm TẤT CẢ công + // thức theo màu đó (kể cả trong thanh tiêu đề). + ho-so: "beamer", // "beamer" | "sach-dethi" | "sach-loigiai" + body, +) = { + _ho-so.update(_chuan-hs(ho-so)) + // Giải màu theo kiểu bìa: auto -> tông mặc định của kieu-bia; nếu người + // dùng truyền màu cụ thể thì GHI ĐÈ. mau-chinh/mau-nhan cũng là màu chủ đạo + // của toàn slide (header/footer/khung) nên bìa và bài luôn đồng bộ. + let _kb = _bia-so(kieu-bia) + let _tm = _bia-mau(_kb) + let mau-chinh = if mau-chinh == auto { _tm.chinh } else { mau-chinh } + let mau-nhan = if mau-nhan == auto { _tm.nhan } else { mau-nhan } + // Nền slide: tên preset hoặc màu tuỳ ý; chân trang tự pha đậm hơn một chút. + let _giay = if type(nen) == str { + ( + trang: white, + kem: rgb("#fdf8ee"), + xanhnhat: rgb("#eef4fb"), + lucnhat: rgb("#f1f8f2"), + xam: rgb("#f4f5f7"), + ).at(lower(nen).replace("-", "").replace("_", ""), default: white) + } else { nen } + _mau.update(( + chinh: mau-chinh, nhan: mau-nhan, + nen: if type(_giay) == color { _giay.darken(3%) } else { rgb("#f7f9fc") }, + )) + _thong-tin.update(( + tieu-de: tieu-de, gv: gv, + ngan: if tieu-de-ngan == none { tieu-de } else { tieu-de-ngan }, + )) + // Cỡ công thức bằng thân; màu: auto = thừa kế (đen ở thân, trắng ở tiêu đề), + // hoặc nhuộm theo mau-cong-thuc nếu chỉ định. + show math.equation: it => { + set text(size: 1em) + // Lưu ý: set text(fill) phải ĐI TRƯỚC `it` trong CÙNG nhánh mới áp được; + // đặt trong `if {...}` rồi để `it` ngoài thì fill KHÔNG tác dụng. + if mau-cong-thuc == auto { it } else { + set text(fill: mau-cong-thuc) + it + } + } + + if _la-sach(ho-so) { + // ================= BẢN IN A4 (sách / đề) ================= + // Công tắc đáp án: "loigiai" -> hiện tất cả; ngược lại (dethi) -> ẩn. + state("ch-hien-da", false).update(_chuan-hs(ho-so).contains("loigiai")) + _buoc-ht.update(1000000) // mọi #lo(n) hiện hết trên bản in + set page( + paper: "a4", + margin: (x: 2cm, top: 2cm, bottom: 2cm), + fill: white, + footer: context align(center, text(size: 9pt, fill: luma(40%), + [Trang #counter(page).display() / #counter(page).final().first()])), + ) + set text(font: phong, size: 11.5pt * ti-le-chu, lang: "vi", fill: rgb("#1c2833")) + _dat-gian(0.6em, gian-dong) + set par(justify: true, leading: 0.6em * gian-dong, spacing: _doan-nen * gian-dong) + // Công thức trong dòng khai ĐÚNG chiều cao nét vẽ ⇒ dòng/ô tự nới đúng chỗ. + show math.equation.where(block: false): _chong-net + set heading(numbering: none) // không đánh số — chỉ dùng thanh màu + // Thanh tiêu đề màu thay cho heading (giữ heading để có bookmark + mục lục). + show heading.where(level: 1): it => block(width: 100%, above: 16pt, below: 10pt, + block(width: 100%, fill: mau-chinh, inset: (x: 12pt, y: 7pt), radius: 4pt, + text(fill: white, weight: "bold", size: 1.15em, it.body))) + show heading.where(level: 2): it => block(width: 100%, above: 13pt, below: 7pt, { + block(width: 100%, fill: mau-chinh, inset: (x: 10pt, y: 5pt), + radius: (top: 4pt, rest: 0pt), + text(fill: white, weight: "bold", size: 1.05em, it.body)) + block(width: 100%, height: 2.5pt, fill: mau-nhan) + }) + // ----- Tiêu đề tài liệu ----- + align(center, { + if don-vi != none { text(size: 12pt, fill: luma(30%), don-vi); linebreak() } + text(weight: "bold", size: 17pt, fill: mau-chinh, tieu-de) + if phu-de != none { linebreak(); v(2pt); text(size: 12pt, fill: luma(25%), phu-de) } + if gv != none { linebreak(); v(3pt); text(size: 11pt, style: "italic", [Giáo viên: #gv]) } + if ngay != none { linebreak(); text(size: 10pt, fill: luma(40%), ngay) } + }) + v(4pt) + line(length: 100%, stroke: 0.8pt + mau-chinh) + v(2pt) + body + } else { + // ================= BẢN TRÌNH CHIẾU 16:9 ================= + // Bật công tắc đáp án: khi trình chiếu, đáp án luôn được đánh dấu + // ở bước lo-da (bước cuối) của mỗi câu. Muốn ẩn hẳn: #tat-dap-an(). + state("ch-hien-da", false).update(true) + set page(paper: "presentation-16-9", margin: 0pt, fill: _giay) + set text(font: phong, size: co-chu * ti-le-chu, lang: "vi", fill: rgb("#1c2833")) + _dat-gian(0.62em, gian-dong) + set par(justify: false, leading: 0.62em * gian-dong, spacing: _doan-nen * gian-dong) + show math.equation.where(block: false): _chong-net + + // ----- Trang bìa (1 trong 5 kiểu) ----- + _ve-bia(_kb, mau-chinh, mau-nhan, don-vi, logo, tieu-de, phu-de, + mon, lop, gv, ngay) + + body + } +} + +// ---------- Gỡ enum trong tiêu đề ---------- +// Nội dung "[1. Tên mục]" bị Typst parse thành MỤC DANH SÁCH đánh số (enum); +// khi căn phải/giữa, số "1." bị tách xa phần chữ. Hàm này gỡ về chữ thường. +#let _go-enum(nd) = { + if type(nd) != content { return nd } + if nd.func() == enum.item { + let so = nd.at("number", default: none) + if so == none { nd.body } else { [#so. #nd.body] } + } else if nd.func() == list.item { + nd.body + } else if nd.has("children") { + nd.children.map(c => _go-enum(c)).join() + } else { nd } +} + +// ---------- Slide mục (chuyển phần) ---------- +// ngan: tên rút gọn hiển thị trên thanh điều hướng (mặc định = ten). +#let muc(ten, ngan: none) = { + let ten = _go-enum(ten) + let ngan = _go-enum(ngan) + _muc-ht.update(ten) + context { + if _la-sach(_ho-so.get()) { + // Bản in: mở phần mới ở đầu trang, tiêu đề = heading cấp 1 (thanh màu). + pagebreak(weak: true) + heading(level: 1, ten) + } else { + pagebreak(weak: true) + // mốc điều hướng: tên mục + vị trí (để tạo liên kết) + place([#metadata(( + loai: "muc", ten: ten, + ngan: if ngan == none { ten } else { ngan }, + ))]) + let m = _mau.get() + block(width: 100%, height: 100%, { + place(rect(width: 100%, height: 100%, fill: m.chinh)) + place(left + horizon, rect(width: 10pt, height: 42%, fill: m.nhan)) + place(center + horizon, align(center, + text(fill: white, weight: "bold", size: 30pt, ten) + )) + }) + } + } +} + +// ---------- Slide thường ---------- +// so-buoc: số bước hoạt hình — slide được in thành so-buoc trang, +// kết hợp với #lo(n)[...] / #chi(n)[...] / lo-da của câu hỏi. +// Nội dung dài quá một trang sẽ TỰ NGẮT sang trang kế tiếp: +// đầu/chân trang lặp lại, số slide và chấm điều hướng giữ nguyên. +#let slide(tieu-de: none, so-buoc: 1, body) = context { + let tieu-de = _go-enum(tieu-de) + // Giãn dòng hiện hành (nen × he-so) — đặt lại ở mỗi slide để #gian-dong(k) + // gọi giữa bài có tác dụng cho các slide phía sau. Với he-so = 1.0 giá trị + // này TRÙNG mốc mặc định nên bố cục không đổi. + let _gl = _gian-ht() + let _gs = _gian-doan() + if _la-sach(_ho-so.get()) { + // Bản in A4: thanh tiêu đề màu (heading cấp 2) + nội dung chảy liên tục. + if tieu-de != none { heading(level: 2, tieu-de) } + block(above: 4pt, below: 12pt, { + set par(leading: _gl, spacing: _gs) + body + }) + } else { + for k in range(1, so-buoc + 1) { + set page( + margin: ( + top: if tieu-de != none { _le-slide.tren } else { _le-slide.tren-tron }, + bottom: _le-slide.duoi, x: 0pt, + ), + header-ascent: 12pt, + footer-descent: 12pt, + // đầu trang: thanh tiêu đề (lặp lại nếu slide tràn trang) + header: if tieu-de != none { + context { + let m = _mau.get() + let tt = _thong-tin.get() + let mh = _muc-ht.get() + // Dải trên (đậm hơn): TÊN BÀI HỌC nổi bật trên MỌI slide, + // kèm tên mục hiện tại ở góc phải. + block(width: 100%, height: 17pt, fill: m.chinh.darken(30%), below: 0pt, + align(horizon, pad(x: 26pt, grid( + columns: (auto, 1fr), column-gutter: 14pt, + align: (left + horizon, right + horizon), + if tt.ngan != none { + text(fill: white, weight: "bold", size: 10.5pt, tt.ngan) + } else { [] }, + if mh != none { + text(fill: white.transparentize(40%), size: 9.5pt, mh) + } else { [] }, + )))) + // Thanh tiêu đề slide (như cũ, đủ chỗ cho tiêu đề dài). + block(width: 100%, height: 44pt, fill: m.chinh, above: 0pt, + align(horizon, pad(x: 26pt, + text(fill: white, weight: "bold", size: 21pt, tieu-de)))) + } + }, + // chân trang: thanh điều hướng + số slide (không tăng theo bước) + footer: context { + let m = _mau.get() + block(width: 100%, height: 22pt, fill: m.nen, { + place(top, line(length: 100%, stroke: 0.5pt + m.chinh.lighten(55%))) + let tat-ca = query() + let mh = _muc-ht.get() + let so-ht = _dem-slide.get().first() + align(horizon, pad(x: 26pt, grid( + columns: (auto, 1fr, auto), + column-gutter: 14pt, + // tên các mục — bấm để nhảy tới đầu mục + { + for (i, e) in tat-ca.filter(e => e.value.loai == "muc").enumerate() { + if i > 0 { h(11pt) } + let ht = e.value.ten == mh + link(e.location(), text(size: 9pt, + weight: if ht { "bold" } else { "regular" }, + fill: if ht { m.chinh } else { m.chinh.lighten(42%) }, + e.value.ngan)) + } + }, + // chấm tròn: các slide trong mục hiện tại — bấm để nhảy tới slide + align(right, { + for e in tat-ca.filter(e => e.value.loai == "slide" and e.value.muc == mh) { + h(4pt) + link(e.location(), box(baseline: 30%, circle(radius: 2.6pt, + fill: if e.value.so == so-ht { m.chinh } else { white }, + stroke: 0.7pt + m.chinh))) + } + }), + text(size: 10pt, fill: m.chinh, + [#so-ht / #_dem-slide.final().first()]), + ))) + }) + }, + ) + pagebreak(weak: true) + _buoc-ht.update(k) + // đặt lại bộ đếm trong-slide ở mỗi bước để số thứ tự không tăng + for bd in _bd-tat-ca { bd.cnt.update(0) } + // mốc điều hướng (chỉ đặt ở bước 1 — đích của liên kết) + if k == 1 { + _dem-slide.step() + place(context [#metadata(( + loai: "slide", ten: tieu-de, + muc: _muc-ht.get(), so: _dem-slide.get().first(), + ))]) + } + pad(x: _le-slide.ngang, { + set par(leading: _gl, spacing: _gs) + body + }) + // hết slide: cộng dồn số đã dùng vào tổng tích luỹ (đúng một lần) + if k == so-buoc { + context { + let so = _bd-tat-ca.map(bd => bd.cnt.get().first()) + _bd-cau.goc.update(v => v + so.at(0)) + _bd-vd.goc.update(v => v + so.at(1)) + _bd-lt.goc.update(v => v + so.at(2)) + _bd-hd.goc.update(v => v + so.at(3)) + _bd-vdtt.goc.update(v => v + so.at(4)) + } + } + } + } +} + +// ---------- Hoạt hình xuất hiện từng bước ---------- +// Dùng trong slide có so-buoc > 1: +// #lo(2)[...] — hiện TỪ bước 2. Trước đó KHÔNG chiếm chỗ +// (như \pause của LaTeX beamer) — nhờ vậy +// slide dài không sinh trang trắng đệm; +// nội dung hiện dần từ trên xuống nên bố cục +// phần đã hiện không đổi giữa các bước. +// #lo(2, giu-cho: true)[...] — ẩn nhưng vẫn giữ chỗ (khi cần cố định +// vị trí phần tử phía dưới; chỉ nên dùng +// cho slide chắc chắn gói gọn 1 trang). +// #chi(2)[...] — CHỈ hiện ở bước 2 +// #chi(2, giu-cho: false)[..] — chỉ hiện ở bước 2, không giữ chỗ +#let lo(n, giu-cho: false, body) = context { + if _la-sach(_ho-so.get()) or _buoc-ht.get() >= n { body } + else if giu-cho { hide(body) } +} + +#let chi(n, giu-cho: true, body) = context { + if _la-sach(_ho-so.get()) { body } // bản in: hiện hết + else if _buoc-ht.get() == n { body } else if giu-cho { hide(body) } +} + +// ---------- Hoạt hình BIẾN MẤT từng bước (đối xứng #lo) ---------- +// Như hiệu ứng "thoát" (exit) của PowerPoint: phần tử hiện từ đầu rồi +// BIẾN MẤT khi tới bước n. +// #an(2)[...] — hiện ở bước 1, BIẾN MẤT từ bước 2 trở đi. +// Mặc định KHÔNG giữ chỗ: nội dung phía dưới +// dồn lên lấp chỗ (như #lo). +// #an(2, giu-cho: true)[...] — biến mất nhưng CHỪA khoảng trống, bố cục +// phía dưới đứng yên (dùng cho slide gói gọn +// một trang). +// Kết hợp #lo + #an để thay thế phần tử tại một vị trí, ví dụ hiện đáp án +// sai ở bước 2 rồi xoá đi ở bước 3 và cho đáp án đúng hiện lên: +// #an(3)[đáp án tạm] #lo(3)[đáp án đúng] +// Bản in (dethi/loigiai) HIỆN HẾT như #lo — muốn bản in chỉ giữ trạng thái +// cuối thì đặt phần biến mất trong #chi (chỉ hiện đúng một bước). +#let an(n, giu-cho: false, body) = context { + if _la-sach(_ho-so.get()) or _buoc-ht.get() < n { body } + else if giu-cho { hide(body) } +} + +// Hiện TRONG MỘT KHOẢNG bước: xuất hiện ở bước `tu`, biến mất ở bước `den` +// (khoảng nửa mở [tu, den) — tức hiện ở các bước tu..den−1). Bằng đúng +// #lo(tu) + #an(den) gộp lại. +// #hien-khoang(2, 4)[...] — hiện ở bước 2 và 3, biến mất từ bước 4. +#let hien-khoang(tu, den, giu-cho: false, body) = context { + if _la-sach(_ho-so.get()) { body } // bản in: hiện hết + else { + let k = _buoc-ht.get() + if k >= tu and k < den { body } + else if giu-cho { hide(body) } + } +} + +// Danh sách hiện dần từng ý; ý đầu tiên xuất hiện ở bước `tu`. +// #tung-buoc([ý 1], [ý 2], [ý 3]) // trong slide có so-buoc đủ lớn +#let tung-buoc(tu: 2, ..cac-y) = { + for (i, y) in cac-y.pos().enumerate() { + lo(tu + i, block(above: 6pt, below: 6pt, y)) + } +} + +// ---------- Khung nội dung ---------- +// Hai block rời (đầu khung "dính" với thân) thay vì stack, +// để khung dài có thể tự ngắt sang trang/slide kế tiếp. +#let _khung(nhan, mau, body) = { + block( + width: 100%, fill: mau, inset: (x: 12pt, y: 6.5pt), + radius: (top: 5pt), above: 11pt, below: 0pt, sticky: true, + text(fill: white, weight: "bold", size: 0.78em, nhan), + ) + block( + width: 100%, fill: mau.lighten(94%), inset: (x: 12pt, y: 10pt), + radius: (bottom: 5pt), stroke: 1pt + mau, above: 0pt, below: 11pt, + body, + ) +} + +#let dinh-nghia(body, ten: none) = _khung( + if ten == none { [ĐỊNH NGHĨA] } else { [ĐỊNH NGHĨA — #ten] }, + rgb("#0f4c81"), body, +) + +#let dinh-ly(body, ten: none) = _khung( + if ten == none { [ĐỊNH LÝ] } else { [ĐỊNH LÝ — #ten] }, + rgb("#a93226"), body, +) + +#let tinh-chat(body, ten: none) = _khung( + if ten == none { [TÍNH CHẤT] } else { [TÍNH CHẤT — #ten] }, + rgb("#6c3483"), body, +) + +#let cong-thuc(body, ten: none) = _khung( + if ten == none { [CÔNG THỨC] } else { [CÔNG THỨC — #ten] }, + rgb("#148f77"), body, +) + +#let vi-du(body, ten: none) = _khung( + { + [VÍ DỤ ] + _so-moi(_bd-vd) + if ten != none { [ — #ten] } + }, + rgb("#1e8449"), body, +) + +#let luyen-tap(body, ten: none) = _khung( + { + [LUYỆN TẬP ] + _so-moi(_bd-lt) + if ten != none { [ — #ten] } + }, + rgb("#b9770e"), body, +) + +// Lời giải: khung trắng viền trái. +#let loi-giai(body) = block( + width: 100%, inset: (left: 12pt, y: 6pt, right: 6pt), + stroke: (left: 2.5pt + rgb("#1e8449")), + above: 10pt, below: 10pt, + { + align(center, text(fill: rgb("#1e8449"), weight: "bold", size: 0.82em, [Lời giải])) + body + }, +) + +#let chu-y(body) = block( + width: 100%, radius: 5pt, fill: rgb("#fef9e7"), + stroke: 1pt + rgb("#f1c40f"), inset: (x: 12pt, y: 9pt), + above: 11pt, below: 11pt, + { + text(fill: rgb("#b7950b"), weight: "bold", size: 0.82em, [⚠ Chú ý. ]) + body + }, +) + +#let ghi-nho(body) = block( + width: 100%, radius: 5pt, fill: rgb("#eaf2f8"), + stroke: (left: 3pt + rgb("#0f4c81")), inset: (x: 12pt, y: 9pt), + above: 11pt, below: 11pt, + { + text(fill: rgb("#0f4c81"), weight: "bold", size: 0.82em, [★ Ghi nhớ. ]) + body + }, +) + +// Nhận xét (mục "Nhận xét" trong SGK): khung nhẹ nền lục nhạt, viền trái. +#let nhan-xet(body) = block( + width: 100%, radius: 5pt, fill: rgb("#e8f6f3"), + stroke: (left: 3pt + rgb("#148f77")), inset: (x: 12pt, y: 9pt), + above: 11pt, below: 11pt, + { + text(fill: rgb("#117a65"), weight: "bold", size: 0.82em, [✎ Nhận xét. ]) + body + }, +) + +// ---------- Bố cục ---------- +// Hai (hoặc nhiều) cột: #chia-cot(a, b) hoặc #chia-cot(a, b, ti-le: (3fr, 2fr)) +#let chia-cot(ti-le: auto, khoang: 16pt, ..noi-dung) = { + let cells = noi-dung.pos() + grid( + columns: if ti-le == auto { (1fr,) * cells.len() } else { ti-le }, + column-gutter: khoang, + ..cells, + ) +} + +// Danh sách bước giải đánh số tròn. +// hien-dan: true => trong slide có so-buoc, các bước lần lượt xuất hiện +// từ bước hoạt hình `tu` (mặc định bước 2). +#let buoc(hien-dan: false, tu: 2, ..cac-buoc) = { + for (i, b) in cac-buoc.pos().enumerate() { + let nd = block(above: 7pt, below: 7pt, { + box(baseline: 22%, circle(radius: 9pt, fill: rgb("#0f4c81"), + align(center + horizon, text(fill: white, size: 11pt, weight: "bold", str(i + 1))))) + h(8pt) + b + }) + if hien-dan { lo(tu + i, nd) } else { nd } + } +} + +// ---------- Slide mục lục điều hướng ---------- +// Tự liệt kê mọi mục và tiêu đề slide; bấm vào là nhảy thẳng tới nơi. +// Đặt ngay sau trang bìa: #muc-luc() +#let muc-luc(tieu-de: [Nội dung bài học], cot-so: 2) = context { + if _la-sach(_ho-so.get()) { + // Bản in: mục lục tự sinh từ các thanh tiêu đề (heading cấp 1 & 2). + block(above: 8pt, below: 12pt, { + text(weight: "bold", size: 1.1em, fill: _mau.get().chinh, tieu-de) + v(5pt) + outline(title: none, depth: 2, indent: 1.2em) + }) + } else { + slide(tieu-de: tieu-de)[ + #context { + let m = _mau.get() + let tat-ca = query() + let cac-muc = tat-ca.filter(e => e.value.loai == "muc") + grid( + columns: (1fr,) * cot-so, + column-gutter: 24pt, row-gutter: 18pt, + ..cac-muc.map(mm => block({ + link(mm.location(), text(weight: "bold", size: 0.88em, fill: m.chinh, mm.value.ten)) + for s in tat-ca.filter(e => e.value.loai == "slide" and e.value.muc == mm.value.ten) { + block(above: 5pt, below: 0pt, link(s.location(), text(size: 0.74em, { + text(fill: m.nhan, [▸ ]) + if s.value.ten == none { [Slide #s.value.so] } else { s.value.ten } + }))) + } + })), + ) + } + ] + } +} + +// Trang kết thúc. +#let trang-cam-on(loi: [Cảm ơn các em đã tích cực hợp tác!]) = context { + if _la-sach(_ho-so.get()) { + // Bản in: kết thúc gọn, không tạo trang bìa cuối. + v(8pt) + line(length: 100%, stroke: 0.5pt + luma(60%)) + align(center, text(style: "italic", fill: luma(40%), loi)) + } else { + pagebreak(weak: true) + let m = _mau.get() + block(width: 100%, height: 100%, { + place(rect(width: 100%, height: 100%, + fill: gradient.linear(m.chinh, m.chinh.darken(35%), angle: 55deg))) + place(bottom, rect(width: 100%, height: 8pt, fill: m.nhan)) + place(center + horizon, text(fill: white, weight: "bold", size: 30pt, loi)) + }) + } +} diff --git a/packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ b/packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ new file mode 100644 index 0000000000..b2f7e36f82 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ @@ -0,0 +1,151 @@ +// ===================================================================== +// so-do-cay.typ — SƠ ĐỒ CÂY (bài toán xác suất, đếm...) +// Cây tổng quát, sâu tuỳ ý. Mỗi nút là ô bo tròn, cạnh có nhãn xác suất, +// nút lá có thể kèm ô KẾT QUẢ (cột phải cùng hàng). +// +// #so-do-cay( +// goc: $1$, +// nhanh: ( +// nut($A$, xs: $1/6$, con: ( +// nut($B$, xs: $1/2$, kq: $A B: 1/12$), +// nut($overline(B)$, xs: $1/2$, kq: $A overline(B): 1/12$), +// )), +// nut($overline(A)$, xs: $5/6$, con: ( +// nut($B$, xs: $1/3$, kq: $overline(A) B: 5/18$), +// nut($overline(B)$, xs: $2/3$, kq: $overline(A) overline(B): 5/9$), +// )), +// ), +// ) +// ===================================================================== +#import "ve.typ": * + +// Một nút: ten (nội dung ô), xs (nhãn trên cạnh đi tới nút), +// kq (ô kết quả bên phải — thường dùng cho nút lá), con (mảng nút con), +// mau (màu nền ô; auto = theo cấp). +#let nut(ten, xs: none, kq: none, con: (), mau: auto) = ( + ten: ten, xs: xs, kq: kq, con: con, mau: mau, +) + +// Ô chữ bo tròn đặt tâm tại P (toạ độ toán). +// Kích thước ô lấy theo max(khung, biên nét chữ): công thức trong dòng như +// $1/6$ bị Typst tính hụt chiều cao khung nên nếu chỉ đo khung thì phân số +// sẽ thò ra ngoài ô (xem `co-net` trong ve.typ). +#let o-bo-tron(ctx, P, nd, to: white, vien: auto, dem-x: 8pt, dem-y: 5pt, bk: 5pt, day: 0.8pt) = context { + let s = measure(nd) + let b = co-net(nd) + let sw = calc.max(s.width, b.width) + let sh = calc.max(s.height, b.height) + let p = toa(ctx, P) + let v = if vien == auto { to.darken(35%) } else { vien } + place( + dx: p.at(0) - sw / 2 - dem-x, + dy: p.at(1) - sh / 2 - dem-y, + rect( + width: sw + 2 * dem-x, height: sh + 2 * dem-y, + radius: bk, fill: to, stroke: day + v, + ), + ) + place(dx: p.at(0) - s.width / 2, dy: p.at(1) - s.height / 2, nd) +} + +// ---------- duyệt cây (nội bộ) ---------- +#let _la-so(n) = if n.con.len() == 0 { 1 } else { n.con.map(_la-so).sum() } +#let _sau-max(n) = if n.con.len() == 0 { 0 } else { 1 + calc.max(..n.con.map(_sau-max)) } + +// Trả về (y: tung độ tâm, cuoi: y0 còn lại, nut/canh/kq: dữ liệu vẽ). +#let _duyet(n, sau, y0) = { + if n.con.len() == 0 { + (y: y0 - 0.5, cuoi: y0 - 1, nut: (), canh: (), kq: ()) + } else { + let yc = y0 + let nuts = () + let canhs = () + let kqs = () + let ys = () + for c in n.con { + let r = _duyet(c, sau + 1, yc) + yc = r.cuoi + ys.push(r.y) + nuts += r.nut + canhs += r.canh + kqs += r.kq + nuts.push((P: (sau + 1, r.y), nd: c.ten, sau: sau + 1, mau: c.mau)) + if c.kq != none { kqs.push((y: r.y, nd: c.kq)) } + } + let y = (ys.first() + ys.last()) / 2 + for (i, c) in n.con.enumerate() { + canhs.push((A: (sau, y), B: (sau + 1, ys.at(i)), xs: c.xs)) + } + (y: y, cuoi: yc, nut: nuts, canh: canhs, kq: kqs) + } +} + +#let so-do-cay( + goc: $1$, + nhanh: (), // mảng nut(...) cấp 1 + w: 11.5cm, + cao-hang: 1.5cm, // khoảng cách dọc giữa hai lá kề nhau + cach-kq: 0.55, // cột kết quả cách cột lá (đơn vị cột) + mau-cap: (rgb("#cfe9f5"), rgb("#dcd7f5"), rgb("#d5eed5"), rgb("#fdeacd")), + mau-kq: rgb("#fdecec"), vien-kq: rgb("#e08a8a"), + mau-canh: black, day-canh: 0.9pt, + co-chu: 10pt, + dem-x: 8pt, dem-y: 5pt, // đệm trong của ô + them: none, // ctx => vẽ thêm +) = { + let root = nut(goc, con: nhanh) + let L = _la-so(root) + let D = _sau-max(root) + let r = _duyet(root, 0, L) + let cac-nut = r.nut + ((P: (0, r.y), nd: root.ten, sau: 0, mau: root.mau),) + let co-kq = r.kq.len() > 0 + let xkq = D + cach-kq + let xmax = if co-kq { xkq + 1.0 } else { D + 0.55 } + hinh( + w: w, h: cao-hang * L, + xmin: -0.55, xmax: xmax, ymin: 0, ymax: L, + co-chu: co-chu, + ctx => context { + // cạnh + nhãn xác suất (đầu mút lùi vào theo bề rộng ô đã đo) + for c in r.canh { + let ncha = cac-nut.find(k => k.P == c.A) + let ncon = cac-nut.find(k => k.P == c.B) + let wa = if ncha != none { measure(ncha.nd).width / 2 + dem-x + 3pt } else { 0pt } + let wb = if ncon != none { measure(ncon.nd).width / 2 + dem-x + 3pt } else { 0pt } + let pa = toa-pt(ctx, c.A) + let pb = toa-pt(ctx, c.B) + let A2 = toa-nguoc(ctx, (pa.at(0) + wa.pt(), pa.at(1))) + let B2 = toa-nguoc(ctx, (pb.at(0) - wb.pt(), pb.at(1))) + mui-ten(ctx, A2, B2, mau: mau-canh, day: day-canh, kich: 6pt) + if c.xs != none { + nhan( + ctx, trung-diem(A2, B2), c.xs, + huong: "above", cach: 3pt, quay: goc-truc(ctx, A2, B2), + ) + } + } + // các ô nút + for k in cac-nut { + let to = if k.mau == auto { mau-cap.at(calc.rem(k.sau, mau-cap.len())) } else { k.mau } + o-bo-tron(ctx, k.P, k.nd, to: to, dem-x: dem-x, dem-y: dem-y) + } + // cột kết quả + for q in r.kq { + let s = measure(q.nd) + let b = co-net(q.nd) + let sw = calc.max(s.width, b.width) + let sh = calc.max(s.height, b.height) + let p = toa(ctx, (xkq, q.y)) + place( + dx: p.at(0), dy: p.at(1) - sh / 2 - dem-y, + rect( + width: sw + 2.4 * dem-x, height: sh + 2 * dem-y, + radius: 5pt, fill: mau-kq, stroke: 0.8pt + vien-kq, + ), + ) + place(dx: p.at(0) + 1.2 * dem-x, dy: p.at(1) - s.height / 2, q.nd) + } + if them != none { them(ctx) } + }, + ) +} diff --git a/packages/preview/conic-toan/0.3.4/lib/tron-xoay.typ b/packages/preview/conic-toan/0.3.4/lib/tron-xoay.typ new file mode 100644 index 0000000000..9606d03a99 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/tron-xoay.typ @@ -0,0 +1,359 @@ +// ===================================================================== +// tron-xoay.typ — MIỀN PHẲNG & KHỐI TRÒN XOAY (Giải tích 12) +// +// #khoi-tron-xoay(x => calc.sqrt(x), 0, 4) +// -> TRÁI: miền phẳng giới hạn bởi y = f(x), trục hoành, x = a, x = b +// (đã tô màu); PHẢI: khối tròn xoay sinh ra khi quay miền đó quanh Ox. +// +// Tham số chính: +// truc : "Ox" (mặc định) hoặc "Oy". Với "Oy" thì f là BÁN KÍNH theo y, +// tức miền giới hạn bởi x = f(y), trục tung, y = a, y = b. +// hien : "ca-hai" (mặc định) | "mien" (chỉ miền phẳng) | "khoi" (chỉ khối) +// g : biên trong (khối rỗng — vành khăn), none = khối đặc +// mat-cat : c -> vẽ thiết diện (đĩa/vành khăn) tại x = c kèm bán kính +// the-tich : true -> ghi công thức thể tích dưới hình +// +// Hàm lõi `ve-khoi-xoay(ctx, f, a, b, ...)` vẽ RIÊNG khối vào một khung +// #hinh có sẵn (gọi được không cần ctx như mọi hàm vẽ khác). +// ===================================================================== +#import "ve.typ": * +#import "do-thi.typ": so-toan + +// ---------- Tiện ích nội bộ ---------- +// Điểm trong hệ "chuẩn hoá": u dọc theo TRỤC QUAY, v vuông góc trục quay. +#let _pxy(ngang, u, v) = if ngang { (u, v) } else { (v, u) } + +// Điểm trên đường tròn thiết diện đã chiếu thành elip (k = tỉ lệ bán trục): +// t = 0° đỉnh trên · 90° mép "sau" · 180° đỉnh dưới · 270° mép "trước" +#let _e-diem(ngang, k, u, R, t) = _pxy( + ngang, + u + k * R * calc.sin(t), + R * calc.cos(t), +) + +// Cung elip thiết diện từ t1 đến t2. +#let _cung-td( + ctx, ngang, k, u, R, t1, t2, + mau: black, day: 1pt, dut: false, n: 40, +) = { + if R <= 0.0001 { return } + duong-cong( + ctx, + range(n + 1).map(i => _e-diem(ngang, k, u, R, t1 + (t2 - t1) * i / n)), + mau: mau, day: day, dut: dut, + ) +} + +// Elip thiết diện ĐẦY ĐỦ (dùng để tô). +#let _to-td(ctx, ngang, k, u, R, mau) = { + if R <= 0.0001 { return } + da-giac-pt( + range(49).map(i => toa-pt(ctx, _e-diem(ngang, k, u, R, 360deg * i / 48))), + to: mau, + ) +} + +// Giá trị lớn nhất của |f| trên [a, b] (lấy mẫu). +#let _ban-kinh-max(f, a, b, n: 120) = calc.max( + 0.0001, + ..range(n + 1).map(i => calc.abs(f(a + (b - a) * i / n))), +) + +// Hệ trục cho hình: trục quay (có mũi tên + tên) và trục vuông góc nếu 0 thuộc +// phạm vi. `ngang = true` -> trục quay là Ox. +#let _truc-xoay( + ctx, ngang, umin, umax, vmin, vmax, + mau: black, day: 0.9pt, ten-goc: $O$, +) = { + let (ten-u, ten-v) = if ngang { ($x$, $y$) } else { ($y$, $x$) } + mui-ten(ctx, _pxy(ngang, umin, 0), _pxy(ngang, umax, 0), mau: mau, day: day) + nhan( + ctx, _pxy(ngang, umax, 0), ten-u, + huong: if ngang { "duoi-phai" } else { "tren-phai" }, cach: 3pt, + ) + if vmin <= 0 and vmax >= 0 and umin <= 0 and umax >= 0 { + mui-ten(ctx, _pxy(ngang, 0, vmin), _pxy(ngang, 0, vmax), mau: mau, day: day) + nhan( + ctx, _pxy(ngang, 0, vmax), ten-v, + huong: if ngang { "tren-phai" } else { "duoi-phai" }, cach: 3pt, + ) + if ten-goc != none { + nhan(ctx, (0, 0), ten-goc, huong: "duoi-trai", cach: 4pt, mau: mau) + } + } +} + +// ===================================================================== +// ve-khoi-xoay — vẽ KHỐI TRÒN XOAY vào khung #hinh có sẵn +// (trục quay đi qua gốc; `ngang: true` = quay quanh Ox, false = quanh Oy) +// ===================================================================== +#let ve-khoi-xoay( + ctx, f, a, b, + g: none, // bán kính trong (vành khăn); none = đặc + ngang: true, + k: 0.26, // tỉ lệ bán trục ngang của elip / bán kính + mau: blue, day: 1.2pt, + mau-to: rgb(70, 130, 200, 60), + duong-sinh: true, // vẽ biên trên/dưới của khối + truc-mo: true, // đoạn trục quay bên trong khối vẽ nét đứt + mat-cat: none, // vẽ thiết diện tại u = c + mau-mat-cat: rgb(235, 130, 40, 120), + ten-ban-kinh: auto, // nhãn bán kính thiết diện (none = bỏ) + ten-mat-cat: auto, // nhãn mốc c trên trục (none = bỏ) + n: 120, +) = { + let R = u => calc.abs(f(u)) + let Rt = if g == none { u => 0.0 } else { u => calc.abs(g(u)) } + let Ra = R(a) + let Rb = R(b) + let M = _ban-kinh-max(f, a, b) + let tren = range(n + 1).map(i => { + let u = a + (b - a) * i / n + _pxy(ngang, u, R(u)) + }) + let duoi = range(n + 1).map(i => { + let u = b + (a - b) * i / n + _pxy(ngang, u, -R(u)) + }) + // ---- tô khối: nắp trái (nửa ngoài) + biên trên + nắp phải + biên dưới + let nap-t = range(41).map(i => _e-diem(ngang, k, a, Ra, 180deg + 180deg * i / 40)) + let nap-p = range(41).map(i => _e-diem(ngang, k, b, Rb, 180deg * i / 40)) + da-giac-pt((nap-t + tren + nap-p + duoi).map(P => toa-pt(ctx, P)), to: mau-to) + // ---- trục quay bên trong khối (bị che -> nét đứt) + if truc-mo { + doan( + ctx, _pxy(ngang, a, 0), _pxy(ngang, b, 0), + mau: black, day: 0.7pt, dut: true, + ) + } + // ---- thiết diện + if mat-cat != none { + let c = mat-cat + let Rc = R(c) + _to-td(ctx, ngang, k, c, Rc, mau-mat-cat) + if g != none { _to-td(ctx, ngang, k, c, Rt(c), white) } + _cung-td(ctx, ngang, k, c, Rc, 0deg, 360deg, mau: mau.darken(10%), day: 0.9pt) + if g != none { + _cung-td(ctx, ngang, k, c, Rt(c), 0deg, 360deg, mau: mau.darken(10%), day: 0.9pt) + } + doan(ctx, _pxy(ngang, c, 0), _pxy(ngang, c, Rc), mau: red.darken(10%), day: 0.9pt) + if ten-ban-kinh != none { + let nd = if ten-ban-kinh == auto { + if ngang { $f(x)$ } else { $f(y)$ } + } else { ten-ban-kinh } + nhan( + ctx, _e-diem(ngang, k, c, Rc, 90deg + 45deg), nd, + huong: if ngang { "phai" } else { "duoi" }, + cach: 3pt, mau: red.darken(10%), + ) + } + if ten-mat-cat != none { + let nd = if ten-mat-cat == auto { so-toan(c) } else { ten-mat-cat } + doan( + ctx, _pxy(ngang, c, -Rc), _pxy(ngang, c, -M - 0.3), + mau: gray.darken(20%), day: 0.7pt, dut: true, + ) + nhan( + ctx, _pxy(ngang, c, -M - 0.3), nd, + huong: if ngang { "duoi" } else { "trai" }, cach: 3pt, + ) + } + } + // ---- đường sinh (biên trên / biên dưới) + if duong-sinh { + duong-cong(ctx, tren, mau: mau, day: day) + duong-cong(ctx, duoi, mau: mau, day: day) + } + // ---- nắp hai đầu: trái nửa trong bị che (đứt), phải thấy trọn + _cung-td(ctx, ngang, k, a, Ra, 180deg, 360deg, mau: mau, day: day) + _cung-td(ctx, ngang, k, a, Ra, 0deg, 180deg, mau: mau, day: 0.8pt, dut: true) + _cung-td(ctx, ngang, k, b, Rb, 0deg, 360deg, mau: mau, day: day) + // ---- khối rỗng: mặt trong + lỗ ở nắp phải + if g != none { + _to-td(ctx, ngang, k, b, Rt(b), white) + _cung-td(ctx, ngang, k, b, Rt(b), 0deg, 360deg, mau: mau, day: day) + _cung-td(ctx, ngang, k, a, Rt(a), 0deg, 360deg, mau: mau, day: 0.8pt, dut: true) + for s in (1, -1) { + duong-cong( + ctx, + range(n + 1).map(i => { + let u = a + (b - a) * i / n + _pxy(ngang, u, s * Rt(u)) + }), + mau: mau, day: 0.9pt, dut: true, + ) + } + } +} + +// ===================================================================== +// ve-mien-xoay — vẽ MIỀN PHẲNG sinh ra khối (tô màu + cận a, b) +// ===================================================================== +#let ve-mien-xoay( + ctx, f, a, b, + g: none, + ngang: true, + mau: blue, day: 1.3pt, + mau-to: rgb(70, 130, 200, 90), + ten-ham: auto, ten-ham-trong: none, + ten-a: auto, ten-b: auto, + giong-ab: true, + n: 120, +) = { + let R = u => calc.abs(f(u)) + let Rt = if g == none { u => 0.0 } else { u => calc.abs(g(u)) } + let tren = range(n + 1).map(i => { + let u = a + (b - a) * i / n + _pxy(ngang, u, R(u)) + }) + let duoi = range(n + 1).map(i => { + let u = b + (a - b) * i / n + _pxy(ngang, u, Rt(u)) + }) + da-giac-pt((tren + duoi).map(P => toa-pt(ctx, P)), to: mau-to) + duong-cong(ctx, tren, mau: mau, day: day) + if g != none { duong-cong(ctx, duoi, mau: mau.darken(25%), day: day) } + if giong-ab { + for (u, t) in ((a, ten-a), (b, ten-b)) { + doan( + ctx, _pxy(ngang, u, Rt(u)), _pxy(ngang, u, R(u)), + mau: gray.darken(25%), day: 0.8pt, dut: true, + ) + // cận trùng gốc toạ độ thì bỏ nhãn (kẻo đè chữ O) + if t != none and not (t == auto and calc.abs(u) < 0.001) { + nhan( + ctx, _pxy(ngang, u, 0), if t == auto { so-toan(u) } else { t }, + huong: if ngang { "duoi" } else { "trai" }, cach: 4pt, + ) + } + } + } + if ten-ham != none { + let u = a + (b - a) * 0.78 + let nd = if ten-ham == auto { + if ngang { $y = f(x)$ } else { $x = f(y)$ } + } else { ten-ham } + nhan( + ctx, _pxy(ngang, u, R(u)), nd, + huong: if ngang { "tren-phai" } else { "phai" }, cach: 5pt, mau: mau, + ) + } + if ten-ham-trong != none and g != none { + let u = a + (b - a) * 0.3 + nhan( + ctx, _pxy(ngang, u, Rt(u)), ten-ham-trong, + huong: if ngang { "duoi-phai" } else { "trai" }, cach: 5pt, mau: mau.darken(25%), + ) + } +} + +// ===================================================================== +// khoi-tron-xoay — HÌNH HOÀN CHỈNH: miền phẳng -> khối tròn xoay +// ===================================================================== +#let khoi-tron-xoay( + f, a, b, + g: none, + truc: "Ox", + hien: "ca-hai", // "ca-hai" | "mien" | "khoi" + w: 6cm, + k: 0.26, + mau: blue, day: 1.3pt, + mau-to: rgb(70, 130, 200, 60), + mau-mien: rgb(70, 130, 200, 95), + ten-ham: auto, ten-ham-trong: none, + ten-a: auto, ten-b: auto, + mat-cat: none, + ten-mat-cat: auto, ten-ban-kinh: auto, + the-tich: false, // ghi công thức thể tích dưới hình + nhan-giua: auto, // nội dung giữa 2 hình + ten-goc: $O$, // nhãn gốc toạ độ (none = bỏ) + co-chu: 10pt, + khung: false, + them: none, them-mien: none, +) = { + let ngang = truc != "Oy" + let M = _ban-kinh-max(f, a, b) + let d = calc.abs(b - a) + let (a, b) = (calc.min(a, b), calc.max(a, b)) + let le = 0.16 * calc.max(M, d / 2) + 0.22 + let e = k * M + let umin = a - e - le + let umax = b + e + le + 0.12 * d + let vmax = M + le + let vmin = -M - le - (if mat-cat != none and ten-mat-cat != none { 0.42 } else { 0 }) + // cửa sổ của hình miền phẳng: chỉ cần chút chỗ dưới trục để ghi cận + let vmin-m = -0.4 * M - 0.22 + let ti-le = w / (umax - umin) // giữ ĐÚNG một tỉ lệ cho cả 2 hình + + let khung-khoi(rong, x1, x2, y1, y2, ve) = hinh( + w: rong, xmin: x1, xmax: x2, ymin: y1, ymax: y2, + co-chu: co-chu, khung: khung, ve, + ) + + let h-khoi = { + let (x1, x2, y1, y2) = if ngang { + (umin, umax, vmin, vmax) + } else { + (vmin, vmax, umin, umax) + } + khung-khoi(ti-le * (x2 - x1), x1, x2, y1, y2, ctx => { + _truc-xoay(ctx, ngang, umin, umax, vmin, vmax, ten-goc: ten-goc) + ve-khoi-xoay( + ctx, f, a, b, g: g, ngang: ngang, k: k, + mau: mau, day: day, mau-to: mau-to, + mat-cat: mat-cat, ten-mat-cat: ten-mat-cat, ten-ban-kinh: ten-ban-kinh, + ) + if them != none { them(ctx) } + }) + } + + let h-mien = { + let (x1, x2, y1, y2) = if ngang { + (umin, umax, vmin-m, vmax) + } else { + (vmin-m, vmax, umin, umax) + } + khung-khoi(ti-le * (x2 - x1), x1, x2, y1, y2, ctx => { + _truc-xoay(ctx, ngang, umin, umax, vmin-m, vmax, ten-goc: ten-goc) + ve-mien-xoay( + ctx, f, a, b, g: g, ngang: ngang, + mau: mau, day: day, mau-to: mau-mien, + ten-ham: ten-ham, ten-ham-trong: ten-ham-trong, + ten-a: ten-a, ten-b: ten-b, + ) + if them-mien != none { them-mien(ctx) } + }) + } + + let giua = if nhan-giua == auto { + align(center, { + text(size: 0.82 * co-chu)[quay quanh #truc] + linebreak() + text(size: 1.3 * co-chu)[$arrow.r.long$] + }) + } else { nhan-giua } + + let ct = if the-tich { + let (bien, kx) = if ngang { ($x$, $dif x$) } else { ($y$, $dif y$) } + let (na, nb) = (so-toan(a), so-toan(b)) + if g == none { + $V = pi integral_(#na)^(#nb) f^2 (#bien) #kx$ + } else { + $V = pi integral_(#na)^(#nb) [f^2 (#bien) - g^2 (#bien)] #kx$ + } + } else { none } + + let than = if hien == "mien" { + h-mien + } else if hien == "khoi" { + h-khoi + } else { + grid( + columns: 3, column-gutter: 10pt, align: horizon, + h-mien, giua, h-khoi, + ) + } + if ct == none { than } else { + align(center, { than; v(2pt); ct }) + } +} diff --git a/packages/preview/conic-toan/0.3.4/lib/ve.typ b/packages/preview/conic-toan/0.3.4/lib/ve.typ new file mode 100644 index 0000000000..45e6200709 --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/lib/ve.typ @@ -0,0 +1,1544 @@ +// ===================================================================== +// ve.typ — ENGINE VẼ HÌNH THUẦN TYPST (không phụ thuộc package nào) +// Hệ toạ độ toán học: gốc do người dùng chọn, trục y hướng LÊN. +// Mọi hàm vẽ nhận `ctx` (do #hinh cung cấp) + toạ độ dạng (x, y) là số. +// Quy ước tham số chung: mau (màu), day (độ dày nét), dut (nét đứt). +// ===================================================================== + +// ---------- Khung vẽ ---------- +// Tạo khung vẽ. `ve` là hàm nhận ctx và trả về nội dung vẽ. +// h: auto => tự tính để 2 trục CÙNG TỈ LỆ (nên giữ auto với hình hình học). +// Ngăn xếp ctx của các khung hình đang vẽ — cho phép GỌI HÀM VẼ KHÔNG CẦN ctx +// (xem _voi-ctx ngay dưới hinh). Đẩy vào khi vào khung, lấy ra khi rời khung. +#let _ctx-ht = state("bg-ctx-ht", ()) + +/// Khung vẽ: đặt cửa sổ toạ độ toán (xmin..xmax, ymin..ymax) rồi vẽ bên trong. +/// Mọi lệnh vẽ đặt trong thân hàm này là tự có `ctx`, không phải gõ. +/// -> content +#let hinh( + w: 8cm, + h: auto, + xmin: -5, xmax: 5, + ymin: -4, ymax: 4, + co-chu: 10pt, + khung: false, // vẽ viền khung (để căn chỉnh khi soạn) + cat: false, // cắt phần tràn ra ngoài khung + ve, +) = { + let hh = if h == auto { w * (ymax - ymin) / (xmax - xmin) } else { h } + let ctx = ( + w: w, h: hh, + xmin: xmin, xmax: xmax, ymin: ymin, ymax: ymax, + sx: w / (xmax - xmin), + sy: hh / (ymax - ymin), + ) + box( + width: w, height: hh, + stroke: if khung { 0.4pt + luma(70%) } else { none }, + clip: cat, + { + set text(size: co-chu) + _ctx-ht.update(s => s + (ctx,)) + ve(ctx) + _ctx-ht.update(s => s.slice(0, -1)) + }, + ) +} + +// Bọc hàm vẽ f(ctx, ...) thành hàm gọi được KHÔNG CẦN ctx: nếu đối số đầu là +// ctx thì gọi thẳng (lối cũ), không thì tự lấy ctx của khung hình đang vẽ. +// Dùng ở baigiang.typ để bọc đồng loạt các hàm vẽ (xem cuối file đó). +#let _voi-ctx(f) = (..a) => { + let p = a.pos() + if p.len() > 0 and type(p.at(0)) == dictionary and "sx" in p.at(0) { + f(..a) + } else { + context { + let ds = _ctx-ht.get() + if ds.len() == 0 { + panic("Hàm vẽ cần khung hình: đặt lệnh trong thân #hinh(...) hoặc trong them: ctx => ..., hoặc truyền ctx như lối cũ.") + } + f(ds.last(), ..a) + } + } +} + +// ---------- Đổi toạ độ ---------- +// Toạ độ toán (x, y) -> toạ độ trang (length, length), gốc trên-trái. +#let toa(ctx, P) = { + let Q = if "bien-doi" in ctx and ctx.bien-doi != none { (ctx.bien-doi)(P) } else { P } + ((Q.at(0) - ctx.xmin) * ctx.sx, (ctx.ymax - Q.at(1)) * ctx.sy) +} +// Như trên nhưng trả về số float tính bằng pt (để tính toán). +#let toa-pt(ctx, P) = { + let q = toa(ctx, P) + (q.at(0).pt(), q.at(1).pt()) +} + +// ---------- Phép biến hình (toạ độ toán) ---------- +// Quay điểm P quanh tâm `tam` góc `goc` (dương = ngược chiều kim đồng hồ). +#let quay-diem(P, tam, goc) = { + let dx = P.at(0) - tam.at(0) + let dy = P.at(1) - tam.at(1) + let c = calc.cos(goc) + let s = calc.sin(goc) + (tam.at(0) + dx * c - dy * s, tam.at(1) + dx * s + dy * c) +} +// Tịnh tiến điểm P theo vectơ v. +#let tinh-tien-diem(P, v) = (P.at(0) + v.at(0), P.at(1) + v.at(1)) +// Toạ độ CỰC kiểu TikZ: điểm cách `tam` một khoảng `bk`, quay góc `goc` +// (số trần = ĐỘ, nhận cả kiểu angle 30deg/0.5rad; dương = ngược kim đồng hồ). +// toa-cuc((0,0), 2, 30) ~ TikZ (30:2) +/// Toạ độ cực kiểu TikZ: điểm cách `tam` một khoảng `bk`, theo `goc` +/// (số trần = độ, dương = ngược kim đồng hồ). TikZ `(30:2)` <=> `toa-cuc((0,0), 2, 30)`. +/// TRẢ VỀ điểm, không vẽ gì. +#let toa-cuc(tam, bk, goc) = { + let g = if type(goc) == angle { goc } else { goc * 1deg } + (tam.at(0) + bk * calc.cos(g), tam.at(1) + bk * calc.sin(g)) +} +// Dựng điểm M sao cho tia AM = tia AB QUAY quanh A một góc lượng giác `goc` +// (số trần = ĐỘ, dương = ngược kim đồng hồ) và AM = `r`. Nói cách khác, góc +// định hướng (AB, AM) = `goc`. Dùng dựng đỉnh tam giác biết một cạnh + một góc. +// let M = dung-diem(A, B, 60, 3) // góc BAM = 60°, AM = 3 +/// TRẢ VỀ điểm M: tia AM là tia AB quay quanh A góc lượng giác `goc` +/// (số trần = độ, dương ngược kim đồng hồ), độ dài AM = `r`. Không vẽ gì. +#let dung-diem(A, B, goc, r) = { + let g = if type(goc) == angle { goc } else { goc * 1deg } + let base = calc.atan2(B.at(0) - A.at(0), B.at(1) - A.at(1)) + toa-cuc(A, r, base + g) +} +// Ghép thêm một phép biến hình f vào ctx (nội bộ; phép cũ làm trước, f làm sau). +#let _ghep-bien-doi(ctx, f) = { + let cu = ctx.at("bien-doi", default: none) + ctx + (bien-doi: if cu == none { f } else { P => f((cu)(P)) }) +} +// Bản sao ctx bị QUAY: mọi hàm vẽ dùng ctx này đều quay quanh `tam` góc `goc`. +// let cq = ctx-quay(ctx, 30deg, tam: (1, 1)); tam-giac(cq, A, B, C) +// Bản sao ctx bị TỊNH TIẾN theo vectơ v; ghép nối tiếp được nhiều phép: +// ctx-tinh-tien(ctx-quay(ctx, 30deg, tam: G), (2, 0)) — quay trước, tịnh tiến sau +// Lưu ý: duong-tron/elip vẫn đúng dưới 2 phép này (tâm tự biến đổi); riêng +// gach-vung mô tả miền theo toạ độ GỐC của khung (không qua bien-doi). +#let ctx-quay(ctx, goc, tam: (0, 0)) = _ghep-bien-doi(ctx, P => quay-diem(P, tam, goc)) +#let ctx-tinh-tien(ctx, v) = _ghep-bien-doi(ctx, P => tinh-tien-diem(P, v)) + +// Kiểu nét +#let net(mau, day, dut) = ( + paint: mau, + thickness: day, + dash: if dut { "dashed" } else { none }, +) + +// ---------- Nguyên thuỷ cấp thấp (toạ độ pt, dùng nội bộ) ---------- +#let doan-pt(p, q, mau: black, day: 1pt, dut: false) = place( + line( + start: (p.at(0) * 1pt, p.at(1) * 1pt), + end: (q.at(0) * 1pt, q.at(1) * 1pt), + stroke: net(mau, day, dut), + ) +) + +// Đa giác tô màu từ toạ độ pt (tự chuẩn hoá về gốc bao). +#let da-giac-pt(pts, to: black, vien: none) = { + let xs = pts.map(p => p.at(0)) + let ys = pts.map(p => p.at(1)) + let x0 = calc.min(..xs) + let y0 = calc.min(..ys) + place( + dx: x0 * 1pt, dy: y0 * 1pt, + polygon( + fill: to, stroke: vien, + ..pts.map(p => ((p.at(0) - x0) * 1pt, (p.at(1) - y0) * 1pt)), + ), + ) +} + +// Đầu mũi tên tại b, hướng a->b (toạ độ pt, dùng nội bộ). +#let _dau-pt(a, b, mau: black, kich: 7pt) = { + let dx = b.at(0) - a.at(0) + let dy = b.at(1) - a.at(1) + let l = calc.sqrt(dx * dx + dy * dy) + if l < 0.001 { return } + let k = kich.pt() + let ux = dx / l + let uy = dy / l + let px = -uy + let py = ux + da-giac-pt( + ( + b, + (b.at(0) - k * ux + 0.38 * k * px, b.at(1) - k * uy + 0.38 * k * py), + (b.at(0) - 0.72 * k * ux, b.at(1) - 0.72 * k * uy), + (b.at(0) - k * ux - 0.38 * k * px, b.at(1) - k * uy - 0.38 * k * py), + ), + to: mau, + ) +} + +// Đầu mũi tên tại B, hướng A->B. +#let dau-mui-ten(ctx, A, B, mau: black, kich: 7pt) = _dau-pt( + toa-pt(ctx, A), toa-pt(ctx, B), mau: mau, kich: kich, +) + +// ---------- Nguyên thuỷ chính ---------- +// (định nghĩa `doan` nằm NGAY SAU `nhan` vì có tuỳ chọn nhãn giữa đoạn) + +// Hướng VUÔNG GÓC với đoạn AB trên TRANG (vectơ đơn vị dùng cho `nhan`): +// ưu tiên phía trên đoạn; đoạn thẳng đứng thì ra phía trái. +#let _phap-tuyen(ctx, A, B) = { + let a = toa-pt(ctx, A) + let b = toa-pt(ctx, B) + let (dx, dy) = (b.at(0) - a.at(0), b.at(1) - a.at(1)) + let l = calc.sqrt(dx * dx + dy * dy) + if l < 0.0001 { return (0, -1) } + let n = (-dy / l, dx / l) + if n.at(1) > 0.000001 { n = (-n.at(0), -n.at(1)) } // xoay lên trên + if calc.abs(n.at(1)) <= 0.000001 and n.at(0) > 0 { n = (-1, 0) } // đoạn dọc -> trái + n +} + +// Góc xoay (kiểu angle) để chữ NẰM DỌC THEO đoạn/đường A -> B (toạ độ toán), +// có tính cả tỉ lệ 2 trục sx, sy. Dùng cho nhãn tiệm cận xiên, nhãn dọc theo trục... +// nhan(ctx, P, $y = x - 1$, quay: goc-truc(ctx, (0, -1), (1, 0))) +// nhan(ctx, (1, y), $x = 1$, quay: goc-truc(ctx, (1, 0), (1, 1))) // dọc, đọc từ dưới lên +#let goc-truc(ctx, A, B) = { + let dx = (B.at(0) - A.at(0)) * ctx.sx.pt() + let dy = (B.at(1) - A.at(1)) * ctx.sy.pt() + -calc.atan2(dx, dy) +} + +// ---------- Đo nhãn theo BIÊN NÉT CHỮ ---------- +// Typst tính HỤT khung của công thức TRONG DÒNG: `measure($1/2$)` chỉ trả về +// chiều cao MỘT DÒNG CHỮ (≈6.8pt ở cỡ 10pt) trong khi phân số vẽ ra cao gấp +// đôi (≈12pt) và tràn cả trên lẫn dưới khung. Nhãn đặt theo khung đó nên bị +// đường vẽ CẮT NGANG (phân số, căn bậc hai, chỉ số trên/dưới…). Đo lại bằng +// top-edge/bottom-edge: "bounds" cho ra đúng biên nét chữ. +// PHẢI gọi trong `context`. Số chữ thường (không tràn) cho kết quả nhỏ hơn +// hoặc bằng khung, nên nơi dùng luôn lấy phần TRÀN = max(0, biên − khung): +// chữ thường bù 0 ⇒ mọi hình cũ giữ nguyên bố cục. +#let co-net(nd) = measure({ + set text(top-edge: "bounds", bottom-edge: "bounds") + nd +}) + +// Phần nét chữ tràn ra NGOÀI khung, chia đều hai bên: (dx, dy) tính bằng length. +#let _tran-net(nd) = { + let k = measure(nd) + let b = co-net(nd) + ( + calc.max(0pt, b.width - k.width) / 2, + calc.max(0pt, b.height - k.height) / 2, + ) +} + +// Nhãn văn bản đặt cạnh điểm P. +// huong: "above", "below", "left", "right", "above-left", "above-right", +// "below-left", "below-right", "center" +// (tên tiếng Việt cũ "tren", "duoi", "trai", "phai", ... vẫn dùng được) +// quay: góc xoay chữ (vd 90deg, -45deg, hoặc goc-truc(...)). Mặc định 0deg. +/// Đặt chữ/công thức tại điểm P. `huong` là phía đặt chữ so với P +/// ("above", "below", "left", "right", "above-left"… hoặc tuple (dx, dy)), +/// `cach` là khoảng cách tới P, `quay` để xoay chữ theo đường. +#let nhan(ctx, P, noi-dung, huong: "above", cach: 6pt, mau: black, quay: 0deg) = { + let p = toa(ctx, P) + // huong: tên hướng, HOẶC vectơ (dx, dy) trên trang (y hướng XUỐNG) — dùng + // cho nhãn đặt vuông góc với một đoạn bất kỳ (xem _phap-tuyen). + let hd = if type(huong) == array { huong } else { + ( + above: (0, -1), below: (0, 1), left: (-1, 0), right: (1, 0), + above-left: (-0.7, -0.7), above-right: (0.7, -0.7), + below-left: (-0.7, 0.7), below-right: (0.7, 0.7), + center: (0, 0), + // bí danh tiếng Việt (tương thích ngược) + tren: (0, -1), duoi: (0, 1), trai: (-1, 0), phai: (1, 0), + tren-trai: (-0.7, -0.7), tren-phai: (0.7, -0.7), + duoi-trai: (-0.7, 0.7), duoi-phai: (0.7, 0.7), + giua: (0, 0), + ).at(huong) + } + context { + let nd = text(fill: mau, noi-dung) + let nd2 = if quay == 0deg { nd } else { rotate(quay, reflow: true, nd) } + let s = measure(nd2) + // bù phần nét chữ tràn ra ngoài khung (phân số, căn…) — xem `co-net` + let (bx, by) = _tran-net(nd2) + place( + dx: p.at(0) - s.width / 2 + hd.at(0) * (cach + s.width / 2 + bx), + dy: p.at(1) - s.height / 2 + hd.at(1) * (cach + s.height / 2 + by), + nd2, + ) + } +} + +// Đoạn thẳng AB, kèm NHÃN CHÚ THÍCH đặt trên đoạn (tuỳ chọn): +// ten : nội dung nhãn (vd $a$, [3 cm]) — none = không ghi +// tai : vị trí nhãn theo TỈ LỆ từ A đến B (0 = tại A, 1 = tại B, .5 = giữa) +// huong : auto = tự đặt VUÔNG GÓC phía trên đoạn (đoạn dọc thì ra trái); +// hoặc tên hướng ("above", "below", "left", ...), hoặc vectơ (dx, dy) +// cach : khoảng cách từ đoạn tới nhãn +// ten-quay: true = chữ NẰM DỌC theo đoạn (tự xoay, luôn đọc xuôi) +// mau-ten : màu chữ (auto = theo màu đoạn) +// Ví dụ: doan(A, B, ten: $2a$) // giữa đoạn, phía trên +// doan(A, B, ten: $h$, tai: 0.7, huong: "right", cach: 4pt) +// doan(A, B, ten: [đường chéo], ten-quay: true) +/// Đoạn thẳng A–B. Kèm nhãn giữa đoạn bằng `ten:` (đặt chỗ khác bằng `tai:` +/// tỉ lệ 0..1, lệch sang bên bằng `huong:`, xoay theo đoạn bằng `ten-quay: true`). +#let doan( + ctx, A, B, + mau: black, day: 1pt, dut: false, + ten: none, tai: 0.5, huong: auto, cach: 6pt, ten-quay: false, mau-ten: auto, +) = { + place(line(start: toa(ctx, A), end: toa(ctx, B), stroke: net(mau, day, dut))) + if ten != none { + let P = (A.at(0) + tai * (B.at(0) - A.at(0)), A.at(1) + tai * (B.at(1) - A.at(1))) + // chữ dọc theo đoạn: chọn chiều A->B hay B->A để không bị lộn ngược + let xuoi = toa-pt(ctx, A).at(0) <= toa-pt(ctx, B).at(0) + nhan( + ctx, P, ten, + huong: if huong == auto { _phap-tuyen(ctx, A, B) } else { huong }, + cach: cach, + mau: if mau-ten == auto { mau } else { mau-ten }, + quay: if ten-quay { if xuoi { goc-truc(ctx, A, B) } else { goc-truc(ctx, B, A) } } else { 0deg }, + ) + } +} + +// Điểm (chấm tròn) + nhãn tuỳ chọn. +// cach: khoảng cách nhãn tới điểm · mau-ten: màu chữ (auto = màu chấm). +/// Chấm một điểm, kèm tên: `diem(A, ten: $A$, huong: "below-left")`. +/// Nhiều điểm một lệnh thì dùng `cac-diem`. +#let diem(ctx, P, ten: none, huong: "tren", bk: 2pt, mau: black, cach: 6pt, mau-ten: black) = { + let p = toa(ctx, P) + place(dx: p.at(0) - bk, dy: p.at(1) - bk, circle(radius: bk, fill: mau, stroke: none)) + if ten != none { + nhan(ctx, P, ten, huong: huong, cach: cach, mau: if mau-ten == auto { mau } else { mau-ten }) + } +} + +// Đặt nhãn cho nhiều điểm, VỊ TRÍ nhãn xác định bằng GÓC LƯỢNG GIÁC (thay cho +// tên hướng "above"/"below"…). Mỗi mục là một tuple: +// (P, nội-dung, goc) — bán kính & màu lấy mặc định +// (P, nội-dung, goc, ban-kinh) — ban-kinh là ĐỘ DÀI trang (vd 8pt) +// (P, nội-dung, goc, ban-kinh, mau) — kèm màu riêng +// `goc` là góc lượng giác (số trần = ĐỘ, dương = ngược kim đồng hồ; nhãn đặt +// về phía góc đó so với điểm). Phần tử sau `goc` nhận diện theo kiểu: color -> +// màu, còn lại -> bán kính, nên thứ tự ban-kinh/mau linh hoạt. +// nhan-goc((A, $A$, 210), (B, $B$, -30, 9pt), (C, $C$, 90, 8pt, red)) +/// Đặt nhãn nhiều điểm theo GÓC LƯỢNG GIÁC. Mỗi mục: +/// `(P, nội-dung, goc[, ban-kinh][, mau])` — `goc` số trần = độ, dương ngược +/// kim đồng hồ; `ban-kinh` là độ dài trang (khoảng cách điểm→nhãn). +#let nhan-goc(ctx, ..muc, mau: black, ban-kinh: 6pt) = { + for m in muc.pos() { + let P = m.at(0) + let nd = m.at(1) + let goc = m.at(2) + let r = ban-kinh + let c = mau + for x in m.slice(3) { + if type(x) == color { c = x } else { r = x } + } + let g = if type(goc) == angle { goc } else { goc * 1deg } + // hướng trên TRANG (y hướng XUỐNG) theo góc lượng giác (y toán hướng lên) + let hd = (calc.cos(g), -calc.sin(g)) + nhan(ctx, P, nd, huong: hd, cach: r, mau: c) + } +} + +// Mũi tên A -> B. +#let mui-ten(ctx, A, B, mau: black, day: 1pt, kich: 7pt, dut: false) = { + doan(ctx, A, B, mau: mau, day: day, dut: dut) + dau-mui-ten(ctx, A, B, mau: mau, kich: kich) +} + +// Vectơ A -> B kèm tên đặt ở trung điểm. +// dut: true = thân nét đứt (vectơ nằm trên cạnh khuất, kiểu hình SGK); +// đầu mũi tên luôn nét liền. +#let vecto(ctx, A, B, ten: none, huong: "tren", mau: black, day: 1.1pt, dut: false) = { + mui-ten(ctx, A, B, mau: mau, day: day, dut: dut) + if ten != none { + nhan(ctx, ((A.at(0) + B.at(0)) / 2, (A.at(1) + B.at(1)) / 2), ten, huong: huong, mau: mau) + } +} + +// ---------- Mũi tên HAI ĐẦU (đường ghi số đo) ---------- +// Mũi tên có đầu ở CẢ HAI phía, kiểu đường ghi kích thước trong bản vẽ: +// thường dùng để chú thích số đo đặt Ở GIỮA đoạn mũi tên. +// ten : nội dung chú thích (vd $2a$, [5 cm]) — none = chỉ vẽ mũi tên +// trong : chữ nằm CHÍNH GIỮA thân, thân bị cắt chừa chỗ (|<-- 5 cm -->|) +// auto = tự bật khi có `ten` và đoạn đủ dài để chứa chữ, +// không đủ dài thì tự đặt chữ ra ngoài (phía trên đoạn) +// huong : chỗ đặt chữ khi KHÔNG nằm giữa thân +// (auto = vuông góc phía trên đoạn, như `doan`) +// ten-quay : true = chữ NẰM DỌC theo mũi tên (luôn đọc xuôi) +// vach : true = kẻ vạch chặn vuông góc ở hai đầu (kiểu ghi kích thước) +// le : lùi hai đầu mũi tên vào trong, để hở khỏi vật đang đo +// nen : màu nền lót sau chữ khi chữ nằm giữa thân (none = không lót) +// Ví dụ: +// mui-ten-2-dau(A, B, ten: [5 cm]) // chữ giữa thân +// mui-ten-2-dau(A, B, ten: $2a$, trong: false) // chữ trên đoạn +// mui-ten-2-dau((0, -0.4), (4, -0.4), ten: $h$, vach: true) +/// Mũi tên HAI ĐẦU giữa A và B, kiểu đường ghi kích thước. `ten:` là số đo +/// đặt GIỮA thân (`trong: false` để đưa chữ ra ngoài), `vach: true` kẻ vạch +/// chặn hai đầu, `le:` lùi hai đầu vào trong cho hở khỏi vật đang đo. +#let mui-ten-2-dau( + ctx, A, B, + mau: black, day: 1pt, kich: 7pt, dut: false, + ten: none, huong: auto, cach: 5pt, ten-quay: false, mau-ten: auto, + trong: auto, dem: 3pt, nen: white, + vach: false, dai-vach: 9pt, + le: 0pt, +) = context { + let a0 = toa-pt(ctx, A) + let b0 = toa-pt(ctx, B) + let (dx, dy) = (b0.at(0) - a0.at(0), b0.at(1) - a0.at(1)) + let l0 = calc.sqrt(dx * dx + dy * dy) + if l0 < 0.001 { return } + let (ux, uy) = (dx / l0, dy / l0) + let lp = calc.min(le.pt(), l0 / 2 - 0.5) + let a = (a0.at(0) + lp * ux, a0.at(1) + lp * uy) + let b = (b0.at(0) - lp * ux, b0.at(1) - lp * uy) + let l = l0 - 2 * lp + let mc = ((a.at(0) + b.at(0)) / 2, (a.at(1) + b.at(1)) / 2) + let mt = ((A.at(0) + B.at(0)) / 2, (A.at(1) + B.at(1)) / 2) + let mau-t = if mau-ten == auto { mau } else { mau-ten } + + // Kích thước chữ (đo cả biên nét để phân số/căn không bị thân mũi tên cắt). + let nd = if ten == none { none } else { + let n = text(fill: mau-t, ten) + let q = if ten-quay { + let g = if a.at(0) <= b.at(0) { goc-truc(ctx, A, B) } else { goc-truc(ctx, B, A) } + rotate(g, reflow: true, n) + } else { n } + q + } + let (kw, kh, bw, bh) = if nd == none { (0pt, 0pt, 0pt, 0pt) } else { + let k = measure(nd) + let bn = co-net(nd) + (k.width, k.height, calc.max(k.width, bn.width), calc.max(k.height, bn.height)) + } + // Bề ngang chỗ chữ chiếm DỌC THEO thân mũi tên + hai đầu mũi tên phải đủ chỗ. + let ho = bw.pt() * calc.abs(ux) + bh.pt() * calc.abs(uy) + 2 * dem.pt() + let o-giua = ( + ten != none + and (if trong == auto { l > ho + 3.2 * kich.pt() } else { trong }) + ) + + // Thân: cắt đôi chừa chỗ cho chữ nếu chữ nằm giữa + if o-giua { + let h2 = calc.min(ho / 2, l / 2 - 0.2) + doan-pt(a, (mc.at(0) - h2 * ux, mc.at(1) - h2 * uy), mau: mau, day: day, dut: dut) + doan-pt((mc.at(0) + h2 * ux, mc.at(1) + h2 * uy), b, mau: mau, day: day, dut: dut) + } else { + doan-pt(a, b, mau: mau, day: day, dut: dut) + } + _dau-pt(b, a, mau: mau, kich: kich) + _dau-pt(a, b, mau: mau, kich: kich) + + // Vạch chặn hai đầu (kiểu đường ghi kích thước) + if vach { + let hv = dai-vach.pt() / 2 + let (px, py) = (-uy, ux) + for q in (a0, b0) { + doan-pt( + (q.at(0) - hv * px, q.at(1) - hv * py), + (q.at(0) + hv * px, q.at(1) + hv * py), + mau: mau, day: day, + ) + } + } + + // Chú thích + if ten != none { + if o-giua { + if nen != none { + place( + dx: (mc.at(0) - bw.pt() / 2 - dem.pt() / 2) * 1pt, + dy: (mc.at(1) - bh.pt() / 2) * 1pt, + rect(width: bw + dem, height: bh, fill: nen, stroke: none), + ) + } + place(dx: (mc.at(0) - kw.pt() / 2) * 1pt, dy: (mc.at(1) - kh.pt() / 2) * 1pt, nd) + } else { + nhan( + ctx, mt, ten, + huong: if huong == auto { _phap-tuyen(ctx, A, B) } else { huong }, + cach: cach, mau: mau-t, + quay: if ten-quay { + if a.at(0) <= b.at(0) { goc-truc(ctx, A, B) } else { goc-truc(ctx, B, A) } + } else { 0deg }, + ) + } + } +} + +// Đường gấp khúc qua dãy điểm. dut: nét đứt (vẽ cách đoạn -> đều trên đường cong). +#let duong-cong(ctx, cac-diem, mau: black, day: 1pt, dut: false, dong: false) = { + let pts = cac-diem + if dong and pts.len() > 1 { pts = pts + (pts.at(0),) } + for i in range(pts.len() - 1) { + if not dut or calc.rem(i, 2) == 0 { + doan(ctx, pts.at(i), pts.at(i + 1), mau: mau, day: day) + } + } +} + +// Đường gấp khúc nối lần lượt A - B - C - ... KHÁC duong-cong ở chỗ: +// dut: true là nét đứt THẬT trên TỪNG đoạn (hợp polyline ít đoạn; +// duong-cong đứt kiểu "bỏ đoạn xen kẽ" chỉ hợp đường nhiều mẫu). +// dong: true nối điểm cuối về điểm đầu. +#let duong-gap-khuc(ctx, cac-diem, mau: black, day: 1pt, dut: false, dong: false) = { + let pts = cac-diem + if dong and pts.len() > 1 { pts = pts + (pts.at(0),) } + for i in range(pts.len() - 1) { + doan(ctx, pts.at(i), pts.at(i + 1), mau: mau, day: day, dut: dut) + } +} + +// Đa giác kín: viền + tô màu tuỳ chọn (to: màu hoặc none). +/// Đa giác khép kín. Đỉnh truyền vào dưới dạng MỘT MẢNG: +/// `da-giac((A, B, C))` — không phải `da-giac(A, B, C)`. `to:` để tô màu. +#let da-giac(ctx, cac-diem, mau: black, day: 1pt, dut: false, to: none) = { + if to != none { + da-giac-pt(cac-diem.map(P => toa-pt(ctx, P)), to: to) + } + duong-cong(ctx, cac-diem, mau: mau, day: day, dut: dut, dong: true) +} + +// ---------- cac-doan: VẼ NHIỀU NÉT TRONG MỘT LỆNH (kiểu \draw của TikZ) ---- +// Đối số vị trí có thể trộn thoải mái: +// • các ĐIỂM liền nhau -> nối thành MỘT đường gấp khúc: +// cac-doan(A, B) // một đoạn +// cac-doan(A, B, C, D) // A-B-C-D +// • một MẢNG ĐIỂM -> một nét riêng: +// cac-doan((A, B), (C, D)) // hai đoạn rời +// • duong(...) -> một nét riêng CÓ KIỂU RIÊNG (đè lên kiểu chung): +// cac-doan( +// duong(A, B, C, dong: true, to: blue.lighten(85%)), // tam giác tô +// duong(S, H, dut: true, ten: $h$), // nét đứt có nhãn +// (M, N), // đoạn thường +// mau: blue, day: 1.2pt, // kiểu CHUNG +// ) +// Tuỳ chọn mỗi nét (đặt ở duong(...) hoặc dùng chung ở cac-doan): +// mau · day · dut · dong (khép kín) · to (tô màu, tự khép kín) +// mui-ten (vẽ đầu mũi tên ở điểm cuối) · ten/tai/huong/cach/ten-quay/mau-ten +// (nhãn đặt tại tỉ lệ `tai` tính theo TỔNG chiều dài nét — xem `doan`) +#let duong( + ..noi-dung, + mau: auto, day: auto, dut: auto, dong: auto, to: auto, mui-ten: auto, + ten: auto, tai: auto, huong: auto, cach: auto, ten-quay: auto, mau-ten: auto, +) = { + let p = noi-dung.pos() + // duong((A, B, C)) — truyền nguyên một mảng điểm + if (p.len() == 1 and type(p.at(0)) == array and p.at(0).len() > 0 + and type(p.at(0).at(0)) == array) { p = p.at(0) } + ( + bg-duong: true, diem: p, + mau: mau, day: day, dut: dut, dong: dong, to: to, mui-ten: mui-ten, + ten: ten, tai: tai, huong: huong, cach: cach, ten-quay: ten-quay, mau-ten: mau-ten, + ) +} + +#let _la-duong(x) = type(x) == dictionary and x.at("bg-duong", default: false) +#let _la-diem(x) = type(x) == array and x.len() == 2 and type(x.at(0)) != array + +/// Vẽ NHIỀU nét trong một lệnh: điểm liền nhau thành một đường gấp khúc, +/// mỗi MẢNG điểm là một nét riêng — `cac-doan((A, B), (C, D), (M, N))`. +/// Nét cần kiểu riêng thì bọc `duong(...)`. +#let cac-doan( + ctx, ..noi-dung, + mau: black, day: 1pt, dut: false, dong: false, to: none, mui-ten: false, + ten: none, tai: 0.5, huong: auto, cach: 6pt, ten-quay: false, mau-ten: auto, +) = { + // gom đối số vị trí thành danh sách các nét + let nets = () + let dem = () + for x in noi-dung.pos() { + if _la-diem(x) { dem.push(x) } else { + if dem.len() > 0 { nets.push(duong(..dem)); dem = () } + if _la-duong(x) { nets.push(x) } else if type(x) == array { nets.push(duong(x)) } + } + } + if dem.len() > 0 { nets.push(duong(..dem)) } + // vẽ từng nét: giá trị auto của nét lấy theo tuỳ chọn chung + for n in nets { + let ly(khoa, chung) = if n.at(khoa) == auto { chung } else { n.at(khoa) } + let (m, d, du) = (ly("mau", mau), ly("day", day), ly("dut", dut)) + let (dg, tt) = (ly("dong", dong), ly("to", to)) + let pts = n.diem + if pts.len() == 0 { continue } + if pts.len() == 1 { diem(ctx, pts.at(0), mau: m); continue } + if tt != none { da-giac-pt(pts.map(P => toa-pt(ctx, P)), to: tt) } + let cd = if dg or tt != none { pts + (pts.at(0),) } else { pts } + for i in range(cd.len() - 1) { + doan(ctx, cd.at(i), cd.at(i + 1), mau: m, day: d, dut: du) + } + if ly("mui-ten", mui-ten) { + dau-mui-ten(ctx, cd.at(cd.len() - 2), cd.at(cd.len() - 1), mau: m) + } + // nhãn: đặt tại tỉ lệ `tai` tính theo TỔNG chiều dài nét (trên trang) + let nh = ly("ten", ten) + if nh != none { + let t = ly("tai", tai) + let dai = range(cd.len() - 1).map(i => { + let a = toa-pt(ctx, cd.at(i)) + let b = toa-pt(ctx, cd.at(i + 1)) + calc.sqrt(calc.pow(b.at(0) - a.at(0), 2) + calc.pow(b.at(1) - a.at(1), 2)) + }) + let tong = dai.sum() + let moc = calc.max(calc.min(t, 1), 0) * tong + let i = 0 + let con = moc + while i < dai.len() - 1 and con > dai.at(i) { con = con - dai.at(i); i = i + 1 } + let ti = if dai.at(i) > 0.0001 { con / dai.at(i) } else { 0.5 } + let (U, V) = (cd.at(i), cd.at(i + 1)) + let P = (U.at(0) + ti * (V.at(0) - U.at(0)), U.at(1) + ti * (V.at(1) - U.at(1))) + let hg = ly("huong", huong) + let mt = ly("mau-ten", mau-ten) + let xuoi = toa-pt(ctx, U).at(0) <= toa-pt(ctx, V).at(0) + nhan( + ctx, P, nh, + huong: if hg == auto { _phap-tuyen(ctx, U, V) } else { hg }, + cach: ly("cach", cach), + mau: if mt == auto { m } else { mt }, + quay: if ly("ten-quay", ten-quay) { + if xuoi { goc-truc(ctx, U, V) } else { goc-truc(ctx, V, U) } + } else { 0deg }, + ) + } + } +} + +// ---------- cac-diem: CHẤM + ĐẶT TÊN NHIỀU ĐIỂM TRONG MỘT LỆNH ---------- +// Mỗi đối số vị trí là một điểm, viết theo 1 trong 4 dạng: +// A -> chỉ chấm, không nhãn +// (A, $A$) -> chấm + nhãn, hướng lấy theo `huong` chung +// (A, $A$, "left") -> + hướng riêng +// (A, $A$, "left", red) -> + màu riêng (cả chấm lẫn chữ nếu mau-ten: auto) +// Tuỳ chọn chung: mau · bk (bán kính chấm) · huong · cach (nhãn cách chấm) +// · mau-ten (auto = theo màu chấm). +// cac-diem( +// (A, $A$, "below-left"), (B, $B$, "below-right"), (C, $C$, "above"), +// mau: blue, bk: 2.2pt, +// ) +/// Chấm + đặt tên nhiều điểm một lệnh. Mỗi mục: `A` | `(A, $A$)` | +/// `(A, $A$, "left")` | `(A, $A$, "left", red)`. +#let cac-diem( + ctx, ..noi-dung, + mau: black, bk: 2pt, huong: "above", cach: 6pt, mau-ten: black, +) = { + for x in noi-dung.pos() { + let la-diem = type(x) == array and x.len() == 2 and type(x.at(0)) != array + let P = if la-diem { x } else { x.at(0) } + let t = if la-diem { none } else { x.at(1, default: none) } + let h = if la-diem { auto } else { x.at(2, default: auto) } + let m = if la-diem { auto } else { x.at(3, default: auto) } + let mc = if m == auto { mau } else { m } + diem( + ctx, P, ten: t, + huong: if h == auto { huong } else { h }, + bk: bk, mau: mc, cach: cach, + mau-ten: if mau-ten == auto or m != auto { mc } else { mau-ten }, + ) + } +} + +// Dãy điểm trên cung elip tâm O bán trục (a, b), góc tu -> den. +#let diem-cung(O, a, b, tu, den, n: 48) = range(n + 1).map(i => { + let t = tu + (den - tu) * (i / n) + (O.at(0) + a * calc.cos(t), O.at(1) + b * calc.sin(t)) +}) + +// Đường tròn tâm O bán kính r (theo đơn vị trục x; nếu 2 trục cùng tỉ lệ +// thì là đường tròn thật, khác tỉ lệ sẽ thành elip tương ứng). +#let duong-tron(ctx, O, r, mau: black, day: 1pt, dut: false, to: none) = { + let c = toa(ctx, O) + let rx = r * ctx.sx + let ry = r * ctx.sy + place( + dx: c.at(0) - rx, dy: c.at(1) - ry, + ellipse(width: 2 * rx, height: 2 * ry, stroke: net(mau, day, dut), fill: to), + ) +} + +// Elip tâm O, bán trục a (ngang), b (dọc); quay: góc xoay quanh tâm +// (dương = ngược chiều kim đồng hồ), n: số mẫu khi vẽ elip xoay. +#let elip(ctx, O, a, b, mau: black, day: 1pt, dut: false, to: none, quay: 0deg, n: 72) = { + if quay == 0deg and not ("bien-doi" in ctx and ctx.bien-doi != none) { + let c = toa(ctx, O) + let rx = a * ctx.sx + let ry = b * ctx.sy + place( + dx: c.at(0) - rx, dy: c.at(1) - ry, + ellipse(width: 2 * rx, height: 2 * ry, stroke: net(mau, day, dut), fill: to), + ) + } else { + // elip xoay (hoặc ctx có phép biến hình): vẽ theo dãy điểm quanh chu vi + let pts = diem-cung(O, a, b, 0deg, 360deg, n: n).map(P => quay-diem(P, O, quay)) + if to != none { da-giac-pt(pts.map(P => toa-pt(ctx, P)), to: to) } + duong-cong(ctx, pts, mau: mau, day: day, dut: dut, dong: true) + } +} + +// Cung tròn tâm O bán kính r từ góc `tu` đến `den` (kiểu angle, vd 30deg). +#let cung(ctx, O, r, tu: 0deg, den: 180deg, mau: black, day: 1pt, dut: false, n: 48, quay: 0deg) = { + duong-cong(ctx, diem-cung(O, r, r, tu + quay, den + quay, n: n), mau: mau, day: day, dut: dut) +} + +// Cung elip (dùng cho hình không gian: đáy nón, trụ...). +#let cung-elip(ctx, O, a, b, tu: 0deg, den: 180deg, mau: black, day: 1pt, dut: false, n: 48, quay: 0deg) = { + let pts = diem-cung(O, a, b, tu, den, n: n) + if quay != 0deg { pts = pts.map(P => quay-diem(P, O, quay)) } + duong-cong(ctx, pts, mau: mau, day: day, dut: dut) +} + +// ---------- Đường xoắn ốc ---------- +// Xoắn ốc Archimedes tâm O: bán kính TĂNG ĐỀU theo góc, quét từ `tu` đến `den`. +// Góc quét |den - tu| ĐƯỢC PHÉP lớn hơn 360° (nhiều vòng); +// den > tu = ngược chiều kim đồng hồ (chiều dương), den < tu = cùng chiều kim đồng hồ. +// r : bán kính tại góc `tu` +// r-cuoi : bán kính tại góc `den` (auto = r + buoc · số vòng quét) +// buoc : khoảng cách giữa hai vòng liên tiếp (dùng khi r-cuoi: auto) +// mui-ten : true|"cuoi" (đầu mũi tên ở điểm cuối) · "dau" · "ca-hai" · false +// ten/huong/cach/mau-ten : nhãn đặt cạnh điểm cuối của xoắn ốc +#let xoan-oc( + ctx, O, + tu: 0deg, den: 360deg, + r: 0.12, r-cuoi: auto, buoc: 0.16, + mau: black, day: 1pt, dut: false, + mui-ten: true, kich: 7pt, + n: auto, + ten: none, huong: "above", cach: 6pt, mau-ten: auto, +) = { + let quet = den - tu + let so-vong = calc.abs(quet.deg()) / 360 + let r2 = if r-cuoi == auto { r + buoc * so-vong } else { r-cuoi } + let m = if n == auto { calc.max(32, int(calc.ceil(so-vong * 72))) } else { n } + let pts = range(m + 1).map(i => { + let s = i / m + let a = tu + quet * s + let rr = r + (r2 - r) * s + (O.at(0) + rr * calc.cos(a), O.at(1) + rr * calc.sin(a)) + }) + duong-cong(ctx, pts, mau: mau, day: day, dut: dut) + if mui-ten == true or mui-ten == "cuoi" or mui-ten == "ca-hai" { + dau-mui-ten(ctx, pts.at(m - 1), pts.at(m), mau: mau, kich: kich) + } + if mui-ten == "dau" or mui-ten == "ca-hai" { + dau-mui-ten(ctx, pts.at(1), pts.at(0), mau: mau, kich: kich) + } + if ten != none { + nhan( + ctx, pts.at(m), ten, + huong: huong, cach: cach, + mau: if mau-ten == auto { mau } else { mau-ten }, + ) + } +} + +// Góc lượng giác (OA, OM): xoắn ốc từ tia OA quay tới tia OM (kiểu SGK 11). +// chieu : "duong" = ngược chiều kim đồng hồ (mặc định) · "am" = cùng chiều +// vong : số vòng quay THÊM (0, 1, 2, ...) trước khi dừng ở tia OM +// so-do : true = tự ghi số đo (vd -430°) cạnh mũi tên; `ten` được ưu tiên +// Ví dụ hình "AOM = 70°, (OA, OM) = -430°": goc-luong-giac(O, A, M, chieu: "am", vong: 1) +#let goc-luong-giac( + ctx, O, A, M, + chieu: "duong", vong: 0, + r: auto, r-cuoi: auto, buoc: auto, + mau: blue, day: 0.9pt, dut: false, kich: 6pt, + ten: none, so-do: false, huong: "above", cach: 6pt, mau-ten: auto, + n: auto, +) = { + let am = chieu in ("am", "-", -1) or chieu == false + // r, buoc mặc định tính THEO chiều dài tia (để hình nào cũng cân đối) + let dai = calc.min( + calc.sqrt(calc.pow(A.at(0) - O.at(0), 2) + calc.pow(A.at(1) - O.at(1), 2)), + calc.sqrt(calc.pow(M.at(0) - O.at(0), 2) + calc.pow(M.at(1) - O.at(1), 2)), + ) + if dai <= 0 { dai = 1 } + let r = if r == auto { 0.12 * dai } else { r } + let buoc = if buoc == auto { 0.14 * dai } else { buoc } + let a1 = calc.atan2(A.at(0) - O.at(0), A.at(1) - O.at(1)) + let a2 = calc.atan2(M.at(0) - O.at(0), M.at(1) - O.at(1)) + let d = a2 - a1 + while d < 0deg { d = d + 360deg } + while d >= 360deg { d = d - 360deg } + if am and d != 0deg { d = d - 360deg } + let tong = d + (if am { -1 } else { 1 }) * vong * 360deg + let nd = if ten != none { ten } else if so-do { + let v = calc.round(tong.deg(), digits: 1) + let s = if calc.abs(v - calc.round(v)) < 0.01 { str(int(calc.round(v))) } else { str(v) } + [#s#sym.degree] + } else { none } + xoan-oc( + ctx, O, tu: a1, den: a1 + tong, + r: r, r-cuoi: r-cuoi, buoc: buoc, + mau: mau, day: day, dut: dut, kich: kich, n: n, + ten: nd, huong: huong, cach: cach, mau-ten: mau-ten, + ) +} + +// ---------- Đường cong uốn lượn (Bézier kiểu \draw ... controls ... của TikZ) ---------- +// `dieu-khien(c1, c2)` — điểm điều khiển của MỘT đoạn cong, đặt XEN GIỮA hai +// điểm neo (giống `.. controls (c1) and (c2) ..` của TikZ): +// duong-luon(A, dieu-khien(c1, c2), B, dieu-khien(c3, c4), C) +// dieu-khien(c1) // một điểm điều khiển (c2 = c1) +// dieu-khien(c1, auto) // c1 tự chọn cho mềm, c2 do người dùng đặt (auto lấy Catmull-Rom) +// Đoạn KHÔNG có dieu-khien giữa hai neo -> tự làm MỀM bằng Catmull-Rom +// (đường trơn đi qua đúng các điểm neo, khỏi tính điểm điều khiển bằng tay). +#let dieu-khien(..a) = { + let p = a.pos() + let c1 = p.at(0, default: auto) + let c2 = p.at(1, default: c1) + (bg-dk: true, c1: c1, c2: c2) +} +#let _la-dk(x) = type(x) == dictionary and x.at("bg-dk", default: false) + +// Lấy MẢNG điểm mẫu của đường uốn lượn (dùng chung cho vẽ và cho nhãn bám đường). +// items: dãy đối số vị trí (điểm neo trộn với dieu-khien(...)); n: mẫu mỗi đoạn. +#let _luon-mau(items, n, dong) = { + let anchors = () + let ctrls = () // ctrls.at(k) = dieu-khien của đoạn anchors[k] -> anchors[k+1] + let pending = none + for x in items { + if _la-dk(x) { pending = x } + else if _la-diem(x) { + if anchors.len() > 0 { ctrls.push(pending); pending = none } + anchors.push(x) + } + } + if anchors.len() < 2 { return anchors } + if dong { + ctrls.push(pending) // điều khiển của đoạn khép kín (cuối -> đầu) + anchors.push(anchors.at(0)) + } + let m = anchors.len() + let getc = i => anchors.at(calc.max(0, calc.min(m - 1, i))) + let getw = i => { + if dong { + let base = m - 1 + anchors.at(calc.rem(calc.rem(i, base) + base, base)) + } else { getc(i) } + } + let pts = (anchors.at(0),) + for k in range(m - 1) { + let p1 = anchors.at(k) + let p2 = anchors.at(k + 1) + let p0 = getw(k - 1) + let p3 = getw(k + 2) + // điểm điều khiển Catmull-Rom (đường trơn qua các neo) + let ec1 = (p1.at(0) + (p2.at(0) - p0.at(0)) / 6, p1.at(1) + (p2.at(1) - p0.at(1)) / 6) + let ec2 = (p2.at(0) - (p3.at(0) - p1.at(0)) / 6, p2.at(1) - (p3.at(1) - p1.at(1)) / 6) + let dk = ctrls.at(k, default: none) + let c1 = if dk == none or dk.c1 == auto { ec1 } else { dk.c1 } + let c2 = if dk == none or dk.c2 == auto { ec2 } else { dk.c2 } + for j in range(1, n + 1) { + let t = j / n + let u = 1 - t + let x = u * u * u * p1.at(0) + 3 * u * u * t * c1.at(0) + 3 * u * t * t * c2.at(0) + t * t * t * p2.at(0) + let y = u * u * u * p1.at(1) + 3 * u * u * t * c1.at(1) + 3 * u * t * t * c2.at(1) + t * t * t * p2.at(1) + pts.push((x, y)) + } + } + pts +} + +// TRẢ VỀ mảng điểm mẫu của đường uốn lượn (dùng cho nhan-cong hoặc để vẽ lại). +// KHÔNG cần ctx (chỉ tính trên toạ độ toán). +#let diem-luon(..noi-dung, n: 16, dong: false) = _luon-mau(noi-dung.pos(), n, dong) + +// Vẽ đường cong uốn lượn qua các điểm neo, điểm điều khiển kiểu `controls` TikZ. +// duong-luon(A, B, C, D) // trơn tự động qua 4 neo +// duong-luon(A, dieu-khien(c1, c2), B) // Bézier bậc ba một đoạn +// duong-luon(A, dieu-khien(c1,c2), B, C, dong: true, to: blue.lighten(85%)) +// Tuỳ chọn: mau · day · dut · dong (khép kín) · to (tô) · mui-ten · kich · +// ten/tai/huong/cach/ten-quay/mau-ten (nhãn đặt theo TỈ LỆ độ dài, như cac-doan) · +// n (số mẫu mỗi đoạn, tăng cho mượt). +#let duong-luon( + ctx, ..noi-dung, + mau: black, day: 1pt, dut: false, dong: false, to: none, + mui-ten: false, kich: 7pt, + ten: none, tai: 0.5, huong: auto, cach: 6pt, ten-quay: false, mau-ten: auto, + n: 16, +) = { + let pts = _luon-mau(noi-dung.pos(), n, dong) + if pts.len() < 2 { return } + if to != none { da-giac-pt(pts.map(P => toa-pt(ctx, P)), to: to) } + duong-cong(ctx, pts, mau: mau, day: day, dut: dut) + if mui-ten { + dau-mui-ten(ctx, pts.at(pts.len() - 2), pts.at(pts.len() - 1), mau: mau, kich: kich) + } + if ten != none { + let pg = pts.map(P => toa-pt(ctx, P)) + let dai = range(pg.len() - 1).map(i => { + let a = pg.at(i) + let b = pg.at(i + 1) + calc.sqrt(calc.pow(b.at(0) - a.at(0), 2) + calc.pow(b.at(1) - a.at(1), 2)) + }) + let tong = dai.sum() + let moc = calc.max(calc.min(tai, 1), 0) * tong + let i = 0 + let con = moc + while i < dai.len() - 1 and con > dai.at(i) { con = con - dai.at(i); i = i + 1 } + let ti = if dai.at(i) > 0.0001 { con / dai.at(i) } else { 0.5 } + let U = pts.at(i) + let V = pts.at(i + 1) + let P = (U.at(0) + ti * (V.at(0) - U.at(0)), U.at(1) + ti * (V.at(1) - U.at(1))) + let xuoi = toa-pt(ctx, U).at(0) <= toa-pt(ctx, V).at(0) + nhan( + ctx, P, ten, + huong: if huong == auto { _phap-tuyen(ctx, U, V) } else { huong }, + cach: cach, + mau: if mau-ten == auto { mau } else { mau-ten }, + quay: if ten-quay { if xuoi { goc-truc(ctx, U, V) } else { goc-truc(ctx, V, U) } } else { 0deg }, + ) + } +} + +// ---------- Nhãn chữ BÁM THEO đường cong ---------- +// Chuỗi văn bản từ nội dung (str hoặc content đơn giản). +#let _chuoi-cong(c) = { + if type(c) == str { c } + else if type(c) == content { + if c.has("text") { c.text } + else if c.has("children") { c.children.map(_chuoi-cong).fold("", (a, b) => a + b) } + else if c.has("body") { _chuoi-cong(c.body) } + else { "" } + } else { str(c) } +} + +// Đặt từng ký tự của `chu` DỌC THEO đường `duong` (mảng điểm toạ độ toán, vd +// lấy từ diem-luon / diem-cung / lay-mau), tự xoay tiếp tuyến, canh theo độ dài cung. +// duong: mảng điểm (>= 2). chu: str (khuyên dùng) hoặc content văn bản thuần. +// tu : vị trí BẮT ĐẦU theo tỉ lệ độ dài đường (0 = đầu, 1 = cuối, .5 = giữa) +// can : "trai" (bắt đầu tại `tu`) · "giua" (canh giữa chữ quanh `tu`) · "phai" +// khoang: giãn cách thêm giữa các ký tự (length) +// co : cỡ chữ (auto = theo cỡ hiện hành) · mau : màu chữ +// phia : "tren" (chữ nằm TRÊN đường) · "duoi" · "giua" (tâm chữ trên đường) +// cach : khoảng hở giữa chữ và đường (khi phia = tren/duoi) +// dao : true = đi ngược đường (khi đường vẽ từ phải sang trái, chữ khỏi lộn ngược) +// Ví dụ: nhan-cong(diem-cung((0,0), 2, 2, 20deg, 160deg), "cung tron") +#let nhan-cong( + ctx, duong, chu, + tu: 0, can: "trai", khoang: 0pt, + co: auto, mau: black, phia: "tren", cach: 2pt, dao: false, +) = { + let pts = if dao { duong.rev() } else { duong } + let txt = _chuoi-cong(chu) + let cl = txt.clusters() + if pts.len() < 2 or cl.len() == 0 { return } + let pg = pts.map(P => toa-pt(ctx, P)) + let cum = (0.0,) + for i in range(pg.len() - 1) { + let a = pg.at(i) + let b = pg.at(i + 1) + cum.push(cum.at(i) + calc.sqrt(calc.pow(b.at(0) - a.at(0), 2) + calc.pow(b.at(1) - a.at(1), 2))) + } + let tong = cum.at(cum.len() - 1) + context { + let els = cl.map(g => if co == auto { text(fill: mau, g) } else { text(size: co, fill: mau, g) }) + let ws = els.map(e => measure(e).width.pt()) + let totw = ws.sum() + khoang.pt() * calc.max(0, els.len() - 1) + let s0 = tu * tong + if can == "giua" { s0 = s0 - totw / 2 } else if can == "phai" { s0 = s0 - totw } + let cursor = s0 + for idx in range(els.len()) { + let w = ws.at(idx) + let mid = calc.max(0, calc.min(tong, cursor + w / 2)) + let i = 0 + while i < cum.len() - 2 and cum.at(i + 1) < mid { i = i + 1 } + let seg = cum.at(i + 1) - cum.at(i) + let t = if seg > 0.000001 { (mid - cum.at(i)) / seg } else { 0 } + let A = pg.at(i) + let B = pg.at(i + 1) + let px = A.at(0) + t * (B.at(0) - A.at(0)) + let py = A.at(1) + t * (B.at(1) - A.at(1)) + let dx = B.at(0) - A.at(0) + let dy = B.at(1) - A.at(1) + let ll = calc.sqrt(dx * dx + dy * dy) + let (ux, uy) = if ll > 0.000001 { (dx / ll, dy / ll) } else { (1, 0) } + let ang = calc.atan2(dx, dy) // góc của vectơ (dx, dy) trên TRANG (y xuống) + let el = els.at(idx) + let hh = measure(el).height.pt() + let off = if phia == "tren" { cach.pt() + hh / 2 } else if phia == "duoi" { -(cach.pt() + hh / 2) } else { 0 } + let cx = px + uy * off // pháp tuyến hướng "lên" trên trang: (uy, -ux) + let cy = py + (-ux) * off + place( + dx: (cx - w / 2) * 1pt, + dy: (cy - hh / 2) * 1pt, + rotate(ang, reflow: false, el), + ) + cursor = cursor + w + khoang.pt() + } + } +} + +// ---------- Đánh dấu góc ---------- +// Góc (thường, không vuông) giữa hai tia O->A và O->B: +// cung nhỏ + nhãn + số cung (1..3) + tô quạt + tự ghi số đo. +// to : màu tô hình quạt (nên trong suốt, vd rgb(255, 170, 0, 70)) +// so-do : true = tự ghi số đo góc (vd 60°) khi không đặt `ten` +// vach : 1..3 vạch CẮT NGANG cung (ký hiệu hai góc bằng nhau, kiểu SGK). +// Khác `so-cung` (vẽ nhiều cung đồng tâm) — `vach` chỉ vẽ MỘT cung +// rồi gạch 1/2/3 vạch nhỏ vuông góc cung tại giữa cung. +// Bí danh `vach-danh-dau:` dùng được y hệt. +/// Đánh dấu góc (không vuông) TẠI O, giữa hai tia O→A và O→B. +/// ĐỈNH là đối số ĐẦU. Quen lối TikZ (đỉnh ở giữa) thì dùng `ve-goc(A, O, B)`. +/// `ten:` nhãn · `so-do: true` tự ghi số đo · `vach: 1..3` vạch góc bằng nhau · +/// `so-cung: 1..3` cung đồng tâm · `to:` tô quạt · `cach-nhan:` nhãn xa/gần cung. +#let goc( + ctx, O, A, B, + r: 0.45, // bán kính cung (đơn vị toạ độ) + ten: none, + so-do: false, // tự ghi số đo (làm tròn 0.1°); `ten` được ưu tiên + so-cung: 1, + vach: 0, // số vạch đánh dấu cắt ngang cung (0 = không) + vach-danh-dau: auto, // bí danh của `vach` + dai-vach: 6pt, // độ dài mỗi vạch + to: none, // tô màu hình quạt của góc + mau: black, day: 0.8pt, + cach-nhan: 1.9, // hệ số đặt nhãn so với r +) = { + let vach = if vach-danh-dau == auto { vach } else { vach-danh-dau } + let a1 = calc.atan2(A.at(0) - O.at(0), A.at(1) - O.at(1)) + let a2 = calc.atan2(B.at(0) - O.at(0), B.at(1) - O.at(1)) + let d = a2 - a1 + if d > 180deg { d = d - 360deg } + if d < -180deg { d = d + 360deg } + if to != none { + da-giac-pt( + ((O,) + diem-cung(O, r, r, a1, a1 + d, n: 32)).map(P => toa-pt(ctx, P)), + to: to, + ) + } + for i in range(so-cung) { + let rr = r * (1 + 0.22 * i) + cung(ctx, O, rr, tu: a1, den: a1 + d, mau: mau, day: day, n: 24) + } + // Vạch đánh dấu cắt ngang cung (ký hiệu góc bằng nhau) + if vach > 0 { + let tm = a1 + d / 2 + let opt = toa-pt(ctx, O) + let ppt = toa-pt(ctx, (O.at(0) + r * calc.cos(tm), O.at(1) + r * calc.sin(tm))) + let rp = calc.sqrt( + calc.pow(ppt.at(0) - opt.at(0), 2) + calc.pow(ppt.at(1) - opt.at(1), 2) + ) + if rp > 0.01 { + let da = (3.4 / rp) * 1rad // khoảng cách 2 vạch ~3.4pt trên cung + let hd = dai-vach.pt() / 2 + for i in range(vach) { + let ang = tm + (i - (vach - 1) / 2) * da + let q = toa-pt(ctx, (O.at(0) + r * calc.cos(ang), O.at(1) + r * calc.sin(ang))) + let l = calc.sqrt( + calc.pow(q.at(0) - opt.at(0), 2) + calc.pow(q.at(1) - opt.at(1), 2) + ) + let (ux, uy) = ((q.at(0) - opt.at(0)) / l, (q.at(1) - opt.at(1)) / l) + doan-pt( + (q.at(0) - hd * ux, q.at(1) - hd * uy), + (q.at(0) + hd * ux, q.at(1) + hd * uy), + mau: mau, day: day, + ) + } + } + } + let nd = if ten != none { ten } else if so-do { + let dd = if d < 0deg { -d } else { d } + let v = calc.round(dd.deg(), digits: 1) + let s = if calc.abs(v - calc.round(v)) < 0.01 { str(int(calc.round(v))) } else { str(v) } + $#s degree$ + } else { none } + if nd != none { + let tm = a1 + d / 2 + nhan( + ctx, + (O.at(0) + r * cach-nhan * calc.cos(tm), O.at(1) + r * cach-nhan * calc.sin(tm)), + nd, huong: "giua", mau: mau, + ) + } +} + +// Ký hiệu góc vuông tại O (giữa hai tia O->A, O->B). +/// Ký hiệu góc vuông TẠI O (giữa hai tia O→A, O→B). ĐỈNH là đối số ĐẦU. +/// Quen lối TikZ (đỉnh ở giữa) thì dùng `ve-goc-vuong(A, O, B)`. +#let goc-vuong(ctx, O, A, B, r: 0.32, mau: black, day: 0.8pt) = { + let ka = calc.sqrt(calc.pow(A.at(0) - O.at(0), 2) + calc.pow(A.at(1) - O.at(1), 2)) + let kb = calc.sqrt(calc.pow(B.at(0) - O.at(0), 2) + calc.pow(B.at(1) - O.at(1), 2)) + if ka == 0 or kb == 0 { return } + let u = ((A.at(0) - O.at(0)) / ka * r, (A.at(1) - O.at(1)) / ka * r) + let v = ((B.at(0) - O.at(0)) / kb * r, (B.at(1) - O.at(1)) / kb * r) + let P1 = (O.at(0) + u.at(0), O.at(1) + u.at(1)) + let P2 = (O.at(0) + u.at(0) + v.at(0), O.at(1) + u.at(1) + v.at(1)) + let P3 = (O.at(0) + v.at(0), O.at(1) + v.at(1)) + duong-cong(ctx, (P1, P2, P3), mau: mau, day: day) +} + +// ---------- Lối viết TikZ: ĐỈNH góc đặt ở GIỮA ---------- +// `goc`/`goc-vuong` ở trên đặt ĐỈNH góc làm đối số ĐẦU: goc-vuong(O, A, B). +// TikZ lại viết đỉnh ở GIỮA (pic angle = A--O--B), nên hai hàm dưới đây nhận +// thứ tự quen thuộc đó — đỉnh là đối số THỨ HAI: +// ve-goc(A, O, B, ...) <=> goc(O, A, B, ...) +// ve-goc-vuong(A, O, B, ...) <=> goc-vuong(O, A, B, ...) +// Mọi tuỳ chọn (r, ten, so-do, so-cung, vach, to, mau, day, cach-nhan…) giữ +// nguyên tên và được truyền thẳng xuống hàm gốc, nên hai lối dùng lẫn được +// trong cùng một hình. Hàm gốc KHÔNG đổi ⇒ mọi bài cũ chạy y như trước. +/// Đánh dấu góc TẠI O theo lối TikZ: ĐỈNH nằm GIỮA — `ve-goc(A, O, B)`. +/// Cùng bộ tuỳ chọn với `goc` (r, ten, so-do, so-cung, vach, to, mau, day, cach-nhan). +#let ve-goc(ctx, A, O, B, ..tuy-chon) = goc(ctx, O, A, B, ..tuy-chon) + +/// Ký hiệu góc vuông TẠI O theo lối TikZ: ĐỈNH nằm GIỮA — `ve-goc-vuong(A, O, B)`. +/// Cùng bộ tuỳ chọn với `goc-vuong` (r, mau, day). +#let ve-goc-vuong(ctx, A, O, B, ..tuy-chon) = goc-vuong(ctx, O, A, B, ..tuy-chon) + +// ---------- Đánh dấu đoạn bằng nhau (1..3 vạch tại trung điểm) ---------- +// so : số vạch song song (1/2/3…) tại trung điểm đoạn. +// nghieng: góc NGHIÊNG của vạch so với pháp tuyến đoạn (0deg = vuông góc như cũ); +// đặt ~20–30deg cho kiểu vạch chéo "/" "//" "///". +// cheo : true = mỗi mốc vẽ dấu CHÉO NHAU "✕" (hai vạch ±góc); khi đó `nghieng` +// = nửa góc mở của dấu ✕ (mặc định 30deg nếu để 0deg). +#let danh-dau(ctx, A, B, so: 1, dai: 6pt, mau: black, day: 1pt, + nghieng: 0deg, cheo: false) = { + let a = toa-pt(ctx, A) + let b = toa-pt(ctx, B) + let dx = b.at(0) - a.at(0) + let dy = b.at(1) - a.at(1) + let l = calc.sqrt(dx * dx + dy * dy) + if l == 0 { return } + let ux = dx / l + let uy = dy / l + let m = ((a.at(0) + b.at(0)) / 2, (a.at(1) + b.at(1)) / 2) + let hd = dai.pt() / 2 + // vẽ MỘT vạch tại tâm c, nghiêng góc g so với pháp tuyến (−uy, ux) của đoạn. + let mot-vach(c, g) = { + let cs = calc.cos(g) + let sn = calc.sin(g) + // pháp tuyến p = (−uy, ux); tiếp tuyến u = (ux, uy) + // vạch nghiêng: v = cos(g)·p + sin(g)·u + let vx = -uy * cs + ux * sn + let vy = ux * cs + uy * sn + doan-pt( + (c.at(0) - hd * vx, c.at(1) - hd * vy), + (c.at(0) + hd * vx, c.at(1) + hd * vy), + mau: mau, day: day, + ) + } + for i in range(so) { + let t = (i - (so - 1) / 2) * 3.2 + let c = (m.at(0) + t * ux, m.at(1) + t * uy) + if cheo { + let g = if nghieng == 0deg { 30deg } else { nghieng } + mot-vach(c, g) + mot-vach(c, -g) + } else { + mot-vach(c, nghieng) + } + } +} + +// ---------- Tiện ích hình học phẳng (tính toán) ---------- +/// TRẢ VỀ trung điểm của A và B (chỉ tính, không vẽ). +#let trung-diem(A, B) = ((A.at(0) + B.at(0)) / 2, (A.at(1) + B.at(1)) / 2) + +#let khoang-cach(A, B) = calc.sqrt( + calc.pow(B.at(0) - A.at(0), 2) + calc.pow(B.at(1) - A.at(1), 2) +) + +// Điểm chia: A + t*(B - A). +/// TRẢ VỀ điểm M trên đường AB với AM/AB = t (t = 0.5 là trung điểm, +/// t > 1 là kéo dài quá B, t < 0 là ngược về phía A). +#let chia(A, B, t) = ( + A.at(0) + t * (B.at(0) - A.at(0)), + A.at(1) + t * (B.at(1) - A.at(1)), +) + +// Chân đường vuông góc hạ từ P xuống đường thẳng AB. +/// TRẢ VỀ hình chiếu vuông góc của P lên đường thẳng AB (chân đường cao). +#let hinh-chieu(P, A, B) = { + let ux = B.at(0) - A.at(0) + let uy = B.at(1) - A.at(1) + let t = ((P.at(0) - A.at(0)) * ux + (P.at(1) - A.at(1)) * uy) / (ux * ux + uy * uy) + (A.at(0) + t * ux, A.at(1) + t * uy) +} + +// ---------- Cửa sổ khung vừa khít hình (TRẢ GIÁ TRỊ, không vẽ) ---------- +// Gom mọi điểm mà một đối tượng chiếm chỗ (nội bộ của khung-vua). +#let _diem-cua(m) = { + if type(m) != array or m.len() == 0 { return () } + let so = (int, float) + if m.len() == 2 and type(m.at(0)) in so and type(m.at(1)) in so { + (m,) // một ĐIỂM (x, y) + } else if m.len() == 2 and type(m.at(0)) == array and type(m.at(1)) in so { + let (O, r) = m // một ĐƯỜNG TRÒN (tâm, bán kính) + ( + (O.at(0) - r, O.at(1)), (O.at(0) + r, O.at(1)), + (O.at(0), O.at(1) - r), (O.at(0), O.at(1) + r), + ) + } else { + let ds = () + for x in m { ds += _diem-cua(x) } // mảng điểm / mảng lồng nhau + ds + } +} + +// Cửa sổ toạ độ vừa khít các đối tượng truyền vào — để hình KHÔNG tràn ra +// ngoài khung. Mỗi đối số là một ĐIỂM (x, y), một ĐƯỜNG TRÒN (tâm, bán kính), +// hoặc một MẢNG gồm các thứ đó. +// #hinh(w: 5cm, ..khung-vua((A, B, C), (O, R)), ctx => { ... }) +// le: chừa lề quanh hình, tính theo TỈ LỆ cạnh lớn của hình (0.12 = 12%). +/// TRẢ VỀ cửa sổ `(xmin:, xmax:, ymin:, ymax:)` vừa khít các điểm/đường tròn +/// truyền vào, để rải thẳng vào `#hinh`: +/// `#hinh(w: 5cm, ..khung-vua((A, B, C), (O, R)), ctx => { … })`. +#let khung-vua(..muc, le: 0.12) = { + let ds = () + for m in muc.pos() { ds += _diem-cua(m) } + if ds.len() == 0 { return (xmin: -5, xmax: 5, ymin: -4, ymax: 4) } + let xs = ds.map(P => P.at(0)) + let ys = ds.map(P => P.at(1)) + let (x1, x2) = (calc.min(..xs), calc.max(..xs)) + let (y1, y2) = (calc.min(..ys), calc.max(..ys)) + let d = calc.max(x2 - x1, y2 - y1, 0.001) + let m = d * le + (xmin: x1 - m, xmax: x2 + m, ymin: y1 - m, ymax: y2 + m) +} + +// Tâm đường tròn ngoại tiếp tam giác ABC. +#let tam-ngoai-tiep(A, B, C) = { + let d = 2 * (A.at(0) * (B.at(1) - C.at(1)) + B.at(0) * (C.at(1) - A.at(1)) + C.at(0) * (A.at(1) - B.at(1))) + let a2 = A.at(0) * A.at(0) + A.at(1) * A.at(1) + let b2 = B.at(0) * B.at(0) + B.at(1) * B.at(1) + let c2 = C.at(0) * C.at(0) + C.at(1) * C.at(1) + ( + (a2 * (B.at(1) - C.at(1)) + b2 * (C.at(1) - A.at(1)) + c2 * (A.at(1) - B.at(1))) / d, + (a2 * (C.at(0) - B.at(0)) + b2 * (A.at(0) - C.at(0)) + c2 * (B.at(0) - A.at(0))) / d, + ) +} + +// Đường tròn ĐI QUA một dãy điểm — dùng cho đường tròn ngoại tiếp ĐA GIÁC. +// 3 điểm: đúng bằng đường tròn ngoại tiếp tam giác. Nhiều hơn 3: khớp theo +// bình phương bé nhất (đa giác NỘI TIẾP ĐƯỢC vẫn cho đúng đường tròn của nó, +// đa giác vẽ hơi lệch thì cho đường tròn sát nhất — không lệch hẳn về 3 đỉnh +// đầu như khi chỉ lấy 3 điểm). +// TRẢ VỀ (tâm, bán kính). +#let tron-qua-diem(ds) = { + let n = ds.len() + if n < 3 { panic("tron-qua-diem: cần ít nhất 3 điểm") } + let xm = ds.map(P => P.at(0)).sum() / n + let ym = ds.map(P => P.at(1)).sum() / n + let us = ds.map(P => P.at(0) - xm) + let vs = ds.map(P => P.at(1) - ym) + let s(f) = range(n).map(i => f(us.at(i), vs.at(i))).sum() + let suu = s((u, v) => u * u) + let svv = s((u, v) => v * v) + let suv = s((u, v) => u * v) + let a1 = (s((u, v) => u * u * u) + s((u, v) => u * v * v)) / 2 + let a2 = (s((u, v) => v * v * v) + s((u, v) => v * u * u)) / 2 + let d = suu * svv - suv * suv + if calc.abs(d) < 1e-12 { panic("tron-qua-diem: các điểm thẳng hàng") } + let uc = (a1 * svv - a2 * suv) / d + let vc = (a2 * suu - a1 * suv) / d + ( + (xm + uc, ym + vc), + calc.sqrt(uc * uc + vc * vc + (suu + svv) / n), + ) +} + +// Tâm đường tròn nội tiếp + bán kính: trả về (I, r). +#let tam-noi-tiep(A, B, C) = { + let a = khoang-cach(B, C) + let b = khoang-cach(C, A) + let c = khoang-cach(A, B) + let p = a + b + c + let I = ( + (a * A.at(0) + b * B.at(0) + c * C.at(0)) / p, + (a * A.at(1) + b * B.at(1) + c * C.at(1)) / p, + ) + let s = p / 2 + let r = calc.sqrt((s - a) * (s - b) * (s - c) / s) + (I, r) +} + +// Trực tâm tam giác ABC (giao ba đường cao). +// Dùng hệ thức Euler: vec(OH) = vec(OA) + vec(OB) + vec(OC) => H = A + B + C - 2O. +#let truc-tam(A, B, C) = { + let O = tam-ngoai-tiep(A, B, C) + ( + A.at(0) + B.at(0) + C.at(0) - 2 * O.at(0), + A.at(1) + B.at(1) + C.at(1) - 2 * O.at(1), + ) +} + +// Tâm bàng tiếp + bán kính đường tròn bàng tiếp TRONG GÓC A +// (tiếp xúc cạnh BC và phần kéo dài của AB, AC): trả về (J, r). +// J_A = (-a*A + b*B + c*C) / (-a + b + c), r_A = S / (p - a) +// Muốn tâm bàng tiếp trong góc B thì gọi tam-bang-tiep(B, C, A). +#let tam-bang-tiep(A, B, C) = { + let a = khoang-cach(B, C) + let b = khoang-cach(C, A) + let c = khoang-cach(A, B) + let t = -a + b + c + let J = ( + (-a * A.at(0) + b * B.at(0) + c * C.at(0)) / t, + (-a * A.at(1) + b * B.at(1) + c * C.at(1)) / t, + ) + let s = (a + b + c) / 2 + let S = calc.sqrt(s * (s - a) * (s - b) * (s - c)) + (J, S / (s - a)) +} + +// Hàm bậc nhất y = f(x) của đường thẳng qua 2 điểm A, B (AB không thẳng đứng) +// — dùng ghép với giao-ham để tìm giao đường thẳng với đường cong. +#let ham-qua-2-diem(A, B) = { + let a = (B.at(1) - A.at(1)) / (B.at(0) - A.at(0)) + x => A.at(1) + a * (x - A.at(0)) +} + +// Giao điểm của 2 đồ thị y = f(x) và y = g(x) trên [tu, den]: +// quét n mẫu tìm chỗ f − g đổi dấu rồi chia đôi 40 lần (chính xác ~1e-12). +// Trả về MẢNG các điểm (x, f(x)) theo x tăng dần (rỗng nếu không cắt). +// Giao đường thẳng AB với đường cong f: giao-ham(f, ham-qua-2-diem(A, B), ...); +// đường thẳng ĐỨNG x = k thì giao là (k, f(k)) — không cần hàm này. +// +// CÁCH GỌI (hàm này chỉ TÍNH, không vẽ — không cần ctx): +// giao-ham(f, g, -3, 2) // cận positional +// giao-ham(f, g, tu: -3, den: 2) // hoặc cận đặt tên (như ve-ham) +// for P in giao-ham(f, g, -3, 2) { diem(ctx, P, mau: red) } +// Lỡ truyền ctx ở đầu (giao-ham(ctx, f, g, ...)) thì tự bỏ qua, không lỗi. +// Chỗ f, g "nhảy" qua tiệm cận đứng cũng đổi dấu — các điểm giả đó bị LOẠI +// (kiểm tra lại |f − g| tại nghiệm), nên quét qua tiệm cận vẫn an toàn. +#let giao-ham(..thamso, tu: auto, den: auto, n: 200) = { + let p = thamso.pos() + // bỏ qua ctx nếu lỡ truyền vào (ctx là dictionary, f/g là hàm) + if p.len() > 0 and type(p.at(0)) == dictionary { p = p.slice(1) } + let f = p.at(0) + let g = p.at(1) + let tu = if p.len() > 2 { p.at(2) } else { tu } + let den = if p.len() > 3 { p.at(3) } else { den } + let h = x => f(x) - g(x) + let kq = () + // lệch mẫu một lượng cực nhỏ để không rơi ĐÚNG vào điểm hàm không xác định + // (vd x = −1 của (x+2)/(x+1) — Typst báo lỗi "cannot divide by zero") + let le = 0.0000001 + for i in range(n) { + let x1 = tu + (den - tu) * (i + le) / n + let x2 = tu + (den - tu) * (i + 1 + le) / n + let (y1, y2) = (h(x1), h(x2)) + if y1 * y2 <= 0 and (y1 != 0 or y2 != 0) { + let (lo, hi) = (x1, x2) + for _ in range(40) { + let m = (lo + hi) / 2 + if h(lo) * h(m) <= 0 { hi = m } else { lo = m } + } + let x = (lo + hi) / 2 + let y = f(x) + // nghiệm THẬT: f(x) ≈ g(x); tiệm cận đứng cho |f − g| rất lớn -> bỏ + let that = calc.abs(h(x)) < 0.000001 * calc.max(1, calc.abs(y)) + if that and (kq.len() == 0 or x - kq.last().at(0) > (den - tu) / n * 0.5) { + kq.push((x, y)) + } + } + } + kq +} + +// Hai tiếp điểm của tiếp tuyến kẻ từ điểm M ngoài đường tròn (O; r). +#let tiep-diem(O, r, M) = { + let d = khoang-cach(O, M) + let a = r * r / d // khoảng cách từ O đến hình chiếu tiếp điểm trên OM + let hcao = r * calc.sqrt(d * d - r * r) / d + let ux = (M.at(0) - O.at(0)) / d + let uy = (M.at(1) - O.at(1)) / d + let H = (O.at(0) + a * ux, O.at(1) + a * uy) + ( + (H.at(0) - hcao * uy, H.at(1) + hcao * ux), + (H.at(0) + hcao * uy, H.at(1) - hcao * ux), + ) +} + +// ---------- Lấy mẫu hàm số ---------- +#let lay-mau(f, a, b, n: 100) = range(n + 1).map(i => { + let x = a + (b - a) * (i / n) + (x, f(x)) +}) + +// Vẽ đồ thị hàm f trên [tu, den]; tự tách nhánh khi ra ngoài cửa sổ y +// (dùng được cho hàm có tiệm cận đứng như 1/x, tan x). +#let ve-ham( + ctx, f, + tu: auto, den: auto, + n: 150, mau: blue, day: 1.3pt, dut: false, +) = { + let a = if tu == auto { ctx.xmin } else { tu } + let b = if den == auto { ctx.xmax } else { den } + let le-y = (ctx.ymax - ctx.ymin) * 0.02 + let nhanh = () + let hien-tai = () + for p in lay-mau(f, a, b, n: n) { + if p.at(1) >= ctx.ymin - le-y and p.at(1) <= ctx.ymax + le-y { + hien-tai.push(p) + } else { + if hien-tai.len() > 1 { nhanh.push(hien-tai) } + hien-tai = () + } + } + if hien-tai.len() > 1 { nhanh.push(hien-tai) } + for nh in nhanh { + duong-cong(ctx, nh, mau: mau, day: day, dut: dut) + } +} + +// Tô miền giữa đồ thị f và trục hoành trên [a, b] (mau nên có độ trong suốt). +#let to-vung(ctx, f, a, b, mau: rgb(30, 100, 200, 60), n: 80) = { + let pts = lay-mau(f, a, b, n: n) + da-giac-pt(((a, 0), ..pts, (b, 0)).map(P => toa-pt(ctx, P)), to: mau) +} + +// Tô miền giữa HAI đồ thị f và g trên [a, b] (mau nên có độ trong suốt). +#let to-vung-2-ham(ctx, f, g, a, b, mau: rgb(30, 100, 200, 60), n: 80) = { + let tren = lay-mau(f, a, b, n: n) + let duoi = lay-mau(g, a, b, n: n).rev() + da-giac-pt((tren + duoi).map(P => toa-pt(ctx, P)), to: mau) +} + +// ---------- Gạch chéo (miền loại bỏ của BPT, vùng cấm...) ---------- +// Toạ độ trang (pt) -> toạ độ toán (nghịch đảo của toa-pt). +#let toa-nguoc(ctx, p) = ( + ctx.xmin + p.at(0) / ctx.sx.pt(), + ctx.ymax - p.at(1) / ctx.sy.pt(), +) + +// Gạch chéo một đa giác LỒI cho trước bằng các đoạn song song. +// pts: toạ độ pt. goc: hướng vạch gạch. buoc: khoảng cách 2 vạch. +#let _gach-loi-pt(pts, goc: 45deg, buoc: 6.5pt, mau: red, day: 0.55pt) = { + if pts.len() < 3 { return } + let d = (calc.cos(goc), -calc.sin(goc)) // trục y trang hướng xuống + let nv = (-d.at(1), d.at(0)) + let ts = pts.map(p => p.at(0) * nv.at(0) + p.at(1) * nv.at(1)) + let t = calc.min(..ts) + buoc.pt() / 2 + let t1 = calc.max(..ts) + while t < t1 { + // giao của đường {X · nv = t} với từng cạnh đa giác + let gd = () + for i in range(pts.len()) { + let P = pts.at(i) + let Q = pts.at(calc.rem(i + 1, pts.len())) + let a1 = P.at(0) * nv.at(0) + P.at(1) * nv.at(1) + let a2 = Q.at(0) * nv.at(0) + Q.at(1) * nv.at(1) + if calc.abs(a2 - a1) > 0.000001 { + let s = (t - a1) / (a2 - a1) + if s >= 0 and s < 1 { + gd.push((P.at(0) + s * (Q.at(0) - P.at(0)), P.at(1) + s * (Q.at(1) - P.at(1)))) + } + } + } + let gd = gd.sorted(key: p => p.at(0) * d.at(0) + p.at(1) * d.at(1)) + let i = 0 + while i + 1 < gd.len() { + doan-pt(gd.at(i), gd.at(i + 1), mau: mau, day: day) + i = i + 2 + } + t = t + buoc.pt() + } +} + +// Gạch chéo đa giác lồi theo toạ độ TOÁN. +#let gach-mien(ctx, cac-dinh, goc: 45deg, buoc: 6.5pt, mau: red, day: 0.55pt) = { + _gach-loi-pt(cac-dinh.map(P => toa-pt(ctx, P)), goc: goc, buoc: buoc, mau: mau, day: day) +} + +// Cắt đa giác bởi nửa mặt phẳng a*x + b*y <= c (Sutherland–Hodgman, toạ độ toán). +#let cat-nua-mp(pts, a, b, c) = { + let kq = () + let n = pts.len() + for i in range(n) { + let P = pts.at(i) + let Q = pts.at(calc.rem(i + 1, n)) + let fp = a * P.at(0) + b * P.at(1) - c + let fq = a * Q.at(0) + b * Q.at(1) - c + if fp <= 0.000001 { kq.push(P) } + if fp * fq < 0 { + let s = fp / (fp - fq) + kq.push((P.at(0) + s * (Q.at(0) - P.at(0)), P.at(1) + s * (Q.at(1) - P.at(1)))) + } + } + kq +} + +// Điểm P có thuộc hình tròn tâm O bán kính r không (toạ độ toán)? +#let trong-tron(P, O, r) = ( + calc.pow(P.at(0) - O.at(0), 2) + calc.pow(P.at(1) - O.at(1), 2) <= r * r +) + +// Điểm P có thuộc hình elip tâm O, bán trục a (ngang), b (dọc) không? +// quay: cùng góc xoay với hàm vẽ elip(ctx, O, a, b, quay: ...). +#let trong-elip(P, O, a, b, quay: 0deg) = { + let Q = if quay == 0deg { P } else { quay-diem(P, O, -quay) } + calc.pow((Q.at(0) - O.at(0)) / a, 2) + calc.pow((Q.at(1) - O.at(1)) / b, 2) <= 1 +} + +// ---------- Miền hình học ĐẶT TÊN (khai báo MỘT lần, vẽ + gạch dùng chung) ---------- +// let A = mien-tron((-0.5, 0.3), 0.9) // đổi bán kính MỘT chỗ duy nhất +// ve-mien(ctx, A) // vẽ biên +// gach-vung(ctx, giao(A, B, bu(C))) // gạch (A ∩ B) \ C — tự khớp +#let mien-tron(O, r) = (kieu: "tron", O: O, r: r) +#let mien-elip(O, a, b, quay: 0deg) = (kieu: "elip", O: O, a: a, b: b, quay: quay) + +// Điểm P có thuộc miền m không? m là mien-tron/mien-elip HOẶC hàm P => bool. +#let trong(P, m) = { + if type(m) == function { m(P) } else if m.kieu == "tron" { + trong-tron(P, m.O, m.r) + } else { + trong-elip(P, m.O, m.a, m.b, quay: m.quay) + } +} + +// Phép tập hợp trên miền: giao ∩, hợp ∪, bù (phần ngoài) — lồng nhau tuỳ ý: +// giao(A, hop(B, C)), giao(A, B, bu(C)), ... Kết quả dùng thẳng cho gach-vung. +#let giao(..ms) = P => ms.pos().all(m => trong(P, m)) +#let hop(..ms) = P => ms.pos().any(m => trong(P, m)) +#let bu(m) = P => not trong(P, m) + +// Vẽ biên miền (đường tròn/elip tương ứng với khai báo). +#let ve-mien(ctx, m, mau: black, day: 1pt, dut: false, to: none) = { + if m.kieu == "tron" { + duong-tron(ctx, m.O, m.r, mau: mau, day: day, dut: dut, to: to) + } else { + elip(ctx, m.O, m.a, m.b, quay: m.quay, mau: mau, day: day, dut: dut, to: to) + } +} + +// Gạch chéo MIỀN BẤT KÌ (biên cong tuỳ ý). +// kiem: miền đặt tên/tổ hợp giao-hop-bu (khuyên dùng) HOẶC hàm P => true/false: +// gach-vung(ctx, giao(A, B, bu(C))) +// gach-vung(ctx, P => trong(P, A) and P.at(1) > 0) +// goc: hướng vạch; buoc: khoảng cách 2 vạch; n: số mẫu mỗi vạch (biên càng mịn). +#let gach-vung(ctx, kiem, goc: 45deg, buoc: 6.5pt, mau: red, day: 0.55pt, n: 180) = { + let W = ctx.w.pt() + let H = ctx.h.pt() + let u = (calc.cos(goc), -calc.sin(goc)) // hướng vạch trên trang (y trang hướng xuống) + let v = (-u.at(1), u.at(0)) // pháp tuyến đơn vị + let b = buoc.pt() + let cac-goc = ((0, 0), (W, 0), (0, H), (W, H)) + let ds = cac-goc.map(p => p.at(0) * v.at(0) + p.at(1) * v.at(1)) + let ts = cac-goc.map(p => p.at(0) * u.at(0) + p.at(1) * u.at(1)) + let dmin = calc.min(..ds) + let dmax = calc.max(..ds) + let tmin = calc.min(..ts) + let tmax = calc.max(..ts) + for i in range(calc.floor((dmax - dmin) / b) + 1) { + let d = dmin + i * b + let dau = none + let truoc = none + for j in range(n + 1) { + let t = tmin + (tmax - tmin) * j / n + let p = (d * v.at(0) + t * u.at(0), d * v.at(1) + t * u.at(1)) + let thuoc = ( + p.at(0) >= 0 and p.at(0) <= W and p.at(1) >= 0 and p.at(1) <= H + and trong(toa-nguoc(ctx, p), kiem) + ) + if thuoc and dau == none { dau = p } + if not thuoc and dau != none { + doan-pt(dau, truoc, mau: mau, day: day) + dau = none + } + truoc = p + } + if dau != none { doan-pt(dau, truoc, mau: mau, day: day) } + } +} diff --git a/packages/preview/conic-toan/0.3.4/typst.toml b/packages/preview/conic-toan/0.3.4/typst.toml new file mode 100644 index 0000000000..de301e12cb --- /dev/null +++ b/packages/preview/conic-toan/0.3.4/typst.toml @@ -0,0 +1,13 @@ +[package] +name = "conic-toan" +version = "0.3.4" +entrypoint = "baigiang.typ" +authors = ["Kim Thu "] +license = "MIT" +description = "Draw math figures, graphs and variation tables." +repository = "https://github.com/Thutran1891/conic-toan" +keywords = ["math", "geometry", "plot", "slides", "exam", "vietnamese", "toan", "de-thi"] +categories = ["visualization", "presentation", "components"] +disciplines = ["mathematics", "education"] +compiler = "0.13.0" +exclude = ["/assets/"] From 42635b59669514d81f4abca561900fef8be871f9 Mon Sep 17 00:00:00 2001 From: Thutran1891 Date: Tue, 18 Aug 2026 17:20:55 +0700 Subject: [PATCH 2/3] 0.3.4: ve.typ - _voi-ctx khong panic khi ngan xep ctx rong (beamer/measure) --- packages/preview/conic-toan/0.3.4/lib/ve.typ | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/preview/conic-toan/0.3.4/lib/ve.typ b/packages/preview/conic-toan/0.3.4/lib/ve.typ index 45e6200709..0f5b37344a 100644 --- a/packages/preview/conic-toan/0.3.4/lib/ve.typ +++ b/packages/preview/conic-toan/0.3.4/lib/ve.typ @@ -48,6 +48,17 @@ // Bọc hàm vẽ f(ctx, ...) thành hàm gọi được KHÔNG CẦN ctx: nếu đối số đầu là // ctx thì gọi thẳng (lối cũ), không thì tự lấy ctx của khung hình đang vẽ. // Dùng ở baigiang.typ để bọc đồng loạt các hàm vẽ (xem cuối file đó). +// +// ⚠ NGĂN XẾP RỖNG — KHÔNG ĐƯỢC panic (xem CLAUDE.md, mục "CTX NGẦM TRONG +// MEASURE"). Nội dung nằm trong `measure(...)` KHÔNG được dựng vào tài liệu nên +// mọi `state.update` bên trong nó vô hiệu: `_ctx-ht.get()` trả về giá trị CUỐI +// tài liệu = () ⇒ trước đây panic, làm beamer chết ở phép đo tự ngắt màn +// (_cat-man-vua) và ở `cot: auto` của cau-mc. Nay: +// · trong measure ⇒ BỎ QUA, không vẽ gì. Phép đo KHÔNG đổi vì #hinh là `box` +// có width/height CỐ ĐỊNH, nét vẽ bên trong đều `place` (không chiếm chỗ); +// bản in thật thì ngăn xếp đầy đủ nên vẫn vẽ đúng như cũ. +// · thật sự quên #hinh(...) ⇒ ghi DẤU ĐỎ (dùng `place` nên không phá bố cục) +// thay cho panic — đủ để thấy mà không giết cả lần biên dịch. #let _voi-ctx(f) = (..a) => { let p = a.pos() if p.len() > 0 and type(p.at(0)) == dictionary and "sx" in p.at(0) { @@ -55,10 +66,11 @@ } else { context { let ds = _ctx-ht.get() - if ds.len() == 0 { - panic("Hàm vẽ cần khung hình: đặt lệnh trong thân #hinh(...) hoặc trong them: ctx => ..., hoặc truyền ctx như lối cũ.") + if ds.len() > 0 { + f(ds.last(), ..a) + } else { + place(text(fill: red, size: 7pt)[⚠ lệnh vẽ đặt NGOÀI khung hình]) } - f(ds.last(), ..a) } } } From d574352e2ee16b7da68015e8e3cf1dd3de732a37 Mon Sep 17 00:00:00 2001 From: Thutran1891 Date: Tue, 18 Aug 2026 17:48:26 +0700 Subject: [PATCH 3/3] 0.3.4: so-do-cay - nhan xac suat nhanh di XUONG dat phia duoi canh --- packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ b/packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ index b2f7e36f82..5c65ac4cac 100644 --- a/packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ +++ b/packages/preview/conic-toan/0.3.4/lib/so-do-cay.typ @@ -118,9 +118,14 @@ let B2 = toa-nguoc(ctx, (pb.at(0) - wb.pt(), pb.at(1))) mui-ten(ctx, A2, B2, mau: mau-canh, day: day-canh, kich: 6pt) if c.xs != none { + // Nhánh đi XUỐNG (nút con thấp hơn nút cha) đặt nhãn phía DƯỚI cạnh + // cho cân đối và khỏi chen vào chỗ rẽ; nhánh đi lên/ngang giữ phía + // trên như cũ. Chữ vẫn quay theo cạnh (đọc xuôi). + let xuong = c.B.at(1) < c.A.at(1) nhan( ctx, trung-diem(A2, B2), c.xs, - huong: "above", cach: 3pt, quay: goc-truc(ctx, A2, B2), + huong: if xuong { "below" } else { "above" }, + cach: 3pt, quay: goc-truc(ctx, A2, B2), ) } }