Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/preview/ezexam/0.1.8/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
79 changes: 79 additions & 0 deletions packages/preview/ezexam/0.1.8/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# `ezexam`
## Introduction
`This template is primarily designed to help Chinese primary, middle and high school teachers or students in creating exams or handouts.`

[Online Documentation](https://ezexam.pages.dev/)


## Changelog

### 0 . 1 . 0

+ 初版发布

### 0 . 1 . 1

+ 修复 `choices` 方法中,若选项为图片时,设置宽度为百分比时,图片宽度无效的问题

### 0 . 1 . 2

+ 将 `secret` 修改为方法,可以自定义显示内容

+ 优化 `choices` 方法,当选项过长时,选项从第二行开始进行缩进。修复选项中既有文字又有图表时,标签和内容对不齐的问题

+ 将 `question` 方法的参数 `with-heading-label` 的默认值修改为 `false`

+ `explain` 方法新增参数 `show-number` 、修改参数 `title` 的默认值为 `none`,默认不显示

+ `setup` 方法新增参数 `enum-numbering`

### 0 . 1 . 3

+ 优化 `choices` 方法

+ 将 `question` 方法的参数名 `points-separate-par` 修改为 `points-separate`

+ 增加英文完型填空、7选5题型的支持,让 `paren` 和 `fillin` 方法可以使用题号作为占位符。使用详情查看 [`paren`](https://ezexam.pages.dev/paren) 和 [`fillin`](https://ezexam.pages.dev/fillin) 方法

+ `setup` 方法新增参数 `heading-numbering`,`heading-hanging-indent`,`enum-spacing`,`enum-indent` 提供更多自定义设置

+ 修复 `question` 个数超过9个时,内容对不齐的问题

### 0 . 1 . 4

+ 将 `LECTURE` 修改为 `HANDOUTS`,更加符合语义

+ 将 `explain` 方法名修改为 `solution`,更加符合语义

+ 修复当修改弥封线类型时,试卷最后一页没有更改的 `bug`

+ 添加水印功能,`setup` 方法新增参数 `watermark`,`watermark-size`,`watermark-color`,`watermark-font`,`watermark-rotate`

### 0 . 1 . 5

+ 修复水印被图片遮挡的 `bug`

### 0 . 1 . 6

+ 修复有序列表,内容带有 `box` 时,编号和内容对不齐的 `bug`

+ 新增化学方程式的单线桥、双线桥的支持;原子、离子结构示意图的支持。使用详情查看 [`化学相关`](https://ezexam.pages.dev/chem)

### 0 . 1 . 7

+ 优化代码,确保 `heading-size` 只修改一级标题;并将其更名为 `h1-size`

+ 为 `title` 方法新增参数 `color`

+ 修复 `solution` 方法,当启用 `title` 时,如果解析内容过多,一页放不下,标题会跑到下一页的 `bug`;并将其参数 `above` 更名为 `top`;参数 `below` 更名为 `bottom`;统一参数名;添加参数 `padding-top`、`padding-bottom`

+ 去除 `question` 方法参数 `line-height`;该参数会影响题干之间的距离;该参数原本用于设置题目内容的行高,当题目中的公式比较高时,题号和题目内容会错位,这时可以通过该参数来微调。但是会造成内容每一行与行之间的间隔变大。可参考 [使用技巧](https://ezexam.pages.dev/tips) 代替;添加参数 `padding-top`、`padding-bottom`

+ 修复 `choices` 方法,调整其上下外边距导致选项之间的距离会跟着影响的 `bug`

### 0 . 1 . 8
+ 为 `mode` 添加新值 `SOLUTION`,当答案解析独立于试题存在时,使用此值可快速统一格式
+ 优化 `choices` 方法;将其参数 `column` 更名为 `columns`,做到和官方的 `columns` 参数一致
+ 废弃 `inline-square` 方法,推荐使用内置的 `table` 方法
+ 修复 `color-box` 方法报错的 `bug`
+ 优化 `secret` 、`zh-arabic` 方法
258 changes: 258 additions & 0 deletions packages/preview/ezexam/0.1.8/ezexam.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
#import "lib/tools.typ": *
#import "lib/outline.typ": *
#import "lib/choice.typ": *
#import "lib/question.typ": answer, fillin, fillinn, paren, parenn, question, score, solution, 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-size: 11pt,
font: source-han,
font-math: source-han,
line-height: 2em,
par-spacing: 2em,
first-line-indent: 0em,
heading-numbering: auto,
heading-hanging-indent: auto,
h1-size: auto,
heading-font: hei-ti,
heading-color: luma(0%),
heading-top: 10pt,
heading-bottom: 15pt,
enum-numbering: "(1.i.a)",
enum-spacing: 2em,
enum-indent: 0pt,
watermark: none,
watermark-color: rgb("#f666"),
watermark-font: source-han,
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 must be HANDOUTS or EXAM or SOLUTION")
mode-state.update(mode)
let _footer(label) = context {
assert(
type(label) in (str, function, none) or label == auto,
message: "expected str or function or none or auto, found " + str(type(label)),
)
if label == none { return }
let _label = label
if label == auto {
if mode == HANDOUTS {
_label = "1 ✏ 1"
} else {
let _prefix = [#subject-state.get()试题#if mode == SOLUTION [答案]]
_label = zh-arabic(prefix: _prefix)
}
}
// 如果传进来的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)
}
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 }
// 根据页码决定是否显示弥封线
// 如果当前页面有<title>,则显示弥封线,并在该章节最后一页的右侧也设置弥封线
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: -2em,
)[
#rotate(-90deg, origin: right + bottom)[
#_create-seal(dash: line-type, info: student-info, supplement: supplement)
]
]
return
}

#if (chapter-last-page-location).contains(current) {
_width = if page.flipped {
page.height
} else { page.width }
// 章节最后页的弥封线
place(dx: _width - page.margin - 2em, dy: 2em)[
#rotate(90deg, origin: left + top, _create-seal(dash: line-type, supplement: supplement))
]
}
]
}
let _background() = {
if paper.columns == 2 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 = "一、"
heading-hanging-indent = 2.3em
} else { heading-numbering = "1.1.1" }
}
set heading(numbering: heading-numbering, hanging-indent: heading-hanging-indent)
show heading: it => {
v(heading-top)
text(heading-color, font: heading-font, it)
v(heading-bottom)
}

show heading.where(level: 1): it => {
let size = h1-size
if size == auto {
if mode == HANDOUTS { size = text.size } else { size = 10.5pt }
}
text(size: size, it)
}
set enum(numbering: enum-numbering, spacing: enum-spacing, indent: enum-indent)
set table(stroke: .5pt, align: center)
set table.cell(align: horizon)

// 分段函数样式
set math.cases(gap: 1em)
// 显示方程编号
set math.equation(numbering: "(1)", supplement: [Eq -]) if mode == HANDOUTS
show math.equation: it => {
// features: 一些特殊符号的设置,如空集符号设置更加漂亮
set text(font: font-math, features: ("cv01",))
// 1. 行内样式默认块级显示样式; 2. 添加数学符号和中文之间间距
let space = h(.25em, weak: true)
space + math.display(it) + space
}

if show-answer {
answer-state.update(true)
answer-color-state.update(answer-color)
}

doc
}



Loading