mirror of
https://github.com/szdytom/LADRSolutions.git
synced 2025-10-19 16:30:16 +00:00
init
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
commit
c4c2a65bef
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*.pdf
|
||||
build/
|
||||
.vscode
|
||||
*.swp
|
||||
fonts/
|
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# 《线性代数应该这样学(第四版)》习题解答
|
||||
|
||||
## 构建 PDF
|
||||
|
||||
1. 安装 [Python 3](https://www.python.org/downloads/),请不要安装过于老旧的版本;
|
||||
1. 安装 Python 依赖:`pip install -r requirements.txt`;
|
||||
1. 执行构建脚本:`python3 make.py`;
|
||||
1. 得到输出文件 `main.pdf`。
|
||||
|
||||
如需要使用 `watch` 模式监听 Typst 源代码变化,可以用命令:`python3 make.py --mode w`
|
26
build.toml
Normal file
26
build.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[[fonts]]
|
||||
url = "https://github.com/notofonts/noto-cjk/releases/download/Serif2.003/09_NotoSerifCJKsc.zip"
|
||||
patterns = [".otf"]
|
||||
|
||||
[[fonts]]
|
||||
url = "https://github.com/notofonts/noto-cjk/releases/download/Sans2.004/08_NotoSansCJKsc.zip"
|
||||
patterns = [".otf"]
|
||||
|
||||
[[fonts]]
|
||||
url = "https://github.com/TrionesType/zhuque/releases/download/v0.211/ZhuqueFangsong-v0.211.zip"
|
||||
patterns = [".ttf"]
|
||||
|
||||
#[[fonts]]
|
||||
#url = "https://github.com/tonsky/FiraCode/releases/download/6.2/Fira_Code_v6.2.zip"
|
||||
#patterns = [".ttf"]
|
||||
|
||||
[typst]
|
||||
version = "0.13.1"
|
||||
aarch64-darwin = "https://github.com/typst/typst/releases/download/v0.13.1/typst-aarch64-apple-darwin.tar.xz"
|
||||
aarch64-windows = "https://github.com/typst/typst/releases/download/v0.13.1/typst-aarch64-pc-windows-msvc.zip"
|
||||
aarch64-linux = "https://github.com/typst/typst/releases/download/v0.13.1/typst-aarch64-unknown-linux-musl.tar.xz"
|
||||
armv7-linux = "https://github.com/typst/typst/releases/download/v0.13.1/typst-armv7-unknown-linux-musleabi.tar.xz"
|
||||
riscv64-linux = "https://github.com/typst/typst/releases/download/v0.13.1/typst-riscv64gc-unknown-linux-gnu.tar.xz"
|
||||
x86_64-darwin = "https://github.com/typst/typst/releases/download/v0.13.1/typst-x86_64-apple-darwin.tar.xz"
|
||||
x86_64-windows = "https://github.com/typst/typst/releases/download/v0.13.1/typst-x86_64-pc-windows-msvc.zip"
|
||||
x86_64-linux = "https://github.com/typst/typst/releases/download/v0.13.1/typst-x86_64-unknown-linux-musl.tar.xz"
|
26
main.typ
Normal file
26
main.typ
Normal file
@ -0,0 +1,26 @@
|
||||
#import "styles.typ": project, setup_main_text
|
||||
|
||||
#show: project.with("线性代数应该这样学 习题解答")
|
||||
|
||||
#let toc = ((
|
||||
title: [向量空间],
|
||||
sections: 1
|
||||
),)
|
||||
|
||||
#[
|
||||
#show: setup_main_text
|
||||
|
||||
#{
|
||||
|
||||
for i in range(0, toc.len()) {
|
||||
pagebreak(weak: true)
|
||||
let chapter = toc.at(i)
|
||||
heading(chapter.title, level: 1)
|
||||
for j in range(0, chapter.sections) {
|
||||
include "sections/" + numbering("1A", i + 1, j + 1) + ".typ"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
]
|
202
make.py
Normal file
202
make.py
Normal file
@ -0,0 +1,202 @@
|
||||
import toml
|
||||
import requests
|
||||
import zipfile
|
||||
import tarfile
|
||||
import platform
|
||||
import os
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from tqdm import tqdm
|
||||
|
||||
def executable_name(name):
|
||||
if platform.system().lower() == "windows":
|
||||
return f"{name}.exe"
|
||||
return name
|
||||
|
||||
# 读取build.toml文件
|
||||
with open('build.toml', 'r') as f:
|
||||
config = toml.load(f)
|
||||
|
||||
# 创建临时目录和字体目录
|
||||
temp_dir = Path('build')
|
||||
fonts_dir = Path('fonts')
|
||||
typst_ver = config["typst"]["version"]
|
||||
typst_bin_path = temp_dir / executable_name(f"typst-{typst_ver}")
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
fonts_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 下载文件的函数
|
||||
def download_file(url, destination):
|
||||
"""
|
||||
下载文件并显示进度条
|
||||
|
||||
:param url: 文件的URL
|
||||
:param destination: 文件保存的路径
|
||||
"""
|
||||
try:
|
||||
# 使用流式下载
|
||||
with requests.get(url, stream=True) as response:
|
||||
response.raise_for_status() # 检查请求是否成功
|
||||
total_size = int(response.headers.get('content-length', 0))
|
||||
# 使用 tqdm 显示下载进度
|
||||
with open(destination, 'wb') as f, tqdm(
|
||||
desc=destination.name,
|
||||
total=total_size,
|
||||
unit='B',
|
||||
unit_scale=True,
|
||||
unit_divisor=1024,
|
||||
) as pbar:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk: # 过滤掉保持连接的块
|
||||
f.write(chunk)
|
||||
pbar.update(len(chunk))
|
||||
print(f"Downloaded {destination.name} to {destination.parent}.")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to download {url}: {e}")
|
||||
# 如果下载失败,删除 .part 文件
|
||||
if destination.exists():
|
||||
destination.unlink()
|
||||
raise # 抛出异常,让调用者处理
|
||||
|
||||
# 下载并提取字体文件
|
||||
def prepare_fonts():
|
||||
ok = True
|
||||
for font in config['fonts']:
|
||||
url = font['url']
|
||||
patterns = font['patterns']
|
||||
zip_name = Path(url).name # 获取压缩包文件名
|
||||
zip_path = temp_dir / zip_name # 压缩包本地路径
|
||||
zip_part_path = temp_dir / f"{zip_name}.part" # 下载中的临时文件
|
||||
|
||||
# 检查压缩包是否已存在
|
||||
if zip_path.exists():
|
||||
print(f"{zip_name} already exists in {temp_dir}. Skipping download.")
|
||||
else:
|
||||
# 下载压缩包
|
||||
print(f"Downloading {url}...")
|
||||
try:
|
||||
download_file(url, zip_part_path)
|
||||
# 下载完成后,将 .part 文件重命名为最终文件名
|
||||
zip_part_path.rename(zip_path)
|
||||
except requests.exceptions.RequestException:
|
||||
ok = False
|
||||
continue # 如果下载失败,跳过当前字体
|
||||
|
||||
# 解压缩并提取匹配的文件
|
||||
print(f"Extracting {zip_path}...")
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
for file in zip_ref.namelist():
|
||||
if any(file.endswith(pattern) for pattern in patterns):
|
||||
target_file = fonts_dir / Path(file).name
|
||||
# 检查文件是否已经存在于fonts目录
|
||||
if target_file.exists():
|
||||
print(f"{target_file.name} already exists in {fonts_dir}. Skipping extraction.")
|
||||
continue
|
||||
# 提取文件
|
||||
print(f"Extracting {file}...")
|
||||
zip_ref.extract(file, temp_dir)
|
||||
# 将文件移动到fonts目录
|
||||
extracted_file = temp_dir / file
|
||||
extracted_file.rename(target_file)
|
||||
|
||||
if ok:
|
||||
print("Fonts download and extraction completed.")
|
||||
else:
|
||||
print("Fonts download and extraction completed with errors.")
|
||||
return ok
|
||||
|
||||
def get_system_info():
|
||||
"""获取当前系统的操作系统和架构信息"""
|
||||
system = platform.system().lower()
|
||||
machine = platform.machine().lower()
|
||||
|
||||
if machine == "amd64":
|
||||
machine = "x86_64"
|
||||
elif machine == "arm64":
|
||||
machine = "aarch64"
|
||||
elif machine == "armv7l":
|
||||
machine = "armv7"
|
||||
|
||||
return f"{machine}-{system}"
|
||||
|
||||
def prepare_typst():
|
||||
"""下载并解压 typst 可执行文件"""
|
||||
system_info = get_system_info()
|
||||
typst_url = config["typst"][system_info]
|
||||
typst_archive_name = Path(typst_url).name
|
||||
typst_archive_path = temp_dir / typst_archive_name
|
||||
typst_part_path = temp_dir / f"{typst_archive_name}.part"
|
||||
|
||||
# 检查是否已经下载
|
||||
if typst_bin_path.exists():
|
||||
print(f"Typst already exists in {temp_dir}. Skipping download.")
|
||||
return True
|
||||
|
||||
# 下载 typst
|
||||
if not typst_archive_path.exists():
|
||||
print(f"Downloading {typst_url}...")
|
||||
try:
|
||||
download_file(typst_url, typst_part_path)
|
||||
typst_part_path.rename(typst_archive_path)
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Failed to download typst: {e}")
|
||||
return False
|
||||
else:
|
||||
print(f"Skipped download of {typst_archive_name}")
|
||||
|
||||
# 解压 typst
|
||||
print(f"Extracting {typst_archive_path}...")
|
||||
typst_exe_name = executable_name("typst")
|
||||
|
||||
if typst_archive_path.suffix == ".zip":
|
||||
with zipfile.ZipFile(typst_archive_path, 'r') as zip_ref:
|
||||
for file in zip_ref.namelist():
|
||||
if file.endswith(typst_exe_name):
|
||||
zip_ref.extract(file, temp_dir)
|
||||
extracted_file = temp_dir / file
|
||||
extracted_file.rename(typst_bin_path)
|
||||
elif typst_archive_path.suffixes == [".tar", ".xz"]:
|
||||
with tarfile.open(typst_archive_path, 'r:xz') as tar_ref:
|
||||
for file in tar_ref.getmembers():
|
||||
if file.name.endswith(typst_exe_name):
|
||||
tar_ref.extract(file, temp_dir)
|
||||
extracted_file = temp_dir / file.path
|
||||
extracted_file.rename(typst_bin_path)
|
||||
|
||||
# 确保 typst 可执行文件存在
|
||||
if not typst_bin_path.exists():
|
||||
print(f"Failed to find typst executable in {temp_dir}.")
|
||||
return False
|
||||
|
||||
print(f"Typst downloaded and extracted to {typst_bin_path}.")
|
||||
return True
|
||||
|
||||
def invoke_typst(mode="c"):
|
||||
"""调用 typst 命令"""
|
||||
if not typst_bin_path.exists():
|
||||
print("Typst executable not found.")
|
||||
return False
|
||||
|
||||
command = [str(typst_bin_path), mode, "--font-path", str(fonts_dir), "--ignore-system-fonts", "main.typ"]
|
||||
print("Executing command: ", " ".join(command))
|
||||
try:
|
||||
os.execv(str(typst_bin_path), command)
|
||||
except OSError as e:
|
||||
print(f"Failed to execute typst command: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Run Typst with specified mode.")
|
||||
parser.add_argument('--mode', type=str, choices=['w', 'c'], default='c',
|
||||
help="Mode to run Typst: 'w' for watch mode, 'c' for compile mode.")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not prepare_fonts():
|
||||
return
|
||||
if not prepare_typst():
|
||||
return
|
||||
|
||||
invoke_typst(mode=args.mode)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
toml==0.10.2
|
||||
requests==2.32.3
|
||||
tqdm==4.67.1
|
237
styles.typ
Normal file
237
styles.typ
Normal file
@ -0,0 +1,237 @@
|
||||
#import "@preview/showybox:2.0.4": showybox
|
||||
#import "@preview/tableau-icons:0.1.0" as tbl
|
||||
#import "@preview/cetz:0.3.2"
|
||||
#import "@preview/cetz-plot:0.1.1": plot
|
||||
|
||||
#let zhfont_sans = ("Noto Sans CJK SC")
|
||||
#let zhfont_serif = ("Noto Serif CJK SC")
|
||||
#let zhfont_fangsong = ("Zhuque Fangsong (technical preview)", "Noto Serif CJK SC")
|
||||
#let monofont = ("Fira Code")
|
||||
|
||||
#let theme_color = color.blue
|
||||
|
||||
#let tab = h(2em)
|
||||
#let halftab = h(1em)
|
||||
#let ee = "e"
|
||||
#let ii = "i"
|
||||
|
||||
#let showy_wrapper(title: "", wrap-border: none, ..args) = {
|
||||
let b = if title != "" {
|
||||
showybox(title: text(font: zhfont_sans, title), ..args)
|
||||
} else {
|
||||
showybox(title: title, ..args)
|
||||
}
|
||||
|
||||
if wrap-border == none {
|
||||
b
|
||||
} else {
|
||||
set align(center)
|
||||
block(inset: (
|
||||
left: wrap-border,
|
||||
), width: 100%, b)
|
||||
}
|
||||
}
|
||||
|
||||
#let simple_box = showy_wrapper.with(
|
||||
breakable: true,
|
||||
title-style: (
|
||||
weight: 900,
|
||||
color: theme_color.darken(40%),
|
||||
sep-thickness: 0pt,
|
||||
font: zhfont_sans
|
||||
//align: center
|
||||
),
|
||||
frame: (
|
||||
title-color: theme_color.lighten(80%),
|
||||
border-color: theme_color.darken(40%),
|
||||
thickness: (left: 1pt),
|
||||
radius: 0pt
|
||||
),
|
||||
)
|
||||
|
||||
#let problem_box = showy_wrapper.with(
|
||||
breakable: false,
|
||||
frame: (
|
||||
title-color: theme_color.lighten(80%),
|
||||
border-color: theme_color.darken(40%),
|
||||
thickness: (left: 1pt),
|
||||
radius: 0pt,
|
||||
align: left,
|
||||
),
|
||||
)
|
||||
|
||||
#let unset-list-indent(body) = {
|
||||
set list(indent: 0.5em)
|
||||
set enum(indent: 0.5em)
|
||||
body
|
||||
}
|
||||
|
||||
#let project(title, body) = {
|
||||
set document(title: title)
|
||||
set text(font: zhfont_serif, lang: "zh")
|
||||
set page(
|
||||
// 1/16 787 x 1092
|
||||
width: 787mm / 4,
|
||||
height: 1092mm / 4,
|
||||
)
|
||||
|
||||
set par(
|
||||
spacing: 1.2em,
|
||||
leading: 0.75em,
|
||||
)
|
||||
set list(marker: (sym.square.filled.small, [--]), indent: 2.5em)
|
||||
set enum(indent: 2.5em)
|
||||
show heading: set text(font: zhfont_sans, weight: "semibold")
|
||||
set par(justify: true)
|
||||
set text(11pt)
|
||||
show heading.where(level: 3): set text(14pt)
|
||||
show figure.caption: set text(9pt, font: zhfont_fangsong)
|
||||
show footnote.entry: set text(9pt, font: zhfont_fangsong)
|
||||
set table(stroke: 1pt + theme_color, inset: 5pt)
|
||||
set grid(stroke: 1pt + theme_color)
|
||||
set highlight(fill: none, stroke: (
|
||||
bottom: 4pt + theme_color.lighten(80%)
|
||||
))
|
||||
|
||||
v(10fr)
|
||||
h(2fr)
|
||||
[
|
||||
#set text(2em, weight: "light", font: zhfont_sans)
|
||||
#title <book-title>
|
||||
]
|
||||
v(3fr)
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
#let note(body) = {
|
||||
text(body, 9pt, font: zhfont_fangsong)
|
||||
}
|
||||
|
||||
#let boxed-figure(body, wrap-placed: false, ..args) = {
|
||||
// TODO: wrap placed figures: https://github.com/typst/typst/issues/5181
|
||||
figure(box(
|
||||
inset: 5pt,
|
||||
stroke: 1pt + theme_color,
|
||||
body
|
||||
), ..args)
|
||||
}
|
||||
|
||||
#let fancy_term_box(title, value) = {
|
||||
box(baseline: 3pt,{
|
||||
box(fill: theme_color.lighten(80%), inset: 3pt, text([#title], fill: theme_color.darken(40%), font: zhfont_sans, weight: "medium"))
|
||||
box(fill: theme_color.lighten(20%), inset: 3pt, text([#value], fill: white, font: zhfont_sans, weight: "medium"))},
|
||||
)
|
||||
}
|
||||
|
||||
#let setup_main_text(body) = {
|
||||
pagebreak(to: "odd")
|
||||
counter(page).update(1)
|
||||
show heading.where(level: 1): it => {
|
||||
counter("chapter_N").step()
|
||||
counter("section_N").update(0)
|
||||
block(width: 100%, {
|
||||
set text(15pt, font: zhfont_sans, weight: "medium")
|
||||
grid(
|
||||
columns: (auto, 1fr),
|
||||
inset: 0.4em,
|
||||
stroke: none,
|
||||
grid.cell(
|
||||
fill: theme_color.lighten(20%),
|
||||
{
|
||||
set text(fill: white)
|
||||
"第"
|
||||
context counter("chapter_N").display("1")
|
||||
"章"
|
||||
}),
|
||||
grid.cell(
|
||||
fill: theme_color.lighten(80%),
|
||||
{
|
||||
it.body
|
||||
})
|
||||
)
|
||||
v(0.5em)
|
||||
})
|
||||
}
|
||||
|
||||
show heading.where(level: 2): it => {
|
||||
counter("section_N").step()
|
||||
counter(figure.where(kind: "exercise-problem")).update(0)
|
||||
block(width: 100%, {
|
||||
set text(30pt, font: zhfont_sans, weight: "light")
|
||||
block(stroke: (bottom: 10pt + theme_color.lighten(80%),), inset: -2pt)[
|
||||
#context{
|
||||
counter("chapter_N").display("1")
|
||||
counter("section_N").display("A")
|
||||
}
|
||||
#h(10pt)
|
||||
#it.body <section-title>
|
||||
]
|
||||
v(10pt)
|
||||
})
|
||||
}
|
||||
|
||||
set page(footer: context {
|
||||
let this_page = counter(page).get().at(0)
|
||||
let isleft = calc.even(this_page)
|
||||
set align(left) if isleft
|
||||
set align(right) if not isleft
|
||||
set text(9pt, font: zhfont_sans, fill: theme_color.darken(20%))
|
||||
|
||||
let prev_headers = query(selector(<section-title>).before(here()))
|
||||
let book_title = query(selector(<book-title>)).first().text
|
||||
let footer_content = if isleft {
|
||||
book_title
|
||||
} else {
|
||||
if prev_headers.len() > 0 {
|
||||
prev_headers.last()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
stack(dir: if isleft { ltr } else { rtl },
|
||||
spacing: 1em,
|
||||
str(this_page),
|
||||
footer_content,
|
||||
)
|
||||
})
|
||||
|
||||
show figure.where(kind: "exercise-problem"): it => {
|
||||
let cat_display = "习题"
|
||||
set align(left)
|
||||
problem_box({
|
||||
context fancy_term_box(cat_display, it.counter.get().at(0))
|
||||
h(0.5em)
|
||||
it.body
|
||||
})
|
||||
}
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
#let exercise_sol(e, s, type: "proof") = {
|
||||
figure(e, kind: "exercise-problem", supplement: "习题")
|
||||
s
|
||||
}
|
||||
|
||||
#let ploting-styles = (
|
||||
mark: (fill: theme_color.lighten(80%), stroke: theme_color),
|
||||
|
||||
nothing: (fill: none, stroke: none),
|
||||
|
||||
s_l20: (stroke: theme_color.lighten(20%)),
|
||||
s: (stroke: theme_color),
|
||||
s_d20: (stroke: theme_color.darken(20%)),
|
||||
s_hl: (stroke: theme_color.darken(20%) + 2pt),
|
||||
s_hl_l20: (stroke: theme_color.lighten(20%) + 2pt),
|
||||
s_black: (stroke: black),
|
||||
|
||||
f_l80: (stroke: none, fill: theme_color.lighten(80%)),
|
||||
f_l90: (stroke: none, fill: theme_color.lighten(90%)),
|
||||
f_l95: (stroke: none, fill: theme_color.lighten(95%)),
|
||||
|
||||
axis: cetz.draw.set-style(axes: (stroke: .5pt, tick: (stroke: .5pt))),
|
||||
)
|
||||
|
||||
#let plot-point(x, y) = plot.add(((x, y),), mark: "o", mark-style: ploting-styles.mark, style: ploting-styles.s)
|
Loading…
x
Reference in New Issue
Block a user