diff --git a/packages/preview/ezexam/0.2.8/LICENSE b/packages/preview/ezexam/0.2.8/LICENSE new file mode 100644 index 0000000000..4c2f9bb7bf --- /dev/null +++ b/packages/preview/ezexam/0.2.8/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 gbchu + +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/ezexam/0.2.8/README.md b/packages/preview/ezexam/0.2.8/README.md new file mode 100644 index 0000000000..619785a4e0 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/README.md @@ -0,0 +1,165 @@ +# ezexam + +![Typst Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fgbchu%2Fezexam%2Frefs%2Fheads%2Fmain%2Ftypst.toml&query=%24.package.version&prefix=v&logo=typst&label=package&color=239DAD) +[![MIT License](https://img.shields.io/badge/license-MIT-brightgreen)](https://github.com/gbchu/ezexam/blob/main/LICENSE) +[![Online Documentation](https://img.shields.io/badge/docs-online-007aff)](https://ezexam.pages.dev/) + + +This template is primarily designed to help Chinese university, primary, middle and high school teachers or students in creating exams or handouts. + +## Example +```typst +#import "@preview/ezexam:0.2.8": * +#show: setup.with( + mode: EXAM, + paper: a3 +) + +#title[XX试卷] + += 选择 +#question[ + $(1 + 5i)i$ 的虚部为 #paren[] + #choices(-1, 0, 1, 6) +] + +...... + += 填空 +#question[ + 一个箱子里有 5 个球,分别以 1$~$5 标号,若有放回取三次,记至少取出一次的球的个数 $X$,则 $E(X) =$#fillin[]. +] + +...... + += 解答题 +#question(points: 15)[ + 设数列 ${a_n}$ 满足 $a_1 = 3", "a_(n+1) / n = a_n / (n+1) + 1 / (n(n+1))$. + + 证明:${n a_n}$ 为等差数列; + + 设 $f(x) = a_1x + a_2x^2 + dots.c + a_m x^m,求 f'(-2)$. +] + +...... +``` + +## Changelog +### 0 . 2 . 8 ++ 更改 `question` 和 `solution` 方法的核心实现,彻底解决有较高的公式时,题号和题干对不齐的问题。删除这两个方法的 `padding-top` 和 `padding-bottom` 参数;`question` 方法新增参数 `hanging-indent` 并将参数 `body-indent` 修改为 `first-line-indent` ++ 修复以字符《 、【 、( 开头的题目或选项和标签间的距离过大的问题 ++ 优化 `choice` 方法 ++ 优化 `text-figure` 方法,删除 `align` 参数 ++ 修复 `draft` 未引入 `title` 方法的引起的报错 ++ `tag` 方法新增 `weight` 和 `x` 参数 ++ 修复 `fillin` 方法有时不显示占位符的 bug ++ 将默认字体修改为 `roman` ,并添加 `TeX Gyre Termes` 字体,修复在线使用 `roman` 字体时,缺少 `Times New Roman` 字体造成的标题西文字体未正确设置的问题 ++ 修改 `title` 方法的 `bottom` 参数和`subject` 方法的 `top` 参数的默认值为 0pt ++ `setup` 方法新增参数 `resume` ++ 废弃 `answer` 方法和 `multi` 常量 + +### 0 . 2 . 7 ++ 优化目录的显示效果 ++ 优化 `fillin` 方法;修改其参数 `len` 的默认值为 1.5cm。修复当指定长度时,某些值会导致第一行线换行的 bug ++ 调整黑体的字体顺序 ++ 修复以数学公式开头的选项,选项和 label 间会增加 .25em 的间距 + +### 0 . 2 . 6 ++ 修复 `fillin` 方法在页面分栏时,不能正确换行的 bug + +### 0 . 2 . 5 ++ 废弃 `color-box` 方法 ;新增 `tag` 方法替代 ++ 重构 `multi` ++ 将 `ROMAN` 修改为 `roman` ++ 优化字体,去掉 `noto serif sc` ;黑体新增 `Heiti SC` ,修复 Mac 用户本地使用时,黑体异常的 bug ++ 优化 `paren` 方法 ++ 优化 `text-figure` 方法;修复当图文在新的一页最上面且文本较少时,图表显示不全的 bug ;修复当在一页的最后部分时,有较少部分文本留在当前页,图表也停留在当前页的 bug ,该 bug 使得图表遮挡前面的内容;添加参数 `align` ++ 优化 `fillin` 方法,修复多行线时,线之间的间隔不等的 bug ++ 废弃 `underdot` 方法;使用新的方法实现中文着重号 + +### 0 . 2 . 4 ++ 优化 `choices` 方法;新增参数 `label-position` ; 该参数可在选项为图表时,修改标签的位置。默认在左侧;将参数 `body-indent` 名修改为 `sapcing` ;更加符合语境 ++ 优化 `question` 方法,修复题号偏移的 bug ++ 修复修改 `setup` 方法的 `paper` 参数时,只修改部分值报错的 bug ++ 修复在 Typst 的最新版 0.14.0 中数学字体报警告问题 + +### 0 . 2 . 3 ++ 为 `solution` 和 `question` 方法添加 `line-height` 参数;方便修改内容的行间距 ++ 优化 `fillin` 方法 ++ 修改 `question` 方法在 `HANDOUTS` 模式下的题号样式 ++ 修复 `solution` 在 Typst 的最新版 0.14.0 中题号显示的 bug ++ 优化 `paren` 方法,默认填写英文不再大写 + +### 0 . 2 . 2 ++ 调整正文默认字体,由原来的 `Source Han Serif` 修改为 `Noto Serif SC` 、 `Noto Serif CJK SC` (二者效果一样,主要是后者压缩后更小,方便上传网盘进行安装)。黑体添加 `Noto Sans SC` 和 `Noto Sans CJK SC` ;修复在 typst app 中应用模板时,字体显示的问题 ++ 添加常量 `ROMAN` ,方便修改字体为新罗马风格的字体,更加符合常见的试卷排版格式 ++ 调整代码逻辑,使得在字体调整时,其它所有西文字体统一进行修改 ++ 精简 `setup` 方法的参数,删除 `font-math` ;使用 `font` 参数即可完成正文字体和西文字体的设置 ++ 调整 π 在罗马字体下显示的样式 ++ 修改平行符号为倾斜 ++ 修复 `choices` 某个选项有多行时,后续行的缩进和第一行不一致的bug ++ 重写 `fillin` 方法, 实现根据长度生成空线,并根据长度自动换行 ++ 修复 `subject` 方法以字符串传入时的bug ++ 优化题号和题干之间的间距 + +### 0 . 2 . 1 ++ 修复试卷模式下,生成 pdf 后的书签会显示题目大标题的问题,确保只显示章节 ++ 西文字体新增 Times New Roman 字体风格;前提是安装了 STIX 2 系列字体。如未安装则默认使用 New Computer Modern Math ++ 优化代码 + +### 0 . 2 . 0 ++ 添加 `cover` 方法;该方法可以生成一个封面 ++ 添加 `underdot` 方法;在一些场景下,可以为文本添加着重号 + +### 0 . 1 . 9 ++ 优化 `text-figure` 方法;考虑到文本内容较多,为了书写方便,将参数 `text` 修改为位置参数;新增参数 `figure` 、`style` 、`gap` ++ 优化 `question` 方法;修复当一个文档中组多套试卷时,会报警告的问题 ++ 优化 `title` 、`score-box` 、`scoring-box` 方法 ++ 优化代码 + +### 0 . 1 . 8 ++ 为 `mode` 添加新值 `SOLUTION`,当答案解析独立于试题存在时,使用此值可快速统一格式 ++ 优化 `choices` 方法;将其参数 `column` 更名为 `columns`,做到和官方的 `columns` 参数一致 ++ 废弃 `inline-square` 方法,推荐使用内置的 `table` 方法 ++ 修复 `color-box` 方法报错的 bug ++ 优化 `secret` 、`zh-arabic` 方法 ++ 优化 `question` 的编号实现方式;修改 `setup` 方法的参数 `enum-numbering` 的默认值为 `(1.i.a)` ++ 优化 `notice` 方法;新增参数 `indent` 、`hanging-indent` + +### 0 . 1 . 7 ++ 优化代码,确保 `heading-size` 只修改一级标题;并将其更名为 `h1-size` ++ 为 `title` 方法新增参数 `color` ++ 修复 `solution` 方法,当启用 `title` 时,如果解析内容过多,一页放不下,标题会跑到下一页的 bug;并将其参数 `above` 更名为 `top`;参数 `below` 更名为 `bottom`;统一参数名;添加参数 `padding-top`、`padding-bottom` ++ 去除 `question` 方法参数 `line-height`;该参数会影响题干之间的距离;该参数原本用于设置题目内容的行高,当题目中的公式比较高时,题号和题目内容会错位,这时可以通过该参数来微调。但是会造成内容每一行与行之间的间隔变大。可参考新增的参数 `padding-top`、`padding-bottom` 代替 ++ 修复 `choices` 方法,调整其上下外边距导致选项之间的距离会跟着影响的 bug + +### 0 . 1 . 6 ++ 修复有序列表,内容带有 `box` 时,编号和内容对不齐的 bug ++ 新增化学方程式的单线桥、双线桥的支持;原子、离子结构示意图的支持。使用详情查看 [`化学相关`](https://ezexam.pages.dev/reference/chem) + +### 0 . 1 . 5 ++ 修复水印被图片遮挡的 bug + +### 0 . 1 . 4 ++ 将 `LECTURE` 修改为 `HANDOUTS`,更加符合语义 ++ 将 `explain` 方法名修改为 `solution`,更加符合语义 ++ 修复当修改弥封线类型时,试卷最后一页没有更改的 bug ++ 添加水印功能,`setup` 方法新增参数 `watermark`,`watermark-size`,`watermark-color`,`watermark-font`,`watermark-rotate` + +### 0 . 1 . 3 ++ 优化 `choices` 方法 ++ 将 `question` 方法的参数名 `points-separate-par` 修改为 `points-separate` ++ 增加英文完型填空、7选5题型的支持,让 `paren` 和 `fillin` 方法可以使用题号作为占位符。使用详情查看 [`paren`](https://ezexam.pages.dev/reference/paren) 和 [`fillin`](https://ezexam.pages.dev/reference/fillin) 方法 ++ `setup` 方法新增参数 `heading-numbering`,`heading-hanging-indent`,`enum-spacing`,`enum-indent` 提供更多自定义设置 ++ 修复 `question` 个数超过9个时,内容对不齐的问题 + +### 0 . 1 . 2 ++ 将 `secret` 修改为方法,可以自定义显示内容 ++ 优化 `choices` 方法,当选项过长时,选项从第二行开始进行缩进。修复选项中既有文字又有图表时,标签和内容对不齐的问题 ++ 将 `question` 方法的参数 `with-heading-label` 的默认值修改为 `false` ++ `explain` 方法新增参数 `show-number` 、修改参数 `title` 的默认值为 `none`,默认不显示 ++ `setup` 方法新增参数 `enum-numbering` + +### 0 . 1 . 1 ++ 修复 `choices` 方法中,若选项为图片时,设置宽度为百分比时,图片宽度无效的问题 + +### 0 . 1 . 0 ++ 初版发布 diff --git a/packages/preview/ezexam/0.2.8/ezexam.typ b/packages/preview/ezexam/0.2.8/ezexam.typ new file mode 100644 index 0000000000..8b8ed6a563 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/ezexam.typ @@ -0,0 +1,278 @@ +#import "lib/tools.typ": draft, tag, zh-arabic +#import "lib/outline.typ": * +#import "lib/choice.typ": choices +#import "lib/question.typ": question +#import "lib/paren-fillin.typ": fillin, fillinn, paren, parenn +#import "lib/solution.typ": * +#import "lib/text-figure.typ": text-figure + +#let setup( + mode: HANDOUTS, + paper: a4, + page-numbering: auto, + page-align: center, + gap: 1in, + show-gap-line: false, + footer-is-separate: true, + outline-page-numbering: "I", + font: roman, + font-size: 11pt, + line-height: 2em, + par-spacing: 2em, + first-line-indent: 0em, + heading-numbering: auto, + heading-hanging-indent: auto, + h1-size: auto, + heading-font: heiti, + heading-color: luma(0), + heading-top: 10pt, + heading-bottom: 15pt, + enum-numbering: "(1.i.a)", + enum-spacing: 2em, + enum-indent: 0pt, + resume: true, + watermark: none, + watermark-color: rgb("#f666"), + watermark-font: roman, + watermark-size: 88pt, + watermark-rotate: -45deg, + show-answer: false, + answer-color: blue, + show-seal-line: true, + seal-line-student-info: ( + 姓名: underline[~~~~~~~~~~~~~], + 准考证号: table( + columns: 14, + inset: .8em, + [], + ), + 考场号: table( + columns: 2, + inset: .8em, + [], + ), + 座位号: table( + columns: 2, + inset: .8em, + [], + ), + ), + seal-line-type: "dashed", + seal-line-supplement: "弥封线内不得答题", + doc, +) = { + assert(mode in (HANDOUTS, EXAM, SOLUTION), message: "mode expected HANDOUTS, EXAM, SOLUTION") + assert(type(font) == array and type(heading-font) == array, message: "font must be an array") + mode-state.update(mode) + paper = a4 + paper + let _footer(label) = context { + assert( + type(label) in (str, function, none) or label == auto, + message: "page-numbering expected str, function, none, auto, found " + str(type(label)), + ) + if label == none { return } + let _label = label + if label == auto { + _label = "1 / 1" + if mode != HANDOUTS { + _label = zh-arabic(prefix: [#subject-state.get()#if mode == SOLUTION [参考答案] else [试题]]) + } + } + // 如果传进来的label包含两个1,两个1中间不能是连续空格、包含数字 + // 支持双:阿拉伯数字、小写、大写罗马,带圈数字页码 + let reg-1 = "^[\D]*1[\D]*[^\d\s]+[\D]*1[\D]*$" + let reg-i = reg-1.replace("1", "i") + let reg-I = reg-1.replace("1", "I") + let reg-circled-number = reg-1.replace("1", "①") + let reg-circled-number2 = reg-1.replace("1", "⓵") + let reg = reg-1 + "|" + reg-i + "|" + reg-I + "|" + reg-circled-number + "|" + reg-circled-number2 + + let current = counter(page).get() + if (type(_label) == str and regex(reg) in _label) or (type(_label) == function) { + current += counter(page).final() + } + + let _numbering = numbering(_label, ..current) + + // 处于分栏下且左右页脚分离 + if page.columns == 2 and footer-is-separate { + current.at(0) += 1 + grid( + columns: (1fr, 1fr), + align: center + horizon, + // 左页码 + _numbering, + // 右页码 + numbering(_label, ..current), + ) + counter(page).step() + return + } + + // 页面的页脚是未分离, 则让奇数页在右侧,偶数页在左侧 + let position = page-align + if not footer-is-separate { + if calc.odd(current.first()) { + position = right + } else { + position = left + } + } + align(position, _numbering) + } + import "lib/tools.typ": _create-seal + let _header( + student-info: seal-line-student-info, + line-type: seal-line-type, + supplement: seal-line-supplement, + ) = context { + if mode != EXAM or not show-seal-line { return } + // 根据页码决定是否显示弥封线 + // 如果当前页面有,则显示弥封线,并在该章节最后一页的右侧也设置弥封线 + let chapter-location = for value in query(<title>) { + counter(page).at(value.location()) + } + + if chapter-location == none or chapter-location.len() == 0 { return } + let current = counter(page).get().first() + let last = counter(page).final() + + // 获取上一章最后一页的页码,给最后一页加上弥封线 + let chapter-last-page-location = chapter-location.map(item => item - 1) + last + if page.columns == 2 and footer-is-separate { + chapter-last-page-location = chapter-location.map(item => item - 2) + (last.first() - 1,) + } + // 去除第一章,因为第一章前面没有章节了 + let _ = chapter-last-page-location.remove(0) + + let _margin-y = page.margin * 2 + let _width = page.height - _margin-y + if page.flipped { _width = page.width - _margin-y } + block(width: _width)[ + // 判断当前是在当前章节第一页还是章节最后一页 + //当前章节第一页弥封线 + #if chapter-location.contains(current) { + place( + dx: -_width - 1em, + dy: -2.4em, + rotate(-90deg, origin: right + bottom, _create-seal( + dash: line-type, + info: student-info, + supplement: supplement, + )), + ) + return + } + + // 章节最后页的弥封线 + #if chapter-last-page-location.contains(current) { + _width = page.width + if page.flipped { _width = page.height } + place( + dx: _width - page.margin - 1em, + dy: 2em, + rotate(90deg, origin: left + top, _create-seal( + dash: line-type, + supplement: supplement, + )), + ) + } + ] + } + let _background() = { + if paper.columns > 1 and show-gap-line { + line(angle: 90deg, length: 100% - paper.margin * 2, stroke: .5pt) + } + } + let _foreground() = { + if watermark == none { return } + set text(size: watermark-size, watermark-color) + set par(leading: .5em) + place(horizon, grid( + columns: paper.columns * (1fr,), + ..paper.columns * (rotate(watermark-rotate, watermark),), + )) + } + set page( + ..paper, + header: _header(), + footer: _footer(page-numbering), + background: _background(), + foreground: _foreground(), + ) + set columns(gutter: gap) + + set outline( + target: if mode == EXAM { <chapter> } else { heading }, + title: text(size: 15pt)[目#h(1em)录], + ) + show outline: it => { + set page(header: none, footer: _footer(outline-page-numbering)) + align(center, it) + pagebreak(weak: true) + counter(page).update(1) // 正文页码从1开始 + } + + set par(leading: line-height, spacing: par-spacing, first-line-indent: (amount: first-line-indent, all: true)) + set text(font: font, size: font-size) + + if heading-numbering == auto { + if mode in (EXAM, SOLUTION) { + heading-numbering = (..item) => numbering("一、", ..item) + h(-0.3em) + heading-hanging-indent = 2em + } else { heading-numbering = "1.1.1.1.1 " } + } + set heading(numbering: heading-numbering, hanging-indent: heading-hanging-indent) + show heading: it => { + v(heading-top) + text(heading-color, font: font.slice(0, -1) + heading-font, it) + v(heading-bottom) + if not resume { counter("question").update(0) } + } + show heading.where(level: 1): it => { + let size = h1-size + if size == auto { + if mode == HANDOUTS { size = 1em } else { size = 10.5pt } + } + text(size: size, it) + } + // 试卷模式下,书签只显示章节 + set heading(bookmarked: false) if mode == EXAM + show heading.where(level: 1).and(<chapter>): set heading(bookmarked: true) + + set enum(numbering: enum-numbering, spacing: enum-spacing, indent: enum-indent) + set table.cell(align: horizon + center, stroke: .5pt) + + // 分段函数样式 + set math.cases(gap: 1em) + // 显示方程编号 + set math.equation(numbering: "(1)", supplement: [Eq -]) if mode == HANDOUTS + show math.equation: it => { + // features: 一些特殊符号的设置,如空集符号设置更加漂亮 + set text(font: font, features: ("cv01",)) + // 1. 行内样式默认块级显示样式; 2. 添加数学符号和中文之间间距 + let space = h(.25em, weak: true) + space + math.display(it) + space + } + // π 在类罗马字体 "TeX Gyre Termes Math" 下显示的样式;默认的有点丑 + show math.pi: it => { + if "TeX Gyre Termes Math" in font { + return text(font: "Times New Roman", "π") + } + it + } + show math.parallel: "//" + + // 中文着重号 + show strong: content => { + show regex("\p{Hani}"): it => box(place(text("·", size: 0.8em), dx: 0.45em, dy: 0.75em) + it) + content.body + } + + if show-answer { + answer-state.update(true) + answer-color-state.update(answer-color) + } + + doc +} diff --git a/packages/preview/ezexam/0.2.8/lib/choice.typ b/packages/preview/ezexam/0.2.8/lib/choice.typ new file mode 100644 index 0000000000..52a397aae6 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/lib/choice.typ @@ -0,0 +1,91 @@ +#import "tools.typ": _content-start-space + +#let _format-choice(choice, label, indent, spacing, label-position) = { + // 为了解决数学公式在左侧加间距的问题 + spacing -= _content-start-space(choice) + if choice.func() not in (image, table) { + return par( + hanging-indent: indent + spacing + measure(label).width, + h(indent) + label + h(spacing, weak: true) + choice, + ) + } + + // 选项为图片、表格的处理 + if label-position == bottom { + return grid( + align: center, + inset: (left: indent), + pad(bottom: spacing, choice), + label, + ) + } + + grid( + columns: 2, + pad(left: indent, label), pad(left: spacing, choice), + ) +} + +#let _count-columns(container-width, choice-number, max-choice-width, columns) = { + // 如果未指定列数,则自动排列,默认4列 + if columns == auto { + columns = 4 + // 排成1行,选项之间的间距 + let choice-gap = container-width / choice-number - max-choice-width + let min-gap = 0.15in + if choice-gap < min-gap { + columns = 2 + // 排成2行,选项之间的间距 + choice-gap = choice-gap * 2 + max-choice-width + if choice-gap < min-gap { columns = 1 } + } + } + columns +} + +#let choices( + columns: auto, + c-gap: 0pt, + r-gap: 2em, + indent: 0pt, + spacing: 5pt, + top: 0pt, + bottom: 0pt, + label: "A.", + label-postion: left, + ..options, +) = { + let args-named = options.named() + assert(args-named.len() == 0, message: "choices no " + repr(args-named) + " parameters") + // 使用layout获取当前父元素的宽度 + layout(container => { + let choices-arr = options.pos() + let choice-number = choices-arr.len() + assert(choice-number > 0, message: "choices must have at least one option") + // 拼接选项并添加标签和间距;获取选项中最长的宽度 + let max-width = 0pt + for index in range(choice-number) { + choices-arr.at(index) = _format-choice( + [#choices-arr.at(index)], + numbering(label, index + 1), + indent, + spacing, + label-postion, + ) + + if columns != auto { continue } + max-width = calc.max(max-width, measure(choices-arr.at(index)).width) + } + + v(top) + grid( + columns: _count-columns(container.width, choice-number, max-width + c-gap, columns) * (1fr,), + column-gutter: c-gap, + row-gutter: r-gap, + align: horizon, + ..choices-arr + ) + v(bottom) + }) +} + diff --git a/packages/preview/ezexam/0.2.8/lib/const-state.typ b/packages/preview/ezexam/0.2.8/lib/const-state.typ new file mode 100644 index 0000000000..7afae85c57 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/lib/const-state.typ @@ -0,0 +1,31 @@ +#let a3 = ( + paper: "a3", + margin: 1in, + columns: 2, + flipped: true, +) + +#let a4 = ( + paper: "a4", + margin: 1in, + columns: 1, + flipped: false, +) + +// #let main-font = ("New Computer Modern Math", "Noto Serif CJK SC") +#let heiti = ("SimHei", "Heiti SC", "Noto Sans CJK SC") +#let roman = ( + (name: "Times New Roman", covers: regex("\w")), + (name: "TeX Gyre Termes", covers: regex("\w")), + "TeX Gyre Termes Math", + "Noto Serif CJK SC", +) + +#let EXAM = "exam" // 试卷模式 +#let HANDOUTS = "handouts" // 讲义模式(默认) +#let SOLUTION = "solution" // 解析模式 + +#let mode-state = state("mode", HANDOUTS) +#let answer-state = state("answer", false) +#let answer-color-state = state("answer-color", blue) +#let subject-state = state("subject", "") diff --git a/packages/preview/ezexam/0.2.8/lib/outline.typ b/packages/preview/ezexam/0.2.8/lib/outline.typ new file mode 100644 index 0000000000..3e61c03244 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/lib/outline.typ @@ -0,0 +1,121 @@ +#import "const-state.typ": * + +// 封面 +#let cover( + title: "ezexam", + subtitle: none, + author: none, + date: auto, +) = { + set page(footer: none, header: none, columns: 1) + set align(center + horizon) + text(size: 25pt)[#title] + + if subtitle != none { + text(font: heiti, size: 22pt)[\ #subtitle] + } + + if author != none { + text(font: "STKaiti", size: 15pt)[\ 作者:#author] + } + + if date != none [ + \ #if date == auto [ + #datetime.today().year()/#datetime.today().month()/#( + datetime.today().day() + )] + ] +} + +#let chapter(body) = { + pagebreak(weak: true) + counter("chapter").step() + set heading(numbering: _ => counter("chapter").display(it => box(width: .6em, align(right)[#it.~]))) + place(hide[= #body <chapter>]) +} + +#let title( + body, + size: auto, + weight: 700, + font: auto, + color: luma(0), + position: center, + top: 0pt, + bottom: 0pt, +) = context { + let _font = font + if _font == auto { _font = text.font } + let _size = size + if size == auto { + _size = 15pt + if mode-state.get() == HANDOUTS { _size = 20pt } + } + v(top) + align(position, text(font: _font, size: _size, weight: weight, color)[#body <title>]) + v(bottom) + counter(heading).update(0) + counter("question").update(0) +} + +#let subject(body, size: 21.5pt, spacing: 1em, font: heiti, top: 0pt, bottom: 0pt) = { + v(top) + align(center, text( + font: font, + size: size, + [#body].text.split("").slice(1, -1).join(h(spacing)), + )) + v(bottom) + subject-state.update([#body].text) +} + +#let secret(body: [绝密★启用前]) = place(top, float: true, clearance: 20pt, text(font: heiti, body)) + +#let exam-type(type, prefix: "试卷类型: ") = context place(top + right, text( + font: text.font.slice(0, -1) + heiti, +)[#prefix#type]) + +#let exam-info( + info: ( + 时间: "120分钟", + 满分: "150分", + ), + weight: 500, + font: auto, + size: 1em, + gap: 2em, + top: 0pt, + bottom: 0pt, +) = context { + assert(info.len() > 0, message: "info cannot be empty") + set text(font: text.font.slice(0, -1) + heiti, size: size, weight: weight) + set align(center) + grid( + columns: info.len(), + gutter: gap, + inset: (top: top, bottom: bottom), + align: center + horizon, + ..for (key, value) in info { + ([#key: #value],) + } + ) +} + +#let scoring-box(x: 0pt, y: 0pt) = place(dx: x, dy: y, right + top, table( + columns: 2, + inset: 8pt, +)[得分][~~~~~~~~~][阅卷人]) + +#let score-box(x: 0pt, y: 0pt) = place(dx: x, dy: y, right + top, table( + inset: 8pt, +)[得分][~~~~~~~~~#v(10pt)]) + +#let notice(label: "1.", indent: 2em, hanging-indent: auto, ..children) = context { + text(font: heiti)[注意事项:] + set enum(numbering: label, indent: indent) + set par(hanging-indent: if hanging-indent == auto { + -indent - enum.body-indent - measure(label).width + } else { hanging-indent }) + for child in children.pos() [+ #par(child)] +} + diff --git a/packages/preview/ezexam/0.2.8/lib/paren-fillin.typ b/packages/preview/ezexam/0.2.8/lib/paren-fillin.typ new file mode 100644 index 0000000000..3b8d353a26 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/lib/paren-fillin.typ @@ -0,0 +1,110 @@ +#import "const-state.typ": answer-color-state, answer-state + +#let _get-answer(body, placeholder, with-number, update) = { + if answer-state.get() { + return text(answer-color-state.get(), body) + } + if not with-number { return placeholder } + counter("placeholder").step() + context counter("placeholder").display() + if update { counter("question").step() } +} + +#let _draw-line(len, stroke, offset, body) = { + let _len = len.to-absolute() + assert(_len > 4pt, message: "len must > 4pt") + + set box(stroke: (bottom: stroke), inset: (bottom: offset), outset: (bottom: offset)) + + let page-width = page.width + if page.flipped { page-width = page.height } + let _columns = page.columns + let here-pos-x = here().position().x + if _columns > 1 { + let one-column-width = (page-width + columns.gutter * (_columns - 1)) / _columns + // 当有多个列时,当前内容所在的那一列加上前面所有的列的总宽度 + page-width = one-column-width * calc.ceil(here-pos-x / one-column-width) + } + + let first-line-available-space = page-width - page.margin - here-pos-x + let rest-len = _len - first-line-available-space + let is-line-break = false + let _space = 1pt + // 当前行剩余空间 < 10pt 时,则直接换行在新的一行从头开始画 + if first-line-available-space < 10pt { + [ \ ] + is-line-break = true + rest-len = _len + } else { + // 当前指定长度 > 当前行剩余空间 >= 10pt,则按照当前行的剩余空间画线 + // 如果当前指定长度 < 剩余空间,则按照指定长度在文字后画线 + if rest-len < 0pt { first-line-available-space = _len } + // 第一行线 + h(_space, weak: true) + box(width: first-line-available-space - _space, inset: 0pt, align(center, body)) + box() // 解决第一行线换行问题 + h(_space, weak: true) + } + + // 超过一行的后续横线 + if rest-len > 5pt { + // 计算可以画多少完整的条数 + let _ratio = rest-len / (page.width - page.margin * 2) + // 多条完整线 + // + "" 是为了解决多条线时,最后一行线与之前的线间距不等的问题 + for _ in range(calc.trunc(_ratio)) { + ( + box(width: 100%)[#if is-line-break { + align(center, body) + is-line-break = false + }] + + "" + ) + } + + // 最后一行的线 + // + "" 是为了解决最后一行线,在这条线之后如果加文本线的间距变大问题 + box(width: calc.fract(_ratio) * 100%)[#if is-line-break { align(center, body) }] + "" + h(_space, weak: true) + } +} + +// 填空的横线 +#let fillin( + body, + len: 1.25cm, + placeholder: "\u{25B2}", + with-number: false, + update: false, + stroke: .45pt + luma(0), + offset: 3pt, +) = context { + assert(type(len) == length, message: "expect length, got " + str(type(len))) + let result = _get-answer(body, placeholder, with-number, update) + if not answer-state.get() or result.child in ([], [ ]) { + return _draw-line(len, stroke, offset / 2, result) + } + + underline( + evade: false, + offset: offset, + stroke: stroke, + result, + ) +} + +// 选项的括号 +#let paren( + body, + justify: false, + placeholder: "\u{25B2}", + with-number: false, + update: false, +) = context [ + #if justify { h(1fr) } + #h(0pt, weak: true)(~~#_get-answer(body, placeholder, with-number, update)~~) +] + +// 类似英文中的7选5题型专用语法糖 +#let parenn = paren.with(with-number: true, update: true) +#let fillinn = fillin.with(with-number: true, update: true) diff --git a/packages/preview/ezexam/0.2.8/lib/question.typ b/packages/preview/ezexam/0.2.8/lib/question.typ new file mode 100644 index 0000000000..dce893c87b --- /dev/null +++ b/packages/preview/ezexam/0.2.8/lib/question.typ @@ -0,0 +1,84 @@ +#import "const-state.typ": HANDOUTS, mode-state +#import "tools.typ": _content-start-space, _trim-content-start-parbreak + +#let _format-label(label, label-color, label-weight, with-heading-label) = context counter( + "question", +).display(num => { + let _label = label + if label == auto { + _label = "1." + if mode-state.get() == HANDOUTS and with-heading-label { + _label = "1.1.1.1.1.1." + } + } + let arr = (num,) + if with-heading-label { + // 去除heading label数组中的0 + arr = counter(heading).get().filter(item => item != 0) + arr + } + + let result = text( + label-color, + weight: label-weight, + numbering(_label, ..arr), + ) + + if mode-state.get() == HANDOUTS { return result } + box(width: 1em, align(right, result)) +}) + +#let _format-points(points, prefix, suffix, separate) = { + if points == none { return } + assert(type(points) == int and points > 0, message: "points be a positive integer!") + [#prefix#points#suffix#if separate [ \ ]] +} + +#let question( + body, + indent: 0em, + first-line-indent: 0em, + hanging-indent: auto, + label: auto, + label-color: luma(0), + label-weight: 400, + with-heading-label: false, + points: none, + points-separate: true, + points-prefix: h(-0.45em, weak: true) + "(", + points-suffix: "分)", + line-height: auto, + top: 0pt, + bottom: 0pt, +) = context { + counter("question").step() + set par(leading: line-height) if line-height != auto + let _label = _format-label( + label, + label-color, + label-weight, + with-heading-label, + ) + let _hanging-indent = hanging-indent + if hanging-indent == auto { _hanging-indent = measure(_label).width + 1em } + + v(top) + terms( + indent: indent, + hanging-indent: _hanging-indent, + separator: h(.9em, weak: true), + ( + _label, + _format-points( + points, + points-prefix, + points-suffix, + points-separate, + ) + + h(first-line-indent - _content-start-space[#body], weak: true) + + _trim-content-start-parbreak[#body], + ), + ) + v(bottom) + // 更新占位符上的题号 + context counter("placeholder").update(counter("question").get().first()) +} diff --git a/packages/preview/ezexam/0.2.8/lib/solution.typ b/packages/preview/ezexam/0.2.8/lib/solution.typ new file mode 100644 index 0000000000..9a3a6c6d64 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/lib/solution.typ @@ -0,0 +1,79 @@ +#import "const-state.typ": answer-state +#import "tools.typ": _trim-content-start-parbreak +#let solution( + body, + title: none, + title-size: 12pt, + title-weight: 700, + title-color: luma(100%), + title-bg-color: maroon, + title-radius: 5pt, + title-align: top + center, + title-x: 0pt, + title-y: 0pt, + border-style: "dashed", + border-width: .5pt, + border-color: maroon, + color: blue, + radius: 5pt, + bg-color: luma(100%), + breakable: true, + line-height: auto, + top: 0pt, + bottom: 0pt, + inset: (x: 10pt, top: 20pt, bottom: 20pt), + show-number: true, +) = context { + if not answer-state.get() { return } + assert(type(inset) == dictionary, message: "inset must be a dictionary") + v(top) + block( + width: 100%, + breakable: breakable, + inset: (top: 20pt, bottom: 20pt) + inset, + radius: radius, + stroke: (thickness: border-width, paint: border-color, dash: border-style), + fill: bg-color, + )[ + // 标题 + #if title != none { + let title-box = box(fill: title-bg-color, inset: 6pt, radius: title-radius, text( + size: title-size, + weight: title-weight, + tracking: 3pt, + title-color, + title, + )) + place( + title-align, + dx: title-x, + dy: -inset.top - measure(title-box).height / 2 + title-y, + title-box, + ) + } + + // 解析题号的格式化 + #counter("explain").step() + #let _label = none + #if show-number { + _label = context numbering("1.", ..counter("explain").get()) + } + #set par(leading: line-height) if line-height != auto + #let _space = 0em + #if show-number { _space = .75em } + #terms( + hanging-indent: 0em, + separator: h(_space, weak: true), + ( + _label, + text(color, _trim-content-start-parbreak(body)), + ), + ) + ] + v(bottom) +} + +// 解析的分值 +#let score(points, color: maroon, score-prefix: h(.2em), score-suffix: "分") = text(color)[#box(width: 1fr, repeat( + $dot$, + ))#score-prefix#points#score-suffix] diff --git a/packages/preview/ezexam/0.2.8/lib/text-figure.typ b/packages/preview/ezexam/0.2.8/lib/text-figure.typ new file mode 100644 index 0000000000..5cd50328c0 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/lib/text-figure.typ @@ -0,0 +1,35 @@ +// 图文混排(左文右图) +#let text-figure( + figure: none, + figure-x: 0pt, + figure-y: 0pt, + top: 0pt, + bottom: 0pt, + gap: 0pt, + style: "tf", + text, +) = context { + assert(style == "tf" or style == "ft", message: "style must be 'tf' or 'ft'") + let body = ( + text, // [ \ ] 是为了在当前页还有一行时,换页 + [ \ ] + box(place(dx: figure-x, dy: figure-y - par.leading * 2, figure)), + ) + + let _columns = (1fr, measure(figure).width) + let _gap = -figure-x + gap + if style == "ft" { + body = body.rev() + _columns = _columns.rev() + _gap = figure-x + gap + } + + grid( + columns: _columns, + inset: ( + top: top, + bottom: bottom, + ), + gutter: _gap, + ..body, + ) +} diff --git a/packages/preview/ezexam/0.2.8/lib/tools.typ b/packages/preview/ezexam/0.2.8/lib/tools.typ new file mode 100644 index 0000000000..9a378ea52e --- /dev/null +++ b/packages/preview/ezexam/0.2.8/lib/tools.typ @@ -0,0 +1,90 @@ +#import "const-state.typ": heiti +#import "outline.typ": title +#let _special-char = "《(【" +// 为了解决数学公式、特殊字符在最左侧没有内容时加间距的问题 +#let _math-or-special-char(body) = { + if body.func() == math.equation { return "math" } + if body.has("text") and body.text.first() in _special-char { "char" } +} + +#let _check-content-starts-with(body) = { + if body.has("children") { + let children = body.children + if children.len() == 0 { return } + body = children.first() + if body == [ ] { body = children.at(1) } + } + _math-or-special-char(body) +} + +#let _content-start-space(body) = { + if _check-content-starts-with(body) == "math" { return .25em } + if _check-content-starts-with(body) == "char" { return .4em } + 0em +} + +#let _trim-content-start-parbreak(body) = { + if body.has("children") { + let children = body.children + if children.len() > 0 and children.first() == parbreak() { + return children.slice(1).join() + } + } + body +} + +#let _create-seal( + dash: "dashed", + supplement: none, + info: (:), +) = { + assert(type(info) == dictionary, message: "expected dictionary, found " + str(type(info))) + set par(spacing: 10pt) + set text(font: heiti, size: 12pt) + set align(center) + set grid(columns: 2, align: horizon, gutter: .5em) + if supplement != none { text(tracking: .8in, supplement) } + grid( + columns: if info.len() == 0 { 1 } else { info.len() }, + gutter: 1em, + ..for (key, value) in info { + ( + grid( + key, + value, + ), + ) + } + ) + line(length: 100%, stroke: (dash: dash)) +} + +#let draft( + name: "草稿纸", + student-info: ( + 姓名: underline[~~~~~~~~~~~~~], + 准考证号: underline[~~~~~~~~~~~~~~~~~~~~~~~~~~], + 考场号: underline[~~~~~~~], + 座位号: underline[~~~~~~~], + ), + dash: "solid", + supplement: none, +) = { + set page(margin: .5in, header: none, footer: none) + title(name.split("").join(h(1em)), bottom: 0pt) + _create-seal(dash: dash, supplement: supplement, info: student-info) +} + +// 一种页码格式: "第x页(共xx页) +#let zh-arabic(prefix: "", suffix: "") = (..nums) => { + let arr = nums.pos() + [#prefix 第#str(arr.at(0))页(共#str(arr.at(-1))页)#suffix] +} + +#let tag(body, color: blue, font: auto, weight: 400, prefix: "【", suffix: "】", x: -.4em) = context { + let _font = font + if font == auto { _font = text.font.slice(0, -1) + heiti } + h(x, weak: true) + text(font: _font, weight: weight, color)[#prefix#body#suffix] + h(.1em, weak: true) +} diff --git a/packages/preview/ezexam/0.2.8/template/17.png b/packages/preview/ezexam/0.2.8/template/17.png new file mode 100644 index 0000000000..c8482216f1 Binary files /dev/null and b/packages/preview/ezexam/0.2.8/template/17.png differ diff --git a/packages/preview/ezexam/0.2.8/template/6.png b/packages/preview/ezexam/0.2.8/template/6.png new file mode 100644 index 0000000000..b5cfd3c8f6 Binary files /dev/null and b/packages/preview/ezexam/0.2.8/template/6.png differ diff --git a/packages/preview/ezexam/0.2.8/template/main.typ b/packages/preview/ezexam/0.2.8/template/main.typ new file mode 100644 index 0000000000..876d2e6862 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/template/main.typ @@ -0,0 +1,176 @@ +#import "@preview/ezexam:0.2.8": * + +#show: setup.with(mode: EXAM) + +#outline() +#chapter[2025新高考I卷] +#title[2025新高考I卷] +#subject[数学] +#secret() +#scoring-box(y: .5in) +#exam-type[A] +#exam-info(info: (命题人: "张三 李四 王五", 审题: "老六教研组")) +#exam-info() + +#notice( + [答题前,请务必将自已的姓名、准考证号用0.5毫米黑色墨水的签字笔填写在试卷及答题卡的规定位置。], + [请认真核对监考员在答题卡上所粘贴的条形码上的姓名、准考证号与本人是否相符。], + [作答选择题必须用2B铅笔将答题卡上对应选项的方框涂满、涂黑;如需改动,请用橡皮擦干净后,再选涂其他答案。作答非选择题,必须用0.5毫米黑色墨水的签字笔在答题卡上的指定位置作答,在其他位置作答一律无效。], + [本试卷共4页,满分150分,考试时间为120分钟。考试结束后,请将本试卷和答题卡一并交回。], +) + += 单选题:本题共 8 小题,每小题 5 分,共 40 分。在每小题给出的四个选项中,只有一项是符合题目要求的。 +#question[ + $(1 + 5i)i$ 的虚部为 #paren[C] + #choices(-1, 0, 1, 6) +] + +#question[ + 集合 $U = {x | x$为小于9的正整数}, $A = {1,3,5}$, 则 $complement_U A$ 中的元素个数为 #paren[C] + #choices(0, 3, 5, 8) +] + +#question[ + 若双曲线 $C$ 的虚轴长为实轴长的 $sqrt(7)$ 倍,则 $C$ 的离心率为 #paren[D] + #choices([$sqrt(2)$], [$2$], [$sqrt(7)$], [$2sqrt(2)$]) +] + +#question[ + 若点 $(a,0) (a > 0)$ 是函数 $y = 2tan(x - pi / 3)$ 的图象的一个对称中心,则 $a$ 的最小值为 #paren[B] + #choices([30°], [60°], [90°], [135°]) +] + +#question[ + 设 $f(x)$ 是定义在 $RR$ 上且周期为 2 的偶函数,当 $2 lt.slant x lt.slant 3$ 时,$f(x) = 5 - 2x$,则 $f(-3 / 4 ) =$ + #paren[A] + #choices([$-1 / 2$], [$-1 / 4$], [$1 / 4$], [$1 / 2$]) +] + +#question[ + 已知视风速是真风速和船风速的和向量,船风速与船行驶速度大小相等,方向相反.则真风速等级是 #paren[A] + #text-figure( + figure: image("6.png", height: 1.5in), + figure-x: -1in, + )[#choices( + columns: 1, + [轻风 (1.6$~$3.3 m/s)], + [微风 (3.4$~$5.4 m/s)], + [和风 (5.5$~$7.8 m/s)], + [劲风 (8.0$~$10.7 m/s)], + )] +] + +#question[ + 若圆 $x^2 + (y + 2)^2 = r^2 (r > 0)$ 上到直线 $y = sqrt(3)x + 2$ 的距离为 1 的点有且仅有 2 个,则 $r$ 的取值范围是 + #paren[B] + #choices([(0, 1)], [(1, 3)], [(3, +∞)], [(0, +∞)]) +] + +#question[ + 若实数 $x, y, z$ 满足 $2 + log_2 x = 3 + log_3y = 5 + log_5 z$,则 $x, y, z$ 的大小关系不可能是 #paren[B] + #choices([$x > y > z$], [$x > z > y$], [$y > x > z$], [$y > z > x$]) +] + += 多选题:本题共 3 小题,每小题 6 分,共 18 分.在每小题给出的选项中,有多项符合题目要求。全部选对的得 6 分,部分选对的得部分分,有选错的得 0 分。 +#question[ + 在正三棱柱 $A B C-A_1B_1C_1$ 中,$D$ 为 $B C$ 中点,则 #paren[BD] + #choices([$A D perp A_1C$], [$B_1C perp "平面" A A_1D$], [$C C_1 parallel "平面" A A_1D$], [$A D parallel A_1B_1$]) +] + +#question[ + 设抛物线 $C: y^2 = 6x$ 的焦点为 $F$,过 $F$的直线交 $C$ 于$A、B$,过 $F$ 且垂直于 $A B$的直线交准线 $l$: $y = -3 / 2x$ + 于 $E$,过点$A$作准线的垂线,垂足为$D$,则 #paren[ACD] + #choices([$|A D| = |A F|$], [$|A E| = |A B|$], [$|A B| gt.slant 6$], [$|A E| dot |B E| gt.slant 18$]) +] + +#question[ + 已知 $triangle A B C$ 的面积为 $1 / 4$,若 $cos 2A + cos 2B + cos 2C = 2,cos A cos B sin C = 1 / 4$,则 #paren[ACD] + #choices([$sin C = sin^2 A + sin^2 B$], [$A B = sqrt(2)$], [$sin A + sin B = sqrt(6) / 2$], [$A C^2 + B C^2 = 3$]) +] + += 填空题:本题共 3 小题,每小题 5 分,共 15 分。 +#question[ + 若直线 $y = 2x +5$ 是曲线 $y = e^x + x + a$ 的切线,则 $a =$#fillin[4]. +] + +#question[ + 若一个正项等比数列的前 4 项和为 4,前 8 项和为 68,则该等比数列的公比为 #fillin[$plus.minus 2$]. +] + +#question[ + 一个箱子里有 5 个球,分别以 1$~$5 标号,若有放回取三次,记至少取出一次的球的个数 $X$,则 $E(X) =$#fillin[$61/25$]. +] + += 解答题:本题共 5 小题,共 77 分.解答应写出文字说明、证明过程或演算步骤。 +#question(points: 13, bottom: 2in)[ + 为研究某疾病与超声波检查结果的关系,从做过超声波检查的人群中随机调查了1000人,得到如下的列联表: + #align(center)[ + #table( + columns: 4, + [], [正常], [不正常], [合计], + [患该疾病], [20], [180], [200], + [未患该疾病], [780], [20], [800], + [合计], [800], [200], [1000], + ) + ] + + 记超声波检查结果不正常者患有该疾病的概率为$p$,求$p$的估计值; + + 根据小概率值$alpha=0.001$的独立性检验,分析超声波检查结果是否与患该疾病有关. + + #text-figure( + figure: table( + columns: 4, + [$P(chi^2 gt.slant k)$], [0.005], [0.010], [0.001], + [$k$], [3.841], [6.635], [10.828], + ), + )[附:$chi^2 = n(a d - b c)^2 / ((a + b)(c + d)(a + c)(b + d))$.] +] + +#question(points: 15, bottom: 1in)[ + 设数列 ${a_n}$ 满足 $a_1 = 3", "a_(n+1) / n = a_n / (n+1) + 1 / (n(n+1))$. + + 证明:${n a_n}$ 为等差数列; + + 设 $f(x) = a_1x + a_2x^2 + dots.c + a_m x^m,求 f'(-2)$. +] + +#question(points: 15, bottom: 2in)[ + 如图所示的四棱锥 $P - A B C D$ 中,$P A perp "平面" A B C D, B C parallel A D, A B perp A D$. + + 证明:平面 $P A B perp "平面" P A D$ + + 若 $P A = A B = sqrt(2), A D = sqrt(3) + 1, B C = 2$,$P, B, C, D$ 在同一个球面上,设该球面的球心为 $O$. + #text-figure( + figure: image("17.png", height: 1.6in), + figure-x: 20pt, + )[ + + 证明:$O$ 在平面 $A B C D$上; + + 求直线 $A C$ 与直线 $P O$ 所成角的余弦值. + ] +] + +#question(points: 17, bottom: 2in)[ + 设椭圆 $C: x^2 / a^2 + y^2 / b^2 = 1 (a > b > 0)$,记 $A$为椭圆下端点,$B$ 为右端点,$|A B| = sqrt(10)$,且椭圆 $C$ + 的离心率为 $(2sqrt(2)) / 3$. + + 求椭圆的标准方程; + + 设点 $P(m, n)$. + + 若 $P$ 不在 $y$ 轴上,设 $R$ 是射线 $A P$ 上一点,$|A R| dot |A P| = 3$,用 $m, n$ 表示点 $RR$ 的坐标; + + 设直线$O Q$ 的斜率为 $k_1$,直线 $O P$ 的斜率为 $k_2$,若 $k_1 = 3k_2$,$M$为椭圆上一点,求 $|P M|$ 的最大值. +] + +#question(points: 17)[ + 设函数 $f(x) = 5cos x - cos 5x$. + + 求 $f(x)$ 在 $[0, pi / 4]$ 的最大值; + + 给定 $theta in (0, pi),a$ 为实数,证明:存在 $y in [a - theta, a + theta]$,使得 $cos y lt.slant cos theta$; + + 若存在 $phi$,使得对任意 $x$,都有 $5cos x - cos(5x + phi) lt.slant b$,求 $b$ 的最小值. +] + +#show: setup.with(mode: SOLUTION, show-answer: true) +#let answer = tag.with(prefix: "答案:", suffix: [ \ ], color: maroon) + +#title[参考答案] + +#solution(title: "解析")[ + #answer[A] + 解: #lorem(6)#score(6) +] + +#solution[ + #answer[B] + 解: #lorem(8)#score(8) +] diff --git a/packages/preview/ezexam/0.2.8/thumbnail.png b/packages/preview/ezexam/0.2.8/thumbnail.png new file mode 100644 index 0000000000..e31a8192c8 Binary files /dev/null and b/packages/preview/ezexam/0.2.8/thumbnail.png differ diff --git a/packages/preview/ezexam/0.2.8/typst.toml b/packages/preview/ezexam/0.2.8/typst.toml new file mode 100644 index 0000000000..20ff1e3123 --- /dev/null +++ b/packages/preview/ezexam/0.2.8/typst.toml @@ -0,0 +1,17 @@ +[package] +name = "ezexam" +version = "0.2.8" +entrypoint = "ezexam.typ" +homepage = "https://ezexam.pages.dev/" +authors = ["gbchu <https://github.com/gbchu>"] +license = "MIT" +description = "An exam template inspired by the LaTeX package exam-zh and also can make handouts" +repository = "https://github.com/gbchu/ezexam.git" +keywords = ["test", "exam", "exam-zh", "handouts", "讲义", "考试", "试卷"] +compiler = "0.14.0" +categories = ["paper","text","layout"] + +[template] +path = "template" +entrypoint = "main.typ" +thumbnail = "thumbnail.png" \ No newline at end of file